refactor: 代码优化与错误处理增强

前端:
- mutator.ts: 响应拦截器增加业务错误码处理
- 多个 ListView/DetailView: 导入语句格式化(按字母排序)
- CollectionListView/CollectionDetailView/CourseListView: 优化下架错误处理,显示具体错误信息

后端:
- CourseCollectionServiceImpl: 下架套餐前检查是否有租户正在使用
- TeacherServiceImpl: 添加教师状态变更日志
- CoursePackageServiceImpl: 导入语句格式化
This commit is contained in:
En 2026-03-24 14:11:16 +08:00
parent e2547daa63
commit 67af92ddfd
7 changed files with 73 additions and 52 deletions

View File

@ -1,4 +1,4 @@
import axios, { type AxiosRequestConfig, type AxiosResponse } from "axios"; import axios, {type AxiosRequestConfig, type AxiosResponse} from "axios";
/** /**
* Axios * Axios
@ -32,6 +32,11 @@ axiosInstance.interceptors.response.use(
if (jsonData.code === 200 || jsonData.code === 0) { if (jsonData.code === 200 || jsonData.code === 0) {
return jsonData.data; return jsonData.data;
} }
// 业务错误码,抛出错误
const error: any = new Error(jsonData.message || '请求失败');
error.response = { data: jsonData, status: 200 };
error.code = jsonData.code;
return Promise.reject(error);
} }
return jsonData; return jsonData;
} catch { } catch {
@ -48,6 +53,11 @@ axiosInstance.interceptors.response.use(
// 返回 data 字段 // 返回 data 字段
return data.data; return data.data;
} }
// 业务错误码,抛出错误
const error: any = new Error(data.message || '请求失败');
error.response = { data, status: 200 };
error.code = data.code;
return Promise.reject(error);
} }
return data; return data;

View File

@ -150,10 +150,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue'; import {onMounted, ref} from 'vue';
import { useRoute, useRouter } from 'vue-router'; import {useRoute, useRouter} from 'vue-router';
import { message, Modal } from 'ant-design-vue'; import {message} from 'ant-design-vue';
import { PlusOutlined } from '@ant-design/icons-vue'; import {PlusOutlined} from '@ant-design/icons-vue';
import * as collectionsApi from '@/api/collections'; import * as collectionsApi from '@/api/collections';
import * as packagesApi from '@/api/course'; import * as packagesApi from '@/api/course';
@ -238,8 +238,9 @@ const handleArchive = async () => {
await collectionsApi.archiveCollection(route.params.id); await collectionsApi.archiveCollection(route.params.id);
message.success('下架成功'); message.success('下架成功');
loadCollection(); loadCollection();
} catch (error) { } catch (error: any) {
message.error('下架失败'); const errorMsg = error?.message || '下架失败';
message.error(errorMsg);
} }
}; };

View File

@ -114,10 +114,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'; import {onMounted, reactive, ref} from 'vue';
import { useRouter } from 'vue-router'; import {useRouter} from 'vue-router';
import { message } from 'ant-design-vue'; import {message} from 'ant-design-vue';
import { PlusOutlined } from '@ant-design/icons-vue'; import {PlusOutlined} from '@ant-design/icons-vue';
import * as collectionsApi from '@/api/collections'; import * as collectionsApi from '@/api/collections';
const router = useRouter(); const router = useRouter();
@ -225,8 +225,9 @@ const handleArchive = async (record: collectionsApi.Collection) => {
await collectionsApi.archiveCollection(record.id); await collectionsApi.archiveCollection(record.id);
message.success('下架成功'); message.success('下架成功');
fetchData(); fetchData();
} catch (error) { } catch (error: any) {
message.error('下架失败'); const errorMsg = error?.message || '下架失败';
message.error(errorMsg);
} }
}; };

View File

@ -198,19 +198,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'; import {onMounted, reactive, ref} from 'vue';
import { useRouter } from 'vue-router'; import {useRouter} from 'vue-router';
import { message, Modal } from 'ant-design-vue'; import {message, Modal} from 'ant-design-vue';
import { PlusOutlined, DownOutlined, AuditOutlined } from '@ant-design/icons-vue'; import {AuditOutlined, DownOutlined, PlusOutlined} from '@ant-design/icons-vue';
import * as courseApi from '@/api/course'; import * as courseApi from '@/api/course';
import { import {getCourseStatusStyle, getGradeTagStyle, getLessonTagStyle, getLessonTypeName, translateCourseStatus, translateGradeTag,} from '@/utils/tagMaps';
translateGradeTag,
getGradeTagStyle,
translateCourseStatus,
getCourseStatusStyle,
getLessonTypeName,
getLessonTagStyle,
} from '@/utils/tagMaps';
// //
const getLessonTypesFromRecord = (record: any): string[] => { const getLessonTypesFromRecord = (record: any): string[] => {
@ -487,8 +480,9 @@ const unpublishCourse = async (id: number) => {
await courseApi.unpublishCourse(id); await courseApi.unpublishCourse(id);
message.success('下架成功'); message.success('下架成功');
fetchCourses(); fetchCourses();
} catch (error) { } catch (error: any) {
message.error('下架失败'); const errorMsg = error?.message || '下架失败';
message.error(errorMsg);
} }
}, },
}); });

View File

@ -7,16 +7,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.reading.platform.common.enums.CourseStatus; import com.reading.platform.common.enums.CourseStatus;
import com.reading.platform.common.enums.TenantPackageStatus; import com.reading.platform.common.enums.TenantPackageStatus;
import com.reading.platform.common.exception.BusinessException; import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.common.response.PageResult;
import com.reading.platform.dto.response.CourseCollectionResponse; import com.reading.platform.dto.response.CourseCollectionResponse;
import com.reading.platform.dto.response.CoursePackageResponse; import com.reading.platform.dto.response.CoursePackageResponse;
import com.reading.platform.dto.response.PackageFilterMetaResponse; import com.reading.platform.dto.response.PackageFilterMetaResponse;
import com.reading.platform.entity.CourseCollection; import com.reading.platform.entity.*;
import com.reading.platform.entity.CourseCollectionPackage;
import com.reading.platform.entity.CourseLesson;
import com.reading.platform.entity.CoursePackage;
import com.reading.platform.entity.TenantPackage;
import com.reading.platform.entity.Theme;
import com.reading.platform.mapper.*; import com.reading.platform.mapper.*;
import com.reading.platform.service.CourseCollectionService; import com.reading.platform.service.CourseCollectionService;
import com.reading.platform.service.CourseLessonService; import com.reading.platform.service.CourseLessonService;
@ -27,12 +21,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -688,6 +677,18 @@ public class CourseCollectionServiceImpl extends ServiceImpl<CourseCollectionMap
throw new IllegalArgumentException("课程套餐不存在"); throw new IllegalArgumentException("课程套餐不存在");
} }
// 检查是否有租户正在使用此套餐
Long tenantCount = tenantPackageMapper.selectCount(
new LambdaQueryWrapper<TenantPackage>()
.eq(TenantPackage::getCollectionId, id)
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE)
);
if (tenantCount > 0) {
log.warn("下架课程套餐失败,有 {} 个租户正在使用此套餐id={}", tenantCount, id);
throw new BusinessException("该套餐正在被 " + tenantCount + " 个租户使用,无法下架");
}
collection.setStatus(CourseStatus.ARCHIVED.getCode()); collection.setStatus(CourseStatus.ARCHIVED.getCode());
collectionMapper.updateById(collection); collectionMapper.updateById(collection);

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.reading.platform.common.enums.CourseStatus; import com.reading.platform.common.enums.CourseStatus;
import com.reading.platform.common.enums.ScheduleStatus;
import com.reading.platform.common.enums.TenantPackageStatus; import com.reading.platform.common.enums.TenantPackageStatus;
import com.reading.platform.common.exception.BusinessException; import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.dto.request.CourseCreateRequest; import com.reading.platform.dto.request.CourseCreateRequest;
@ -11,16 +12,8 @@ import com.reading.platform.dto.request.CourseUpdateRequest;
import com.reading.platform.dto.response.CourseLessonResponse; import com.reading.platform.dto.response.CourseLessonResponse;
import com.reading.platform.dto.response.CourseResponse; import com.reading.platform.dto.response.CourseResponse;
import com.reading.platform.dto.response.LessonStepResponse; import com.reading.platform.dto.response.LessonStepResponse;
import com.reading.platform.entity.CourseCollectionPackage; import com.reading.platform.entity.*;
import com.reading.platform.entity.CourseLesson; import com.reading.platform.mapper.*;
import com.reading.platform.entity.CoursePackage;
import com.reading.platform.entity.LessonStep;
import com.reading.platform.entity.TenantPackage;
import com.reading.platform.entity.Theme;
import com.reading.platform.mapper.CourseCollectionPackageMapper;
import com.reading.platform.mapper.CoursePackageMapper;
import com.reading.platform.mapper.TenantPackageMapper;
import com.reading.platform.mapper.ThemeMapper;
import com.reading.platform.service.CourseLessonService; import com.reading.platform.service.CourseLessonService;
import com.reading.platform.service.CoursePackageService; import com.reading.platform.service.CoursePackageService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -50,6 +43,7 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
private final CourseCollectionPackageMapper collectionPackageMapper; private final CourseCollectionPackageMapper collectionPackageMapper;
private final TenantPackageMapper tenantPackageMapper; private final TenantPackageMapper tenantPackageMapper;
private final ThemeMapper themeMapper; private final ThemeMapper themeMapper;
private final SchedulePlanMapper schedulePlanMapper;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -218,10 +212,26 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void archiveCourse(Long id) { public void archiveCourse(Long id) {
log.info("下架课程包id={}", id);
CoursePackage entity = getCourseById(id); CoursePackage entity = getCourseById(id);
// 检查课程包是否被排课使用
Long scheduleCount = schedulePlanMapper.selectCount(
new LambdaQueryWrapper<com.reading.platform.entity.SchedulePlan>()
.eq(com.reading.platform.entity.SchedulePlan::getCoursePackageId, id)
.ne(com.reading.platform.entity.SchedulePlan::getStatus, ScheduleStatus.CANCELLED.getCode())
);
if (scheduleCount > 0) {
log.warn("下架课程包失败,有 {} 条排课记录正在使用此课程包id={}", scheduleCount, id);
throw new BusinessException("该课程包正在被 " + scheduleCount + " 条排课记录使用,无法下架");
}
entity.setStatus(CourseStatus.ARCHIVED.getCode()); entity.setStatus(CourseStatus.ARCHIVED.getCode());
coursePackageMapper.updateById(entity); coursePackageMapper.updateById(entity);
log.info("课程归档成功id={}", id);
log.info("课程包下架成功id={}", id);
} }
@Override @Override

View File

@ -2,8 +2,8 @@ package com.reading.platform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.enums.GenericStatus;
import com.reading.platform.common.enums.ErrorCode; import com.reading.platform.common.enums.ErrorCode;
import com.reading.platform.common.enums.GenericStatus;
import com.reading.platform.common.exception.BusinessException; import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.dto.request.TeacherCreateRequest; import com.reading.platform.dto.request.TeacherCreateRequest;
import com.reading.platform.dto.request.TeacherUpdateRequest; import com.reading.platform.dto.request.TeacherUpdateRequest;
@ -122,6 +122,10 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
teacher.setBio(request.getBio()); teacher.setBio(request.getBio());
} }
if (StringUtils.hasText(request.getStatus())) { if (StringUtils.hasText(request.getStatus())) {
if (!request.getStatus().equals(teacher.getStatus())) {
log.info("教师状态变更ID={}, name={}, oldStatus={}, newStatus={}",
teacher.getId(), teacher.getName(), teacher.getStatus(), request.getStatus());
}
teacher.setStatus(request.getStatus()); teacher.setStatus(request.getStatus());
} }