From e92e6ffecad4adadddb59c0af5402e3c24a9c28f Mon Sep 17 00:00:00 2001 From: GoCo Date: Thu, 18 Sep 2025 09:18:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jeecg-module-aiol/pom.xml | 7 + .../aiol/constant/ResourceTypeConst.java | 7 + .../aiol/controller/AiolClassController.java | 49 +++- .../controller/AiolResourceController.java | 248 +++++++++++++++++- 4 files changed, 292 insertions(+), 19 deletions(-) create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/constant/ResourceTypeConst.java diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/pom.xml b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/pom.xml index c85e37c7..07cb05af 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/pom.xml +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/pom.xml @@ -32,5 +32,12 @@ org.jeecgframework.boot jeecg-system-biz + + + + ws.schild + jave-all-deps + 3.5.0 + diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/constant/ResourceTypeConst.java b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/constant/ResourceTypeConst.java new file mode 100644 index 00000000..59d6491d --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/constant/ResourceTypeConst.java @@ -0,0 +1,7 @@ +package org.jeecg.modules.aiol.constant; + +public final class ResourceTypeConst { + public static final int VIDEO = 0; + public static final int IMAGE = 1; + public static final int DOCUMENT = 2; +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolClassController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolClassController.java index 84c14638..c442b5e6 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolClassController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolClassController.java @@ -480,10 +480,10 @@ public class AiolClassController extends JeecgController> createStudentAndAddToClass(@RequestBody Map body, HttpServletRequest request) { @@ -524,17 +524,37 @@ public class AiolClassController extends JeecgController checkWrapper = new QueryWrapper<>(); - checkWrapper.eq("class_id", classId).eq("student_id", created.getId()); - AiolClassStudent exist = aiolClassStudentService.getOne(checkWrapper); - if (exist == null) { - AiolClassStudent relation = new AiolClassStudent(); - relation.setClassId(classId); - relation.setStudentId(created.getId()); - relation.setCreateBy(sysUser.getUsername()); - relation.setCreateTime(new Date()); - aiolClassStudentService.save(relation); + // 支持多个班级ID,用逗号分割 + String[] classIds = classId.split(","); + int successCount = 0; + int skipCount = 0; + + for (String singleClassId : classIds) { + if (singleClassId == null || singleClassId.trim().isEmpty()) { + continue; + } + singleClassId = singleClassId.trim(); + + // 检查是否已存在关系,避免重复 + QueryWrapper checkWrapper = new QueryWrapper<>(); + checkWrapper.eq("class_id", singleClassId).eq("student_id", created.getId()); + AiolClassStudent exist = aiolClassStudentService.getOne(checkWrapper); + + if (exist == null) { + AiolClassStudent relation = new AiolClassStudent(); + relation.setClassId(singleClassId); + relation.setStudentId(created.getId()); + relation.setCreateBy(sysUser.getUsername()); + relation.setCreateTime(new Date()); + boolean saved = aiolClassStudentService.save(relation); + if (saved) { + successCount++; + log.info("成功将学生 {} 添加到班级 {}", created.getUsername(), singleClassId); + } + } else { + skipCount++; + log.info("学生 {} 已在班级 {} 中,跳过", created.getUsername(), singleClassId); + } } Map resp = new HashMap<>(); @@ -542,6 +562,9 @@ public class AiolClassController extends JeecgController> upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception { - if (file == null || file.isEmpty()) return Result.error("没有找到上传的文件"); - Map qualityUrls = aiolResourceService.uploadHls(file, request); - return Result.OK(qualityUrls); + @PostMapping("/video_upload") + @Operation(summary = "课程视频文件上传", description = "课程视频文件上传,创建资源记录并关联到课程") + public Result> upload( + @RequestParam("file") MultipartFile file, + @RequestParam("courseId") String courseId, + @RequestParam("name") String name, + @RequestParam(value = "description", required = false) String description, + HttpServletRequest request) throws Exception { + + if (file == null || file.isEmpty()) { + return Result.error("没有找到上传的文件"); + } + + if (courseId == null || courseId.trim().isEmpty()) { + return Result.error("课程ID不能为空"); + } + + if (name == null || name.trim().isEmpty()) { + return Result.error("视频名称不能为空"); + } + + try { + // 1. 上传视频文件,获取各清晰度的m3u8地址 + Map qualityUrls = aiolResourceService.uploadHls(file, request); + + // 2. 将m3u8地址用逗号拼接成字符串 + String fileUrl = String.join(",", qualityUrls.values()); + + // 3. TODO 获取视频时长和文件大小 + Integer duration = (int) 0; + Integer fileSize = (int) file.getSize(); + + // 4. 创建资源记录 + AiolResource resource = new AiolResource(); + resource.setName(name.trim()); + resource.setDescription(description != null ? description.trim() : ""); + resource.setType(ResourceTypeConst.VIDEO); + resource.setFileUrl(fileUrl); + resource.setDuration(duration); + resource.setFileSize(fileSize); + + // 5. 保存资源记录 + boolean saved = aiolResourceService.save(resource); + if (!saved) { + return Result.error("保存资源记录失败"); + } + + // 6. 创建课程与资源的关联关系 + entityLinkBizService.save( + EntityLinkConst.SourceType.COURSE, + courseId, + EntityLinkConst.TargetType.RESOURCE, + resource.getId() + ); + + log.info("视频上传成功: resourceId={}, courseId={}, name={}", + resource.getId(), courseId, name); + + return Result.OK(resource.getId()); + } catch (Exception e) { + log.error("视频上传失败: courseId={}, name={}, error={}", + courseId, name, e.getMessage(), e); + return Result.error("视频上传失败: " + e.getMessage()); + } + } + + @PostMapping("/document_upload") + @Operation(summary = "课程文档文件上传", description = "上传文档文件并关联到课程,支持word、ppt、excel、pdf、txt、markdown等格式") + public Result uploadDocument( + @RequestParam("file") MultipartFile file, + @RequestParam("courseId") String courseId, + @RequestParam("name") String name, + @RequestParam(value = "description", required = false) String description, + HttpServletRequest request) throws Exception { + + if (file == null || file.isEmpty()) { + return Result.error("没有找到上传的文件"); + } + + if (courseId == null || courseId.trim().isEmpty()) { + return Result.error("课程ID不能为空"); + } + + if (name == null || name.trim().isEmpty()) { + return Result.error("文档名称不能为空"); + } + + // 检查文件类型 + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null || !isValidDocumentType(originalFilename)) { + return Result.error("不支持的文件类型,仅支持word、ppt、excel、pdf、txt、markdown等格式"); + } + + try { + // 1. 上传文档文件 + String fileUrl = uploadDocumentFile(file, request); + + // 2. 获取文件大小 + Integer fileSize = (int) file.getSize(); + + // 3. 创建资源记录 + AiolResource resource = new AiolResource(); + resource.setName(name.trim()); + resource.setDescription(description != null ? description.trim() : ""); + resource.setType(ResourceTypeConst.DOCUMENT); // 2:文档 + resource.setFileUrl(fileUrl); + resource.setFileSize(fileSize); + + // 4. 保存资源记录 + boolean saved = aiolResourceService.save(resource); + if (!saved) { + return Result.error("保存资源记录失败"); + } + + // 5. 创建课程与资源的关联关系 + entityLinkBizService.save( + EntityLinkConst.SourceType.COURSE, + courseId, + EntityLinkConst.TargetType.RESOURCE, + resource.getId() + ); + + log.info("文档上传成功: resourceId={}, courseId={}, name={}", + resource.getId(), courseId, name); + + return Result.OK(resource.getId()); + + } catch (Exception e) { + log.error("文档上传失败: courseId={}, name={}, error={}", + courseId, name, e.getMessage(), e); + return Result.error("文档上传失败: " + e.getMessage()); + } + } + + @GetMapping("/course_materials") + @Operation(summary = "查询课程课件", description = "根据课程ID、资源分类和资源名关键词查询课程相关资源") + @IgnoreAuth + public Result> queryCourseMaterials( + @RequestParam("courseId") String courseId, + @RequestParam(value = "resourceType", required = false) Integer resourceType, + @RequestParam(value = "name", required = false) String name) { + try { + // 1. 根据课程ID查询关联的资源ID列表 + List resourceIds = entityLinkBizService.listTargetIds( + EntityLinkConst.SourceType.COURSE, + courseId, + EntityLinkConst.TargetType.RESOURCE + ); + + if (resourceIds.isEmpty()) { + return Result.OK(new ArrayList<>()); + } + + // 2. 根据资源ID列表查询资源详情 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("id", resourceIds); + + // 3. 根据资源分类过滤 + if (resourceType != null) { + queryWrapper.eq("type", resourceType); + } + + // 4. 根据资源名关键词过滤 + if (name != null && !name.trim().isEmpty()) { + queryWrapper.like("name", name.trim()); + } + + List resourceList = aiolResourceService.list(queryWrapper); + + log.info("查询课程课件成功: courseId={}, resourceType={}, name={}, 结果数量={}", + courseId, resourceType, name, resourceList.size()); + + return Result.OK(resourceList); + + } catch (Exception e) { + log.error("查询课程课件失败: courseId={}, resourceType={}, name={}, error={}", + courseId, resourceType, name, e.getMessage(), e); + return Result.error("查询课程课件失败: " + e.getMessage()); + } + } + + /** + * 检查是否为有效的文档类型 + */ + private boolean isValidDocumentType(String filename) { + if (filename == null) return false; + + String lowerFilename = filename.toLowerCase(); + return lowerFilename.endsWith(".doc") || lowerFilename.endsWith(".docx") || // Word + lowerFilename.endsWith(".ppt") || lowerFilename.endsWith(".pptx") || // PowerPoint + lowerFilename.endsWith(".xls") || lowerFilename.endsWith(".xlsx") || // Excel + lowerFilename.endsWith(".pdf") || // PDF + lowerFilename.endsWith(".txt") || // Text + lowerFilename.endsWith(".md") || lowerFilename.endsWith(".markdown"); // Markdown + } + + /** + * 上传文档文件 + */ + private String uploadDocumentFile(MultipartFile file, HttpServletRequest request) throws Exception { + // 读取上传类型 + String configUploadType = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("jeecg.uploadType", "minio"); + String uploadType = configUploadType; + + // 生成文件路径 + String uuid = UUID.randomUUID().toString(); + String originalFilename = file.getOriginalFilename(); + String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".")); + String fileName = uuid + fileExtension; + String relativePath = "document/" + fileName; + + try (InputStream inputStream = file.getInputStream()) { + String fileUrl = ""; + if ("minio".equals(uploadType)) { + fileUrl = MinioUtil.upload(inputStream, relativePath); + } else if ("alioss".equals(uploadType)) { + OssBootUtil.upload(inputStream, relativePath); + fileUrl = relativePath; // 可在网关拼域名 + } else { + // 本地存储 + String uploadpath = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("jeecg.path.upload"); + Path target = Path.of(uploadpath, relativePath); + Files.createDirectories(target.getParent()); + Files.copy(inputStream, target, StandardCopyOption.REPLACE_EXISTING); + fileUrl = relativePath; + } + + log.info("文档文件上传成功: originalFilename={}, fileUrl={}", originalFilename, fileUrl); + return fileUrl; + } } }