大表单提交

This commit is contained in:
renwanhong
2025-12-16 14:41:36 +08:00
parent 3de308fee1
commit 317bdb161c
24 changed files with 3917 additions and 53 deletions

View File

@@ -35,6 +35,16 @@ public class UrlSecurityFilterImpl implements IUrlSecurityFilter{
noLoginUrls.add("/xboe/m/course/manage/test");
noLoginUrls.add("/xboe/m/course/manage/redirectDetail");
noLoginUrls.add("/xboe/account/update-avatar");
// 全量测试
noLoginUrls.add("/xboe/m/course/content/save");
// 新的测试接口
noLoginUrls.add("/xboe/m/course/content/courseware/save");
noLoginUrls.add("/xboe/m/course/content/courseware/atomic-upload");
noLoginUrls.add("/xboe/m/course/content/courseware/hierarchical-upload");
}
@Override

View File

@@ -1,24 +1,30 @@
package com.xboe.module.course.api;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import javax.annotation.Resource;
import javax.annotation.Resource;import com.xboe.module.course.dao.CourseDao;
import com.xboe.module.course.dto.*;
import com.xboe.module.course.service.*;
import com.xboe.module.course.vo.TypeTreeVo;
import com.xboe.system.organization.vo.OrganizationVo;
import org.apache.commons.collections4.CollectionUtils;
import com.xboe.common.OrderCondition;
import com.xboe.common.utils.StringUtil;
import com.xboe.core.log.AutoLog;
import com.xboe.core.orm.FieldFilters;
import com.xboe.module.course.dao.CourseContentDao;
import com.xboe.module.course.dao.CourseSectionDao;
import com.xboe.module.course.entity.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import com.xboe.core.JsonResponse;
import com.xboe.core.api.ApiBaseController;
import com.xboe.module.course.dto.CourseContentDto;
import com.xboe.module.course.dto.SortItem;
import com.xboe.module.course.entity.CourseAssess;
import com.xboe.module.course.entity.CourseExam;
import com.xboe.module.course.entity.CourseHomeWork;
import com.xboe.module.course.entity.CourseSection;
import com.xboe.module.course.service.ICourseContentService;
import com.xboe.module.course.service.ICourseSectionService;
import com.xboe.standard.BaseConstant;
import lombok.extern.slf4j.Slf4j;
@@ -40,10 +46,31 @@ public class CourseContentApi extends ApiBaseController{
@Resource
ICourseContentService ccontentService;
@Resource
ICourseContentService courseWareService;
@Resource
CourseSectionDao csectionDao; @Resource
TypeTreeService typeTreeService;
@Resource
CourseDao courseDao;
@Resource
private IAtomicBatchUploadService atomicBatchUploadService;
@Resource
private CourseContentValidationService courseContentValidationService;
// 测试导入包
@Resource
private CourseContentDao ccDao;
/**
* 获取课程的作业信息
* @param cid
* @param ccid
* @return
*/
@RequestMapping(value="/homework",method= {RequestMethod.POST,RequestMethod.GET})
@@ -159,7 +186,10 @@ public class CourseContentApi extends ApiBaseController{
return badRequest("无课程关联");
}
try {
ccontentService.saveOrUpdate(cc);
// 测试
// log.info("CourseContentDto: {}", cc);
ccontentService.saveOrUpdate(cc);
return success(cc);
}catch(Exception e) {
log.error("保存课程内容错误",e);
@@ -167,6 +197,390 @@ public class CourseContentApi extends ApiBaseController{
}
}
/**
* 保存课件内容,同时把保存后的内容返回给前端,以便前端应用转化
* @param cws
* @return
*/
@PostMapping("/courseware/save")
public JsonResponse<CourseWareSaveDto> saveCourseWare(@RequestBody CourseWareSaveDto cws) {
// 1. 参数基础校验
if (cws.getCourse() == null) {
return badRequest("课程基础信息不能为空");
}
// 课程名称必填校验
if (StringUtils.isBlank(cws.getCourse().getName())) {
return badRequest("课程名称不能为空");
}
// 课程名称长度校验
if (cws.getCourse().getName().length() > 100) {
return badRequest("课程名称长度不能超过100字");
}
if (CollectionUtils.isEmpty(cws.getTeacherList())) {
return badRequest("至少选择一位授课教师");
}
if (cws.getSysType1() == null || StringUtils.isBlank(cws.getSysType1().getId())) {
return badRequest("课程一级分类不能为空");
}
if (cws.getResOwner1() == null || StringUtils.isBlank(cws.getResOwner1().getId())) {
return badRequest("资源一级归属不能为空");
}
if (cws.getDevice() == null || (cws.getDevice() < 1 || cws.getDevice() > 3)) {
return badRequest("观看设置参数错误必须是1、2或3");
}
// 2. 业务逻辑处理
try {
// 记录操作日志
String courseName = cws.getCourse().getId();
log.info("开始保存/更新课程课件课程ID{}", courseName);
// 调用ICourseContentService的saveOrUpdateCourseware方法
ccontentService.saveOrUpdateCourseware(cws);
// 操作成功返回原DTO给前端
log.info("课程课件保存/更新成功课程ID{}", courseName);
return success(cws, "保存成功");
} catch (Exception e) {
log.error("保存课件内容错误,课程名称:{}", cws.getCourse().getName(), e);
return error("保存失败:" + e.getMessage(), e.getMessage());
}
}
/**
* 信息批量上传(扁平化传参方式)
*/
@PostMapping("/courseware/atomic-upload")
public JsonResponse<BatchUploadResponseDto> atomicBatchUpload(
@Validated @RequestBody BatchUploadWithNullDto uploadDto) {
try {
log.info("接收批量上传请求课程ID: {}, 操作类型: {}, 内容项数量: {}",
uploadDto.getCourseId(),
uploadDto.getOperationType(),
uploadDto.getContentItems() != null ? uploadDto.getContentItems().size() : 0);
// 基础验证
if (StringUtils.isBlank(uploadDto.getCourseId())) {
return badRequest("课程ID不能为空");
}
if (uploadDto.getOperationType() == null) {
return badRequest("操作类型不能为空");
}
if (uploadDto.getOperationType() != 1 ) {
return badRequest("操作类型必须是1(新增)");
}
if (CollectionUtils.isEmpty(uploadDto.getContentItems())) {
return badRequest("内容项列表不能为空");
}
// 验证每个内容项(使用服务层的验证方法)
List<String> validationErrors = courseContentValidationService.validateBatchUpload(uploadDto);
if (CollectionUtils.isNotEmpty(validationErrors)) {
return badRequest(String.join("; ", validationErrors));
}
// 验证章节结构和资源唯一性
List<String> structureErrors = validateChapterSectionStructure(uploadDto);
if (CollectionUtils.isNotEmpty(structureErrors)) {
return badRequest(String.join("; ", structureErrors));
}
// 执行批量上传
BatchUploadResponseDto response = atomicBatchUploadService.atomicBatchUpload(uploadDto);
log.info("批量上传成功课程ID: {}, 成功: {}, 失败: {}",
uploadDto.getCourseId(), response.getSuccessCount(), response.getFailCount());
return success(response, "原子批量上传成功");
} catch (RuntimeException e) {
log.error("原子批量上传失败", e);
return error("原子批量上传失败: " + e.getMessage());
} catch (Exception e) {
log.error("系统异常", e);
return error("系统异常: " + e.getMessage());
}
}
/**
* 保存课程结构(课程->章->节->内容)
*/
@PostMapping("/courseware/hierarchical-upload")
public JsonResponse<BatchUploadResponseDto> hierarchicalUpload(
@Validated @RequestBody HierarchicalCourseDto hierarchicalDto) {
try {
log.info("接收层级结构上传请求课程ID: {}, 章节数量: {}",
hierarchicalDto.getCourseId(),
hierarchicalDto.getChapters() != null ? hierarchicalDto.getChapters().size() : 0);
// 层级结构->扁平结构
BatchUploadWithNullDto batchDto = convertToBatchUploadDto(hierarchicalDto);
// 基础验证
if (StringUtils.isBlank(batchDto.getCourseId())) {
return badRequest("课程ID不能为空");
}
if (batchDto.getOperationType() == null) {
return badRequest("操作类型不能为空");
}
if (batchDto.getOperationType() != 1 ) {
return badRequest("操作类型必须是1(新增)");
}
if (CollectionUtils.isEmpty(batchDto.getContentItems())) {
return badRequest("内容项列表不能为空");
}
// 验证每个内容项
List<String> validationErrors = courseContentValidationService.validateBatchUpload(batchDto);
if (CollectionUtils.isNotEmpty(validationErrors)) {
return badRequest(String.join("; ", validationErrors));
}
// 验证章节结构和资源唯一性
List<String> structureErrors = validateChapterSectionStructure(batchDto);
if (CollectionUtils.isNotEmpty(structureErrors)) {
return badRequest(String.join("; ", structureErrors));
}
// 执行批量上传
BatchUploadResponseDto response = atomicBatchUploadService.atomicBatchUpload(batchDto);
log.info("层级结构上传成功课程ID: {}, 成功: {}, 失败: {}",
batchDto.getCourseId(), response.getSuccessCount(), response.getFailCount());
return success(response, "层级结构上传成功");
} catch (RuntimeException e) {
log.error("层级结构上传失败", e);
return error("层级结构上传失败: " + e.getMessage());
} catch (Exception e) {
log.error("系统异常", e);
return error("系统异常: " + e.getMessage());
}
}
/**
* 将层级结构DTO转换为扁平结构DTO
*/
private BatchUploadWithNullDto convertToBatchUploadDto(HierarchicalCourseDto hierarchicalDto) {
BatchUploadWithNullDto batchDto = new BatchUploadWithNullDto();
batchDto.setCourseId(hierarchicalDto.getCourseId());
batchDto.setOperationType(hierarchicalDto.getOperationType() != null ?
hierarchicalDto.getOperationType() : 1); // 默认为新增操作
batchDto.setOwnerInfo(hierarchicalDto.getOwnerInfo());
List<BatchUploadWithNullDto.ContentItem> contentItems = new ArrayList<>();
// 遍历章节和节
for (HierarchicalCourseDto.Chapter chapter : hierarchicalDto.getChapters()) {
for (HierarchicalCourseDto.Section section : chapter.getSections()) {
BatchUploadWithNullDto.ContentItem contentItem = section.getContent();
contentItem.setCsectionId(chapter.getChapterId()); // 章ID
contentItems.add(contentItem);
}
}
batchDto.setContentItems(contentItems);
return batchDto;
}
/**
* 验证章节结构和资源唯一性
* 确保:
* 1. 一个课程下面对应多个章
* 2. 一个章底下对应多个节
* 3. 一个节底下对应一个资源
* 4. 每个节只能上传一种资源
*
* @param uploadDto 批量上传DTO
* @return 验证错误列表
*/
private List<String> validateChapterSectionStructure(BatchUploadWithNullDto uploadDto) {
List<String> errors = new ArrayList<>();
// 检查是否有重复的章节ID
Map<String, List<BatchUploadWithNullDto.ContentItem>> sectionContentMap = new HashMap<>();
for (int i = 0; i < uploadDto.getContentItems().size(); i++) {
BatchUploadWithNullDto.ContentItem item = uploadDto.getContentItems().get(i);
// 检查是否指定章节ID
if (StringUtils.isBlank(item.getCsectionId())) {
errors.add("" + (i + 1) + "必须指定章节ID");
continue;
}
// 将内容项按照章节ID分组
sectionContentMap.computeIfAbsent(item.getCsectionId(), k -> new ArrayList<>()).add(item);
}
// 检查每个章节是否只有一个资源
for (Map.Entry<String, List<BatchUploadWithNullDto.ContentItem>> entry : sectionContentMap.entrySet()) {
String sectionId = entry.getKey();
List<BatchUploadWithNullDto.ContentItem> itemsInSection = entry.getValue();
if (itemsInSection.size() > 1) {
errors.add("章节ID " + sectionId + " 下存在 " + itemsInSection.size() + " 个资源,每个章节只能有一个资源");
}
}
// 验证章节ID是否存在+属于当前课程
if (errors.isEmpty()) {
List<String> sectionIds = new ArrayList<>(sectionContentMap.keySet());
long validSections = csectionDao.count(
FieldFilters.eq("courseId", uploadDto.getCourseId()),
FieldFilters.in("id", sectionIds)
);
if (validSections != sectionIds.size()) {
errors.add("部分章节ID无效或不属于当前课程");
}
}
return errors;
}
/**
* 获取课程分类树(多级联查)
*/
@GetMapping("/type/tree")
public JsonResponse<List<TypeTreeVo>> getCourseTypeTree() {
try {
List<TypeTreeVo> typeTree = typeTreeService.getCourseTypeTree();
return success(typeTree, "获取课程分类树成功");
} catch (Exception e) {
log.error("获取课程分类树失败", e);
return error("获取课程分类树失败:" + e.getMessage());
}
}
/**
* 获取资源归属树(多级联查)
*/
@GetMapping("/owner/tree")
public JsonResponse<List<OrganizationVo>> getResourceOwnerTree() {
try {
List<OrganizationVo> orgTree = typeTreeService.getResourceOwnerTree();
return success(orgTree, "获取资源归属树成功");
} catch (Exception e) {
log.error("获取资源归属树失败", e);
return error("获取资源归属树失败:" + e.getMessage());
}
}
/**
* 根据父级ID获取子分类
*/
@GetMapping("/type/children")
public JsonResponse<List<TypeTreeVo>> getChildTypes(@RequestParam String parentId) {
try {
if (StringUtils.isBlank(parentId)) {
return badRequest("父级ID不能为空");
}
List<TypeTreeVo> children = typeTreeService.getChildTypes(parentId);
return success(children, "获取子分类成功");
} catch (Exception e) {
log.error("获取子分类失败", e);
return error("获取子分类失败:" + e.getMessage());
}
}
/**
* 根据父级ID获取子组织
*/
@GetMapping("/owner/children")
public JsonResponse<List<OrganizationVo>> getChildOrgs(@RequestParam String parentId) {
try {
if (StringUtils.isBlank(parentId)) {
return badRequest("父级ID不能为空");
}
List<OrganizationVo> children = typeTreeService.getChildOrgs(parentId);
return success(children, "获取子组织成功");
} catch (Exception e) {
log.error("获取子组织失败", e);
return error("获取子组织失败:" + e.getMessage());
}
}
/**
* 根据课程名称查询课程
*/
@GetMapping("/by-name")
public JsonResponse<Course> getCourseByName(@RequestParam String name) {
try {
if (StringUtils.isBlank(name)) {
return badRequest("课程名称不能为空");
}
Course course = courseDao.findOne(
FieldFilters.eq("name", name),
FieldFilters.eq("deleted", false)
);
if (course == null) {
return success(null, "课程不存在");
}
return success(course, "查询成功");
} catch (Exception e) {
log.error("根据课程名称查询失败", e);
return error("查询失败:" + e.getMessage());
}
}
// 测试接口
@PostMapping("/test")
public JsonResponse<List<CourseContentDto>> test() {
try {
// 1. 查询系统中所有未被删除的课程内容(核心全量数据)
List<CourseContent> allContentList = ccDao.findList(
OrderCondition.asc("courseId"),
// 按课程ID+排序号排序
FieldFilters.eq("deleted", false) // 过滤已删除数据
);
// 2. 无数据时返回空列表+提示
if (allContentList.isEmpty()) {
return success(Collections.emptyList(), "系统中暂无课程内容数据");
}
// 3. 组装每个课程内容的完整DTO关联作业/考试/评估)
List<CourseContentDto> allDtoList = new ArrayList<>();
for (CourseContent content : allContentList) {
CourseContentDto dto = new CourseContentDto();
dto.setContent(content); // 课程内容基础数据
dto.setHomework(ccontentService.getHomework(content.getId())); // 关联作业
dto.setExam(ccontentService.getExam(content.getId())); // 关联考试
dto.setAssess(ccontentService.getAssess(content.getId())); // 关联评估
allDtoList.add(dto);
}
log.info("全查询返回数据条数:{}", allDtoList.size());
// 5. 返回全量数据
return success(allDtoList, "全查询成功,共返回 " + allDtoList.size() + " 条课程内容数据");
} catch (Exception e) {
log.error("全查询课程内容数据失败", e);
return error("全查询失败:" + e.getMessage());
}
}
@PostMapping("/save-section")
public JsonResponse<CourseSection> saveSection(CourseSection section){

View File

@@ -31,6 +31,47 @@ import javax.persistence.Query;
public class CourseDao extends BaseDao<Course> {
@PersistenceContext
private EntityManager entityManager;
/**
* 单条课程查询
* @param filters 查询条件
* @return Course对象
*/
public Course findOneWithoutDuration(List<IFieldFilter> filters) throws Exception {
QueryBuilder query = QueryBuilder.from(Course.class);
// 只查询必要字段彻底排除courseDuration
query.addFields("id", "name", "deleted", "status", "published", "enabled", "orgId", "sysCreateBy");
if (filters != null && !filters.isEmpty()) {
query.addFilters(filters);
}
// 只查1条
query.setPageSize(1);
List<Object[]> list = this.findListFields(query.builder());
if (list.isEmpty()) {
return null;
}
// 封装为Course对象仅赋值查询的字段
Object[] objs = list.get(0);
Course course = new Course();
course.setId((String) objs[0]);
course.setName((String) objs[1]);
course.setDeleted((Boolean) objs[2]);
course.setStatus((Integer) objs[3]);
course.setPublished((Boolean) objs[4]);
course.setEnabled((Boolean) objs[5]);
course.setOrgId((String) objs[6]);
course.setSysCreateBy((String) objs[7]);
return course;
}
// 按课程名称查询
public Course findByNameWithoutDuration(String courseName) throws Exception {
List<IFieldFilter> filters = new ArrayList<>();
filters.add(FieldFilters.eq("name", courseName));
filters.add(FieldFilters.eq("deleted", false));
return findOneWithoutDuration(filters);
}
/**
* 课程分页 搜索查询
* */

View File

@@ -0,0 +1,41 @@
package com.xboe.module.course.dto;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 批量上传响应 DTO
*/
@Data
public class BatchUploadResponseDto {
private boolean success;
private String message;
private int totalCount;
private int successCount;
private int failCount;
private List<UploadResult> results;
private String courseId;
@Data
public static class UploadResult {
private int itemIndex; // 标识的是第几个上传项
private String contentName;
private Integer contentType;
private Integer resourceType;
private boolean success;
private String message;
private String contentId;
private List<ResourceResult> resources;
}
@Data
public static class ResourceResult {
private String resourceName;
private boolean success;
private String message;
private String fileId;
private String fileUrl;
}
}

View File

@@ -0,0 +1,256 @@
package com.xboe.module.course.dto;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 批量上传DTO支持9种资源视频、音频、文档、图文、SCORM包、外部链接、作业、考试、评估
*/
@Data
public class BatchUploadWithNullDto {
@NotBlank(message = "课程ID不能为空")
private String courseId;
@NotNull(message = "内容项列表不能为空")
private List<ContentItem> contentItems;
/**
* 资源归属信息(课件资源使用)
*/
private ResourceOwnerInfo ownerInfo;
@NotNull(message = "操作类型不能为空")
private Integer operationType; // 1新增
/**
* 内容项DTO支持9种资源
*/
@Data
public static class ContentItem {
/**
* 内容ID
*/
private String contentId;
@Size(max = 200, message = "内容名称不能超过200字")
private String contentName;
/**
* 内容类型:
* 1: 课件包含6种资源类型
* 2: 作业
* 3: 考试
* 4: 评估
*/
private Integer contentType;
/**
* 排序索引
*/
private Integer sortIndex;
/**
* 课时(分钟)
*/
private Integer duration;
/**
* 是否删除
*/
private Boolean deleted;
/**
* 章节ID关联到特定的小节
*/
private String csectionId;
// ---------- 课件相关字段 ----------
/**
* 资源类型(仅课件使用):
* 10: 视频
* 20: 音频
* 40: 文档
* 41: 图文
* 50: SCORM包
* 90: 外部链接
*/
private Integer resourceType;
/**
* 文件资源信息
*/
private FileResourceInfo fileResource;
/**
* 图文资源信息
*/
private GraphicTextResourceInfo graphicTextResource;
/**
* 外部链接资源信息
*/
private ExternalLinkResourceInfo externalLinkResource;
/**
* 作业相关字段
*/
private HomeworkInfo homeworkInfo;
/**
* 考试相关字段
*/
private ExamInfo examInfo;
/**
* 评估相关字段
*/
private AssessInfo assessInfo;
}
/**
* 文件资源信息
*/
@Data
public static class FileResourceInfo {
private String fileBase64;
private String originalFileName;
private Boolean down;
private String remark;
private Integer device;
private String decoder;
private Integer videoWidth;
private Integer videoHeight;
}
/**
* 图文资源信息
*/
@Data
public static class GraphicTextResourceInfo {
private String title;
private String content;
private String imageBase64;
private String imageName;
private TextFormat format;
private String fontSize;
private String lineHeight;
private String textAlign;
}
/**
* 外部链接资源信息
*/
@Data
public static class ExternalLinkResourceInfo {
private String url;
private Integer openType;
private String title;
private String description;
}
/**
* 作业信息
*/
@Data
public static class HomeworkInfo {
private String name;
private String content;
private String file;
private LocalDateTime deadTime;
private Integer submitMode;
}
/**
* 考试信息
*/
@Data
public static class ExamInfo {
private String testName;
private Integer testDuration;
private Boolean showAnalysis;
private Boolean showAnswer;
private Integer times;
private Integer arrange;
private Integer scoringType;
private Integer passLine;
private Boolean randomMode;
private Integer qnum;
private Float qscore;
private Integer paperType;
private Boolean percentScore;
private String paperId;
private String paperContent;
private String info;
}
/**
* 评估信息
*/
@Data
public static class AssessInfo {
private String assessId;
private String question;
private Integer qType;
}
/**
* 资源归属信息
*/
@Data
public static class ResourceOwnerInfo {
private String orgId;
private String orgName;
private String resOwner1;
private String resOwner2;
private String resOwner3;
private String ownership1;
private String ownership2;
private String ownership3;
}
/**
* 文本格式
*/
@Data
public static class TextFormat {
private Boolean bold;
private Boolean italic;
private Boolean underline;
private Boolean strikethrough;
}
/**
* 验证整个DTO
*/
public List<String> validate() {
List<String> errors = new ArrayList<>();
if (StringUtils.isBlank(courseId)) {
errors.add("课程ID不能为空");
}
if (operationType == null) {
errors.add("操作类型不能为空");
} else if (operationType != 1 ) {
errors.add("操作类型必须是1(新增)");
}
if (CollectionUtils.isEmpty(contentItems)) {
errors.add("内容项列表不能为空");
}
return errors;
}
}

View File

@@ -0,0 +1,134 @@
package com.xboe.module.course.dto;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
/**
* 课程资源上传 DTO用于单个课件资源处理
*/
@Data
public class CourseResourceUploadDto {
@NotBlank(message = "课程ID不能为空")
private String courseId;
@NotNull(message = "资源列表不能为空")
private List<CourseFileResource> resources;
/**
* 资源归属信息
*/
private ResourceOwnerInfo ownerInfo;
/**
* 资源归属信息DTO
*/
@Data
public static class ResourceOwnerInfo {
private String orgId;
private String orgName;
private String resOwner1;
private String resOwner2;
private String resOwner3;
private String ownership1;
private String ownership2;
private String ownership3;
}
/**
* 课程文件资源
*/
@Data
public static class CourseFileResource {
/**
* 资源类型:
* 10: 视频
* 20: 音频
* 40: 文档
* 41: 图文
* 50: SCORM包
* 90: 外部链接
*/
@NotNull(message = "资源类型不能为空")
private Integer resType;
@NotBlank(message = "资源名称不能为空")
@Size(max = 100, message = "资源名称不能超过100字")
private String name;
private String fileName;
private String fileType;
private Integer fileSize;
private String filePath;
private String previewFilePath;
private Integer duration;
private Boolean down = false;
@Size(max = 200, message = "备注不能超过200字")
private String remark;
private Integer device;
private String fileBase64;
private String originalFileName;
private GraphicTextContent graphicText;
private ExternalLinkContent externalLink;
private String decoder;
private Integer videoWidth;
private Integer videoHeight;
private String ownership1;
private String ownership2;
private String ownership3;
private Integer converStatus = 0;
private String converError;
}
/**
* 图文内容DTO
*/
@Data
public static class GraphicTextContent {
@NotBlank(message = "图文标题不能为空")
@Size(max = 100, message = "图文标题不能超过100字")
private String title;
@NotBlank(message = "图文内容不能为空")
private String content;
private String imageBase64;
private String imageName;
private TextFormat format;
private String fontSize;
private String lineHeight;
private String textAlign;
}
/**
* 外部链接内容DTO
*/
@Data
public static class ExternalLinkContent {
@NotBlank(message = "链接地址不能为空")
private String url;
private Integer openType = 2;
private String title;
private String description;
}
/**
* 文本格式DTO
*/
@Data
public static class TextFormat {
private Boolean bold = false;
private Boolean italic = false;
private Boolean underline = false;
private Boolean strikethrough = false;
}
}

View File

@@ -0,0 +1,113 @@
package com.xboe.module.course.dto;
import com.xboe.module.course.entity.CourseCrowd;
import com.xboe.module.course.entity.CourseTeacher;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
/**
* 课程课件保存/编辑 DTO
*/
@Data
public class CourseWareSaveDto {
@NotNull(message = "课程基础信息不能为空")
private CourseInfo course;
@NotNull(message = "至少选择一位授课教师")
private List<CourseTeacher> teacherList;
private List<CourseCrowd> crowdList;
/**
* 课程分类(三级)
*/
@Data
public static class TypeLevelInfo {
@NotBlank(message = "分类ID不能为空")
@Size(max = 20, message = "分类ID长度不能超过20位")
private String id;
@Size(max = 20, message = "分类名称长度不能超过20位")
private String name;
private String parentId;
}
@NotNull(message = "课程一级分类不能为空")
private TypeLevelInfo sysType1;
private TypeLevelInfo sysType2;
private TypeLevelInfo sysType3;
/**
* 资源归属(三级)
*/
@Data
public static class OrgLevelInfo {
@NotBlank(message = "组织ID不能为空")
@Size(max = 20, message = "组织ID长度不能超过20位")
private String id;
@Size(max = 50, message = "组织名称长度不能超过50位")
private String name;
private String parentId;
}
@NotNull(message = "资源一级归属不能为空")
private OrgLevelInfo resOwner1;
private OrgLevelInfo resOwner2;
private OrgLevelInfo resOwner3;
@NotNull(message = "观看设置不能为空")
private Integer device;
/**
* 课程信息DTO
*/
@Data
public static class CourseInfo {
private String id;
private Integer sysVersion;
@NotBlank(message = "课程名称不能为空")
@Size(max = 100, message = "课程名称不能超过100字")
private String name;
@Size(max = 200, message = "课程价值不能超过200字")
private String value;
@Size(max = 500, message = "课程简介不能超过500字")
private String summary;
@Size(max = 50, message = "目标人群不能超过50字")
private String forUsers;
@Size(max = 200, message = "课程标签不能超过200字")
private String tags;
private String coverImg;
private Integer type;
private String orgId;
private Boolean enabled = true;
private Boolean isTop = false;
private Integer studyTime;
private String overview;
private Integer openCourse;
private Boolean visible = true;
private Boolean orderStudy = false;
private String keywords;
private String forScene;
private String createFrom;
}
}

View File

@@ -0,0 +1,53 @@
package com.xboe.module.course.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 层级结构课程DTO
*/
@Data
public class HierarchicalCourseDto {
@NotBlank(message = "课程ID不能为空")
private String courseId;
@NotNull(message = "操作类型不能为空")
private Integer operationType;
private BatchUploadWithNullDto.ResourceOwnerInfo ownerInfo;
@NotEmpty(message = "章列表不能为空")
private List<Chapter> chapters;
@Data
public static class Chapter {
@NotBlank(message = "章ID不能为空")
private String chapterId;
private String chapterName;
@NotEmpty(message = "节列表不能为空")
private List<Section> sections;
}
/**
* 节信息
*/
@Data
public static class Section {
@NotBlank(message = "节ID不能为空")
private String sectionId;
private String sectionName;
@NotNull(message = "内容项不能为空")
private BatchUploadWithNullDto.ContentItem content;
}
}

View File

@@ -126,7 +126,7 @@ public class Course extends BaseEntity {
/**
* 课程名称
* */
@Column(name = "name",nullable=false, length = 100)
@Column(name = "name",nullable=false, length = 50)
private String name;
@Column(name = "fulltext_id",length = 36)
@@ -409,13 +409,15 @@ public class Course extends BaseEntity {
/**
* 课程时长(秒)
*/
@Column(name = "course_duration")
// @Column(name = "course_duration")
@Transient
private Long courseDuration;
/**
* 排序权重
*/
@Column(name = "sort_weight")
// @Column(name = "sort_weight")
@Transient
private Integer sortWeight;
/**
@@ -423,7 +425,8 @@ public class Course extends BaseEntity {
* teacher-教师端
* admin-管理员端
*/
@Column(name = "create_from")
// @Column(name = "create_from")
@Transient
private String createFrom;
public Course(String id,String name,String summary,String coverImg,String sysCreateAid,String sysCreateBy,Integer type,LocalDateTime publishTime){

View File

@@ -0,0 +1,63 @@
package com.xboe.module.course.service;
import com.xboe.module.course.dto.BatchUploadResponseDto;
import com.xboe.module.course.dto.BatchUploadWithNullDto;
import com.xboe.module.course.dto.CourseResourceUploadDto;
import java.util.List;
/**
* 课程内容验证 服务接口
*/
public interface CourseContentValidationService {
/**
* 验证批量上传DTO
*/
List<String> validateBatchUpload(BatchUploadWithNullDto dto);
/**
* 验证内容项
*/
List<String> validateContentItem(BatchUploadWithNullDto.ContentItem item, Integer operationType, int index);
/**
* 验证资源上传DTO
*/
String validateResource(CourseResourceUploadDto.CourseFileResource resource);
/**
* 验证资源类型是否有效
*/
boolean isValidResourceType(Integer resType);
/**
* 验证文件扩展名
*/
boolean isValidFileExtension(Integer resType, String fileName);
/**
* 获取文件扩展名
*/
String getFileExtension(String filename);
/**
* 获取资源类型名称
*/
String getResourceTypeName(Integer resType);
/**
* 创建一个新的批量上传响应DTO
* @param courseId 课程ID
* @param totalCount 总数
* @return BatchUploadResponseDto
*/
BatchUploadResponseDto createBatchUploadResponse(String courseId, int totalCount);
/**
* 添加结果到批量上传响应DTO
* @param response 响应DTO
* @param result 结果
*/
void addResultToBatchUploadResponse(BatchUploadResponseDto response, BatchUploadResponseDto.UploadResult result);
}

View File

@@ -0,0 +1,54 @@
package com.xboe.module.course.service;
import com.xboe.module.course.dto.CourseResourceUploadDto;
import com.xboe.module.course.entity.CourseFile;
import java.util.List;
/**
* 课程资源服务接口
*/
public interface CourseResourceService {
/**
* 处理课程资源(核心方法)
*
* @param courseId 课程ID
* @param resources 资源列表
* @param orgId 组织ID
* @param orgName 组织名称
* @param resOwner1 资源归属1级
* @param resOwner2 资源归属2级
* @param resOwner3 资源归属3级
* @param ownership1 所有权1级
* @param ownership2 所有权2级
* @param ownership3 所有权3级
*/
void processCourseResources(String courseId,
List<CourseResourceUploadDto.CourseFileResource> resources,
String orgId, String orgName,
String resOwner1, String resOwner2, String resOwner3,
String ownership1, String ownership2, String ownership3);
/**
* 获取课程资源列表
*
* @param courseId 课程ID
* @return 资源列表
*/
List<CourseFile> getCourseResources(String courseId);
/**
* 删除课程资源
*
* @param courseId 课程ID
*/
void deleteCourseResources(String courseId);
/**
* 删除单个资源
*
* @param resourceId 资源ID
*/
void deleteResource(String resourceId);
}

View File

@@ -0,0 +1,18 @@
package com.xboe.module.course.service;
import com.xboe.module.course.dto.BatchUploadWithNullDto;
import com.xboe.module.course.dto.BatchUploadResponseDto;
/**
* 原子批量上传服务接口
* 支持9种资源的上传
*/
public interface IAtomicBatchUploadService {
/**
* 原子批量上传
* @param uploadDto 上传数据
* @return 上传结果
*/
BatchUploadResponseDto atomicBatchUpload(BatchUploadWithNullDto uploadDto);
}

View File

@@ -2,16 +2,18 @@ package com.xboe.module.course.service;
import com.xboe.common.PageList;
import com.xboe.module.course.dto.CourseContentDto;
import com.xboe.module.course.dto.CourseWareSaveDto;
import com.xboe.module.course.dto.SortItem;
import com.xboe.module.course.entity.CourseAssess;
import com.xboe.module.course.entity.CourseContent;
import com.xboe.module.course.entity.CourseExam;
import com.xboe.module.course.entity.CourseHomeWork;
import java.util.List;
/**
* 课程内容,当前是分着处理,之后看是否与课程服务合并在一起
* 课程内容
* */
public interface ICourseContentService{
@@ -20,6 +22,13 @@ public interface ICourseContentService{
* @param dto
*/
void saveOrUpdate(CourseContentDto dto);
/**
* 保存更新课件
* @param dto
* @return
*/
void saveOrUpdateCourseware(CourseWareSaveDto dto);
/**
@@ -37,13 +46,12 @@ public interface ICourseContentService{
void updateName(String id,String name);
/**
* 对于已发布过的课程 ,采用逻辑删除,对于未发布过的课程采用物理删除
* @param id
*/
void delete(String id,int ctype, boolean flag);
/**
* 用于检查课程是否完整,是否可以提交了,只是部分必要的字段
* 用于检查课程是否完整,是否可以提交了
* @param courseId
* @return
*/
@@ -58,7 +66,6 @@ public interface ICourseContentService{
/**
* 根据课程id、章节id得到课程所有目录即章节分页顺序按orderIndex 从小到大的顺序
* 25.11.26新增
*
* @param pageIndex 页码
* @param pageSize 每页数量

View File

@@ -0,0 +1,37 @@
package com.xboe.module.course.service;
import com.xboe.module.course.vo.TypeTreeVo;
import com.xboe.system.organization.vo.OrganizationVo;
import java.util.List;
/**
* 分类树和组织树服务接口
*/
public interface TypeTreeService {
/**
* 获取课程分类树(三级)
*/
List<TypeTreeVo> getCourseTypeTree();
/**
* 获取资源归属树(组织机构树)
*/
List<OrganizationVo> getResourceOwnerTree();
/**
* 根据父级ID获取子分类
*/
List<TypeTreeVo> getChildTypes(String parentId);
/**
* 根据父级ID获取子组织
*/
List<OrganizationVo> getChildOrgs(String parentId);
/**
* 验证分类层级关系
*/
boolean validateTypeHierarchy(String childId, String parentId);
}

View File

@@ -0,0 +1,707 @@
package com.xboe.module.course.service.impl;
import com.xboe.core.orm.FieldFilters;
import com.xboe.module.course.dao.*;
import com.xboe.module.course.dto.BatchUploadWithNullDto;
import com.xboe.module.course.dto.BatchUploadResponseDto;
import com.xboe.module.course.dto.CourseResourceUploadDto;
import com.xboe.module.course.entity.*;
import com.xboe.module.course.service.CourseContentValidationService;
import com.xboe.module.course.service.IAtomicBatchUploadService;
import com.xboe.module.course.service.CourseResourceService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Slf4j
@Service
public class AtomicBatchUploadServiceImpl implements IAtomicBatchUploadService {
@Resource
private CourseContentDao courseContentDao;
@Resource
private CourseFileDao courseFileDao;
@Resource
private CourseHomeWorkDao courseHomeWorkDao;
@Resource
private CourseExamDao courseExamDao;
@Resource
private CourseAssessDao courseAssessDao;
@Resource
private CourseResourceService courseResourceService;
@Resource
private CourseContentValidationService courseContentValidationService;
/**
* 原子批量上传
*/
@Transactional(rollbackFor = Exception.class)
@Override
public BatchUploadResponseDto atomicBatchUpload(BatchUploadWithNullDto uploadDto) {
log.info("开始原子批量上传课程ID: {}, 操作类型: {}, 内容项数量: {}",
uploadDto.getCourseId(),
uploadDto.getOperationType(),
uploadDto.getContentItems().size());
// 创建响应对象
BatchUploadResponseDto response = courseContentValidationService.createBatchUploadResponse(
uploadDto.getCourseId(),
uploadDto.getContentItems().size()
);
try {
// 验证输入
List<String> validationErrors = uploadDto.validate();
if (CollectionUtils.isNotEmpty(validationErrors)) {
throw new RuntimeException("参数验证失败: " + String.join("; ", validationErrors));
}
// 设置资源归属信息(用于课件资源)
BatchUploadWithNullDto.ResourceOwnerInfo ownerInfo = uploadDto.getOwnerInfo();
String orgId = ownerInfo != null ? ownerInfo.getOrgId() : null;
String orgName = ownerInfo != null ? ownerInfo.getOrgName() : null;
String resOwner1 = ownerInfo != null ? ownerInfo.getResOwner1() : null;
String resOwner2 = ownerInfo != null ? ownerInfo.getResOwner2() : null;
String resOwner3 = ownerInfo != null ? ownerInfo.getResOwner3() : null;
String ownership1 = ownerInfo != null ? ownerInfo.getOwnership1() : null;
String ownership2 = ownerInfo != null ? ownerInfo.getOwnership2() : null;
String ownership3 = ownerInfo != null ? ownerInfo.getOwnership3() : null;
// 处理每个内容项
for (int i = 0; i < uploadDto.getContentItems().size(); i++) {
BatchUploadWithNullDto.ContentItem item = uploadDto.getContentItems().get(i);
BatchUploadResponseDto.UploadResult itemResult = processContentItem(
item, i, uploadDto.getOperationType(), uploadDto.getCourseId(),
item.getCsectionId(), orgId, orgName, resOwner1, resOwner2,
resOwner3, ownership1, ownership2, ownership3
);
courseContentValidationService.addResultToBatchUploadResponse(response, itemResult);
// 如果有失败项,抛出异常触发回滚
if (!itemResult.isSuccess()) {
throw new RuntimeException("" + (i + 1) + "项处理失败: " + itemResult.getMessage());
}
}
log.info("原子批量上传成功课程ID: {}, 成功: {}",
uploadDto.getCourseId(), response.getSuccessCount());
return response;
} catch (Exception e) {
log.error("原子批量上传失败课程ID: {}", uploadDto.getCourseId(), e);
throw new RuntimeException("原子批量上传失败: " + e.getMessage(), e);
}
}
/**
* 处理单个内容项
*/
private BatchUploadResponseDto.UploadResult processContentItem(
BatchUploadWithNullDto.ContentItem item, int itemIndex, Integer operationType,
String courseId, String csectionId,
String orgId, String orgName, String resOwner1, String resOwner2, String resOwner3,
String ownership1, String ownership2, String ownership3) {
BatchUploadResponseDto.UploadResult result = new BatchUploadResponseDto.UploadResult();
result.setItemIndex(itemIndex);
result.setContentName(item.getContentName());
result.setContentType(item.getContentType());
result.setResourceType(item.getResourceType());
try {
String contentId;
if (operationType == 1) { // 新增操作
contentId = createContentItem(item, courseId, csectionId,
orgId, orgName, resOwner1, resOwner2, resOwner3,
ownership1, ownership2, ownership3);
} else if (operationType == 2) { // 更新操作
contentId = updateContentItem(item, courseId, csectionId,
orgId, orgName, resOwner1, resOwner2, resOwner3,
ownership1, ownership2, ownership3);
} else {
throw new RuntimeException("不支持的操作类型: " + operationType);
}
result.setContentId(contentId);
result.setSuccess(true);
result.setMessage("处理成功");
} catch (Exception e) {
result.setSuccess(false);
result.setMessage("处理失败: " + e.getMessage());
log.error("处理第{}项内容失败: {}", itemIndex + 1, e.getMessage(), e);
}
return result;
}
/**
* 创建内容项
*/
private String createContentItem(
BatchUploadWithNullDto.ContentItem item, String courseId, String csectionId,
String orgId, String orgName, String resOwner1, String resOwner2, String resOwner3,
String ownership1, String ownership2, String ownership3) {
// 验证新增操作的必要字段
if (item.getContentType() == null) {
throw new RuntimeException("新增操作时内容类型不能为null");
}
// 创建课程内容
CourseContent courseContent = new CourseContent();
courseContent.setCourseId(courseId);
courseContent.setContentName(getContentName(item, true));
courseContent.setContentType(item.getContentType());
courseContent.setSortIndex(item.getSortIndex() != null ? item.getSortIndex() : 0);
courseContent.setCsectionId(csectionId);
courseContent.setDuration(item.getDuration() != null ? item.getDuration() : 0);
courseContent.setDeleted(item.getDeleted() != null ? item.getDeleted() : false);
courseContentDao.save(courseContent);
String contentId = courseContent.getId();
log.info("创建课程内容成功ID: {}, 类型: {}", contentId, item.getContentType());
// 根据内容类型处理具体内容
processContentByType(item, contentId, courseId, 1,
orgId, orgName, resOwner1, resOwner2, resOwner3,
ownership1, ownership2, ownership3);
return contentId;
}
/**
* 更新内容项(更新操作)
*/
private String updateContentItem(
BatchUploadWithNullDto.ContentItem item, String courseId, String csectionId,
String orgId, String orgName, String resOwner1, String resOwner2, String resOwner3,
String ownership1, String ownership2, String ownership3) {
// 验证内容ID
if (StringUtils.isBlank(item.getContentId())) {
throw new RuntimeException("更新操作时内容ID不能为空");
}
// 获取现有内容
CourseContent courseContent = courseContentDao.get(item.getContentId());
if (courseContent == null) {
throw new RuntimeException("课程内容不存在: " + item.getContentId());
}
// 验证课程ID匹配
if (!courseContent.getCourseId().equals(courseId)) {
throw new RuntimeException("课程ID不匹配");
}
String contentId = item.getContentId();
// 更新课程内容只更新非null字段
updateCourseContentWithNull(courseContent, item, csectionId);
courseContentDao.update(courseContent);
log.info("更新课程内容成功ID: {}", contentId);
// 根据内容类型处理具体内容
processContentByType(item, contentId, courseId, 2,
orgId, orgName, resOwner1, resOwner2, resOwner3,
ownership1, ownership2, ownership3);
return contentId;
}
/**
* 更新课程内容只更新非null字段
*/
private void updateCourseContentWithNull(CourseContent courseContent,
BatchUploadWithNullDto.ContentItem item,
String csectionId) {
if (item.getContentName() != null) {
courseContent.setContentName(item.getContentName());
}
if (item.getContentType() != null) {
courseContent.setContentType(item.getContentType());
}
if (item.getSortIndex() != null) {
courseContent.setSortIndex(item.getSortIndex());
}
if (csectionId != null) {
courseContent.setCsectionId(csectionId);
}
if (item.getDuration() != null) {
courseContent.setDuration(item.getDuration());
}
if (item.getDeleted() != null) {
courseContent.setDeleted(item.getDeleted());
}
}
/**
* 根据内容类型处理具体内容
*/
private void processContentByType(
BatchUploadWithNullDto.ContentItem item, String contentId, String courseId,
Integer operationType,
String orgId, String orgName, String resOwner1, String resOwner2, String resOwner3,
String ownership1, String ownership2, String ownership3) {
Integer contentType = item.getContentType();
switch (contentType != null ? contentType : 0) {
case 1: // 课件
processCourseware(item, contentId, courseId, operationType,
orgId, orgName, resOwner1, resOwner2, resOwner3,
ownership1, ownership2, ownership3);
break;
case 2: // 作业
processHomework(item, contentId, courseId, operationType);
break;
case 3: // 考试
processExam(item, contentId, courseId, operationType);
break;
case 4: // 评估
processAssess(item, contentId, courseId, operationType);
break;
case 0: // contentType为null更新时不修改类型
// 不处理,保持原内容类型
break;
}
}
/**
* 处理课件
*/
private void processCourseware(
BatchUploadWithNullDto.ContentItem item, String contentId, String courseId,
Integer operationType,
String orgId, String orgName, String resOwner1, String resOwner2, String resOwner3,
String ownership1, String ownership2, String ownership3) {
// 如果是新增操作resourceType不能为null
if (operationType == 1 && item.getResourceType() == null) {
throw new RuntimeException("新增课件时资源类型不能为null");
}
// 如果resourceType不为null处理对应的资源
if (item.getResourceType() != null) {
List<CourseResourceUploadDto.CourseFileResource> resources =
convertToCourseFileResources(item);
if (!resources.isEmpty()) {
// 调用资源处理服务
courseResourceService.processCourseResources(contentId, resources,
orgId, orgName,
resOwner1, resOwner2, resOwner3,
ownership1, ownership2, ownership3);
log.info("处理课件资源成功内容ID: {}, 资源类型: {}", contentId, item.getResourceType());
}
}
}
/**
* 转换为课程文件资源
*/
private List<CourseResourceUploadDto.CourseFileResource> convertToCourseFileResources(
BatchUploadWithNullDto.ContentItem item) {
List<CourseResourceUploadDto.CourseFileResource> resources = new ArrayList<>();
// 根据资源类型创建对应的资源
if (item.getResourceType() != null) {
CourseResourceUploadDto.CourseFileResource resource =
new CourseResourceUploadDto.CourseFileResource();
resource.setResType(item.getResourceType());
resource.setName(item.getContentName() != null ? item.getContentName() :
getResourceTypeName(item.getResourceType()) + "资源");
resource.setDevice(3); // 默认多端可见
// 处理文件资源视频、音频、文档、SCORM包
if (item.getFileResource() != null &&
(item.getResourceType() == 10 || item.getResourceType() == 20 ||
item.getResourceType() == 40 || item.getResourceType() == 50)) {
BatchUploadWithNullDto.FileResourceInfo fileInfo = item.getFileResource();
resource.setFileBase64(fileInfo.getFileBase64());
resource.setOriginalFileName(fileInfo.getOriginalFileName());
resource.setDown(fileInfo.getDown());
resource.setRemark(fileInfo.getRemark());
resource.setDevice(fileInfo.getDevice() != null ? fileInfo.getDevice() : 3);
resource.setDecoder(fileInfo.getDecoder());
resource.setVideoWidth(fileInfo.getVideoWidth());
resource.setVideoHeight(fileInfo.getVideoHeight());
}
// 处理图文资源
if (item.getGraphicTextResource() != null && item.getResourceType() == 41) {
BatchUploadWithNullDto.GraphicTextResourceInfo textInfo = item.getGraphicTextResource();
CourseResourceUploadDto.GraphicTextContent graphicText =
new CourseResourceUploadDto.GraphicTextContent();
graphicText.setTitle(textInfo.getTitle());
graphicText.setContent(textInfo.getContent());
graphicText.setImageBase64(textInfo.getImageBase64());
graphicText.setImageName(textInfo.getImageName());
if (textInfo.getFormat() != null) {
CourseResourceUploadDto.TextFormat format = new CourseResourceUploadDto.TextFormat();
format.setBold(textInfo.getFormat().getBold());
format.setItalic(textInfo.getFormat().getItalic());
format.setUnderline(textInfo.getFormat().getUnderline());
format.setStrikethrough(textInfo.getFormat().getStrikethrough());
graphicText.setFormat(format);
}
graphicText.setFontSize(textInfo.getFontSize());
graphicText.setLineHeight(textInfo.getLineHeight());
graphicText.setTextAlign(textInfo.getTextAlign());
resource.setGraphicText(graphicText);
}
// 处理外部链接资源
if (item.getExternalLinkResource() != null && item.getResourceType() == 90) {
BatchUploadWithNullDto.ExternalLinkResourceInfo linkInfo = item.getExternalLinkResource();
CourseResourceUploadDto.ExternalLinkContent externalLink =
new CourseResourceUploadDto.ExternalLinkContent();
externalLink.setUrl(linkInfo.getUrl());
externalLink.setOpenType(linkInfo.getOpenType() != null ? linkInfo.getOpenType() : 2);
externalLink.setTitle(linkInfo.getTitle());
externalLink.setDescription(linkInfo.getDescription());
resource.setExternalLink(externalLink);
}
resources.add(resource);
}
return resources;
}
/**
* 处理作业
*/
private void processHomework(BatchUploadWithNullDto.ContentItem item,
String contentId, String courseId,
Integer operationType) {
if (operationType == 1) { // 新增操作
if (item.getHomeworkInfo() == null) {
throw new RuntimeException("新增作业时作业信息不能为null");
}
BatchUploadWithNullDto.HomeworkInfo homeworkInfo = item.getHomeworkInfo();
CourseHomeWork homework = new CourseHomeWork();
homework.setContentId(contentId);
homework.setCourseId(courseId);
homework.setName(homeworkInfo.getName());
homework.setContent(homeworkInfo.getContent());
homework.setFile(homeworkInfo.getFile());
homework.setDeadTime(homeworkInfo.getDeadTime());
homework.setSubmitMode(homeworkInfo.getSubmitMode() != null ?
homeworkInfo.getSubmitMode() : 1);
courseHomeWorkDao.save(homework);
log.info("创建作业成功内容ID: {}", contentId);
} else if (operationType == 2 && item.getHomeworkInfo() != null) { // 更新操作
// 更新操作:查找现有作业
CourseHomeWork existingHomework = courseHomeWorkDao.findOne(
FieldFilters.eq("contentId", contentId));
if (existingHomework != null) {
// 更新现有作业只更新非null字段
updateHomeworkWithNull(existingHomework, item.getHomeworkInfo());
courseHomeWorkDao.update(existingHomework);
log.info("更新作业成功内容ID: {}", contentId);
} else {
// 创建新作业
BatchUploadWithNullDto.HomeworkInfo homeworkInfo = item.getHomeworkInfo();
CourseHomeWork homework = new CourseHomeWork();
homework.setContentId(contentId);
homework.setCourseId(courseId);
homework.setName(homeworkInfo.getName());
homework.setContent(homeworkInfo.getContent());
homework.setFile(homeworkInfo.getFile());
homework.setDeadTime(homeworkInfo.getDeadTime());
homework.setSubmitMode(homeworkInfo.getSubmitMode() != null ?
homeworkInfo.getSubmitMode() : 1);
courseHomeWorkDao.save(homework);
log.info("创建作业成功内容ID: {}", contentId);
}
}
}
/**
* 更新作业只更新非null字段
*/
private void updateHomeworkWithNull(CourseHomeWork homework,
BatchUploadWithNullDto.HomeworkInfo homeworkInfo) {
if (homeworkInfo.getName() != null) {
homework.setName(homeworkInfo.getName());
}
if (homeworkInfo.getContent() != null) {
homework.setContent(homeworkInfo.getContent());
}
if (homeworkInfo.getFile() != null) {
homework.setFile(homeworkInfo.getFile());
}
if (homeworkInfo.getDeadTime() != null) {
homework.setDeadTime(homeworkInfo.getDeadTime());
}
if (homeworkInfo.getSubmitMode() != null) {
homework.setSubmitMode(homeworkInfo.getSubmitMode());
}
}
/**
* 处理考试
*/
private void processExam(BatchUploadWithNullDto.ContentItem item,
String contentId, String courseId,
Integer operationType) {
if (operationType == 1) { // 新增操作
if (item.getExamInfo() == null) {
throw new RuntimeException("新增考试时考试信息不能为null");
}
CourseExam exam = createExamFromInfo(contentId, courseId, item.getExamInfo());
courseExamDao.save(exam);
log.info("创建考试成功内容ID: {}", contentId);
} else if (operationType == 2 && item.getExamInfo() != null) { // 更新操作
// 更新操作:查找现有考试
CourseExam existingExam = courseExamDao.findOne(
FieldFilters.eq("contentId", contentId));
if (existingExam != null) {
// 更新现有考试只更新非null字段
updateExamWithNull(existingExam, item.getExamInfo());
courseExamDao.update(existingExam);
log.info("更新考试成功内容ID: {}", contentId);
} else {
// 创建新考试
CourseExam exam = createExamFromInfo(contentId, courseId, item.getExamInfo());
courseExamDao.save(exam);
log.info("创建考试成功内容ID: {}", contentId);
}
}
}
/**
* 创建考试信息
*/
private CourseExam createExamFromInfo(String contentId, String courseId,
BatchUploadWithNullDto.ExamInfo examInfo) {
CourseExam exam = new CourseExam();
exam.setContentId(contentId);
exam.setCourseId(courseId);
exam.setTestName(examInfo.getTestName());
exam.setTestDuration(examInfo.getTestDuration());
exam.setShowAnalysis(examInfo.getShowAnalysis() != null ? examInfo.getShowAnalysis() : false);
exam.setShowAnswer(examInfo.getShowAnswer() != null ? examInfo.getShowAnswer() : false);
exam.setTimes(examInfo.getTimes() != null ? examInfo.getTimes() : 0);
exam.setArrange(examInfo.getArrange() != null ? examInfo.getArrange() : 0);
exam.setScoringType(examInfo.getScoringType() != null ? examInfo.getScoringType() : 1);
exam.setPassLine(examInfo.getPassLine() != null ? examInfo.getPassLine() : 60);
exam.setRandomMode(examInfo.getRandomMode() != null ? examInfo.getRandomMode() : false);
exam.setQnum(examInfo.getQnum());
exam.setQscore(examInfo.getQscore());
exam.setPaperType(examInfo.getPaperType());
exam.setPercentScore(examInfo.getPercentScore() != null ? examInfo.getPercentScore() : true);
exam.setPaperId(examInfo.getPaperId());
exam.setPaperContent(examInfo.getPaperContent());
exam.setInfo(examInfo.getInfo());
return exam;
}
/**
* 更新考试信息
*/
private void updateExamWithNull(CourseExam exam, BatchUploadWithNullDto.ExamInfo examInfo) {
if (examInfo.getTestName() != null) {
exam.setTestName(examInfo.getTestName());
}
if (examInfo.getTestDuration() != null) {
exam.setTestDuration(examInfo.getTestDuration());
}
if (examInfo.getShowAnalysis() != null) {
exam.setShowAnalysis(examInfo.getShowAnalysis());
}
if (examInfo.getShowAnswer() != null) {
exam.setShowAnswer(examInfo.getShowAnswer());
}
if (examInfo.getTimes() != null) {
exam.setTimes(examInfo.getTimes());
}
if (examInfo.getArrange() != null) {
exam.setArrange(examInfo.getArrange());
}
if (examInfo.getScoringType() != null) {
exam.setScoringType(examInfo.getScoringType());
}
if (examInfo.getPassLine() != null) {
exam.setPassLine(examInfo.getPassLine());
}
if (examInfo.getRandomMode() != null) {
exam.setRandomMode(examInfo.getRandomMode());
}
if (examInfo.getQnum() != null) {
exam.setQnum(examInfo.getQnum());
}
if (examInfo.getQscore() != null) {
exam.setQscore(examInfo.getQscore());
}
if (examInfo.getPaperType() != null) {
exam.setPaperType(examInfo.getPaperType());
}
if (examInfo.getPercentScore() != null) {
exam.setPercentScore(examInfo.getPercentScore());
}
if (examInfo.getPaperId() != null) {
exam.setPaperId(examInfo.getPaperId());
}
if (examInfo.getPaperContent() != null) {
exam.setPaperContent(examInfo.getPaperContent());
}
if (examInfo.getInfo() != null) {
exam.setInfo(examInfo.getInfo());
}
}
/**
* 处理评估
*/
private void processAssess(BatchUploadWithNullDto.ContentItem item,
String contentId, String courseId,
Integer operationType) {
if (operationType == 1) { // 新增操作
if (item.getAssessInfo() == null) {
throw new RuntimeException("新增评估时评估信息不能为null");
}
CourseAssess assess = createAssessFromInfo(contentId, courseId, item.getAssessInfo());
courseAssessDao.save(assess);
log.info("创建评估成功内容ID: {}", contentId);
} else if (operationType == 2 && item.getAssessInfo() != null) { // 更新操作
// 更新操作:查找现有评估
CourseAssess existingAssess = courseAssessDao.findOne(
FieldFilters.eq("contentId", contentId));
if (existingAssess != null) {
// 更新现有评估只更新非null字段
updateAssessWithNull(existingAssess, item.getAssessInfo());
courseAssessDao.update(existingAssess);
log.info("更新评估成功内容ID: {}", contentId);
} else {
// 创建新评估
CourseAssess assess = createAssessFromInfo(contentId, courseId, item.getAssessInfo());
courseAssessDao.save(assess);
log.info("创建评估成功内容ID: {}", contentId);
}
}
}
/**
* 创建评估信息
*/
private CourseAssess createAssessFromInfo(String contentId, String courseId,
BatchUploadWithNullDto.AssessInfo assessInfo) {
CourseAssess assess = new CourseAssess();
assess.setContentId(contentId);
assess.setCourseId(courseId);
assess.setAssessId(assessInfo.getAssessId());
assess.setQuestion(assessInfo.getQuestion());
assess.setQType(assessInfo.getQType() != null ? assessInfo.getQType() : 1);
return assess;
}
/**
* 更新评估信息
*/
private void updateAssessWithNull(CourseAssess assess, BatchUploadWithNullDto.AssessInfo assessInfo) {
if (assessInfo.getAssessId() != null) {
assess.setAssessId(assessInfo.getAssessId());
}
if (assessInfo.getQuestion() != null) {
assess.setQuestion(assessInfo.getQuestion());
}
if (assessInfo.getQType() != null) {
assess.setQType(assessInfo.getQType());
}
}
/**
* 获取内容名称
*/
private String getContentName(BatchUploadWithNullDto.ContentItem item, boolean isNew) {
if (item.getContentName() != null) {
return item.getContentName();
}
if (isNew) {
// 新增操作:生成默认名称
if (item.getContentType() != null) {
switch (item.getContentType()) {
case 1:
String resourceTypeName = "课件";
if (item.getResourceType() != null) {
resourceTypeName = getResourceTypeName(item.getResourceType());
}
return resourceTypeName + "-" + UUID.randomUUID().toString().substring(0, 8);
case 2:
return item.getHomeworkInfo() != null && item.getHomeworkInfo().getName() != null ?
item.getHomeworkInfo().getName() : "未命名作业";
case 3:
return item.getExamInfo() != null && item.getExamInfo().getTestName() != null ?
item.getExamInfo().getTestName() : "未命名考试";
case 4:
return "课程评估";
default:
return "未命名内容";
}
}
}
return null;
}
/**
* 获取资源类型名称
*/
private String getResourceTypeName(Integer resType) {
if (resType == null) return "资源";
switch (resType) {
case 10: return "视频";
case 20: return "音频";
case 40: return "文档";
case 41: return "图文";
case 50: return "SCORM包";
case 90: return "外部链接";
default: return "资源";
}
}
}

View File

@@ -11,17 +11,21 @@ import com.xboe.core.cache.IXaskCache;
import com.xboe.core.cache.XaskCacheProvider;
import com.xboe.core.orm.FieldFilters;
import com.xboe.core.orm.IFieldFilter;
import com.xboe.core.orm.IFieldUpdate;
import com.xboe.core.orm.UpdateBuilder;
import com.xboe.module.course.dao.*;
import com.xboe.module.course.dto.CourseContentDto;
import com.xboe.module.course.dto.CourseWareSaveDto;
import com.xboe.module.course.dto.SortItem;
import com.xboe.module.course.entity.CourseAssess;
import com.xboe.module.course.entity.CourseContent;
import com.xboe.module.course.entity.CourseExam;
import com.xboe.module.course.entity.CourseHomeWork;
import com.xboe.module.course.service.ICourseContentService;
import com.xboe.module.course.service.TypeTreeService;
import com.xboe.module.exam.dao.ExamPaperDao;
import com.xboe.module.exam.vo.TestQuestionVo;
import com.xboe.module.teacher.dao.TeacherDao;
import com.xboe.standard.enums.BoedxContentType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -29,11 +33,24 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.transaction.Transactional;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.xboe.module.course.entity.Course;
import com.xboe.module.course.entity.CourseCrowd;
import com.xboe.module.course.entity.CourseTeacher;
import com.xboe.module.teacher.entity.Teacher;
import com.xboe.module.course.dao.CourseDao;
import com.xboe.module.course.dao.CourseCrowdDao;
import com.xboe.module.course.dao.CourseTeacherDao;
import org.apache.commons.collections4.CollectionUtils;
import com.xboe.module.course.dao.*;
@Slf4j
@Service
public class CourseContentServiceImpl implements ICourseContentService {
@@ -59,59 +76,453 @@ public class CourseContentServiceImpl implements ICourseContentService {
@Resource
private ExamPaperDao examPaperDao;
@Resource
private CourseDao courseDao;
@Resource
private CourseTeacherDao courseTeacherDao;
@Resource
private CourseCrowdDao courseCrowdDao;
@Resource
private TeacherDao teacherDao;
@Resource
private TypeTreeService typeTreeService;
@Override
@Transactional
public void saveOrUpdate(CourseContentDto dto) {
CourseContent cc=dto.getContent();
CourseAssess assess=dto.getAssess();
CourseExam exam=dto.getExam();
CourseHomeWork homework=dto.getHomework();
CourseContent cc=dto.getContent(); // 获取内容信息
CourseAssess assess=dto.getAssess(); // 获取评估信息
CourseExam exam=dto.getExam(); // 获取考试信息
CourseHomeWork homework=dto.getHomework(); // 获取作业信息
// 当新增的情况cc.getId() 为空,
// 表示这是一个新创建的对象,而不是更新已有对象
if(StringUtils.isBlank(cc.getId())) {
//新增的情况
// 设置课程内容的删除状态标记,
// 采用的是"软删除".
// 重点:表示没有删除
cc.setDeleted(false);
// 检查课程内容的时长是否为空
if(cc.getDuration()==null) {
// 如果时长为空,将其设置为默认值 0
cc.setDuration(0);
}
//如果是没有目录的,并具是课程内容
//检查是否为无目录的课程
if(dto.getType()!=null && dto.getType()==10) {
// 如果是无目录的课程,检查是否是表课件,如果是表的课件
if(cc.getSortIndex()==1) {
//先删除之前其它的
// 防止同一课程下出现多个具有相同特殊标识的内容项,比如我之前封面
ccDao.deleteByField("courseId",cc.getCourseId());
}
}
ccDao.save(cc);
ccDao.save(cc); // 将信息保存下来
}else {
ccDao.update(cc);
cc.setSysVersion(ccDao.getVersion(cc.getId()));
cc.setSysVersion(ccDao.getVersion(cc.getId())); // 保存我的版本
}
//添加或保存其它信息
//保存其它信息
if(assess!=null) {
assess.setCourseId(cc.getCourseId());
assessDao.saveOrUpdate(assess);
assess.setCourseId(cc.getCourseId()); // 将我的课程id信息保存进去
assessDao.saveOrUpdate(assess); // 保存评估信息
}
if(exam!=null) {
// 校验选择是否合法
if ((exam.getRandomMode() && !(exam.getQnum() > 0)) || (!exam.getRandomMode() && exam.getQnum() > 0)) {
throw new RuntimeException("随机选题处参数错误");
}
exam.setCourseId(cc.getCourseId());
exam.setContentId(cc.getId());
exam.setCourseId(cc.getCourseId()); // 设置课程id
exam.setContentId(cc.getId()); // 设置内容id
// 是否是百分之测试
if(exam.getPercentScore()==null) {
exam.setPercentScore(true);
exam.setPercentScore(true); // 如果不是的话,改成我的迷人百分制测试
}
examDao.saveOrUpdate(exam);
exam.setSysVersion(examDao.getVersion(exam.getId()));
examDao.saveOrUpdate(exam); // 将内容进行保存
exam.setSysVersion(examDao.getVersion(exam.getId())); // 保存我的版本的ID编号
}
if(homework!=null) {
homework.setCourseId(cc.getCourseId());
homework.setContentId(cc.getId());
homework=homeworkDao.saveOrUpdate(homework);
homework.setSysVersion(homeworkDao.getVersion(homework.getId()));
homework.setCourseId(cc.getCourseId()); // 设置课程id
homework.setContentId(cc.getId()); // 设置内容id
homework=homeworkDao.saveOrUpdate(homework); // 保存作业信息
homework.setSysVersion(homeworkDao.getVersion(homework.getId())); // 保存我的版本的ID编号
}
}
@Override
@Override
@Transactional
public void saveOrUpdateCourseware(CourseWareSaveDto dto) {
try {
// 1. 基础校验
validateBaseInfo(dto);
// 2. 校验分类和归属的层级关系
validateTypeHierarchy(dto);
// 3. 核心逻辑只根据课程ID是否为null判断是新增还是更新
String courseIdFromDto = dto.getCourse().getId();
boolean isNew = (courseIdFromDto == null);
String courseId;
if (isNew) {
// 如果课程ID为null执行新增操作
log.info("课程ID为null执行新增操作");
courseId = createNewCourse(dto.getCourse());
} else {
// 如果课程ID不为null执行更新操作
log.info("课程ID不为null执行更新操作courseId={}", courseIdFromDto);
Course existingCourse = courseDao.get(courseIdFromDto);
if (existingCourse != null) {
courseId = updateExistingCourse(existingCourse, dto.getCourse());
} else {
// 课程ID不为null但找不到对应课程仍然创建新课程
log.info("课程ID '{}' 在数据库中不存在,执行新增操作", courseIdFromDto);
courseId = createNewCourse(dto.getCourse());
}
}
// 4. 绑定分类(三级)
bindClassification(courseId, dto);
// 5. 绑定资源归属(三级)
bindResourceOwnership(courseId, dto);
// 6. 处理观看设置
if (dto.getDevice() != null) {
courseDao.updateFieldById(courseId, "device", dto.getDevice());
} else {
// 默认多端可看
courseDao.updateFieldById(courseId, "device", 3);
}
// 7. 处理授课教师
processTeachers(courseId, dto.getTeacherList());
// 8. 处理受众列表
processCrowds(courseId, dto.getCrowdList());
log.info("课程课件保存成功,操作类型:{}courseId={}",
isNew ? "新增" : "更新", courseId);
} catch (RuntimeException e) {
log.error("课程课件保存/更新异常:{}", e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("课程课件保存/更新系统异常", e);
throw new RuntimeException("课程课件保存/更新失败:" + e.getMessage(), e);
}
}
/**
* 基础信息校验
*/
private void validateBaseInfo(CourseWareSaveDto dto) {
if (dto == null) {
throw new RuntimeException("课程课件参数不能为空");
}
if (StringUtils.isBlank(dto.getCourse().getName())) {
throw new RuntimeException("课程名称不能为空");
}
if (CollectionUtils.isEmpty(dto.getTeacherList())) {
throw new RuntimeException("至少选择一位授课教师");
}
if (dto.getSysType1() == null || StringUtils.isBlank(dto.getSysType1().getId())) {
throw new RuntimeException("课程一级分类不能为空");
}
if (dto.getResOwner1() == null || StringUtils.isBlank(dto.getResOwner1().getId())) {
throw new RuntimeException("资源一级归属不能为空");
}
if (dto.getDevice() == null || (dto.getDevice() < 1 || dto.getDevice() > 3)) {
throw new RuntimeException("观看设置参数错误必须是1、2或3");
}
// 课程名称长度校验
if (dto.getCourse().getName().length() > 100) {
throw new RuntimeException("课程名称不能超过100字");
}
}
/**
* 校验分类和归属的层级关系
*/
private void validateTypeHierarchy(CourseWareSaveDto dto) {
// 校验分类层级
if (dto.getSysType2() != null && StringUtils.isNotBlank(dto.getSysType2().getId())) {
if (dto.getSysType1() == null || StringUtils.isBlank(dto.getSysType1().getId())) {
throw new RuntimeException("选择了二级分类必须有一级分类");
}
// 验证二级分类的父级是否是一级分类
if (!typeTreeService.validateTypeHierarchy(dto.getSysType2().getId(), dto.getSysType1().getId())) {
throw new RuntimeException("二级分类的父级必须是一级分类");
}
}
if (dto.getSysType3() != null && StringUtils.isNotBlank(dto.getSysType3().getId())) {
if (dto.getSysType2() == null || StringUtils.isBlank(dto.getSysType2().getId())) {
throw new RuntimeException("选择了三级分类必须有二级分类");
}
if (!typeTreeService.validateTypeHierarchy(dto.getSysType3().getId(), dto.getSysType2().getId())) {
throw new RuntimeException("三级分类的父级必须是二级分类");
}
}
}
/**
* 创建新课程
*/
private String createNewCourse(CourseWareSaveDto.CourseInfo courseInfo) {
Course course = new Course();
// 设置基础信息
course.setName(courseInfo.getName());
course.setValue(courseInfo.getValue());
course.setSummary(courseInfo.getSummary());
course.setForUsers(courseInfo.getForUsers());
course.setTags(courseInfo.getTags());
course.setCoverImg(courseInfo.getCoverImg());
course.setType(courseInfo.getType());
course.setOrgId(courseInfo.getOrgId());
course.setEnabled(courseInfo.getEnabled());
course.setIsTop(courseInfo.getIsTop());
course.setStudyTime(courseInfo.getStudyTime());
course.setOverview(courseInfo.getOverview());
course.setOpenCourse(courseInfo.getOpenCourse());
course.setVisible(courseInfo.getVisible());
course.setOrderStudy(courseInfo.getOrderStudy());
course.setKeywords(courseInfo.getKeywords());
course.setForScene(courseInfo.getForScene());
course.setCreateFrom(courseInfo.getCreateFrom());
// 设置默认值
setDefaultCourseValues(course);
courseDao.save(course);
String courseId = course.getId();
log.info("新增课程成功courseId={}, 课程名称={}", courseId, course.getName());
return courseId;
}
/**
* 更新现有课程
*/
private String updateExistingCourse(Course existingCourse, CourseWareSaveDto.CourseInfo newCourseData) {
String courseId = existingCourse.getId();
// 使用UpdateBuilder更新字段避免直接修改实体
List<IFieldUpdate> updates = new ArrayList<>();
// 更新基础信息
updates.add(UpdateBuilder.create("name", newCourseData.getName()));
if (newCourseData.getValue() != null) {
updates.add(UpdateBuilder.create("value", newCourseData.getValue()));
}
if (newCourseData.getSummary() != null) {
updates.add(UpdateBuilder.create("summary", newCourseData.getSummary()));
}
if (newCourseData.getForUsers() != null) {
updates.add(UpdateBuilder.create("forUsers", newCourseData.getForUsers()));
}
if (newCourseData.getTags() != null) {
updates.add(UpdateBuilder.create("tags", newCourseData.getTags()));
}
if (newCourseData.getCoverImg() != null) {
updates.add(UpdateBuilder.create("coverImg", newCourseData.getCoverImg()));
}
if (newCourseData.getType() != null) {
updates.add(UpdateBuilder.create("type", newCourseData.getType()));
}
if (newCourseData.getStudyTime() != null) {
updates.add(UpdateBuilder.create("studyTime", newCourseData.getStudyTime()));
}
if (newCourseData.getOverview() != null) {
updates.add(UpdateBuilder.create("overview", newCourseData.getOverview()));
}
// 版本号递增
// updates.add(UpdateBuilder.create("sysVersion", existingCourse.getSysVersion() + 1));
Integer currentVersion = existingCourse.getSysVersion();
if (currentVersion == null) {
currentVersion = 0; // 如果为null当做0处理
}
updates.add(UpdateBuilder.create("sysVersion", currentVersion + 1));
// 执行更新
for (IFieldUpdate update : updates) {
courseDao.updateFieldById(courseId, update.getField(), update.getValue());
}
log.info("更新课程成功courseId={}, 课程名称={}", courseId, newCourseData.getName());
return courseId;
}
/**
* 设置课程默认值
*/
private void setDefaultCourseValues(Course course) {
course.setDeleted(false);
course.setStatus(Course.STATUS_NONE); // 草稿状态
course.setPublished(false);
course.setEnabled(true);
course.setVisible(true);
course.setErasable(true);
course.setIsTop(false);
course.setOrderStudy(false);
// 设置统计默认值
course.setViews(0);
course.setComments(0);
course.setPraises(0);
course.setShares(0);
course.setFavorites(0);
course.setStudys(0);
course.setScore(0f);
course.setTrampleCount(0);
}
/**
* 绑定分类(三级)
*/
private void bindClassification(String courseId, CourseWareSaveDto dto) {
List<IFieldUpdate> updates = new ArrayList<>();
if (dto.getSysType1() != null) {
updates.add(UpdateBuilder.create("sysType1", dto.getSysType1().getId()));
}
if (dto.getSysType2() != null && StringUtils.isNotBlank(dto.getSysType2().getId())) {
updates.add(UpdateBuilder.create("sysType2", dto.getSysType2().getId()));
} else {
updates.add(UpdateBuilder.create("sysType2", null));
}
if (dto.getSysType3() != null && StringUtils.isNotBlank(dto.getSysType3().getId())) {
updates.add(UpdateBuilder.create("sysType3", dto.getSysType3().getId()));
} else {
updates.add(UpdateBuilder.create("sysType3", null));
}
if (!updates.isEmpty()) {
for (IFieldUpdate update : updates) {
courseDao.updateFieldById(courseId, update.getField(), update.getValue());
}
}
}
/**
* 绑定资源归属(三级)
*/
private void bindResourceOwnership(String courseId, CourseWareSaveDto dto) {
List<IFieldUpdate> updates = new ArrayList<>();
if (dto.getResOwner1() != null) {
updates.add(UpdateBuilder.create("resOwner1", dto.getResOwner1().getId()));
}
if (dto.getResOwner2() != null && StringUtils.isNotBlank(dto.getResOwner2().getId())) {
updates.add(UpdateBuilder.create("resOwner2", dto.getResOwner2().getId()));
} else {
updates.add(UpdateBuilder.create("resOwner2", null));
}
if (dto.getResOwner3() != null && StringUtils.isNotBlank(dto.getResOwner3().getId())) {
updates.add(UpdateBuilder.create("resOwner3", dto.getResOwner3().getId()));
} else {
updates.add(UpdateBuilder.create("resOwner3", null));
}
if (!updates.isEmpty()) {
for (IFieldUpdate update : updates) {
courseDao.updateFieldById(courseId, update.getField(), update.getValue());
}
}
}
/**
* 处理授课教师
*/
private void processTeachers(String courseId, List<CourseTeacher> teacherList) {
// 1. 先删除该课程下旧的教师关联
courseTeacherDao.deleteByField("courseId", courseId);
// 2. 遍历新增新的教师关联
for (CourseTeacher teacher : teacherList) {
// 校验教师ID非空
if (StringUtils.isBlank(teacher.getTeacherId())) {
throw new RuntimeException("添加失败教师ID不能为空");
}
// 核心校验教师姓名必须传null/空串/全空格都判定为未传)
// if (StringUtils.isBlank(teacher.getTeacherName())) {
// throw new RuntimeException("添加失败教师姓名必须填写teacherId=" + teacher.getTeacherId());
// }
// 3. 查询教师是否存在,不存在则自动新增
Teacher existTeacher = teacherDao.get(teacher.getTeacherId());
if (existTeacher == null) {
log.warn("教师ID{}不存在",
teacher.getTeacherId(), teacher.getTeacherName());
// 构建新教师实体
existTeacher = new Teacher();
existTeacher.setId(teacher.getTeacherId());
existTeacher.setName(teacher.getTeacherName());
// 基础默认值
existTeacher.setDeleted(false);
existTeacher.setSysVersion(1);
// 保存新教师
teacherDao.save(existTeacher);
log.info("自动新增教师成功teacherId={}, 教师姓名={}",
existTeacher.getId(), existTeacher.getName());
}
// 4. 绑定课程ID并保存关联关系姓名已校验非空
// teacher.setCourseId(courseId);
// courseTeacherDao.save(teacher);
teacher.setCourseId(courseId);
// 清除ID和版本号避免主键冲突
teacher.setId(null);
courseTeacherDao.save(teacher);
}
log.info("处理授课教师完成courseId={}, 教师数量={}", courseId, teacherList.size());
}
/**
* 处理受众列表
*/
private void processCrowds(String courseId, List<CourseCrowd> crowdList) {
if (CollectionUtils.isNotEmpty(crowdList)) {
// 先删除旧关联
courseCrowdDao.deleteByField("courseId", courseId);
// 新增新关联
for (CourseCrowd crowd : crowdList) {
if (StringUtils.isBlank(crowd.getGroupId())) {
throw new RuntimeException("受众ID不能为空");
}
crowd.setCourseId(courseId); // 绑定课程ID
courseCrowdDao.save(crowd);
}
log.info("处理受众完成courseId={}, 受众数量={}", courseId, crowdList.size());
}
}
@Override
@Transactional
public void delete(String id,int contentType,boolean flag) {
if(flag) {

View File

@@ -0,0 +1,537 @@
package com.xboe.module.course.service.impl;
import com.xboe.module.course.dto.BatchUploadResponseDto;
import com.xboe.module.course.dto.BatchUploadWithNullDto;
import com.xboe.module.course.dto.CourseResourceUploadDto;
import com.xboe.module.course.service.CourseContentValidationService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 课程内容验证 服务实现类
*/
@Service
public class CourseContentValidationServiceImpl implements CourseContentValidationService {
/**
* 验证批量上传DTO
*/
@Override
public List<String> validateBatchUpload(BatchUploadWithNullDto dto) {
List<String> errors = new ArrayList<>();
if (StringUtils.isBlank(dto.getCourseId())) {
errors.add("课程ID不能为空");
}
if (dto.getOperationType() == null) {
errors.add("操作类型不能为空");
} else if (dto.getOperationType() != 1 ) {
errors.add("操作类型必须是1(新增)");
}
if (CollectionUtils.isEmpty(dto.getContentItems())) {
errors.add("内容项列表不能为空");
return errors;
}
// 验证每个内容项
for (int i = 0; i < dto.getContentItems().size(); i++) {
BatchUploadWithNullDto.ContentItem item = dto.getContentItems().get(i);
List<String> itemErrors = validateContentItem(item, dto.getOperationType(), i + 1);
errors.addAll(itemErrors);
}
return errors;
}
/**
* 验证内容项
*/
@Override
public List<String> validateContentItem(BatchUploadWithNullDto.ContentItem item, Integer operationType, int index) {
List<String> errors = new ArrayList<>();
// 验证操作类型
if (operationType == 2) { // 更新操作
if (StringUtils.isBlank(item.getContentId())) {
errors.add("" + index + "更新操作时内容ID不能为空");
}
} else if (operationType == 1) { // 新增操作
// contentType不能为null
if (item.getContentType() == null) {
errors.add("" + index + "项:新增操作时内容类型不能为空");
} else {
validateForCreate(errors, item, index);
}
}
// 验证
validateNonNullFields(errors, item, index);
// 验证章节ID
if (StringUtils.isBlank(item.getCsectionId())) {
errors.add("" + index + "章节ID不能为空");
}
return errors;
}
/**
* 验证资源上传DTO
*/
@Override
public String validateResource(CourseResourceUploadDto.CourseFileResource resource) {
Integer resType = resource.getResType();
switch (resType) {
case 10: // 视频
case 20: // 音频
case 40: // 文档
case 50: // SCORM包
if (StringUtils.isBlank(resource.getFileBase64())) {
return getResourceTypeName(resType) + "文件内容不能为空";
}
if (StringUtils.isBlank(resource.getOriginalFileName())) {
return getResourceTypeName(resType) + "文件名称不能为空";
}
if (!isValidFileExtension(resType, resource.getOriginalFileName())) {
return getResourceTypeName(resType) + "文件格式不支持";
}
break;
case 41: // 图文
if (resource.getGraphicText() == null) {
return "图文内容不能为空";
}
if (StringUtils.isBlank(resource.getGraphicText().getTitle())) {
return "图文标题不能为空";
}
if (StringUtils.isBlank(resource.getGraphicText().getContent())) {
return "图文内容不能为空";
}
break;
case 90: // 外部链接
if (resource.getExternalLink() == null) {
return "外部链接内容不能为空";
}
if (StringUtils.isBlank(resource.getExternalLink().getUrl())) {
return "链接地址不能为空";
}
if (resource.getExternalLink().getOpenType() != null &&
resource.getExternalLink().getOpenType() != 1 &&
resource.getExternalLink().getOpenType() != 2) {
return "链接打开方式必须是1(页面嵌入)或2(新窗口打开)";
}
break;
}
return null;
}
/**
* 验证资源类型是否有效
*/
@Override
public boolean isValidResourceType(Integer resType) {
return resType != null && (
resType == 10 || // 视频
resType == 20 || // 音频
resType == 40 || // 文档
resType == 41 || // 图文
resType == 50 || // SCORM包
resType == 90 // 外部链接
);
}
/**
* 验证文件扩展名
*/
@Override
public boolean isValidFileExtension(Integer resType, String fileName) {
String extension = getFileExtension(fileName);
if (StringUtils.isBlank(extension)) {
return false;
}
switch (resType) {
case 10: // 视频
return "mp4".equalsIgnoreCase(extension);
case 20: // 音频
return "mp3".equalsIgnoreCase(extension);
case 40: // 文档
return extension.matches("(?i)(doc|docx|xls|xlsx|pptx|txt|pdf)");
case 50: // SCORM包
return "zip".equalsIgnoreCase(extension);
default:
return true;
}
}
/**
* 获取文件扩展名
*/
@Override
public String getFileExtension(String filename) {
if (StringUtils.isBlank(filename) || !filename.contains(".")) {
return "";
}
return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
}
/**
* 获取资源类型名称
*/
@Override
public String getResourceTypeName(Integer resType) {
switch (resType) {
case 10: return "视频";
case 20: return "音频";
case 40: return "文档";
case 41: return "图文";
case 50: return "SCORM包";
case 90: return "外部链接";
default: return "未知资源";
}
}
/**
* 创建一个新的批量上传响应DTO
* @param courseId 课程ID
* @param totalCount 总数
* @return BatchUploadResponseDto
*/
@Override
public BatchUploadResponseDto createBatchUploadResponse(String courseId, int totalCount) {
BatchUploadResponseDto response = new BatchUploadResponseDto();
response.setCourseId(courseId);
response.setTotalCount(totalCount);
response.setSuccessCount(0);
response.setFailCount(0);
response.setResults(new ArrayList<>());
return response;
}
/**
* 添加结果到批量上传响应DTO
* @param response 响应DTO
* @param result 结果
*/
@Override
public void addResultToBatchUploadResponse(BatchUploadResponseDto response, BatchUploadResponseDto.UploadResult result) {
if (result.isSuccess()) {
response.setSuccessCount(response.getSuccessCount() + 1);
} else {
response.setFailCount(response.getFailCount() + 1);
}
response.getResults().add(result);
response.setSuccess(response.getFailCount() == 0);
response.setMessage(String.format("上传完成,成功:%d失败%d", response.getSuccessCount(), response.getFailCount()));
}
// ------------------------ 私有辅助方法(保留在实现类中) ------------------------
private void validateForCreate(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
switch (item.getContentType()) {
case 1: // 课件
validateCoursewareForCreate(errors, item, index);
break;
case 2: // 作业
validateHomeworkForCreate(errors, item, index);
break;
case 3: // 考试
validateExamForCreate(errors, item, index);
break;
case 4: // 评估
validateAssessForCreate(errors, item, index);
break;
}
}
private void validateCoursewareForCreate(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getResourceType() == null) {
errors.add("" + index + "项:新增课件时资源类型不能为空");
return;
}
switch (item.getResourceType()) {
case 10: // 视频
case 20: // 音频
case 40: // 文档
case 50: // SCORM包
validateFileResourceForCreate(errors, item, index, item.getResourceType());
break;
case 41: // 图文
validateGraphicTextForCreate(errors, item, index);
break;
case 90: // 外部链接
validateExternalLinkForCreate(errors, item, index);
break;
default:
errors.add("" + index + "项:不支持的资源类型: " + item.getResourceType());
}
}
private void validateFileResourceForCreate(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index, Integer resourceType) {
if (item.getFileResource() == null) {
errors.add("" + index + "项:新增" + getResourceTypeName(resourceType) + "时文件资源不能为空");
return;
}
// 文件内容必填
if (StringUtils.isBlank(item.getFileResource().getFileBase64())) {
errors.add("" + index + "项:" + getResourceTypeName(resourceType) + "文件内容不能为空");
}
// 文件名必填
if (StringUtils.isBlank(item.getFileResource().getOriginalFileName())) {
errors.add("" + index + "项:" + getResourceTypeName(resourceType) + "文件名称不能为空");
} else if (!isValidFileExtension(resourceType, item.getFileResource().getOriginalFileName())) {
errors.add("" + index + "项:" + getResourceTypeName(resourceType) + "文件格式不支持");
}
}
private void validateGraphicTextForCreate(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getGraphicTextResource() == null) {
errors.add("" + index + "项:新增图文时图文资源不能为空");
return;
}
// 标题必填
if (StringUtils.isBlank(item.getGraphicTextResource().getTitle())) {
errors.add("" + index + "项:图文标题不能为空");
}
// 内容必填
if (StringUtils.isBlank(item.getGraphicTextResource().getContent())) {
errors.add("" + index + "项:图文内容不能为空");
}
}
private void validateExternalLinkForCreate(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getExternalLinkResource() == null) {
errors.add("" + index + "项:新增外部链接时链接资源不能为空");
return;
}
// 链接地址必填
if (StringUtils.isBlank(item.getExternalLinkResource().getUrl())) {
errors.add("" + index + "项:链接地址不能为空");
}
}
private void validateHomeworkForCreate(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getHomeworkInfo() == null) {
errors.add("" + index + "项:新增作业时作业信息不能为空");
return;
}
if (StringUtils.isBlank(item.getHomeworkInfo().getName())) {
errors.add("" + index + "项:作业名称不能为空");
}
if (item.getHomeworkInfo().getSubmitMode() == null) {
errors.add("" + index + "项:提交模式不能为空");
}
}
private void validateExamForCreate(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getExamInfo() == null) {
errors.add("" + index + "项:新增考试时考试信息不能为空");
return;
}
if (StringUtils.isBlank(item.getExamInfo().getTestName())) {
errors.add("" + index + "项:考试名称不能为空");
}
if (item.getExamInfo().getPaperType() == null) {
errors.add("" + index + "项:试卷类型不能为空");
}
// 根据试卷类型验证
if (item.getExamInfo().getPaperType() != null) {
if (item.getExamInfo().getPaperType() == 1 && StringUtils.isBlank(item.getExamInfo().getPaperContent())) {
errors.add("" + index + "项:自定义试卷内容不能为空");
}
if (item.getExamInfo().getPaperType() == 2 && StringUtils.isBlank(item.getExamInfo().getPaperId())) {
errors.add("" + index + "试卷ID不能为空");
}
}
// 验证随机选题参数
if (item.getExamInfo().getRandomMode() != null && item.getExamInfo().getRandomMode()) {
if (item.getExamInfo().getQnum() == null || item.getExamInfo().getQnum() <= 0) {
errors.add("" + index + "随机选题模式下试题数量必须大于0");
}
if (item.getExamInfo().getQscore() == null || item.getExamInfo().getQscore() <= 0) {
errors.add("" + index + "随机选题模式下试题分值必须大于0");
}
}
}
private void validateAssessForCreate(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getAssessInfo() == null) {
errors.add("" + index + "项:新增评估时评估信息不能为空");
return;
}
if (StringUtils.isBlank(item.getAssessInfo().getQuestion())) {
errors.add("" + index + "项:评估问题不能为空");
}
if (item.getAssessInfo().getQType() == null) {
errors.add("" + index + "项:问题类型不能为空");
}
}
private void validateNonNullFields(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getContentType() != null) {
switch (item.getContentType()) {
case 1: // 课件
validateCoursewareNonNull(errors, item, index);
break;
case 2: // 作业
validateHomeworkNonNull(errors, item, index);
break;
case 3: // 考试
validateExamNonNull(errors, item, index);
break;
case 4: // 评估
validateAssessNonNull(errors, item, index);
break;
}
}
}
private void validateCoursewareNonNull(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getResourceType() != null) {
if (!isValidResourceType(item.getResourceType())) {
errors.add("" + index + "项:不支持的资源类型: " + item.getResourceType());
return;
}
switch (item.getResourceType()) {
case 10: // 视频
case 20: // 音频
case 40: // 文档
case 50: // SCORM包
if (item.getFileResource() != null) {
validateFileResourceNonNull(item.getFileResource(), item.getResourceType(), index, errors);
}
break;
case 41: // 图文
if (item.getGraphicTextResource() != null) {
validateGraphicTextResourceNonNull(item.getGraphicTextResource(), index, errors);
}
break;
case 90: // 外部链接
if (item.getExternalLinkResource() != null) {
validateExternalLinkResourceNonNull(item.getExternalLinkResource(), index, errors);
}
break;
}
}
}
private void validateFileResourceNonNull(BatchUploadWithNullDto.FileResourceInfo resource, Integer resourceType,
int index, List<String> errors) {
if (resource.getFileBase64() != null) {
if (StringUtils.isBlank(resource.getOriginalFileName())) {
errors.add("" + index + "项:" + getResourceTypeName(resourceType) + "文件名称不能为空");
} else if (!isValidFileExtension(resourceType, resource.getOriginalFileName())) {
errors.add("" + index + "项:" + getResourceTypeName(resourceType) + "文件格式不支持");
}
}
if (resource.getDevice() != null && (resource.getDevice() < 1 || resource.getDevice() > 3)) {
errors.add("" + index + "设备类型必须是1,2或3");
}
}
private void validateGraphicTextResourceNonNull(BatchUploadWithNullDto.GraphicTextResourceInfo resource,
int index, List<String> errors) {
if (resource.getTitle() != null && StringUtils.isBlank(resource.getTitle())) {
errors.add("" + index + "项:图文标题不能为空");
}
if (resource.getContent() != null && StringUtils.isBlank(resource.getContent())) {
errors.add("" + index + "项:图文内容不能为空");
}
if (resource.getImageBase64() != null && StringUtils.isBlank(resource.getImageName())) {
errors.add("" + index + "项:图片名称不能为空");
}
}
private void validateExternalLinkResourceNonNull(BatchUploadWithNullDto.ExternalLinkResourceInfo resource,
int index, List<String> errors) {
if (resource.getUrl() != null && StringUtils.isBlank(resource.getUrl())) {
errors.add("" + index + "项:链接地址不能为空");
}
if (resource.getOpenType() != null &&
(resource.getOpenType() != 1 && resource.getOpenType() != 2)) {
errors.add("" + index + "链接打开方式必须是1(页面嵌入)或2(新窗口打开)");
}
}
private void validateHomeworkNonNull(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getHomeworkInfo() != null) {
if (item.getHomeworkInfo().getName() != null && StringUtils.isBlank(item.getHomeworkInfo().getName())) {
errors.add("" + index + "项:作业名称不能为空");
}
if (item.getHomeworkInfo().getSubmitMode() != null &&
(item.getHomeworkInfo().getSubmitMode() < 1 || item.getHomeworkInfo().getSubmitMode() > 3)) {
errors.add("" + index + "提交模式必须是1,2或3");
}
}
}
private void validateExamNonNull(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getExamInfo() != null) {
if (item.getExamInfo().getTestName() != null && StringUtils.isBlank(item.getExamInfo().getTestName())) {
errors.add("" + index + "项:考试名称不能为空");
}
if (item.getExamInfo().getPaperType() != null) {
if (item.getExamInfo().getPaperType() == 1 &&
item.getExamInfo().getPaperContent() != null &&
StringUtils.isBlank(item.getExamInfo().getPaperContent())) {
errors.add("" + index + "项:自定义试卷内容不能为空");
}
if (item.getExamInfo().getPaperType() == 2 &&
item.getExamInfo().getPaperId() != null &&
StringUtils.isBlank(item.getExamInfo().getPaperId())) {
errors.add("" + index + "试卷ID不能为空");
}
}
if (item.getExamInfo().getRandomMode() != null && item.getExamInfo().getRandomMode()) {
if (item.getExamInfo().getQnum() != null && item.getExamInfo().getQnum() <= 0) {
errors.add("" + index + "随机选题模式下试题数量必须大于0");
}
if (item.getExamInfo().getQscore() != null && item.getExamInfo().getQscore() <= 0) {
errors.add("" + index + "随机选题模式下试题分值必须大于0");
}
}
}
}
private void validateAssessNonNull(List<String> errors, BatchUploadWithNullDto.ContentItem item, int index) {
if (item.getAssessInfo() != null) {
if (item.getAssessInfo().getQuestion() != null && StringUtils.isBlank(item.getAssessInfo().getQuestion())) {
errors.add("" + index + "项:评估问题不能为空");
}
}
}
}

View File

@@ -0,0 +1,435 @@
package com.xboe.module.course.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xboe.common.OrderCondition;
import com.xboe.core.orm.FieldFilters;
import com.xboe.module.course.dao.CourseFileDao;
import com.xboe.module.course.dto.CourseResourceUploadDto;
import com.xboe.module.course.entity.CourseFile;
import com.xboe.module.course.service.CourseContentValidationService;
import com.xboe.module.course.service.CourseResourceService;
import com.xboe.module.util.FileUploadUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
@Slf4j
@Service
public class CourseResourceServiceImpl implements CourseResourceService {
@Resource
private CourseFileDao courseFileDao;
@Resource
private FileUploadUtil fileUploadUtil;
@Resource
private CourseContentValidationService courseContentValidationService;
private final ObjectMapper objectMapper = new ObjectMapper();
// 资源类型映射
private static final Map<Integer, String[]> RESOURCE_TYPE_EXTENSIONS = new HashMap<>();
private static final Map<Integer, String> RESOURCE_TYPE_NAMES = new HashMap<>();
static {
// 文件扩展名映射
RESOURCE_TYPE_EXTENSIONS.put(10, new String[]{"mp4"}); // 视频
RESOURCE_TYPE_EXTENSIONS.put(20, new String[]{"mp3"}); // 音频
RESOURCE_TYPE_EXTENSIONS.put(40, new String[]{"doc", "docx", "xls", "xlsx", "pptx", "txt", "pdf"}); // 文档
RESOURCE_TYPE_EXTENSIONS.put(50, new String[]{"zip"}); // SCORM包
// 资源类型名称映射
RESOURCE_TYPE_NAMES.put(10, "视频");
RESOURCE_TYPE_NAMES.put(20, "音频");
RESOURCE_TYPE_NAMES.put(40, "文档");
RESOURCE_TYPE_NAMES.put(41, "图文");
RESOURCE_TYPE_NAMES.put(50, "SCORM包");
RESOURCE_TYPE_NAMES.put(90, "外部链接");
}
@Override
@Transactional
public void processCourseResources(String courseId,
List<CourseResourceUploadDto.CourseFileResource> resources,
String orgId, String orgName,
String resOwner1, String resOwner2, String resOwner3,
String ownership1, String ownership2, String ownership3) {
if (resources == null || resources.isEmpty()) {
log.info("课程ID={} 没有需要处理的资源", courseId);
return;
}
log.info("开始处理课程资源课程ID={},资源数量={}", courseId, resources.size());
int successCount = 0;
int failCount = 0;
for (CourseResourceUploadDto.CourseFileResource resource : resources) {
try {
processSingleResource(courseId, resource, orgId, orgName,
resOwner1, resOwner2, resOwner3,
ownership1, ownership2, ownership3);
successCount++;
log.info("处理资源成功: 课程ID={}, 资源名称={}, 类型={}",
courseId, resource.getName(), resource.getResType());
} catch (Exception e) {
failCount++;
log.error("处理资源失败: 课程ID={}, 资源名称={}, 类型={}, 错误: {}",
courseId, resource.getName(), resource.getResType(), e.getMessage(), e);
// 继续处理其他资源
}
}
log.info("资源处理完成: 课程ID={}, 成功={}, 失败={}", courseId, successCount, failCount);
}
/**
* 处理单个资源
*/
private void processSingleResource(String courseId,
CourseResourceUploadDto.CourseFileResource resource,
String orgId, String orgName,
String resOwner1, String resOwner2, String resOwner3,
String ownership1, String ownership2, String ownership3) throws IOException {
// 验证资源
validateResource(resource);
// 创建CourseFile实体
CourseFile courseFile = new CourseFile();
courseFile.setName(resource.getName());
courseFile.setCourseId(courseId);
courseFile.setOrgId(orgId);
courseFile.setOrgName(orgName);
courseFile.setResType(resource.getResType());
courseFile.setResOwner1(resOwner1);
courseFile.setResOwner2(resOwner2);
courseFile.setResOwner3(resOwner3);
courseFile.setOwnership1(ownership1);
courseFile.setOwnership2(ownership2);
courseFile.setOwnership3(ownership3);
courseFile.setDown(resource.getDown() != null ? resource.getDown() : false);
courseFile.setRemark(resource.getRemark());
courseFile.setDevice(resource.getDevice() != null ? resource.getDevice() : 3); // 默认多端可见
courseFile.setDecoder(resource.getDecoder());
courseFile.setVideoWidth(resource.getVideoWidth());
courseFile.setVideoHeight(resource.getVideoHeight());
courseFile.setConverStatus(resource.getConverStatus() != null ? resource.getConverStatus() : 0);
courseFile.setConverError(resource.getConverError());
// 根据资源类型处理
Integer resType = resource.getResType();
if (resType == 10 || resType == 20 || resType == 40 || resType == 50) {
// 文件资源视频、音频、文档、SCORM包
processFileResource(courseFile, resource);
} else if (resType == 41) {
// 图文资源
processGraphicTextResource(courseFile, resource);
} else if (resType == 90) {
// 外部链接资源
processExternalLinkResource(courseFile, resource);
} else {
throw new IllegalArgumentException("不支持的资源类型: " + resType);
}
// 保存到数据库
courseFileDao.save(courseFile);
log.info("资源保存到数据库成功: ID={}, 名称={}, 类型={}",
courseFile.getId(), courseFile.getName(), courseFile.getResType());
}
/**
* 处理文件资源视频、音频、文档、SCORM包通用方法
*/
private void processFileResource(CourseFile courseFile,
CourseResourceUploadDto.CourseFileResource resource) throws IOException {
Integer resType = resource.getResType();
String resourceTypeName = RESOURCE_TYPE_NAMES.get(resType);
String[] allowedExtensions = RESOURCE_TYPE_EXTENSIONS.get(resType);
// 验证文件
if (StringUtils.isBlank(resource.getFileBase64())) {
throw new IllegalArgumentException(resourceTypeName + "文件内容不能为空");
}
String originalFileName = StringUtils.isNotBlank(resource.getOriginalFileName())
? resource.getOriginalFileName()
: getDefaultFileName(resType);
// 验证文件类型
if (!fileUploadUtil.validateFileType(originalFileName, allowedExtensions)) {
throw new IllegalArgumentException(resourceTypeName + "文件只支持: " +
String.join(", ", allowedExtensions));
}
// 验证文件大小
validateFileSize(resource.getFileBase64(), resType);
// 保存文件
FileUploadUtil.FileSaveResult saveResult = fileUploadUtil.saveBase64File(
resource.getFileBase64(), originalFileName);
// 设置文件信息
courseFile.setFileName(saveResult.getFileName());
courseFile.setFileType(fileUploadUtil.getFileExtension(originalFileName));
courseFile.setFileSize((int) (saveResult.getFileSize() / 1024)); // KB
courseFile.setFilePath(saveResult.getFilePath());
courseFile.setPreviewFilePath(saveResult.getFileUrl());
// 设置时长(视频/音频)
if (resType == 10 || resType == 20) {
courseFile.setDuration(resource.getDuration() != null ? resource.getDuration() : 0);
}
// 视频特定设置
if (resType == 10) {
courseFile.setDecoder(StringUtils.isNotBlank(resource.getDecoder()) ? resource.getDecoder() : "h264");
courseFile.setConverStatus(0); // 视频可能需要转码
} else {
courseFile.setConverStatus(2); // 其他文件不需要转化
}
}
/**
* 处理图文资源
*/
private void processGraphicTextResource(CourseFile courseFile,
CourseResourceUploadDto.CourseFileResource resource) throws IOException {
// 验证图文内容
if (resource.getGraphicText() == null) {
throw new IllegalArgumentException("图文内容不能为空");
}
CourseResourceUploadDto.GraphicTextContent graphicText = resource.getGraphicText();
if (StringUtils.isBlank(graphicText.getTitle())) {
throw new IllegalArgumentException("图文标题不能为空");
}
if (StringUtils.isBlank(graphicText.getContent())) {
throw new IllegalArgumentException("图文内容不能为空");
}
// 处理图片(如果有)
String imageUrl = null;
String imagePath = null;
if (StringUtils.isNotBlank(graphicText.getImageBase64())) {
try {
String imageName = StringUtils.isNotBlank(graphicText.getImageName())
? graphicText.getImageName()
: "image_" + System.currentTimeMillis() + ".jpg";
FileUploadUtil.FileSaveResult imageResult = fileUploadUtil.saveBase64File(
graphicText.getImageBase64(), imageName);
imageUrl = imageResult.getFileUrl();
imagePath = imageResult.getFilePath();
} catch (Exception e) {
log.warn("处理图文图片失败,继续处理其他内容", e);
}
}
// 构建图文内容JSON
Map<String, Object> contentMap = new HashMap<>();
contentMap.put("title", graphicText.getTitle());
contentMap.put("content", graphicText.getContent());
contentMap.put("format", graphicText.getFormat());
contentMap.put("fontSize", graphicText.getFontSize());
contentMap.put("lineHeight", graphicText.getLineHeight());
contentMap.put("textAlign", graphicText.getTextAlign());
contentMap.put("imageUrl", imageUrl);
contentMap.put("imageName", graphicText.getImageName());
String contentJson = objectMapper.writeValueAsString(contentMap);
// 设置文件信息
courseFile.setFileName("graphic_text.json");
courseFile.setFileType("json");
courseFile.setFileSize((int) (contentJson.length() / 1024)); // KB
courseFile.setContent(contentJson);
if (imageUrl != null) {
courseFile.setPreviewFilePath(imageUrl);
}
if (imagePath != null) {
courseFile.setFilePath(imagePath);
} else {
courseFile.setFilePath("graphic_text/" + courseFile.getId() + ".json");
}
courseFile.setConverStatus(2); // 图文不需要转化
}
/**
* 处理外部链接资源
*/
private void processExternalLinkResource(CourseFile courseFile,
CourseResourceUploadDto.CourseFileResource resource) throws IOException {
// 验证链接内容
if (resource.getExternalLink() == null) {
throw new IllegalArgumentException("外部链接内容不能为空");
}
CourseResourceUploadDto.ExternalLinkContent externalLink = resource.getExternalLink();
if (StringUtils.isBlank(externalLink.getUrl())) {
throw new IllegalArgumentException("链接地址不能为空");
}
// 构建链接内容JSON
Map<String, Object> contentMap = new HashMap<>();
contentMap.put("url", externalLink.getUrl());
contentMap.put("openType", externalLink.getOpenType() != null ? externalLink.getOpenType() : 2);
contentMap.put("title", externalLink.getTitle());
contentMap.put("description", externalLink.getDescription());
String contentJson = objectMapper.writeValueAsString(contentMap);
// 设置文件信息
courseFile.setFileName("external_link.json");
courseFile.setFileType("json");
courseFile.setFileSize((int) (contentJson.length() / 1024)); // KB
courseFile.setContent(contentJson);
courseFile.setFilePath("external_link/" + courseFile.getId() + ".json");
courseFile.setConverStatus(2); // 链接不需要转化
}
/**
* 验证资源
*/
private void validateResource(CourseResourceUploadDto.CourseFileResource resource) {
if (resource == null) {
throw new IllegalArgumentException("资源不能为空");
}
if (resource.getResType() == null) {
throw new IllegalArgumentException("资源类型不能为空");
}
if (StringUtils.isBlank(resource.getName())) {
throw new IllegalArgumentException("资源名称不能为空");
}
// 使用服务层的验证方法替代DTO中的静态方法
String validationResult = courseContentValidationService.validateResource(resource);
if (validationResult != null) {
throw new IllegalArgumentException(validationResult);
}
}
/**
* 验证文件大小
*/
private void validateFileSize(String base64Content, Integer resType) {
if (StringUtils.isBlank(base64Content)) {
return;
}
try {
// 估算文件大小Base64编码比原始大约33%
int base64Length = base64Content.length();
if (base64Content.contains(",")) {
base64Length = base64Content.split(",")[1].length();
}
// Base64解码后的实际大小约为 base64Length * 3 / 4
long estimatedSize = (long) base64Length * 3 / 4;
// 根据类型设置限制
long maxSize;
switch (resType) {
case 10: // 视频
case 40: // 文档
case 50: // SCORM包
maxSize = 1024L * 1024 * 1024; // 1GB
break;
case 20: // 音频
maxSize = 16L * 1024 * 1024; // 16MB
break;
default:
maxSize = 100L * 1024 * 1024; // 100MB 默认
}
if (estimatedSize > maxSize) {
throw new IllegalArgumentException(String.format(
"文件大小超过限制,估计大小: %.2fMB, 最大限制: %.2fMB",
estimatedSize / (1024.0 * 1024.0),
maxSize / (1024.0 * 1024.0)));
}
} catch (Exception e) {
log.warn("验证文件大小失败", e);
}
}
/**
* 获取默认文件名
*/
private String getDefaultFileName(Integer resType) {
switch (resType) {
case 10: return "video.mp4";
case 20: return "audio.mp3";
case 40: return "document.pdf";
case 50: return "scorm.zip";
default: return "file.bin";
}
}
@Override
public List<CourseFile> getCourseResources(String courseId) {
try {
List<CourseFile> resources = courseFileDao.findList(
OrderCondition.asc("sysCreateTime"),
FieldFilters.eq("courseId", courseId),
FieldFilters.eq("deleted", false)
);
log.info("获取课程资源成功: courseId={}, 数量={}", courseId, resources.size());
return resources;
} catch (Exception e) {
log.error("获取课程资源失败: courseId={}", courseId, e);
throw new RuntimeException("获取课程资源失败: " + e.getMessage());
}
}
@Override
@Transactional
public void deleteCourseResources(String courseId) {
try {
List<CourseFile> resources = getCourseResources(courseId);
for (CourseFile resource : resources) {
deleteResource(resource.getId());
}
log.info("删除课程资源成功: courseId={}, 数量={}", courseId, resources.size());
} catch (Exception e) {
log.error("删除课程资源失败: courseId={}", courseId, e);
throw new RuntimeException("删除课程资源失败: " + e.getMessage());
}
}
@Override
@Transactional
public void deleteResource(String resourceId) {
try {
CourseFile resource = courseFileDao.get(resourceId);
if (resource != null) {
// 标记为删除
resource.setDeleted(true);
courseFileDao.update(resource);
// 删除物理文件
if (StringUtils.isNotBlank(resource.getFilePath())) {
fileUploadUtil.deleteFile(resource.getFilePath());
}
log.info("删除资源成功: resourceId={}, 名称={}", resourceId, resource.getName());
}
} catch (Exception e) {
log.error("删除资源失败: resourceId={}", resourceId, e);
throw new RuntimeException("删除资源失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,275 @@
package com.xboe.module.course.service.impl;
import com.xboe.common.OrderCondition;
import com.xboe.core.orm.FieldFilters;
import com.xboe.module.course.vo.TypeTreeVo;
import com.xboe.module.course.service.TypeTreeService;
import com.xboe.module.type.dao.TypeDao;
import com.xboe.module.type.entity.Type;
import com.xboe.system.organization.dao.OrganizationDao;
import com.xboe.system.organization.entity.Organization;
import com.xboe.system.organization.vo.OrganizationVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
public class TypeTreeServiceImpl implements TypeTreeService {
@Resource
private TypeDao typeDao;
@Resource
private OrganizationDao organizationDao;
@Override
public List<TypeTreeVo> getCourseTypeTree() {
try {
// 查询课程相关的分类直接用数据库列名sys_res_type
List<Type> allTypes = typeDao.findList(
OrderCondition.asc("orderIndex"),
FieldFilters.eq("sys_res_type", 1), // 关键:用数据库原生列名
FieldFilters.eq("status", 1),
FieldFilters.eq("deleted", false)
);
return buildTypeTree(allTypes);
} catch (Exception e) {
log.error("获取课程分类树失败", e);
throw new RuntimeException("获取课程分类树失败:" + e.getMessage());
}
}
@Override
public List<OrganizationVo> getResourceOwnerTree() {
try {
// 查询组织机构树
List<Organization> allOrgs = organizationDao.findList(
OrderCondition.asc("code"),
FieldFilters.eq("status", 1),
FieldFilters.eq("deleted", false)
);
return buildOrgTree(allOrgs);
} catch (Exception e) {
log.error("获取资源归属树失败", e);
throw new RuntimeException("获取资源归属树失败:" + e.getMessage());
}
}
@Override
public List<TypeTreeVo> getChildTypes(String parentId) {
try {
List<Type> childTypes = typeDao.findList(
OrderCondition.asc("orderIndex"),
FieldFilters.eq("parent_id", parentId), // 关键:用数据库原生列名
FieldFilters.eq("status", 1),
FieldFilters.eq("deleted", false),
FieldFilters.eq("sys_res_type", 1) // 关键:用数据库原生列名
);
return childTypes.stream().map(this::convertTypeToTreeVo).collect(Collectors.toList());
} catch (Exception e) {
log.error("获取子分类失败parentId={}", parentId, e);
throw new RuntimeException("获取子分类失败:" + e.getMessage());
}
}
@Override
public List<OrganizationVo> getChildOrgs(String parentId) {
try {
List<Organization> childOrgs = organizationDao.findList(
OrderCondition.asc("code"),
FieldFilters.eq("parentId", parentId),
FieldFilters.eq("status", 1),
FieldFilters.eq("deleted", false)
);
return childOrgs.stream().map(this::convertOrgToVo).collect(Collectors.toList());
} catch (Exception e) {
log.error("获取子组织失败parentId={}", parentId, e);
throw new RuntimeException("获取子组织失败:" + e.getMessage());
}
}
@Override
public boolean validateTypeHierarchy(String childId, String parentId) {
try {
// 1. 非空校验
if (StringUtils.isBlank(childId) || StringUtils.isBlank(parentId)) {
log.warn("分类层级校验失败子分类ID[{}]或父分类ID[{}]为空", childId, parentId);
return false;
}
// 确认传入的ID
log.info("开始校验分类层级子分类ID={}, 父分类ID={}", childId, parentId);
// 2. 查询子分类
List<Type> childTypeList = typeDao.findList(
FieldFilters.eq("id", childId),
FieldFilters.eq("sys_res_type", 1), // 数据库列名sys_res_type
FieldFilters.eq("status", 1),
FieldFilters.eq("deleted", false)
);
if (childTypeList == null || childTypeList.isEmpty()) {
log.warn("分类层级校验失败子分类ID={} 不存在(或非课程类型/未启用/已删除)", childId);
// 查询所有课程类型分类
List<Type> allCourseTypes = typeDao.findList(FieldFilters.eq("sys_res_type", 1));
log.info("当前数据库中课程类型分类列表:{}",
allCourseTypes.stream().map(Type::getId).collect(Collectors.toList()));
return false;
}
Type childType = childTypeList.get(0);
log.info("查询到子分类信息ID={}, 父ID={}, 资源类型={}, 状态={}, 删除标识={}",
childType.getId(), childType.getParentId(), childType.getSysResType(),
childType.getStatus(), childType.getDeleted());
// 3. 查询父分类
List<Type> parentTypeList = typeDao.findList(
FieldFilters.eq("id", parentId),
FieldFilters.eq("sys_res_type", 1), // 数据库列名sys_res_type
FieldFilters.eq("status", 1),
FieldFilters.eq("deleted", false)
);
if (parentTypeList == null || parentTypeList.isEmpty()) {
log.warn("分类层级校验失败父分类ID={} 不存在(或非课程类型/未启用/已删除)", parentId);
return false;
}
Type parentType = parentTypeList.get(0);
log.info("查询到父分类信息ID={}, 父ID={}, 资源类型={}, 状态={}, 删除标识={}",
parentType.getId(), parentType.getParentId(), parentType.getSysResType(),
parentType.getStatus(), parentType.getDeleted());
// 4. 验证父分类是一级分类
if (!StringUtils.isBlank(parentType.getParentId())) {
String parentParentId = parentType.getParentId();
// 一级分类
boolean isRootLevel = "0".equals(parentParentId) || "-1".equals(parentParentId);
if (!isRootLevel) {
log.warn("分类层级校验失败父分类ID={} 不是一级分类其parentId={}",
parentId, parentType.getParentId());
return false;
}
log.info("父分类是一级分类parentId={}, parentParentId={}", parentId, parentParentId);
} else {
log.info("父分类是一级分类parentId={}, parentParentId为空", parentId);
}
// 5. 验证
boolean isParentMatch = parentId.equals(childType.getParentId());
if (!isParentMatch) {
log.warn("分类层级校验失败子分类ID={} 的父ID={} 与传入的父ID={} 不匹配",
childId, childType.getParentId(), parentId);
}
log.info("分类层级校验完成子分类ID={}, 父分类ID={}, 校验结果={}", childId, parentId, isParentMatch);
return isParentMatch;
} catch (Exception e) {
log.error("验证分类层级关系异常, childId={}, parentId={}", childId, parentId, e);
return false;
}
}
/**
* 构建分类树
*/
private List<TypeTreeVo> buildTypeTree(List<Type> allTypes) {
Map<String, TypeTreeVo> voMap = new HashMap<>();
List<TypeTreeVo> rootNodes = new ArrayList<>();
// 第一遍:创建所有节点
for (Type type : allTypes) {
TypeTreeVo vo = convertTypeToTreeVo(type);
voMap.put(type.getId(), vo);
}
// 第二遍:建立父子关系
for (Type type : allTypes) {
TypeTreeVo vo = voMap.get(type.getId());
if (StringUtils.isBlank(type.getParentId()) ||
"0".equals(type.getParentId()) ||
"-1".equals(type.getParentId())) {
rootNodes.add(vo);
} else {
TypeTreeVo parent = voMap.get(type.getParentId());
if (parent != null) {
if (parent.getChildren() == null) {
parent.setChildren(new ArrayList<>());
}
parent.getChildren().add(vo);
}
}
}
return rootNodes;
}
/**
* 构建组织机构树
*/
private List<OrganizationVo> buildOrgTree(List<Organization> allOrgs) {
Map<String, OrganizationVo> voMap = new HashMap<>();
List<OrganizationVo> rootNodes = new ArrayList<>();
// 第一遍:创建所有节点
for (Organization org : allOrgs) {
OrganizationVo vo = convertOrgToVo(org);
voMap.put(org.getId(), vo);
}
// 第二遍:建立父子关系
for (Organization org : allOrgs) {
OrganizationVo vo = voMap.get(org.getId());
if (StringUtils.isBlank(org.getParentId())) {
rootNodes.add(vo);
} else {
OrganizationVo parent = voMap.get(org.getParentId());
if (parent != null) {
if (parent.getChildren() == null) {
parent.setChildren(new ArrayList<>());
}
parent.getChildren().add(vo);
}
}
}
return rootNodes;
}
/**
* 转TypeTreeVo
*/
private TypeTreeVo convertTypeToTreeVo(Type type) {
TypeTreeVo vo = new TypeTreeVo();
vo.setValue(type.getId());
vo.setLabel(type.getName());
vo.setParentId(type.getParentId());
vo.setOrderIndex(type.getOrderIndex());
vo.setStatus(type.getStatus());
vo.setSysResType(type.getSysResType());
return vo;
}
/**
* 转OrganizationVo
*/
private OrganizationVo convertOrgToVo(Organization org) {
OrganizationVo vo = new OrganizationVo();
vo.setId(org.getId());
vo.setName(org.getName());
vo.setCode(org.getCode());
vo.setParentId(org.getParentId());
vo.setStatus(org.getStatus());
vo.setNamePath(org.getNamePath());
vo.setDescription(org.getDescription());
return vo;
}
}

View File

@@ -0,0 +1,27 @@
package com.xboe.module.course.vo;
import lombok.Data;
import java.util.List;
/**
* 分类树VO用于前端级联选择器
*/
@Data
public class TypeTreeVo {
private String value;
private String label;
private String parentId;
private Integer orderIndex;
private Integer status;
private Integer sysResType;
private List<TypeTreeVo> children;
public TypeTreeVo() {}
public TypeTreeVo(String value, String label, String parentId) {
this.value = value;
this.label = label;
this.parentId = parentId;
}
}

View File

@@ -333,7 +333,7 @@ public class TeacherApi extends ApiBaseController {
OutputStream = response.getOutputStream();
LinkedHashMap<String,String> map = new LinkedHashMap<>();
map.put("姓名","name");
map.put("工号","userNo");
map.put("工号","userNo"); // 重点
map.put("部门","departName");
map.put("创建时间","sysCreateTime");
map.put("修改时间","sysUpdateTime");

View File

@@ -0,0 +1,223 @@
package com.xboe.module.util;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.UUID;
@Slf4j
@Component
public class FileUploadUtil {
@Value("${xboe.upload.file.save_path:D:/file_save}")
private String uploadSavePath;
@Value("${xboe.upload.file.http_path:http://192.168.0.253/upload}")
private String uploadHttpPath;
@Value("${xboe.upload.file.temp_path:D:/temp}")
private String uploadTempPath;
/**
* 保存Base64文件到磁盘
*/
public FileSaveResult saveBase64File(String base64Content, String originalFileName) throws IOException {
if (StringUtils.isBlank(base64Content)) {
throw new IllegalArgumentException("文件内容不能为空");
}
log.info("开始保存文件,原始文件名: {}, 保存路径: {}", originalFileName, uploadSavePath);
// 解码Base64
byte[] fileBytes;
try {
if (base64Content.contains(",")) {
// 去除data:image/png;base64,前缀
base64Content = base64Content.split(",")[1];
}
fileBytes = Base64.getDecoder().decode(base64Content);
} catch (IllegalArgumentException e) {
throw new IOException("Base64解码失败: " + e.getMessage());
}
// 生成文件名
String fileExtension = getFileExtension(originalFileName);
String uuid = UUID.randomUUID().toString().replace("-", "");
String fileName = uuid + (StringUtils.isNotBlank(fileExtension) ? "." + fileExtension : "");
// 按日期组织目录
String datePath = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String relativePath = datePath + "/" + fileName;
// 处理Windows路径分隔符
Path fullPath = Paths.get(uploadSavePath, datePath, fileName);
// 创建目录
Path directory = Paths.get(uploadSavePath, datePath);
if (!Files.exists(directory)) {
Files.createDirectories(directory);
log.info("创建目录: {}", directory.toString());
}
// 保存文件
try (FileOutputStream fos = new FileOutputStream(fullPath.toFile())) {
fos.write(fileBytes);
}
// 构建访问URL
String fileUrl = uploadHttpPath + "/" + relativePath.replace("\\", "/");
FileSaveResult result = new FileSaveResult();
result.setFilePath(relativePath); // 相对路径,用于数据库存储
result.setFileUrl(fileUrl); // 完整的HTTP访问URL
result.setFileName(fileName); // 保存后的文件名
result.setOriginalFileName(originalFileName); // 原始文件名
result.setFileSize(fileBytes.length); // 文件大小(字节)
log.info("文件保存成功: 原始文件={}, 保存文件={}, 大小={}字节, 路径={}",
originalFileName, fileName, fileBytes.length, relativePath);
return result;
}
/**
* 获取文件扩展名
*/
public String getFileExtension(String filename) {
if (StringUtils.isBlank(filename) || !filename.contains(".")) {
return "";
}
return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
}
/**
* 验证文件类型
*/
public boolean validateFileType(String fileName, String[] allowedExtensions) {
String extension = getFileExtension(fileName);
if (StringUtils.isBlank(extension)) {
return false;
}
for (String allowed : allowedExtensions) {
if (allowed.equalsIgnoreCase(extension)) {
return true;
}
}
return false;
}
/**
* 获取支持的文档类型
*/
public String[] getDocumentExtensions() {
return new String[]{"doc", "docx", "xls", "xlsx", "pptx", "txt", "pdf"};
}
/**
* 删除文件
*/
public boolean deleteFile(String filePath) {
if (StringUtils.isBlank(filePath)) {
return false;
}
try {
Path fullPath = Paths.get(uploadSavePath, filePath);
File file = fullPath.toFile();
if (file.exists()) {
boolean deleted = file.delete();
log.info("删除文件: {}, 结果: {}", fullPath.toString(), deleted);
return deleted;
}
log.warn("文件不存在,无法删除: {}", fullPath.toString());
return false;
} catch (Exception e) {
log.error("删除文件失败: {}", filePath, e);
return false;
}
}
/**
* 获取文件完整路径
*/
public String getFullPath(String relativePath) {
if (StringUtils.isBlank(relativePath)) {
return "";
}
return Paths.get(uploadSavePath, relativePath).toString();
}
/**
* 检查文件是否存在
*/
public boolean fileExists(String relativePath) {
if (StringUtils.isBlank(relativePath)) {
return false;
}
String fullPath = getFullPath(relativePath);
File file = new File(fullPath);
return file.exists();
}
@PostConstruct
public void init() {
try {
// 确保上传目录存在
File saveDir = new File(uploadSavePath);
if (!saveDir.exists()) {
boolean created = saveDir.mkdirs();
log.info("创建上传目录: {}, 结果: {}", uploadSavePath, created);
} else {
log.info("上传目录已存在: {}", uploadSavePath);
}
// 确保临时目录存在
File tempDir = new File(uploadTempPath);
if (!tempDir.exists()) {
boolean created = tempDir.mkdirs();
log.info("创建临时目录: {}, 结果: {}", uploadTempPath, created);
}
log.info("文件上传配置初始化完成: savePath={}, httpPath={}, tempPath={}",
uploadSavePath, uploadHttpPath, uploadTempPath);
// 检查目录权限
if (!saveDir.canWrite()) {
log.error("上传目录不可写: {}", uploadSavePath);
}
if (!tempDir.canWrite()) {
log.error("临时目录不可写: {}", uploadTempPath);
}
} catch (Exception e) {
log.error("初始化上传目录失败", e);
}
}
/**
* 文件保存结果
*/
@Data
public static class FileSaveResult {
private String filePath; // 相对路径,用于数据库存储
private String fileUrl; // 完整的HTTP访问URL
private String fileName; // 保存后的文件名
private String originalFileName; // 原始文件名
private long fileSize; // 文件大小(字节)
}
}

View File

@@ -16,12 +16,15 @@ spring:
cloud:
nacos:
discovery:
server-addr: 192.168.0.253:8848
server-addr: 127.0.0.1:8848
# server-addr: 192.168.0.253:8848
config:
server-addr: 192.168.0.253:8848
server-addr: 127.0.0.1:8848
# server-addr: 192.168.0.253:8848
redis:
database: 1
host: 192.168.0.253
host: 127.0.0.1
# host: 192.168.0.253
password: boe@123
port: 6379
jpa:
@@ -29,9 +32,9 @@ spring:
ddl-auto: none
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.0.253:3306/boe_base?useSSL=false&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull
username: root
password: boe#1234A
url: jdbc:mysql://rm-hp3cpkk0u50q90eu9vo.mysql.huhehaote.rds.aliyuncs.com:3306/ebiz_doc_manage_bpic?characterEncoding=utf8&useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=false&allowMultiQueries=true
username: ebiz_ai
password: ebiz_ai123
type: com.zaxxer.hikari.HikariDataSource
hikari:
auto-commit: true
@@ -92,18 +95,19 @@ xboe:
ai-api-code: 30800
case-knowledge-id: de2e006e-82fb-4ace-8813-f25c316be4ff
file-upload-callback-url: http://192.168.0.253:9090/xboe/m/boe/caseDocumentLog/uploadCallback
alert-email-recipients:
alert-email-recipients:
- liu.zixi@ebiz-digits.com
xxl:
job:
accessToken: 65ddc683-22f5-83b4-de3a-3c97a0a29af0
# accessToken: 65ddc683-22f5-83b4-de3a-3c97a0a29af0
accessToken: default_token
admin:
addresses: http://192.168.0.253/jobAdmin
addresses: http://127.0.0.1:8080/xxl-job-admin
executor:
appname: java-servers-job-api
port: 9995
address:
ip:
ip: 127.0.0.1
logpath: /var/log/xxl-job/dw/
logretentiondays: 30
aop-log-record:

View File

@@ -146,6 +146,7 @@ boe:
domain-name: https://u.boe.com
pcPageUrl: ${boe.domain-name}/pc/course/studyindex?id=
h5PageUrl: ${boe.domain-name}/mobile/pages/study/courseStudy?id=
# 问题所在:访问不了------- 页面闪退+502+404
pcLoginUrl: ${boe.domain-name}/web/
h5LoginUrl: ${boe.domain-name}/m/loginuser