Compare commits

...

2 Commits

Author SHA1 Message Date
GoCo
a73cc74342 Merge branch 'dev2' of http://110.42.96.64:19890/GoCo/OL-LearnPlatform-Backend into dev2
merge
2025-09-18 09:18:24 +08:00
GoCo
e92e6ffeca feat: 🎸 资源接口 2025-09-18 09:18:15 +08:00
4 changed files with 292 additions and 19 deletions

View File

@ -32,5 +32,12 @@
<groupId>org.jeecgframework.boot</groupId> <groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-biz</artifactId> <artifactId>jeecg-system-biz</artifactId>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/ws.schild/jave-all-deps -->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -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;
}

View File

@ -480,10 +480,10 @@ public class AiolClassController extends JeecgController<AiolClass, IAiolClassSe
/** /**
* 新建学生并添加至班级 * 新建学生并添加至班级
* *
* 前端传参realName(姓名)studentNumber(学号)password(登录密码可选)school(所属学校)classId(所属班级ID) * 前端传参realName(姓名)studentNumber(学号)password(登录密码可选)school(所属学校)classId(所属班级ID支持多个用逗号分割)
*/ */
@AutoLog(value = "班级学生-新建学生并添加至班级") @AutoLog(value = "班级学生-新建学生并添加至班级")
@Operation(summary = "新建学生并添加至班级", description = "创建sysUser保存aiol_user_info的学校信息并写入aiol_class_student") @Operation(summary = "新建学生并添加至班级", description = "创建sysUser保存aiol_user_info的学校信息并写入aiol_class_student。支持多个班级ID用逗号分割")
@PostMapping(value = "/create_and_add_student") @PostMapping(value = "/create_and_add_student")
public Result<Map<String, Object>> createStudentAndAddToClass(@RequestBody Map<String, Object> body, public Result<Map<String, Object>> createStudentAndAddToClass(@RequestBody Map<String, Object> body,
HttpServletRequest request) { HttpServletRequest request) {
@ -524,17 +524,37 @@ public class AiolClassController extends JeecgController<AiolClass, IAiolClassSe
aiolUserInfoService.save(userInfo); aiolUserInfoService.save(userInfo);
// 3) 写入班级关系 aiol_class_student // 3) 写入班级关系 aiol_class_student
// 检查是否已存在关系避免重复 // 支持多个班级ID用逗号分割
QueryWrapper<AiolClassStudent> checkWrapper = new QueryWrapper<>(); String[] classIds = classId.split(",");
checkWrapper.eq("class_id", classId).eq("student_id", created.getId()); int successCount = 0;
AiolClassStudent exist = aiolClassStudentService.getOne(checkWrapper); int skipCount = 0;
if (exist == null) {
AiolClassStudent relation = new AiolClassStudent(); for (String singleClassId : classIds) {
relation.setClassId(classId); if (singleClassId == null || singleClassId.trim().isEmpty()) {
relation.setStudentId(created.getId()); continue;
relation.setCreateBy(sysUser.getUsername()); }
relation.setCreateTime(new Date()); singleClassId = singleClassId.trim();
aiolClassStudentService.save(relation);
// 检查是否已存在关系避免重复
QueryWrapper<AiolClassStudent> 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<String, Object> resp = new HashMap<>(); Map<String, Object> resp = new HashMap<>();
@ -542,6 +562,9 @@ public class AiolClassController extends JeecgController<AiolClass, IAiolClassSe
resp.put("username", created.getUsername()); resp.put("username", created.getUsername());
resp.put("classId", classId); resp.put("classId", classId);
resp.put("school", school); resp.put("school", school);
resp.put("totalClassCount", classIds.length);
resp.put("successCount", successCount);
resp.put("skipCount", skipCount);
return Result.OK(resp); return Result.OK(resp);
} catch (Exception e) { } catch (Exception e) {

View File

@ -2,18 +2,28 @@ package org.jeecg.modules.aiol.controller;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum; import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.util.MinioUtil;
import org.jeecg.common.util.oss.OssBootUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.config.shiro.IgnoreAuth; import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.aiol.constant.EntityLinkConst; import org.jeecg.modules.aiol.constant.EntityLinkConst;
import org.jeecg.modules.aiol.constant.ResourceTypeConst;
import org.jeecg.modules.aiol.entity.AiolResource; import org.jeecg.modules.aiol.entity.AiolResource;
import org.jeecg.modules.aiol.service.IAiolEntityLinkService; import org.jeecg.modules.aiol.service.IAiolEntityLinkService;
import org.jeecg.modules.aiol.service.IAiolResourceService; import org.jeecg.modules.aiol.service.IAiolResourceService;
@ -22,6 +32,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.MultimediaInfo;
import org.jeecgframework.poi.excel.ExcelImportUtil; import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants; import org.jeecgframework.poi.excel.def.NormalExcelConstants;
@ -222,11 +234,235 @@ public class AiolResourceController extends JeecgController<AiolResource, IAiolR
return Result.OK(list); return Result.OK(list);
} }
@PostMapping("/upload") @PostMapping("/video_upload")
@Operation(summary = "课程视频文件上传", description = "课程视频文件上传返回各清晰度的m3u8文件地址") @Operation(summary = "课程视频文件上传", description = "课程视频文件上传,创建资源记录并关联到课程")
public Result<Map<String, String>> upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception { public Result<Map<String, Object>> upload(
if (file == null || file.isEmpty()) return Result.error("没有找到上传的文件"); @RequestParam("file") MultipartFile file,
Map<String, String> qualityUrls = aiolResourceService.uploadHls(file, request); @RequestParam("courseId") String courseId,
return Result.OK(qualityUrls); @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<String, String> 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<String> 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<List<AiolResource>> queryCourseMaterials(
@RequestParam("courseId") String courseId,
@RequestParam(value = "resourceType", required = false) Integer resourceType,
@RequestParam(value = "name", required = false) String name) {
try {
// 1. 根据课程ID查询关联的资源ID列表
List<String> resourceIds = entityLinkBizService.listTargetIds(
EntityLinkConst.SourceType.COURSE,
courseId,
EntityLinkConst.TargetType.RESOURCE
);
if (resourceIds.isEmpty()) {
return Result.OK(new ArrayList<>());
}
// 2. 根据资源ID列表查询资源详情
QueryWrapper<AiolResource> 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<AiolResource> 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;
}
} }
} }