Compare commits

...

27 Commits

Author SHA1 Message Date
liu.zixi
0644706aca 预留一套完整的DTO,未完待续 2025-12-16 15:56:53 +08:00
liu.zixi
68166f6cf1 预留一套完整的DTO,未完待续 2025-12-16 15:42:17 +08:00
renwanhong
317bdb161c 大表单提交 2025-12-16 14:41:36 +08:00
yangxinyu
3de308fee1 feat:BPM提交审核修改(缺少插入数据库的部分) 2025-12-16 09:29:44 +08:00
yangxinyu
0981eaaf52 feat:BPM回调接口 2025-12-15 16:44:46 +08:00
yangxinyu
639ac253f1 feat:回调接口(未完成功能,仅为空壳) 2025-12-12 16:34:08 +08:00
yangxinyu
f43d52c210 feat:BPM提交审核(创建审核部分,还缺少编辑审核部分) 2025-12-12 11:36:24 +08:00
yangxinyu
c01a69c9df feat:提交审核(目前只完成停用/启用部分内容) 2025-12-10 17:50:46 +08:00
yangxinyu
eba13bb602 feat:修改学习记录导出中学习时长字段格式为两位小数 2025-12-09 09:36:11 +08:00
yangxinyu
240d4725e6 feat:修改资源学习列表count数计算 2025-12-08 09:16:16 +08:00
liu.zixi
0b5716ed7d feat: 课程列表导出,组织名称改为导出全路径 2025-12-07 15:26:25 +08:00
liu.zixi
a1121e8700 fix: 课程列表接口纠错 2025-12-06 17:37:55 +08:00
liu.zixi
5f42139407 fix: 课程列表接口纠错 2025-12-06 17:37:12 +08:00
liu.zixi
3d35519615 fix: 课程列表接口增加返回最后修改时间 2025-12-06 17:25:56 +08:00
liu.zixi
e76833a3c2 fix: 课程列表接口增加返回课程类型 2025-12-06 17:22:07 +08:00
miaowenbo
c3027517c5 fix:修复了资源学习情况列表查询的有没有状态名称模糊查询不一致的问题,现在统一模糊查询
status为空查询字段加上aid,方便前端获取工号部门信息
2025-12-05 17:46:49 +08:00
yangxinyu
ce2524fdcb feat:修改资源学习列表无完成人数无法查到资源的问题 2025-12-04 18:40:18 +08:00
miaowenbo
4f53a268ba fix:学习记录查询/导出接口中,学习时长条件查询重新赋值补全实体类解耦逻辑,避免影响原表数据 2025-12-04 14:38:16 +08:00
yangxinyu
8e1a68d416 feat:修改完成人数统计错误 2025-12-04 14:04:59 +08:00
yangxinyu
4e6d1a6b04 Reapply "Merge branch '251114-feature-course-online' of https://codeup.aliyun.com/67762337eccfc218f6110e0e/per-boe/java-servers into 251114-feature-course-online"
This reverts commit f506fe49f2.
2025-12-04 13:33:05 +08:00
yangxinyu
f506fe49f2 Revert "Merge branch '251114-feature-course-online' of https://codeup.aliyun.com/67762337eccfc218f6110e0e/per-boe/java-servers into 251114-feature-course-online"
This reverts commit 3c582e9ade, reversing
changes made to 716ba91c2e.
2025-12-04 13:16:35 +08:00
yangxinyu
3c582e9ade Merge branch '251114-feature-course-online' of https://codeup.aliyun.com/67762337eccfc218f6110e0e/per-boe/java-servers into 251114-feature-course-online 2025-12-04 13:15:16 +08:00
yangxinyu
716ba91c2e feat资源学习列表新增content_id 2025-12-04 13:14:03 +08:00
miaowenbo
f39562ffc9 fix:分页查询课程学习记录以及导出课程学习记录修改时间条件(初版) 2025-12-03 18:58:04 +08:00
miaowenbo
2b7d0ef35a fix:学习记录导出接口补全缺少的空值校验 2025-12-02 17:41:11 +08:00
liu.zixi
7e21784a74 feat: 数据权限确认 2025-12-01 19:10:37 +08:00
yangxinyu
4b064171e6 feat:增加查询资源学习列表的contentName字段 2025-12-01 17:22:10 +08:00
47 changed files with 4737 additions and 241 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,6 +186,9 @@ public class CourseContentApi extends ApiBaseController{
return badRequest("无课程关联");
}
try {
// 测试
// log.info("CourseContentDto: {}", cc);
ccontentService.saveOrUpdate(cc);
return success(cc);
}catch(Exception e) {
@@ -168,6 +198,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){
if(StringUtils.isBlank(section.getCourseId())) {

View File

@@ -68,6 +68,9 @@ public class CourseManageApi extends ApiBaseController{
@Autowired
IOutSideDataService outsideService;
@Autowired
ICourseManageService courseManageService;
@Resource
IEmailService service;
@@ -131,6 +134,16 @@ public class CourseManageApi extends ApiBaseController{
return success(coursePageService.pageQuery(null, coursePageQueryDTO));
}
/**
* 新-教师端 我开发的课程
* @param coursePageQueryDTO
* @return
*/
@PostMapping("/develop_page")
public JsonResponse<PageList<CoursePageVo>> teacherPage(@RequestBody CoursePageQueryDTO coursePageQueryDTO) {
return success(coursePageService.pageQuery(getCurrent(), coursePageQueryDTO));
}
/**
* 当前用户是否在管理端显示置顶相关功能
* @return
@@ -678,6 +691,81 @@ public class CourseManageApi extends ApiBaseController{
return error("提交课程处理失败",e.getMessage());
}
}
/**
* 25.12.10新增提交审核到BPM
*暂无BPM接口
*非空判断需要加/日志
*/
@PostMapping("/bpm-submit")
@AutoLog(module = "课程",action = "提交审核到BPM",info = "")
public JsonResponse<String> submitBPMCourseFull(@RequestBody CourseFullDto dto){
try {
BPMResponseDto response = null;
//首先判断是否为停用审核
if(dto.getAuditType() != null && dto.getAuditType()==0)
{
// 准备停用审核的JSON请求体,暂时先按照京东方大学堂后端调用BPM系统需要的接口文档的入参示例完成等到有外部接口时再修改
String jsonRequestBody = courseManageService.prepareDisableAuditRequest(dto);
// TODO: 调用BPM接口
//BPMResponseDto bpmResponsedto= courseManageService.callBPMInterface(jsonRequestBody);
}
//再判断是否为启用审核
else if(dto.getAuditType() != null && dto.getAuditType()==3)
{
// 准备启用审核的JSON请求体,暂时先按照京东方大学堂后端调用BPM系统需要的接口文档的入参示例完成等到有外部接口时再修改
String jsonRequestBody = courseManageService.prepareDisableAuditRequest(dto);
// TODO: 调用BPM接口
// BPMResponseDto bpmResponsedto= courseManageService.callBPMInterface(jsonRequestBody);
}
else{
//通过查看在boe_course_hrbp_audit表当中有没有旧的审核记录数据判断为创建审核还是编辑审核
String courseId=dto.getCourse().getId();
CourseHRBPAudit previousAudit = hrbpAuditService.hadAuditing(courseId);
if (previousAudit != null) {
// 存在历史审核记录,视为编辑审核
//编辑课程审核需要后端服务整理出本次编辑的内容与编辑前的前后差异提交至BPM系统。
dto.setAuditType(2);
} else {
// 无历史审核记录,视为创建审核
dto.setAuditType(1);
// 准备创建审核的JSON请求体,暂时先按照京东方大学堂后端调用BPM系统需要的接口文档的入参示例完成等到有外部接口时再修改
String jsonRequestBody = courseManageService.prepareCreateAuditRequest(dto);
// TODO: 调用BPM接口
// BPMResponseDto bpmResponsedto= courseManageService.callBPMInterface(jsonRequestBody);
}
}
// 构造DTO
//实际使用中返回的值从BPM接口返回的JSON中获取
BPMResponseDto bpmResponsedto = new BPMResponseDto();
bpmResponsedto.setStatus("success");
bpmResponsedto.setAuditId("audit123456");
bpmResponsedto.setAuditApprover("管理员hrbp");
return success("success");
} catch (Exception e) {
log.error("提交保存课程信息错误",e);
return error("error");
}
}
/**
* 25.12.12新增,审核完成的回调接口
* 注意需和项目经理核对:
* boe_course_HRBP_audit表里没有直接对应的字段原表auditUser和lastAuditUser原表auditRemark/lastRemark和auditComment
*/
@PostMapping("/audit/callback")
public JsonResponse<String> callbackBPM(@RequestBody BPMCallbackDto dto){
try {
if(StringUtils.isBlank(dto.getAuditId()))
{
log.error("BPM回调失败auditId不能为空");
return error("auditId不能为空");
}
hrbpAuditService.bpmRecallUpdate(dto);
return success("BPM回调成功");
} catch (Exception e) {
log.error("回调错误",e);
return error("error");
}
}
private String createEmailHtml(String name,String orgId, String orgName,String createBy,String courseName) throws Exception {
StringBuffer htmlMsg=new StringBuffer("<div style=\"line-height:30px;border:2px solid #2990ca;padding:20px\">");

View File

@@ -6,6 +6,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.xboe.module.course.dto.CoursePageQueryDTO;
@@ -30,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);
}
/**
* 课程分页 搜索查询
* */
@@ -101,6 +143,7 @@ public class CourseDao extends BaseDao<Course> {
StringBuilder builder = new StringBuilder("select ");
builder.append("c.id,");
builder.append("c.name,");
builder.append("c.type,");
builder.append("c.cover_img AS coverImg,");
builder.append("c.sys_type1 AS sysType1,");
builder.append("c.sys_type2 AS sysType2,");
@@ -122,7 +165,8 @@ public class CourseDao extends BaseDao<Course> {
builder.append("c.open_course AS openCourse,");
builder.append("c.is_top AS isTop,");
builder.append("COALESCE(tch.teacher_names, '') AS teacherName,");
builder.append("c.sort_weight AS sortWeight");
builder.append("c.sort_weight AS sortWeight,");
builder.append("c.sys_update_time AS sysUpdateTime");
// 拼接FROM及查询条件语句
appendFrom(builder, queryDTO, isSystemAdmin, orgIds, currentAccountId, userCenterSchema);
// 排序语句
@@ -142,59 +186,65 @@ public class CourseDao extends BaseDao<Course> {
}
vo.setName((String) row[1]);
vo.setCoverImg((String) row[2]);
vo.setSysType1((String) row[3]);
vo.setSysType2((String) row[4]);
vo.setSysType3((String) row[5]);
vo.setOrgId((String) row[6]);
vo.setOrgName((String) row[7]);
vo.setOrgFullName((String) row[8]);
vo.setSysCreateBy((String) row[9]);
vo.setCreateFrom((String) row[10]);
vo.setType((Integer) row[2]);
vo.setCoverImg((String) row[3]);
vo.setSysType1((String) row[4]);
vo.setSysType2((String) row[5]);
vo.setSysType3((String) row[6]);
vo.setOrgId((String) row[7]);
vo.setOrgName((String) row[8]);
vo.setOrgFullName((String) row[9]);
vo.setSysCreateBy((String) row[10]);
vo.setCreateFrom((String) row[11]);
// 增加对Timestamp和LocalDateTime的兼容性防止Timestamp为null的情况
Timestamp sysCreateTimestamp = (Timestamp) row[11];
Timestamp sysCreateTimestamp = (Timestamp) row[12];
if (sysCreateTimestamp != null) {
vo.setSysCreateTime(sysCreateTimestamp.toLocalDateTime());
}
vo.setForUsers((String) row[12]);
vo.setStatus((Integer) row[13]);
vo.setPublished((Boolean) row[14]);
vo.setForUsers((String) row[13]);
vo.setStatus((Integer) row[14]);
vo.setPublished((Boolean) row[15]);
// 增加对Timestamp和LocalDateTime的兼容性防止Timestamp为null的情况
Timestamp publishTimestamp = (Timestamp) row[15];
Timestamp publishTimestamp = (Timestamp) row[16];
if (publishTimestamp != null) {
vo.setPublishTime(publishTimestamp.toLocalDateTime());
}
// 防止Number为null的情况
Number studysNum = (Number) row[16];
Number studysNum = (Number) row[17];
if (studysNum != null) {
vo.setStudys(studysNum.intValue());
} else {
vo.setStudys(0);
}
Number scoreNum = (Number) row[17];
Number scoreNum = (Number) row[18];
if (scoreNum != null) {
vo.setScore(scoreNum.floatValue());
} else {
vo.setScore(0.0f);
}
Number durationNum = (Number) row[18];
Number durationNum = (Number) row[19];
if (durationNum != null) {
vo.setCourseDuration(durationNum.longValue());
} else {
vo.setCourseDuration(0L);
}
vo.setEnabled((Boolean) row[19]);
vo.setOpenCourse((Integer) row[20]);
vo.setIsTop((Boolean) row[21]);
vo.setTeacherName((String) row[22]);
vo.setSortWeight((Integer) row[23]);
vo.setEnabled((Boolean) row[20]);
vo.setOpenCourse((Integer) row[21]);
vo.setIsTop((Boolean) row[22]);
vo.setTeacherName((String) row[23]);
vo.setSortWeight((Integer) row[24]);
// 增加对Timestamp和LocalDateTime的兼容性防止Timestamp为null的情况
Timestamp sysUpdateTimestamp = (Timestamp) row[25];
if (sysUpdateTimestamp != null) {
vo.setSysUpdateTime(sysUpdateTimestamp.toLocalDateTime());
}
coursePageVos.add(vo);
}
return coursePageVos;
@@ -229,7 +279,7 @@ public class CourseDao extends BaseDao<Course> {
// 开头判断课程培训时间的两个参数是否不为null
boolean filterLearningTime = queryDTO.getLearningTimeStart() != null && queryDTO.getLearningTimeEnd() != null;
builder.append(" FROM boe_course c");
// 聚合教师姓名(仅未删除的教师)
// 聚合教师姓名
builder.append(System.lineSeparator());
builder.append("LEFT JOIN (SELECT course_id, GROUP_CONCAT(teacher_name ORDER BY id SEPARATOR ',') AS teacher_names FROM boe_course_teacher GROUP BY course_id) tch ON c.id = tch.course_id");
// 学习人数聚合(满足时间条件的学习记录,且学习记录有效)
@@ -253,6 +303,9 @@ public class CourseDao extends BaseDao<Course> {
builder.append(System.lineSeparator());
builder.append("LEFT JOIN ").append(userCenterSchema)
.append(".organization org ON c.org_id = org.organization_id AND org.deleted = 0");
// 教师联查
builder.append(System.lineSeparator());
builder.append("LEFT JOIN boe_course_teacher ct ON c.id = ct.course_id");
// where条件
// 第一个条件deleted = 0
@@ -302,9 +355,14 @@ public class CourseDao extends BaseDao<Course> {
builder.append("AND (:learningTimeStart IS NULL OR :learningTimeEnd IS NULL OR EXISTS (SELECT 1 FROM boe_study_course sc WHERE sc.course_id = c.id AND sc.finish_time IS NOT NULL AND sc.add_time >= :learningTimeStart AND sc.finish_time <= :learningTimeEnd))");
}
// 授课教师
if (StringUtils.isNotBlank(queryDTO.getTeacherName())) {
if (StringUtils.isNotBlank(queryDTO.getTeacherId())) {
builder.append(System.lineSeparator());
builder.append("AND tch.teacher_names LIKE CONCAT('%', :teacherName, '%')");
// 判断teacherId是一个还是多个
if (queryDTO.getTeacherId().contains(",")) {
builder.append("AND ct.teacher_id IN (:teacherIdList)");
} else {
builder.append("AND ct.teacher_id = :teacherId");
}
}
// 展开后条件
if (queryDTO.getEnabled() != null) {
@@ -319,9 +377,13 @@ public class CourseDao extends BaseDao<Course> {
builder.append(System.lineSeparator());
builder.append("AND c.org_id = :orgId");
}
if (StringUtils.isNotBlank(queryDTO.getCreateUser())) {
if (StringUtils.isNotBlank(queryDTO.getCreateUserId())) {
builder.append(System.lineSeparator());
builder.append("AND c.sys_create_by LIKE CONCAT('%', :createUser, '%')");
if (queryDTO.getCreateUserId().contains(",")) {
builder.append("AND c.sys_create_aid IN (:createUserIdList)");
} else {
builder.append("AND c.sys_create_aid = :createUserId");
}
}
if (StringUtils.isNotBlank(queryDTO.getCreateFrom())) {
builder.append(System.lineSeparator());
@@ -402,8 +464,14 @@ public class CourseDao extends BaseDao<Course> {
query.setParameter("learningTimeStart", queryDTO.getLearningTimeStart());
query.setParameter("learningTimeEnd", queryDTO.getLearningTimeEnd());
}
if (StringUtils.isNotBlank(queryDTO.getTeacherName())) {
query.setParameter("teacherName", queryDTO.getTeacherName());
if (StringUtils.isNotBlank(queryDTO.getTeacherId())) {
String teacherIdStr = queryDTO.getTeacherId();
if (teacherIdStr.contains(",")) {
List<String> teacherIdList = Arrays.asList(teacherIdStr.split(","));
query.setParameter("teacherIdList", teacherIdList);
} else {
query.setParameter("teacherId", queryDTO.getTeacherId());
}
}
if (queryDTO.getEnabled() != null) {
query.setParameter("enabled", queryDTO.getEnabled());
@@ -414,8 +482,14 @@ public class CourseDao extends BaseDao<Course> {
if (StringUtils.isNotBlank(queryDTO.getResOwner1())) {
query.setParameter("orgId", queryDTO.getResOwner1());
}
if (StringUtils.isNotBlank(queryDTO.getCreateUser())) {
query.setParameter("createUser", queryDTO.getCreateUser());
if (StringUtils.isNotBlank(queryDTO.getCreateUserId())) {
String createUserIdStr = queryDTO.getCreateUserId();
if (createUserIdStr.contains(",")) {
List<String> createUserIdList = Arrays.asList(createUserIdStr.split(","));
query.setParameter("createUserIdList", createUserIdList);
} else {
query.setParameter("createUserId", queryDTO.getCreateUserId());
}
}
if (StringUtils.isNotBlank(queryDTO.getCreateFrom())) {
query.setParameter("createFrom", queryDTO.getCreateFrom());

View File

@@ -1,5 +1,6 @@
package com.xboe.module.course.dao;
import com.xboe.module.course.dto.BPMCallbackDto;
import org.springframework.stereotype.Repository;
import com.xboe.core.orm.BaseDao;

View File

@@ -0,0 +1,30 @@
package com.xboe.module.course.dto;
import lombok.Data;
/**
* 25.12.12新增BPM回调接口入参
*
*/
@Data
public class BPMCallbackDto {
/**
* 审批流程ID
*/
private String auditId;
/**
* 审核结果,可选值:`success` (通过), `reject` (驳回)
*/
private String auditResult;
/**
* 最终审核意见
* auditResult为reject时传最后驳回时的驳回意见
*/
private String auditComment;
/**
* 最终审核人
* auditResult为reject时传最后驳回的用户
*/
private String lastAuditUser;
}

View File

@@ -0,0 +1,26 @@
package com.xboe.module.course.dto;
import lombok.Data;
/**
* 25.12.10新增提交审核到BPM时的返回结果
*
*/
@Data
public class BPMResponseDto {
/**
* 调用BPM返回的状态success/error
*/
private String status;
/**
* 调用BPM成功时返回的审批单ID
*/
private String auditId;
/**
* 调用BPM成功时返回的审批人姓名
*/
private String auditApprover;
/**
* 调用BPM失败时返回的错误信息
*/
private String message;
}

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

@@ -18,6 +18,20 @@ import lombok.Data;
*/
@Data
public class CourseFullDto {
/**
* 25.12.10新增
* 审核类型
* 0: 停用审核
* 1: 创建审核
* 2: 编辑审核
* 3启用审核
*/
private Integer auditType;
/**
* 25.12.10新增
* 提交审核的用户id
*/
private String userId;
/**是否是新建课程*/
private Boolean isNew;

View File

@@ -31,6 +31,11 @@ public class CoursePageQueryDTO {
/**创建人*/
private String createUser;
/**
* 创建人id
*/
private String createUserId;
/**课程分类的一级*/
private String sysType1;
@@ -43,6 +48,11 @@ public class CoursePageQueryDTO {
/**授课教师*/
private String teacherName;
/**
* 授课教师id
*/
private String teacherId;
/**培训时间筛选类型*/
private String learningTimeType;
@@ -77,4 +87,11 @@ public class CoursePageQueryDTO {
/**排序顺序*/
private Boolean orderAsc;
/**
* 是否是新建在线课程页面
* false时代表是在管理端的“在线管理”、“我开发的课程”
* true时代表是在学习路径图/项目管理 中,创建在线课程的选择页面
*/
private Boolean isCreateCourse;
}

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

@@ -0,0 +1,69 @@
package com.xboe.module.course.dto.content;
import com.xboe.standard.enums.BoedxContentType;
import lombok.Data;
/**
* 课程内容DTO
*/
@Data
public class CourseContentDTO {
/**
* 内容ID
*/
private String contentId;
/**
* 内容名称
*/
private String contentName;
/**
* 内容类型
* @see BoedxContentType
* 有效的类型10-视频
* 20-音频
* 40-文档
* 41-图文Html
* 52-链接
* 50-SCORM
* 60-作业
* 61-考试
* 62-评估
*/
private Integer contentType;
/**
* 排序索引
*/
private Integer sortIndex;
/**
* 课时(分钟)
*/
private Integer duration;
/**
* 具体课件内容
* 图文会用到
*/
private String content;
/**
* 是否使用新上传的课件
* 0-否
* 1-是
*/
private Integer isNewFile;
/**
* 课件内容引用ID
* 当课件不是新上传,而是选择已有的情况下,用这个字段
*/
private String contentRefId;
/**
* 文件相关信息
*/
private FileResourceInfoDTO file;
}

View File

@@ -0,0 +1,23 @@
package com.xboe.module.course.dto.content;
import lombok.Data;
import java.util.List;
/**
* 课程内容DTO
* 涉及一个课程中所有的课程内容,结构是章节结构
*/
@Data
public class CourseFullContentDTO {
/**
* 课程id
*/
private String courseId;
/**
* 章列表
*/
private List<CourseSectionDTO> sections;
}

View File

@@ -0,0 +1,27 @@
package com.xboe.module.course.dto.content;
import lombok.Data;
import java.util.List;
/**
* 课程章DTO
*/
@Data
public class CourseSectionDTO {
/**
* 章id
*/
private String sectionId;
/**
* 章名称
*/
private String sectionName;
/**
* 章里面的课件内容
*/
private List<CourseContentDTO> contents;
}

View File

@@ -0,0 +1,25 @@
package com.xboe.module.course.dto.content;
import lombok.Data;
/**
* 课程内容对应的文件资源
*/
@Data
public class FileResourceInfoDTO {
/**
* 文件名称
*/
private String fileName;
/**
* 文件类型
*/
private String fileType;
/**
* 文件路径
*/
private String filePath;
}

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

@@ -141,6 +141,28 @@ public class CourseHRBPAudit extends IdEntity {
@Column(name = "last_remark")
private String lastRemark;
/**
* 25.12.15新增
* 审核类型
*/
@Column(name = "audit_type")
private Integer auditType;
/**
* 25.12.15新增
* 审核结果
* 1通过0驳回
*/
@Column(name = "audit_result")
private Integer auditResult;
/**
* 25.12.15新增
* 审核流程Id
*/
@Column(name = "audit_id")
private String auditId;
@Transient
private String courseName;

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{
@@ -21,6 +23,13 @@ public interface ICourseContentService{
*/
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

@@ -4,6 +4,7 @@ package com.xboe.module.course.service;
import java.util.List;
import com.xboe.common.PageList;
import com.xboe.module.course.dto.BPMCallbackDto;
import com.xboe.module.course.dto.CourseHRBPAuditDto;
import com.xboe.module.course.entity.CourseHRBPAudit;
@@ -11,6 +12,10 @@ import com.xboe.module.course.entity.CourseHRBPAudit;
* 人员课程审核
* */
public interface ICourseHRBPAuditService {
/**
* BPM回调更新
* */
void bpmRecallUpdate(BPMCallbackDto dto);
/**
* 提交审核
@@ -32,6 +37,14 @@ public interface ICourseHRBPAuditService {
*/
CourseHRBPAudit hasAuditing(String courseId);
/**
* 25.12.10新增
* 检查课程是否已经有过审核记录了
* @param courseId
* @return
*/
CourseHRBPAudit hadAuditing(String courseId);
/**
* 根据课程、id获取课程的审核记录
* @param info 审核信息

View File

@@ -0,0 +1,19 @@
package com.xboe.module.course.service;
import com.xboe.module.course.dto.BPMResponseDto;
import com.xboe.module.course.dto.CourseFullDto;
public interface ICourseManageService {
/**
* 25.12.10新增,调用外部接口前,启用/停用进行数据准备和json请求体构建
*/
String prepareDisableAuditRequest(CourseFullDto dto);
/**
* 25.12.10新增调用外部接口前创建进行数据准备和json请求体构建
*/
String prepareCreateAuditRequest(CourseFullDto dto);
/**
* 25.12.10新增BPM外部接口调用
*/
BPMResponseDto callBPMInterface(String jsonRequestBody);
}

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,58 +76,452 @@ 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
@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) {

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

@@ -5,6 +5,8 @@ import java.util.List;
import javax.annotation.Resource;
import com.xboe.core.orm.UpdateBuilder;
import com.xboe.module.course.dto.BPMCallbackDto;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@@ -19,6 +21,7 @@ import com.xboe.module.course.entity.CourseHRBPAudit;
import com.xboe.module.course.service.ICourseHRBPAuditService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@@ -26,6 +29,31 @@ public class CourseHRBPAuditServiceImpl implements ICourseHRBPAuditService {
@Resource
CourseHRBPAuditDao courseHRBPAuditDao;
/**
* BPM回调后更新审批结果
* @param dto
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void bpmRecallUpdate(BPMCallbackDto dto){
if(dto.getAuditResult().equals("success")){
courseHRBPAuditDao.update(
UpdateBuilder.from(CourseHRBPAudit.class)
.addUpdateField("auditResult", 1)
.addFilter(FieldFilters.eq("auditId", dto.getAuditId()))
.builder()
);
}else{
courseHRBPAuditDao.update(
UpdateBuilder.from(CourseHRBPAudit.class)
.addUpdateField("auditResult", 0)
.addUpdateField("auditRemark", dto.getAuditComment())
.addUpdateField("auditUser", dto.getLastAuditUser())
.addFilter(FieldFilters.eq("auditId", dto.getAuditId()))
.builder()
);
}
}
@Override
public void save(CourseHRBPAudit coursePersonAudit) {
@@ -171,6 +199,19 @@ public class CourseHRBPAuditServiceImpl implements ICourseHRBPAuditService {
return hrbp;
}
/**
* 25.12.10新增
* 检查课程是否已经有过审核记录了
* @param courseId
* @return
*/
@Override
public CourseHRBPAudit hadAuditing(String courseId) {
//未审核的
CourseHRBPAudit hrbp = courseHRBPAuditDao.findOne(FieldFilters.eq("courseId", courseId),FieldFilters.eq("status", 9));
return hrbp;
}
@Override
public PageList<CourseHRBPAudit> pageList(Integer pageIndex, Integer pageSize, int userType, CourseHRBPAudit info) {
QueryBuilder query=QueryBuilder.from(CourseHRBPAudit.class.getSimpleName()+" a,"+Course.class.getSimpleName()+" c");

View File

@@ -0,0 +1,133 @@
package com.xboe.module.course.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xboe.core.utils.OkHttpUtil;
import com.xboe.module.course.dto.BPMResponseDto;
import com.xboe.module.course.dto.CourseFullDto;
import com.xboe.module.course.service.ICourseManageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class CourseManageServiceImpl implements ICourseManageService {
@Autowired
private OkHttpUtil okHttpUtil;
/**
* 启用/停用调用外部接口前讲DTO转换成JSON字符串
*/
@Override
public String prepareDisableAuditRequest(CourseFullDto dto) {
try {
// 创建顶层对象
Map<String, Object> request = new HashMap<>();
// 构造auditContent部分
Map<String, Object> auditContent = new HashMap<>();
//auditContent.put("auditType", "停用课程");
if(dto.getAuditType()==0)
{
auditContent.put("auditType", "停用课程");
}
else{
auditContent.put("auditType", "启用课程");
}
// 构造courseInfo部分
Map<String, String> courseInfo = new HashMap<>();
courseInfo.put("name", dto.getCourse().getName());
auditContent.put("courseInfo", courseInfo);
// 将auditContent放入请求体
request.put("auditContent", auditContent);
// 添加用户ID
request.put("userId", dto.getUserId());
// 转换为JSON字符串
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(request);
} catch (Exception e) {
throw new RuntimeException("构造停用审核JSON请求体失败", e);
}
}
/**
* 创建调用外部接口前讲DTO转换成JSON字符串
*/
@Override
public String prepareCreateAuditRequest(CourseFullDto dto) {
try {
// 创建顶层对象
Map<String, Object> request = new HashMap<>();
// 构造auditContent部分
Map<String, Object> auditContent = new HashMap<>();
auditContent.put("auditType", "创建课程");
// 构造courseInfo部分
Map<String, Object> courseInfo = new HashMap<>();
courseInfo.put("name", dto.getCourse().getName());
//几级分类?几级归属?
courseInfo.put("sysType", dto.getCourse().getSysType1());
courseInfo.put("resOwner", dto.getCourse().getResOwner1());
courseInfo.put("teacherName", dto.getCourse().getTeacher());
courseInfo.put("forUsers", dto.getCourse().getForUsers());
courseInfo.put("tags", dto.getCourse().getTags());
courseInfo.put("device", dto.getCourse().getDevice());
courseInfo.put("coverImg", dto.getCourse().getCoverImg());
courseInfo.put("value", dto.getCourse().getValue());
courseInfo.put("summary", dto.getCourse().getSummary());
//课程目录等到拿到BPM接口需要查看getCatalogTree()得到的格式是否与BPM需要的格式一致
courseInfo.put("sections", dto.getCatalogTree());
auditContent.put("courseInfo", courseInfo);
// 将auditContent放入请求体
request.put("auditContent", auditContent);
// 添加用户ID
request.put("userId", dto.getUserId());
// 转换为JSON字符串
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(request);
} catch (Exception e) {
throw new RuntimeException("构造创建审核JSON请求体失败", e);
}
}
/**
* 调用BPM外部接口
*/
@Override
public BPMResponseDto callBPMInterface(String jsonRequestBody){
try {
String token = "";
// BPM接口地址,先按照接口文档写,得到实际的地址之后再修改
String url = "http://bpm-system-address/xboe/bpm/audit/submit";
// 设置请求头
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
// 添加认证头
headers.put("Authorization", "Bearer " + token);
// Map转String数组
List<String> headerList = new ArrayList<>();
for (Map.Entry<String, String> entry : headers.entrySet()) {
headerList.add(entry.getKey() + ":" + entry.getValue());
}
String[] headerArr = headerList.toArray(new String[0]);
// 发送POST请求
String response = okHttpUtil.doPostJson(url, jsonRequestBody,headerArr);
// 解析响应
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(response, BPMResponseDto.class);
} catch (Exception e) {
throw new RuntimeException("调用BPM接口失败: " + e.getMessage());
}
}
}

View File

@@ -82,65 +82,9 @@ public class CoursePageServiceImpl implements ICoursePageService {
String currentAccountId = currentUser == null ? null : currentUser.getAccountId();
// 第一版废弃
// // 构建查询条件
// List<IFieldFilter> filters = new ArrayList<>();
//
// // 自动添加过滤已删除
// filters.add(FieldFilters.eq("c.deleted", false));
//
// // 添加权限过滤条件
// if (!isSystemAdmin) {
// // 非系统管理员需要进行权限过滤
// List<IFieldFilter> permissionFilters = new ArrayList<>();
//
// // 添加组织权限过滤
// if (orgIds != null && !orgIds.isEmpty()) {
// permissionFilters.add(FieldFilters.in("c.orgId", orgIds));
// }
//
// // 添加创建人过滤(可以看到自己创建的课程)
// if (StringUtils.isNotBlank(currentAccountId)) {
// permissionFilters.add(FieldFilters.eq("c.sysCreateAid", currentAccountId));
// }
//
// // 如果有权限条件则添加OR条件
// if (!permissionFilters.isEmpty()) {
// filters.add(FieldFilters.or(permissionFilters));
// }
// }
// filters.addAll(createFilters(coursePageQueryDTO));
//
// QueryBuilder query = QueryBuilder.from(Course.class.getSimpleName() + " c").addFilters(filters);
// // 处理排序
// handleOrder(query, coursePageQueryDTO.getOrderField(), coursePageQueryDTO.getOrderAsc());
//
// // 设置分页参数
// int pageIndex = coursePageQueryDTO.getPageIndex() != null ? coursePageQueryDTO.getPageIndex() : 0;
// int pageSize = coursePageQueryDTO.getPageSize() != null ? coursePageQueryDTO.getPageSize() : 10;
// query.setPageIndex(pageIndex);
// query.setPageSize(pageSize);
//
// // 执行查询
// PageList<Course> coursePageList = courseDao.findPage(query.builder());
//
// // 转换为CoursePageVo
// PageList<CoursePageVo> result = new PageList<>();
// result.setCount(coursePageList.getCount());
// result.setPageSize(coursePageList.getPageSize());
//
// // 子查询根据课程id查询课程下的教师
// List<String> courseIds = coursePageList.getList().stream()
// .map(Course::getId)
// .collect(Collectors.toList());
// List<CourseTeacher> courseTeacherList = getCourseTeacherList(courseIds);
// List<CoursePageVo> voList = coursePageList.getList().stream()
// .map(c -> convertToVo(c, courseTeacherList))
// .collect(Collectors.toList());
// result.setList(voList);
//
// return result;
// 第二版
List<String> readIds = userOrgIds.getReadIds();
orgIds.addAll(readIds);
long total = courseDao.countCourse(coursePageQueryDTO, isSystemAdmin, orgIds, currentAccountId, mySqlSchemaProperties.getUserCenterSchema());
List<CoursePageVo> voList = courseDao.queryCourse(coursePageQueryDTO, isSystemAdmin, orgIds, currentAccountId, true, mySqlSchemaProperties.getUserCenterSchema());
PageList<CoursePageVo> result = new PageList<>();
@@ -306,7 +250,7 @@ public class CoursePageServiceImpl implements ICoursePageService {
map.put("published", coursePageVo.getPublished() == null || !coursePageVo.getPublished() ? "未发布" : "已发布");
map.put("enabled", coursePageVo.getEnabled() == null || coursePageVo.getEnabled() ? "停用" : "启用");
map.put("openCourse", coursePageVo.getOpenCourse() == null || coursePageVo.getOpenCourse() == 0 ? "" : "");
map.put("orgName", coursePageVo.getOrgName());
map.put("orgName", coursePageVo.getOrgFullName()); // 修改为组织全名
map.put("sysCreateBy", coursePageVo.getSysCreateBy());
map.put("createFrom", CourseCreateFromEnum.getByCode(coursePageVo.getCreateFrom()).getLabel());
map.put("sysCreateTime", formatter.format(coursePageVo.getSysCreateTime()));

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

@@ -19,6 +19,11 @@ public class CoursePageVo {
*/
private String name;
/**
* 课程类型
*/
private Integer type;
/**
* 课程封面图片地址
*/
@@ -88,6 +93,13 @@ public class CoursePageVo {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime sysCreateTime;
/**
* 最后修改时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime sysUpdateTime;
/**
* 目标人群
*/

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

@@ -133,15 +133,15 @@ public class StudyCourseApi extends ApiBaseController{
*
* @param pager 分页参数
* @param courseId 25.11.25新增
*
* @param contentName 资源名称
*/
@RequestMapping(value = "/pagelist-resource", method = {RequestMethod.GET, RequestMethod.POST})
public JsonResponse<PageList<CourseFinishCountDto>> findPageResource(Pagination pager, String courseId) {
public JsonResponse<PageList<CourseFinishCountDto>> findPageResource(Pagination pager, String courseId, String contentName) {
if (StringUtil.isBlank(courseId)) {
return error("课程id不能为空");
}
try {
PageList<CourseFinishCountDto> courseFinishCountDTOs = service.findPageResource(pager.getPageIndex(), pager.getPageSize(), courseId);
PageList<CourseFinishCountDto> courseFinishCountDTOs = service.findPageResource(pager.getPageIndex(), pager.getPageSize(), courseId, contentName);
return success(courseFinishCountDTOs);
} catch (Exception e) {
log.error("查询课程学习记录错误:{}", e.getMessage());
@@ -451,8 +451,8 @@ public class StudyCourseApi extends ApiBaseController{
map.put("学习开始时间", studyCourse1.getStartTime());
// 结束时间为空的,说明还没学习结束(有值)
map.put("学习结束时间", studyCourse1.getFinishTime());
// 学习时长学习总时间是秒现在要求的学习时间是分钟向上取整1秒算1分钟60秒算1分钟61秒算2分钟
map.put("学习时长(分)", studyCourse1.getTotalDuration() == null ? null : (int) Math.ceil(studyCourse1.getTotalDuration() / 60.0));
// 学习时长(保留两位小数):
map.put("学习时长(分)", studyCourse1.getTotalDuration() == null ? null : String.format("%.2f", studyCourse1.getTotalDuration() / 60.0));
// 学习状态需要转换
String statusText = "";
if (studyCourse1.getStatus() != null) {

View File

@@ -6,6 +6,7 @@ import com.xboe.school.study.dto.CourseFinishCountDto;
import com.xboe.school.study.entity.StudyCourse;
import org.springframework.stereotype.Repository;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
@@ -18,33 +19,41 @@ public class CourseStatDao extends BaseDao<StudyCourse> {
* @param startIndex 分页开始索引
* @param pageSize 分页大小
* @param courseId 课程ID
* @param contentName 资源名称
* @return 课程完成人数统计DTO集合
*/
public List<CourseFinishCountDto> findFinishCountPage(int startIndex, int pageSize, String courseId) {
public List<CourseFinishCountDto> findFinishCountPage(int startIndex, int pageSize, String courseId, String contentName) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ")
// 课程名和DTO字段对应
.append("c.content_name AS contentName, ")
// 资源名
.append("cc.content_name AS contentName, ")
// 资源ID
.append("cc.id AS contentId, ")
// 完成人数(去重统计)
.append("COUNT(DISTINCT c.aid) AS finishCount, ")
//2025.11.27新增:资源类型
.append("cc.content_type AS contentType ")
// 你的课程表名
.append("FROM boe_study_course_item c ")
.append("FROM boe_course_content cc ")
// 2025.11.27新增:连表 boe_course_content
.append("LEFT JOIN boe_course_content cc ON cc.id = c.content_id ")
.append("WHERE 1=1 ")
// 条件:已完成
.append("AND c.status = 9 ");
.append("LEFT JOIN boe_study_course_item c ON cc.id = c.content_id AND c.status = 9 ")
.append("WHERE 1=1 ");
List<Object> params = new ArrayList<>();
// courseId非空则过滤参数化防注入
if (StringUtils.isNotBlank(courseId)) {
sql.append("AND c.course_id = ? ");
sql.append("AND cc.course_id = ? ");
params.add(courseId);
}
// 25.12.1修改 新增contentName模糊查询粗略匹配
if (StringUtils.isNotBlank(contentName)) {
// 实现“包含contentName”的模糊查询
sql.append("AND cc.content_name LIKE ? ");
// 通配符%拼接在参数上防注入表示“前后任意字符包含contentName”
params.add("%" + contentName + "%");
}
// 分组+排序+分页聚合函数必须分组排序参考第一个代码的desc id
sql.append("GROUP BY c.content_id, c.content_name, cc.content_type ")
.append("ORDER BY c.content_id DESC ")
sql.append("GROUP BY cc.id, cc.content_name, cc.content_type ")
.append("ORDER BY cc.id DESC ")
// MySQL分页偏移量每页条数
.append("LIMIT ?, ?");
// 补充分页参数顺序startIndex → pageSize
@@ -57,29 +66,41 @@ public class CourseStatDao extends BaseDao<StudyCourse> {
for (Object[] objs : resultList) {
CourseFinishCountDto dto = new CourseFinishCountDto();
dto.setContentName(objs[0] != null ? (String) objs[0] : "");
dto.setFinishCount(objs[1] != null ? ((Number) objs[1]).intValue() : 0);
dto.setContentType((Integer) objs[2]);
dto.setContentId(((BigInteger) objs[1]).toString());
dto.setFinishCount(objs[2] != null ? ((Number) objs[2]).intValue() : 0);
dto.setContentType((Integer) objs[3]);
dtoList.add(dto);
}
return dtoList;
}
// 查总条数返回int类型匹配PageList的count字段
public int findFinishCountTotal(String courseId) {
/**
* 查询课程完成人数
*
* @param courseId 课程ID
* @param contentName 资源名称
* @return 课程完成人数
*/
public int findFinishCountTotal(String courseId, String contentName) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ")
// 总条数=去重后的人数
.append("COUNT(DISTINCT c.content_id) ")
.append("FROM boe_study_course_item c ")
.append("WHERE 1=1 ")
// 条件:已完成
.append("AND c.status = 9 ");
.append("COUNT(DISTINCT c.id) ")
.append("FROM boe_course_content c ")
.append("WHERE 1=1 ");
List<Object> params = new ArrayList<>();
// courseId非空则过滤参数化防注入
if (StringUtils.isNotBlank(courseId)) {
sql.append("AND c.course_id = ? ");
params.add(courseId);
}
// 25.12.1修改 新增contentName模糊查询粗略匹配
if (StringUtils.isNotBlank(contentName)) {
// 实现“包含contentName”的模糊查询
sql.append("AND c.content_name LIKE ? ");
// 通配符%拼接在参数上防注入表示“前后任意字符包含contentName”
params.add("%" + contentName + "%");
}
// 用sqlCount替代sqlFindObject直接返回int类型结果
// sqlCount会执行SQL并返回COUNT的结果无需手动转换Object
return this.sqlCount(sql.toString(), params.toArray());

View File

@@ -2,6 +2,8 @@ package com.xboe.school.study.dto;
import lombok.Data;
import java.math.BigInteger;
/**
* 课程完成人数统计DTO
*/
@@ -13,6 +15,11 @@ public class CourseFinishCountDto {
*/
private String contentName;
/**
* 内容ID
*/
private String contentId;
/**
* 完成人数(数据库 count 统计得出)
*/
@@ -24,4 +31,5 @@ public class CourseFinishCountDto {
*/
private Integer contentType;
}

View File

@@ -69,8 +69,9 @@ public interface IStudyCourseService {
* @param pageIndex 页码
* @param pageSize 每页数据条数
* @param courseId 课程id
* @param contentName 资源名称
*/
PageList<CourseFinishCountDto> findPageResource(int pageIndex, int pageSize, String courseId);
PageList<CourseFinishCountDto> findPageResource(int pageIndex, int pageSize, String courseId, String contentName);
/**
* 热度榜

View File

@@ -2,7 +2,10 @@ package com.xboe.school.study.service.impl;
import com.xboe.common.OrderCondition;
import com.xboe.common.PageList;
import com.xboe.core.orm.*;
import com.xboe.core.orm.FieldFilters;
import com.xboe.core.orm.FieldUpdateType;
import com.xboe.core.orm.QueryBuilder;
import com.xboe.core.orm.UpdateBuilder;
import com.xboe.core.utils.ConvertUtil;
import com.xboe.data.outside.IOutSideDataService;
import com.xboe.module.course.dao.CourseContentDao;
@@ -24,15 +27,17 @@ import com.xboe.system.user.vo.UserSimpleVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -56,6 +61,10 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
@Autowired
IOrganizationService organizationService;
@Autowired
@Lazy
IStudyCourseService studyCourseService;
@Autowired
StudyCourseDao studyCourseDao;
@@ -92,6 +101,13 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
@Autowired(required = false)
ICourseStudySearch esSearch;//对ES的处理
/**
* 用于避免JPA查询后修改entity实体字段自动更新到数据库
*/
@Autowired
private EntityManager entityManager;
@Override
public StudyCourse findByCourseIdAndAid(String courseId, String aid) {
//加上排序,如果是多条学习记录,只会取最新的一条
@@ -146,36 +162,6 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
if(StringUtils.isNotBlank(sc.getAid())) {
query.addFilter(FieldFilters.eq("aid", sc.getAid()));
}
// 25.11.25新增:添加基于时间的查询条件
if (StringUtils.isNotBlank(sc.getQueryStartTime()) && StringUtils.isNotBlank(sc.getQueryFinishTime())) {
// 开始结束时间均传入的情况,实现筛选逻辑
// (startTime >= 查询开始时间 AND startTime <= 查询结束时间) OR (finishTime >= 查询开始时间 AND finishTime <= 查询结束时间)
// 这样兼容查询结束时间为空值的情况,因为学员课程未结束时没有结束时间
LocalDate startDate = LocalDate.parse(sc.getQueryStartTime());
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDate finishDate = LocalDate.parse(sc.getQueryFinishTime());
LocalDateTime finishDateTime = finishDate.atTime(LocalTime.MAX);
// 筛选开始时间
IFieldFilter startTimeInRange = FieldFilters.and(FieldFilters.ge("startTime", startDateTime), FieldFilters.le("startTime", finishDateTime));
// 筛选结束时间
IFieldFilter finishTimeInRange = FieldFilters.and(FieldFilters.ge("finishTime", startDateTime), FieldFilters.le("finishTime", finishDateTime));
// OR条件查询
query.addFilter(FieldFilters.or(startTimeInRange, finishTimeInRange));
} else {
// 只输出单个参数情况
// 筛选开始时间
if (StringUtils.isNotBlank(sc.getQueryStartTime())) {
LocalDate startDate = LocalDate.parse(sc.getQueryStartTime());
LocalDateTime startDateTime = startDate.atStartOfDay();
query.addFilter(FieldFilters.ge("startTime", startDateTime));
}
// 筛选结束时间
if (StringUtils.isNotBlank(sc.getQueryFinishTime())) {
LocalDate finishDate = LocalDate.parse(sc.getQueryFinishTime());
LocalDateTime finishDateTime = finishDate.atTime(LocalTime.MAX);
query.addFilter(FieldFilters.le("finishTime", finishDateTime));
}
}
}
// 原有查询是否结束逻辑
if (isFinish != null) {
@@ -231,17 +217,73 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
}
}
}
// 25.12.3新增,添加基于时间的查询条件,规则如下:
if (sc != null && StringUtils.isNotBlank(sc.getCourseId()) && StringUtils.isNotBlank(sc.getQueryStartTime()) && StringUtils.isNotBlank(sc.getQueryFinishTime())) {
List<StudyCourseItem> studyCourseItemList = studyCourseService.getStudyCourseItemByCourseId(sc.getCourseId());
if (studyCourseItemList != null && !studyCourseItemList.isEmpty()) {
// 将学习内容按照studyId进行分组便于后续计算每个studyId下的总学习时长
Map<String, List<StudyCourseItem>> itemsByStudyId = studyCourseItemList.stream().filter(item -> item.getFinishTime() != null).collect(Collectors.groupingBy(StudyCourseItem::getStudyId));
// 获取查询的起止时间
String queryStartTime = sc.getQueryStartTime();
String queryFinishTime = sc.getQueryFinishTime();
for (StudyCourse studyCourse : studyCourses) {
// 实体类解耦
entityManager.detach(studyCourse);
// 解析查询时间范围
LocalDate startDate = LocalDate.parse(queryStartTime);
LocalDate endDate = LocalDate.parse(queryFinishTime);
// 如果有查询时间范围,则计算筛选后的学习时长
if (itemsByStudyId.containsKey(studyCourse.getId())) {
List<StudyCourseItem> items = itemsByStudyId.get(studyCourse.getId());
int filteredDuration = 0;
for (StudyCourseItem item : items) {
LocalDateTime finishTime = item.getFinishTime();
LocalDate finishDate = finishTime.toLocalDate();
// 判断结束时间是否在筛选范围内
boolean inRange = true;
if (finishDate.isBefore(startDate)) {
inRange = false;
}
if (finishDate.isAfter(endDate)) {
inRange = false;
}
// 如果在范围内,则累加学习时长
if (inRange) {
Integer duration = item.getStudyDuration();
if (duration != null) {
filteredDuration += duration;
}
}
}
// 更新学习时长
studyCourse.setTotalDuration(filteredDuration);
} else {
// 查询不到item的情况默认置空
studyCourse.setTotalDuration(0);
}
}
}
}
return studyCoursePageList;
}
/**
* 分页查询课程的资源名称以及资源学习完成人数
*
* @param pageIndex 页码
* @param pageSize 每页数据条数
* @param courseId 课程id
* @param contentName 资源名称
*/
@Override
public PageList<CourseFinishCountDto> findPageResource(int pageIndex, int pageSize, String courseId) {
public PageList<CourseFinishCountDto> findPageResource(int pageIndex, int pageSize, String courseId, String contentName) {
// 1. 手动计算分页偏移量(数据库分页必需)
// pageIndex<1时设为0避免数据库LIMIT偏移量为负数
int startIndex = (pageIndex < 1) ? 0 : (pageIndex - 1) * pageSize;
// 2. 调用Dao层查当前页数据传入偏移量、每页条数、courseId条件
List<CourseFinishCountDto> dtoList = courseStatDao.findFinishCountPage(startIndex, pageSize, courseId);
List<CourseFinishCountDto> dtoList = courseStatDao.findFinishCountPage(startIndex, pageSize, courseId,contentName);
// 3. 调用Dao层查总条数对应PageList的count字段用int类型和PageList一致
int totalCount = courseStatDao.findFinishCountTotal(courseId);
int totalCount = courseStatDao.findFinishCountTotal(courseId,contentName);
// 4. 按PageList构造函数创建对象只传list和count
PageList<CourseFinishCountDto> pageList = new PageList<>(dtoList, totalCount);
// 5. 设置pageSize覆盖默认10确保总页数计算正确
@@ -809,36 +851,6 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
if (StringUtils.isNotBlank(sc.getAid())) {
query.addFilter(FieldFilters.eq("aid", sc.getAid()));
}
// 25.11.25新增:添加基于时间的查询条件
if (sc.getQueryStartTime() != null && sc.getQueryFinishTime() != null) {
// 开始结束时间均传入的情况,实现筛选逻辑
// (startTime >= 查询开始时间 AND startTime <= 查询结束时间) OR (finishTime >= 查询开始时间 AND finishTime <= 查询结束时间)
// 这样兼容查询结束时间为空值的情况,因为学员课程未结束时没有结束时间
LocalDate startDate = LocalDate.parse(sc.getQueryStartTime());
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDate finishDate = LocalDate.parse(sc.getQueryFinishTime());
LocalDateTime finishDateTime = finishDate.atTime(LocalTime.MAX);
// 筛选开始时间
IFieldFilter startTimeInRange = FieldFilters.and(FieldFilters.ge("startTime", startDateTime), FieldFilters.le("startTime", finishDateTime));
// 筛选结束时间
IFieldFilter finishTimeInRange = FieldFilters.and(FieldFilters.ge("finishTime", startDateTime), FieldFilters.le("finishTime", finishDateTime));
// OR条件查询
query.addFilter(FieldFilters.or(startTimeInRange, finishTimeInRange));
} else {
// 只输出单个参数情况
// 筛选开始时间
if (sc.getQueryStartTime() != null) {
LocalDate startDate = LocalDate.parse(sc.getQueryStartTime());
LocalDateTime startDateTime = startDate.atStartOfDay();
query.addFilter(FieldFilters.ge("startTime", startDateTime));
}
// 筛选结束时间
if (sc.getQueryFinishTime() != null) {
LocalDate finishDate = LocalDate.parse(sc.getQueryFinishTime());
LocalDateTime finishDateTime = finishDate.atTime(LocalTime.MAX);
query.addFilter(FieldFilters.le("finishTime", finishDateTime));
}
}
}
if (isFinish != null) {
if (isFinish) {
@@ -847,7 +859,55 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
query.addFilter(FieldFilters.lt("status", 9));
}
}
return studyCourseDao.findList(query.builder());
// 25.12.3新增,添加基于时间的查询条件,规则如下:
List<StudyCourse> studyCourses = studyCourseDao.findList(query.builder());
if (sc != null && StringUtils.isNotBlank(sc.getCourseId()) && StringUtils.isNotBlank(sc.getQueryStartTime()) && StringUtils.isNotBlank(sc.getQueryFinishTime())) {
List<StudyCourseItem> studyCourseItemList = studyCourseService.getStudyCourseItemByCourseId(sc.getCourseId());
if (studyCourseItemList != null && !studyCourseItemList.isEmpty()) {
// 将学习内容按照studyId进行分组便于后续计算每个studyId下的总学习时长
Map<String, List<StudyCourseItem>> itemsByStudyId = studyCourseItemList.stream().filter(item -> item.getFinishTime() != null).collect(Collectors.groupingBy(StudyCourseItem::getStudyId));
// 获取查询的起止时间
String queryStartTime = sc.getQueryStartTime();
String queryFinishTime = sc.getQueryFinishTime();
for (StudyCourse studyCourse : studyCourses) {
// 实体类解耦
entityManager.detach(studyCourse);
// 解析查询时间范围
LocalDate startDate = LocalDate.parse(queryStartTime);
LocalDate endDate = LocalDate.parse(queryFinishTime);
// 如果有查询时间范围,则计算筛选后的学习时长
if (itemsByStudyId.containsKey(studyCourse.getId())) {
List<StudyCourseItem> items = itemsByStudyId.get(studyCourse.getId());
int filteredDuration = 0;
for (StudyCourseItem item : items) {
LocalDateTime finishTime = item.getFinishTime();
LocalDate finishDate = finishTime.toLocalDate();
// 判断结束时间是否在筛选范围内
boolean inRange = true;
if (finishDate.isBefore(startDate)) {
inRange = false;
}
if (finishDate.isAfter(endDate)) {
inRange = false;
}
// 如果在范围内,则累加学习时长
if (inRange) {
Integer duration = item.getStudyDuration();
if (duration != null) {
filteredDuration += duration;
}
}
}
// 更新学习时长
studyCourse.setTotalDuration(filteredDuration);
} else {
// 查询不到item的情况默认置空
studyCourse.setTotalDuration(0);
}
}
}
}
return studyCourses;
}
@Override

View File

@@ -251,7 +251,7 @@ public class StudyServiceImpl implements IStudyService{
query.addFilter(FieldFilters.eq("courseId",courseId));
}
if(StringUtils.isNotBlank(name)) {
query.addFilter(FieldFilters.eq("aname", name));
query.addFilter(FieldFilters.like("aname", name));
}
int pageIndex2 = (pageIndex-1)*10;
if(status!=null) {
@@ -272,7 +272,7 @@ public class StudyServiceImpl implements IStudyService{
}
}
// 未传输status的情况查询所有资源学习情况数据
String sql = "select a.id, a.course_id, a.course_name, a.aname, " + "IFNULL(b.finish_time, '0') as finish_time, IFNULL(b.progress, 0) as progress, IFNULL(b.status, 1) as status " + ",b.score,b.item_id " + "from (select id, course_id, course_name, aname, 0, 1 from boe_study_course where course_id = '" + courseId + "'" + (StringUtils.isBlank(name) ? "" : "and aname like '%" + name + "%'") + ") a " + "inner join " + "(select bsc.id, bsc.course_id, bsc.course_name, bsc.aname, item.id as item_id,item.finish_time, item.progress, item.status,MAX(item.score) score " + "from boe_study_course bsc left join boe_study_course_item item on item.course_id = bsc.course_id and item.study_id = bsc.id " + "where bsc.course_id = '" + courseId + "'" +
String sql = "select a.id, a.course_id, a.course_name, a.aname, " + "IFNULL(b.finish_time, '0') as finish_time, IFNULL(b.progress, 0) as progress, IFNULL(b.status, 1) as status " + ",b.score,b.item_id,b.aid " + "from (select id, course_id, course_name, aname, 0, 1 from boe_study_course where course_id = '" + courseId + "'" + (StringUtils.isBlank(name) ? "" : "and aname like '%" + name + "%'") + ") a " + "inner join " + "(select bsc.id, bsc.course_id, bsc.course_name, bsc.aname, item.id as item_id,item.finish_time, item.progress, item.status,MAX(item.score) score,item.aid " + "from boe_study_course bsc left join boe_study_course_item item on item.course_id = bsc.course_id and item.study_id = bsc.id " + "where bsc.course_id = '" + courseId + "'" +
(StringUtils.isBlank(contentId) ? "" : "and item.content_id = '" + contentId + "'") +
(StringUtils.isBlank(name) ? "" : "and item.aname like '%" + name +"%'") + " group by bsc.id) b " +
"on a.course_id = b.course_id and a.id = b.id " +
@@ -284,6 +284,7 @@ public class StudyServiceImpl implements IStudyService{
"on a.course_id = b.course_id and a.id = b.id " +
"group by a.id) as total";
log.info("资源完成情况sql{}", sql);
log.info("数量查询sql{}", sql2);
List<Object[]> list = scDao.sqlFindList(sql);
int totalCount = scDao.sqlCount(sql2);
List<StudyCourseItem> item = new ArrayList<>();
@@ -304,6 +305,10 @@ public class StudyServiceImpl implements IStudyService{
if(objs[7] != null){
sc.setScore(Float.valueOf(objs[7].toString()));
}
// 25.12.5新增补全aid查询
if (objs[8] != null) {
sc.setAid(String.valueOf(objs[8].toString()));
}
item.add(sc);
}
PageList<StudyCourseItem> pageList = new PageList<>(item);

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
@@ -96,14 +99,15 @@ xboe:
- 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