Compare commits
3 Commits
f020747b1f
...
a9ae28e3e1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a9ae28e3e1 | ||
![]() |
35fb15e2e3 | ||
![]() |
40bfe138f0 |
@ -0,0 +1,9 @@
|
|||||||
|
package org.jeecg.modules.aiol.constant;
|
||||||
|
|
||||||
|
public class RedisConst {
|
||||||
|
// 视频资源信息
|
||||||
|
public static final String VIDEO_RESOURCE_CACHE_KEY_PREFIX = "aiol:video_resource:";
|
||||||
|
public static final int VIDEO_RESOURCE_CACHE_EXPIRE = 30 * 24 * 60 * 60; // 30天过期
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -68,14 +68,13 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
* @param req
|
* @param req
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
//@AutoLog(value = "课程-分页列表查询")
|
// @AutoLog(value = "课程-分页列表查询")
|
||||||
@Operation(summary = "课程-分页列表查询")
|
@Operation(summary = "课程-分页列表查询")
|
||||||
@GetMapping(value = "/list")
|
@GetMapping(value = "/list")
|
||||||
public Result<IPage<AiolCourse>> queryPageList(AiolCourse aiolCourse,
|
public Result<IPage<AiolCourse>> queryPageList(AiolCourse aiolCourse,
|
||||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||||
HttpServletRequest req) {
|
HttpServletRequest req) {
|
||||||
|
|
||||||
|
|
||||||
QueryWrapper<AiolCourse> queryWrapper = QueryGenerator.initQueryWrapper(aiolCourse, req.getParameterMap());
|
QueryWrapper<AiolCourse> queryWrapper = QueryGenerator.initQueryWrapper(aiolCourse, req.getParameterMap());
|
||||||
Page<AiolCourse> page = new Page<AiolCourse>(pageNo, pageSize);
|
Page<AiolCourse> page = new Page<AiolCourse>(pageNo, pageSize);
|
||||||
@ -108,7 +107,7 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
@AutoLog(value = "课程-编辑")
|
@AutoLog(value = "课程-编辑")
|
||||||
@Operation(summary = "课程-编辑")
|
@Operation(summary = "课程-编辑")
|
||||||
@RequiresPermissions("aiol:aiol_course:edit")
|
@RequiresPermissions("aiol:aiol_course:edit")
|
||||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
@RequestMapping(value = "/edit", method = { RequestMethod.PUT, RequestMethod.POST })
|
||||||
public Result<String> edit(@RequestBody AiolCourse aiolCourse) {
|
public Result<String> edit(@RequestBody AiolCourse aiolCourse) {
|
||||||
aiolCourseService.updateById(aiolCourse);
|
aiolCourseService.updateById(aiolCourse);
|
||||||
return Result.OK("编辑成功!");
|
return Result.OK("编辑成功!");
|
||||||
@ -150,7 +149,7 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
* @param id
|
* @param id
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
//@AutoLog(value = "课程-通过id查询")
|
// @AutoLog(value = "课程-通过id查询")
|
||||||
@Operation(summary = "课程-通过id查询")
|
@Operation(summary = "课程-通过id查询")
|
||||||
@GetMapping(value = "/queryById")
|
@GetMapping(value = "/queryById")
|
||||||
public Result<AiolCourse> queryById(@RequestParam(name = "id", required = true) String id) {
|
public Result<AiolCourse> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||||
@ -202,12 +201,15 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
switch (sort) {
|
switch (sort) {
|
||||||
case "hottest":
|
case "hottest":
|
||||||
list = list.stream()
|
list = list.stream()
|
||||||
.sorted(Comparator.comparing(AiolCourse::getEnrollCount, Comparator.nullsLast(Integer::compareTo)).reversed())
|
.sorted(Comparator
|
||||||
|
.comparing(AiolCourse::getEnrollCount, Comparator.nullsLast(Integer::compareTo))
|
||||||
|
.reversed())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
break;
|
break;
|
||||||
case "latest":
|
case "latest":
|
||||||
list = list.stream()
|
list = list.stream()
|
||||||
.sorted(Comparator.comparing(AiolCourse::getCreateTime, Comparator.nullsLast(java.util.Date::compareTo)).reversed())
|
.sorted(Comparator.comparing(AiolCourse::getCreateTime,
|
||||||
|
Comparator.nullsLast(java.util.Date::compareTo)).reversed())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
break;
|
break;
|
||||||
case "recommend":
|
case "recommend":
|
||||||
@ -273,7 +275,8 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
|
|
||||||
@GetMapping("/{courseId}/section_video/{sectionId}")
|
@GetMapping("/{courseId}/section_video/{sectionId}")
|
||||||
@Operation(summary = "查询视频章节详情", description = "该接口需要携带用户登录token。根据章节id查询章节详情,不同类型的章节,返回的内容不同")
|
@Operation(summary = "查询视频章节详情", description = "该接口需要携带用户登录token。根据章节id查询章节详情,不同类型的章节,返回的内容不同")
|
||||||
public Result<List<AiolResource>> querySectionDetail(@PathVariable(value = "courseId") String courseId, @PathVariable(value = "sectionId") String sectionId) {
|
public Result<List<AiolResource>> querySectionDetail(@PathVariable(value = "courseId") String courseId,
|
||||||
|
@PathVariable(value = "sectionId") String sectionId) {
|
||||||
// TODO GC 获取用户id,根据courseId判断当前用户是否报名课程,只有已报名的课程才能查看章节详情
|
// TODO GC 获取用户id,根据courseId判断当前用户是否报名课程,只有已报名的课程才能查看章节详情
|
||||||
|
|
||||||
List<AiolResource> list = aiolCourseService.getCourseSectionDetail(0, sectionId, AiolResource.class);
|
List<AiolResource> list = aiolCourseService.getCourseSectionDetail(0, sectionId, AiolResource.class);
|
||||||
@ -282,7 +285,8 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
|
|
||||||
@GetMapping("/{courseId}/section_document/{sectionId}")
|
@GetMapping("/{courseId}/section_document/{sectionId}")
|
||||||
@Operation(summary = "查询文档章节详情", description = "该接口需要携带用户登录token。根据章节id查询章节详情,不同类型的章节,返回的内容不同")
|
@Operation(summary = "查询文档章节详情", description = "该接口需要携带用户登录token。根据章节id查询章节详情,不同类型的章节,返回的内容不同")
|
||||||
public Result<List<AiolResource>> querySectionDocumentDetail(@PathVariable(value = "courseId") String courseId, @PathVariable(value = "sectionId") String sectionId) {
|
public Result<List<AiolResource>> querySectionDocumentDetail(@PathVariable(value = "courseId") String courseId,
|
||||||
|
@PathVariable(value = "sectionId") String sectionId) {
|
||||||
// TODO GC 获取用户id,根据courseId判断当前用户是否报名课程,只有已报名的课程才能查看章节详情
|
// TODO GC 获取用户id,根据courseId判断当前用户是否报名课程,只有已报名的课程才能查看章节详情
|
||||||
|
|
||||||
List<AiolResource> list = aiolCourseService.getCourseSectionDetail(1, sectionId, AiolResource.class);
|
List<AiolResource> list = aiolCourseService.getCourseSectionDetail(1, sectionId, AiolResource.class);
|
||||||
@ -291,7 +295,8 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
|
|
||||||
@GetMapping("/{courseId}/section_homework/{sectionId}")
|
@GetMapping("/{courseId}/section_homework/{sectionId}")
|
||||||
@Operation(summary = "查询作业章节详情", description = "该接口需要携带用户登录token。根据章节id查询章节详情,不同类型的章节,返回的内容不同")
|
@Operation(summary = "查询作业章节详情", description = "该接口需要携带用户登录token。根据章节id查询章节详情,不同类型的章节,返回的内容不同")
|
||||||
public Result<List<AiolHomework>> querySectionHomeworkDetail(@PathVariable(value = "courseId") String courseId, @PathVariable(value = "sectionId") String sectionId) {
|
public Result<List<AiolHomework>> querySectionHomeworkDetail(@PathVariable(value = "courseId") String courseId,
|
||||||
|
@PathVariable(value = "sectionId") String sectionId) {
|
||||||
// TODO GC 获取用户id,根据courseId判断当前用户是否报名课程,只有已报名的课程才能查看章节详情
|
// TODO GC 获取用户id,根据courseId判断当前用户是否报名课程,只有已报名的课程才能查看章节详情
|
||||||
|
|
||||||
List<AiolHomework> list = aiolCourseService.getCourseSectionDetail(3, sectionId, AiolHomework.class);
|
List<AiolHomework> list = aiolCourseService.getCourseSectionDetail(3, sectionId, AiolHomework.class);
|
||||||
@ -308,7 +313,8 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
|
|
||||||
@PostMapping("/{courseId}/enroll")
|
@PostMapping("/{courseId}/enroll")
|
||||||
@Operation(summary = "报名课程", description = "该接口需要携带用户登录token。根据课程id报名课程。返回值为报名结果,报名成功返回success")
|
@Operation(summary = "报名课程", description = "该接口需要携带用户登录token。根据课程id报名课程。返回值为报名结果,报名成功返回success")
|
||||||
public Result<String> enrollCourse(@PathVariable(value = "courseId") String courseId, HttpServletRequest request, HttpServletResponse response) {
|
public Result<String> enrollCourse(@PathVariable(value = "courseId") String courseId, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||||
String username = JwtUtil.getUsername(token);
|
String username = JwtUtil.getUsername(token);
|
||||||
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
||||||
@ -318,7 +324,8 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
|
|
||||||
@GetMapping("/{courseId}/is_enrolled")
|
@GetMapping("/{courseId}/is_enrolled")
|
||||||
@Operation(summary = "查询课程是否已报名", description = "该接口需要携带用户登录token。根据课程id查询课程是否已报名。判断返回值的result是否为true")
|
@Operation(summary = "查询课程是否已报名", description = "该接口需要携带用户登录token。根据课程id查询课程是否已报名。判断返回值的result是否为true")
|
||||||
public Result<Boolean> isEnrolled(@PathVariable(value = "courseId") String courseId, HttpServletRequest request, HttpServletResponse response) {
|
public Result<Boolean> isEnrolled(@PathVariable(value = "courseId") String courseId, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||||
String username = JwtUtil.getUsername(token);
|
String username = JwtUtil.getUsername(token);
|
||||||
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
||||||
@ -355,7 +362,8 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
|
|
||||||
@PostMapping("/{courseId}/add_students")
|
@PostMapping("/{courseId}/add_students")
|
||||||
@Operation(summary = "批量导入学生", description = "请求体为JSON格式,包含ids字段,ids为逗号分隔的学生ID字符串")
|
@Operation(summary = "批量导入学生", description = "请求体为JSON格式,包含ids字段,ids为逗号分隔的学生ID字符串")
|
||||||
public Result<Map<String, Object>> addStudents(@PathVariable(value = "courseId") String courseId, @RequestBody Map<String, Object> requestBody, HttpServletRequest request) {
|
public Result<Map<String, Object>> addStudents(@PathVariable(value = "courseId") String courseId,
|
||||||
|
@RequestBody Map<String, Object> requestBody, HttpServletRequest request) {
|
||||||
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||||
String username = JwtUtil.getUsername(token);
|
String username = JwtUtil.getUsername(token);
|
||||||
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
||||||
@ -371,7 +379,8 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
|
|
||||||
@GetMapping("/{courseId}/progress")
|
@GetMapping("/{courseId}/progress")
|
||||||
@Operation(summary = "查询课程学习进度")
|
@Operation(summary = "查询课程学习进度")
|
||||||
public Result<Map<String, Object>> queryCourseProgress(@PathVariable(value = "courseId") String courseId, HttpServletRequest request, HttpServletResponse response) {
|
public Result<Map<String, Object>> queryCourseProgress(@PathVariable(value = "courseId") String courseId,
|
||||||
|
HttpServletRequest request, HttpServletResponse response) {
|
||||||
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||||
String username = JwtUtil.getUsername(token);
|
String username = JwtUtil.getUsername(token);
|
||||||
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
||||||
@ -380,6 +389,38 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
return Result.OK(progress);
|
return Result.OK(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{courseId}/record_video_progress")
|
||||||
|
@Operation(summary = "记录视频学习时长", description = "记录用户观看视频的学习时长,当观看时长达到视频总时长的95%时自动标记为已完成")
|
||||||
|
public Result<Map<String, Object>> recordVideoProgress(
|
||||||
|
@PathVariable(value = "courseId") String courseId,
|
||||||
|
@RequestParam(value = "sectionId") String sectionId,
|
||||||
|
@RequestParam(value = "duration") Integer duration,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
|
||||||
|
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||||
|
String username = JwtUtil.getUsername(token);
|
||||||
|
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
||||||
|
|
||||||
|
Map<String, Object> result = aiolCourseService.recordVideoProgress(courseId, sectionId, duration,
|
||||||
|
sysUser.getId());
|
||||||
|
return Result.OK(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{courseId}/get_video_progress")
|
||||||
|
@Operation(summary = "查询视频学习进度", description = "查询用户在指定视频章节的学习进度,包括已学习时长和视频总时长")
|
||||||
|
public Result<Map<String, Object>> queryVideoProgress(
|
||||||
|
@PathVariable(value = "courseId") String courseId,
|
||||||
|
@RequestParam(value = "sectionId") String sectionId,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
|
||||||
|
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||||
|
String username = JwtUtil.getUsername(token);
|
||||||
|
LoginUser sysUser = sysBaseApi.getUserByName(username);
|
||||||
|
|
||||||
|
Map<String, Object> result = aiolCourseService.queryVideoProgress(courseId, sectionId, sysUser.getId());
|
||||||
|
return Result.OK(result);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/count")
|
@GetMapping("/count")
|
||||||
@Operation(summary = "查询课程总数", description = "返回系统中所有课程的总数量")
|
@Operation(summary = "查询课程总数", description = "返回系统中所有课程的总数量")
|
||||||
@IgnoreAuth
|
@IgnoreAuth
|
||||||
@ -388,7 +429,6 @@ public class AiolCourseController extends JeecgController<AiolCourse, IAiolCours
|
|||||||
return Result.OK(count);
|
return Result.OK(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/test")
|
@GetMapping("/test")
|
||||||
@IgnoreAuth
|
@IgnoreAuth
|
||||||
public Result<String> test() {
|
public Result<String> test() {
|
||||||
|
@ -22,7 +22,7 @@ import lombok.experimental.Accessors;
|
|||||||
/**
|
/**
|
||||||
* @Description: 课程
|
* @Description: 课程
|
||||||
* @Author: jeecg-boot
|
* @Author: jeecg-boot
|
||||||
* @Date: 2025-08-31
|
* @Date: 2025-09-02
|
||||||
* @Version: V1.0
|
* @Version: V1.0
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ -36,105 +36,109 @@ public class AiolCourse implements Serializable {
|
|||||||
/**主键*/
|
/**主键*/
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
@Schema(description = "主键")
|
@Schema(description = "主键")
|
||||||
private String id;
|
private java.lang.String id;
|
||||||
/**课程名*/
|
/**课程名*/
|
||||||
@Excel(name = "课程名", width = 15)
|
@Excel(name = "课程名", width = 15)
|
||||||
@Schema(description = "课程名")
|
@Schema(description = "课程名")
|
||||||
private String name;
|
private java.lang.String name;
|
||||||
/**封面图*/
|
/**封面图*/
|
||||||
@Excel(name = "封面图", width = 15)
|
@Excel(name = "封面图", width = 15)
|
||||||
@Schema(description = "封面图")
|
@Schema(description = "封面图")
|
||||||
private String cover;
|
private java.lang.String cover;
|
||||||
/**介绍视频*/
|
/**介绍视频*/
|
||||||
@Excel(name = "介绍视频", width = 15)
|
@Excel(name = "介绍视频", width = 15)
|
||||||
@Schema(description = "介绍视频")
|
@Schema(description = "介绍视频")
|
||||||
private String video;
|
private java.lang.String video;
|
||||||
/**学校*/
|
/**学校*/
|
||||||
@Excel(name = "学校", width = 15)
|
@Excel(name = "学校", width = 15)
|
||||||
@Schema(description = "学校")
|
@Schema(description = "学校")
|
||||||
private String school;
|
private java.lang.String school;
|
||||||
/**课程概述*/
|
/**课程概述*/
|
||||||
@Excel(name = "课程概述", width = 15)
|
@Excel(name = "课程概述", width = 15)
|
||||||
@Schema(description = "课程概述")
|
@Schema(description = "课程概述")
|
||||||
private String description;
|
private java.lang.String description;
|
||||||
/**课程类型*/
|
/**课程类型*/
|
||||||
@Excel(name = "课程类型", width = 15, dicCode = "course_type")
|
@Excel(name = "课程类型", width = 15, dicCode = "course_type")
|
||||||
@Dict(dicCode = "course_type")
|
@Dict(dicCode = "course_type")
|
||||||
@Schema(description = "课程类型")
|
@Schema(description = "课程类型")
|
||||||
private Integer type;
|
private java.lang.Integer type;
|
||||||
/**授课目标*/
|
/**授课目标*/
|
||||||
@Excel(name = "授课目标", width = 15)
|
@Excel(name = "授课目标", width = 15)
|
||||||
@Schema(description = "授课目标")
|
@Schema(description = "授课目标")
|
||||||
private String target;
|
private java.lang.String target;
|
||||||
/**课程难度*/
|
/**课程难度*/
|
||||||
@Excel(name = "课程难度", width = 15, dicCode = "course_difficulty")
|
@Excel(name = "课程难度", width = 15, dicCode = "course_difficulty")
|
||||||
@Dict(dicCode = "course_difficulty")
|
@Dict(dicCode = "course_difficulty")
|
||||||
@Schema(description = "课程难度")
|
@Schema(description = "课程难度")
|
||||||
private Integer difficulty;
|
private java.lang.Integer difficulty;
|
||||||
/**所属专题*/
|
/**所属专题*/
|
||||||
@Excel(name = "所属专题", width = 15, dicCode = "course_subject")
|
@Excel(name = "所属专题", width = 15, dicCode = "course_subject")
|
||||||
@Dict(dicCode = "course_subject")
|
@Dict(dicCode = "course_subject")
|
||||||
@Schema(description = "所属专题")
|
@Schema(description = "所属专题")
|
||||||
private String subject;
|
private java.lang.String subject;
|
||||||
/**课程大纲*/
|
/**课程大纲*/
|
||||||
@Excel(name = "课程大纲", width = 15)
|
@Excel(name = "课程大纲", width = 15)
|
||||||
@Schema(description = "课程大纲")
|
@Schema(description = "课程大纲")
|
||||||
private String outline;
|
private java.lang.String outline;
|
||||||
/**预备知识*/
|
/**预备知识*/
|
||||||
@Excel(name = "预备知识", width = 15)
|
@Excel(name = "预备知识", width = 15)
|
||||||
@Schema(description = "预备知识")
|
@Schema(description = "预备知识")
|
||||||
private String prerequisite;
|
private java.lang.String prerequisite;
|
||||||
/**参考资料*/
|
/**参考资料*/
|
||||||
@Excel(name = "参考资料", width = 15)
|
@Excel(name = "参考资料", width = 15)
|
||||||
@Schema(description = "参考资料")
|
@Schema(description = "参考资料")
|
||||||
private String reference;
|
private java.lang.String reference;
|
||||||
/**学时安排*/
|
/**学时安排*/
|
||||||
@Excel(name = "学时安排", width = 15)
|
@Excel(name = "学时安排", width = 15)
|
||||||
@Schema(description = "学时安排")
|
@Schema(description = "学时安排")
|
||||||
private String arrangement;
|
private java.lang.String arrangement;
|
||||||
/**开课时间*/
|
/**开课时间*/
|
||||||
@Excel(name = "开课时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
@Excel(name = "开课时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||||
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
||||||
@Schema(description = "开课时间")
|
@Schema(description = "开课时间")
|
||||||
private Date startTime;
|
private java.util.Date startTime;
|
||||||
/**结课时间*/
|
/**结课时间*/
|
||||||
@Excel(name = "结课时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
@Excel(name = "结课时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||||
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
||||||
@Schema(description = "结课时间")
|
@Schema(description = "结课时间")
|
||||||
private Date endTime;
|
private java.util.Date endTime;
|
||||||
/**已报名人数*/
|
/**已报名人数*/
|
||||||
@Excel(name = "已报名人数", width = 15)
|
@Excel(name = "已报名人数", width = 15)
|
||||||
@Schema(description = "已报名人数")
|
@Schema(description = "已报名人数")
|
||||||
private Integer enrollCount;
|
private java.lang.Integer enrollCount;
|
||||||
/**最大报名人数*/
|
/**最大报名人数*/
|
||||||
@Excel(name = "最大报名人数", width = 15)
|
@Excel(name = "最大报名人数", width = 15)
|
||||||
@Schema(description = "最大报名人数")
|
@Schema(description = "最大报名人数")
|
||||||
private Integer maxEnroll;
|
private java.lang.Integer maxEnroll;
|
||||||
/**状态*/
|
/**状态*/
|
||||||
@Excel(name = "状态", width = 15, dicCode = "course_status")
|
@Excel(name = "状态", width = 15, dicCode = "course_status")
|
||||||
@Dict(dicCode = "course_status")
|
@Dict(dicCode = "course_status")
|
||||||
@Schema(description = "状态")
|
@Schema(description = "状态")
|
||||||
private Integer status;
|
private java.lang.Integer status;
|
||||||
/**常见问题*/
|
/**常见问题*/
|
||||||
@Excel(name = "常见问题", width = 15)
|
@Excel(name = "常见问题", width = 15)
|
||||||
@Schema(description = "常见问题")
|
@Schema(description = "常见问题")
|
||||||
private String question;
|
private java.lang.String question;
|
||||||
|
/**是否ai伴学模式*/
|
||||||
|
@Excel(name = "是否ai伴学模式", width = 15)
|
||||||
|
@Schema(description = "是否ai伴学模式")
|
||||||
|
private java.lang.Integer izAi;
|
||||||
/**创建人*/
|
/**创建人*/
|
||||||
@Schema(description = "创建人")
|
@Schema(description = "创建人")
|
||||||
private String createBy;
|
private java.lang.String createBy;
|
||||||
/**创建时间*/
|
/**创建时间*/
|
||||||
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
private Date createTime;
|
private java.util.Date createTime;
|
||||||
/**更新人*/
|
/**更新人*/
|
||||||
@Schema(description = "更新人")
|
@Schema(description = "更新人")
|
||||||
private String updateBy;
|
private java.lang.String updateBy;
|
||||||
/**更新时间*/
|
/**更新时间*/
|
||||||
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
||||||
@Schema(description = "更新时间")
|
@Schema(description = "更新时间")
|
||||||
private Date updateTime;
|
private java.util.Date updateTime;
|
||||||
}
|
}
|
||||||
|
@ -94,4 +94,24 @@ public interface IAiolCourseService extends IService<AiolCourse> {
|
|||||||
|
|
||||||
|
|
||||||
Map<String, Object> getCourseProgress(String courseId, String id);
|
Map<String, Object> getCourseProgress(String courseId, String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录视频学习时长
|
||||||
|
* 前端传递course_id、section_id、duration(单位秒),更新aiol_learn_progress。同时,后端需要根据section_id,先去aiol_entity_link表,根据source_type=course_section和source_id=section_id,查询对应target_type=resource的target_id,再根据target_id去aiol_resource表查询对应文件信息的duration(视频总时长),判断前端传递的duration是否>=视频duration的95%,如果大于等于,则更新aiol_learn_progress表的status=2(已完成)
|
||||||
|
* @param courseId
|
||||||
|
* @param sectionId
|
||||||
|
* @param duration
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Map<String, Object> recordVideoProgress(String courseId, String sectionId, Integer duration, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询视频学习进度
|
||||||
|
* @param courseId
|
||||||
|
* @param sectionId
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Map<String, Object> queryVideoProgress(String courseId, String sectionId, String userId);
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,17 @@ package org.jeecg.modules.aiol.service.impl;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.common.util.CommonUtils;
|
import org.jeecg.common.util.CommonUtils;
|
||||||
import org.jeecg.common.util.MinioUtil;
|
import org.jeecg.common.util.MinioUtil;
|
||||||
|
import org.jeecg.common.util.RedisUtil;
|
||||||
import org.jeecg.common.util.SpringContextUtils;
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
import org.jeecg.common.util.oss.OssBootUtil;
|
import org.jeecg.common.util.oss.OssBootUtil;
|
||||||
import org.jeecg.modules.aiol.constant.EntityLinkConst;
|
import org.jeecg.modules.aiol.constant.EntityLinkConst;
|
||||||
|
import org.jeecg.modules.aiol.constant.RedisConst;
|
||||||
import org.jeecg.modules.aiol.dto.CourseWithTeacherInfo;
|
import org.jeecg.modules.aiol.dto.CourseWithTeacherInfo;
|
||||||
import org.jeecg.modules.aiol.dto.TeacherInfo;
|
import org.jeecg.modules.aiol.dto.TeacherInfo;
|
||||||
import org.jeecg.modules.aiol.entity.*;
|
import org.jeecg.modules.aiol.entity.*;
|
||||||
@ -38,10 +43,11 @@ import java.util.stream.Stream;
|
|||||||
/**
|
/**
|
||||||
* @Description: 课程
|
* @Description: 课程
|
||||||
* @Author: jeecg-boot
|
* @Author: jeecg-boot
|
||||||
* @Date: 2025-08-31
|
* @Date: 2025-08-31
|
||||||
* @Version: V1.0
|
* @Version: V1.0
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCourse> implements IAiolCourseService {
|
public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCourse> implements IAiolCourseService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private AiolCourseMapper courseMapper;
|
private AiolCourseMapper courseMapper;
|
||||||
@ -76,6 +82,9 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AiolLearnProgressMapper learnProgressMapper;
|
private AiolLearnProgressMapper learnProgressMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,18 +221,18 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
// 第一个条件,直接开始
|
// 第一个条件,直接开始
|
||||||
wrapper.and(subWrapper -> {
|
wrapper.and(subWrapper -> {
|
||||||
subWrapper.like("subject", topicValue + ",") // 开头匹配
|
subWrapper.like("subject", topicValue + ",") // 开头匹配
|
||||||
.or().like("subject", "," + topicValue + ",") // 中间匹配
|
.or().like("subject", "," + topicValue + ",") // 中间匹配
|
||||||
.or().like("subject", "," + topicValue) // 结尾匹配
|
.or().like("subject", "," + topicValue) // 结尾匹配
|
||||||
.or().eq("subject", topicValue); // 单独匹配
|
.or().eq("subject", topicValue); // 单独匹配
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 后续条件用OR连接,表示任一专题匹配即可
|
// 后续条件用OR连接,表示任一专题匹配即可
|
||||||
wrapper.or(subWrapper -> {
|
wrapper.or(subWrapper -> {
|
||||||
subWrapper.like("subject", topicValue + ",") // 开头匹配
|
subWrapper.like("subject", topicValue + ",") // 开头匹配
|
||||||
.or().like("subject", "," + topicValue + ",") // 中间匹配
|
.or().like("subject", "," + topicValue + ",") // 中间匹配
|
||||||
.or().like("subject", "," + topicValue) // 结尾匹配
|
.or().like("subject", "," + topicValue) // 结尾匹配
|
||||||
.or().eq("subject", topicValue); // 单独匹配
|
.or().eq("subject", topicValue); // 单独匹配
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,8 +277,10 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
public String uploadHls(MultipartFile file, HttpServletRequest request) throws Exception {
|
public String uploadHls(MultipartFile file, HttpServletRequest request) throws Exception {
|
||||||
// 读取上传类型(header 优先)
|
// 读取上传类型(header 优先)
|
||||||
String headerUploadType = request.getHeader("uploadType");
|
String headerUploadType = request.getHeader("uploadType");
|
||||||
String configUploadType = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("jeecg.uploadType", "minio");
|
String configUploadType = SpringContextUtils.getApplicationContext().getEnvironment()
|
||||||
String uploadType = (headerUploadType != null && headerUploadType.trim().length() > 0) ? headerUploadType : configUploadType;
|
.getProperty("jeecg.uploadType", "minio");
|
||||||
|
String uploadType = (headerUploadType != null && headerUploadType.trim().length() > 0) ? headerUploadType
|
||||||
|
: configUploadType;
|
||||||
|
|
||||||
// 1) 保存临时原始视频
|
// 1) 保存临时原始视频
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
@ -303,7 +314,8 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
String base = "video/hls/" + uuid;
|
String base = "video/hls/" + uuid;
|
||||||
try (Stream<Path> paths = Files.list(hlsDir)) {
|
try (Stream<Path> paths = Files.list(hlsDir)) {
|
||||||
for (Path f : (Iterable<Path>) paths::iterator) {
|
for (Path f : (Iterable<Path>) paths::iterator) {
|
||||||
if (!Files.isRegularFile(f)) continue;
|
if (!Files.isRegularFile(f))
|
||||||
|
continue;
|
||||||
String rel = base + "/" + f.getFileName().toString();
|
String rel = base + "/" + f.getFileName().toString();
|
||||||
try (InputStream in = Files.newInputStream(f)) {
|
try (InputStream in = Files.newInputStream(f)) {
|
||||||
if ("minio".equals(uploadType)) {
|
if ("minio".equals(uploadType)) {
|
||||||
@ -317,7 +329,8 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
m3u8Url = rel; // 可在网关拼域名
|
m3u8Url = rel; // 可在网关拼域名
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String uploadpath = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("jeecg.path.upload");
|
String uploadpath = SpringContextUtils.getApplicationContext().getEnvironment()
|
||||||
|
.getProperty("jeecg.path.upload");
|
||||||
Path target = Path.of(uploadpath, rel);
|
Path target = Path.of(uploadpath, rel);
|
||||||
Files.createDirectories(target.getParent());
|
Files.createDirectories(target.getParent());
|
||||||
Files.copy(f, target, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(f, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
@ -338,24 +351,29 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
/** 删除临时目录文件 */
|
/** 删除临时目录文件 */
|
||||||
private static void deleteQuietly(File file) {
|
private static void deleteQuietly(File file) {
|
||||||
try {
|
try {
|
||||||
if (file == null || !file.exists()) return;
|
if (file == null || !file.exists())
|
||||||
|
return;
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
File[] children = file.listFiles();
|
File[] children = file.listFiles();
|
||||||
if (children != null) {
|
if (children != null) {
|
||||||
for (File c : children) deleteQuietly(c);
|
for (File c : children)
|
||||||
|
deleteQuietly(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.delete();
|
file.delete();
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TeacherInfo> getCourseTeacherList(String courseId) {
|
public List<TeacherInfo> getCourseTeacherList(String courseId) {
|
||||||
List<AiolCourseTeacher> list = courseTeacherMapper.selectList(new QueryWrapper<AiolCourseTeacher>().eq("course_id", courseId));
|
List<AiolCourseTeacher> list = courseTeacherMapper
|
||||||
|
.selectList(new QueryWrapper<AiolCourseTeacher>().eq("course_id", courseId));
|
||||||
|
|
||||||
List<TeacherInfo> result = new ArrayList<>();
|
List<TeacherInfo> result = new ArrayList<>();
|
||||||
for (AiolCourseTeacher item : list) {
|
for (AiolCourseTeacher item : list) {
|
||||||
AiolUserInfo userInfo = userInfoMapper.selectOne(new QueryWrapper<AiolUserInfo>().eq("user_id", item.getTeacherId()));
|
AiolUserInfo userInfo = userInfoMapper
|
||||||
|
.selectOne(new QueryWrapper<AiolUserInfo>().eq("user_id", item.getTeacherId()));
|
||||||
SysUser sysUser = sysUserMapper.selectById(item.getTeacherId());
|
SysUser sysUser = sysUserMapper.selectById(item.getTeacherId());
|
||||||
|
|
||||||
TeacherInfo teacherInfo = new TeacherInfo();
|
TeacherInfo teacherInfo = new TeacherInfo();
|
||||||
@ -512,7 +530,8 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
int currentEnrollCount = course.getEnrollCount() == null ? 0 : course.getEnrollCount();
|
int currentEnrollCount = course.getEnrollCount() == null ? 0 : course.getEnrollCount();
|
||||||
if (course.getMaxEnroll() != null) {
|
if (course.getMaxEnroll() != null) {
|
||||||
if (currentEnrollCount + newStudentIds.size() > course.getMaxEnroll()) {
|
if (currentEnrollCount + newStudentIds.size() > course.getMaxEnroll()) {
|
||||||
throw new RuntimeException("添加学生后将超过最大报名人数限制,当前:" + currentEnrollCount + ",最大:" + course.getMaxEnroll() + ",新增:" + newStudentIds.size());
|
throw new RuntimeException("添加学生后将超过最大报名人数限制,当前:" + currentEnrollCount + ",最大:" + course.getMaxEnroll()
|
||||||
|
+ ",新增:" + newStudentIds.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,8 +588,8 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 分别查询视频、考试、作业章节
|
// 2. 分别查询视频、考试、作业章节
|
||||||
List<AiolCourseSection> videoSections = getCourseSectionsByType(courseId, 0); // 视频
|
List<AiolCourseSection> videoSections = getCourseSectionsByType(courseId, 0); // 视频
|
||||||
List<AiolCourseSection> examSections = getCourseSectionsByType(courseId, 2); // 考试
|
List<AiolCourseSection> examSections = getCourseSectionsByType(courseId, 2); // 考试
|
||||||
List<AiolCourseSection> homeworkSections = getCourseSectionsByType(courseId, 3); // 作业
|
List<AiolCourseSection> homeworkSections = getCourseSectionsByType(courseId, 3); // 作业
|
||||||
|
|
||||||
// 3. 分别查询各类型章节的完成情况
|
// 3. 分别查询各类型章节的完成情况
|
||||||
@ -647,7 +666,7 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
progressQuery.eq("user_id", userId)
|
progressQuery.eq("user_id", userId)
|
||||||
.eq("course_id", courseId)
|
.eq("course_id", courseId)
|
||||||
.in("section_id", sectionIds)
|
.in("section_id", sectionIds)
|
||||||
.eq("status", 2); // status=2表示学习完成
|
.eq("status", 2); // status=2表示学习完成
|
||||||
|
|
||||||
return Math.toIntExact(learnProgressMapper.selectCount(progressQuery));
|
return Math.toIntExact(learnProgressMapper.selectCount(progressQuery));
|
||||||
}
|
}
|
||||||
@ -661,4 +680,185 @@ public class AiolCourseServiceImpl extends ServiceImpl<AiolCourseMapper, AiolCou
|
|||||||
}
|
}
|
||||||
return Math.round((float) completed / total * 100);
|
return Math.round((float) completed / total * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Map<String, Object> recordVideoProgress(String courseId, String sectionId, Integer duration, String userId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 根据section_id查询aiol_entity_link表,获取对应的resource_id
|
||||||
|
String sourceType = EntityLinkConst.SourceType.COURSE_SECTION;
|
||||||
|
String targetType = EntityLinkConst.TargetType.RESOURCE;
|
||||||
|
|
||||||
|
List<String> resourceIds = entityLinkBizService.listTargetIds(sourceType, sectionId, targetType);
|
||||||
|
if (resourceIds.isEmpty()) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "未找到章节对应的视频资源");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 从Redis缓存获取视频资源信息,如果没有则从数据库查询并缓存
|
||||||
|
String resourceId = resourceIds.get(0); // 一个章节对应一个视频资源,取第一个即可
|
||||||
|
AiolResource resource = getVideoResourceFromCache(resourceId);
|
||||||
|
|
||||||
|
if (resource == null || resource.getDuration() == null) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "视频资源信息不完整");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer totalDuration = resource.getDuration();
|
||||||
|
Integer thresholdDuration = (int) Math.ceil(totalDuration * 0.95); // 95%阈值
|
||||||
|
|
||||||
|
// 3. 判断学习时长是否达到完成阈值
|
||||||
|
boolean isCompleted = duration >= thresholdDuration;
|
||||||
|
|
||||||
|
// 4. 查询或创建学习进度记录
|
||||||
|
QueryWrapper<AiolLearnProgress> progressQuery = new QueryWrapper<>();
|
||||||
|
progressQuery.eq("user_id", userId)
|
||||||
|
.eq("course_id", courseId)
|
||||||
|
.eq("section_id", sectionId);
|
||||||
|
|
||||||
|
AiolLearnProgress learnProgress = learnProgressMapper.selectOne(progressQuery);
|
||||||
|
|
||||||
|
if (learnProgress == null) {
|
||||||
|
// 创建新的学习进度记录
|
||||||
|
learnProgress = new AiolLearnProgress();
|
||||||
|
learnProgress.setUserId(userId);
|
||||||
|
learnProgress.setCourseId(courseId);
|
||||||
|
learnProgress.setSectionId(sectionId);
|
||||||
|
learnProgress.setCreateTime(new Date());
|
||||||
|
learnProgress.setCreateBy(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 更新学习进度信息
|
||||||
|
learnProgress.setUpdateTime(new Date());
|
||||||
|
learnProgress.setUpdateBy(userId);
|
||||||
|
learnProgress.setDuration(duration);
|
||||||
|
|
||||||
|
if (isCompleted) {
|
||||||
|
learnProgress.setStatus(2); // 已完成
|
||||||
|
learnProgress.setUpdateTime(new Date());
|
||||||
|
} else {
|
||||||
|
learnProgress.setStatus(1); // 学习中
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 保存或更新学习进度
|
||||||
|
if (learnProgress.getId() == null) {
|
||||||
|
learnProgressMapper.insert(learnProgress);
|
||||||
|
} else {
|
||||||
|
learnProgressMapper.updateById(learnProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 构建返回结果
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("isCompleted", isCompleted);
|
||||||
|
result.put("currentDuration", duration);
|
||||||
|
result.put("totalDuration", totalDuration);
|
||||||
|
result.put("thresholdDuration", thresholdDuration);
|
||||||
|
result.put("progress", Math.min(100, (duration * 100) / totalDuration));
|
||||||
|
result.put("message", isCompleted ? "视频学习完成!" : "学习进度已记录");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("记录视频学习进度失败: courseId={}, sectionId={}, userId={}, error={}",
|
||||||
|
courseId, sectionId, userId, e.getMessage(), e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "记录学习进度失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从Redis缓存获取视频资源信息,如果没有则从数据库查询并缓存
|
||||||
|
*
|
||||||
|
* @param resourceId 资源ID
|
||||||
|
* @return 视频资源信息
|
||||||
|
*/
|
||||||
|
private AiolResource getVideoResourceFromCache(String resourceId) {
|
||||||
|
String cacheKey = RedisConst.VIDEO_RESOURCE_CACHE_KEY_PREFIX + resourceId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 尝试从Redis获取缓存
|
||||||
|
Object cachedResource = redisUtil.get(cacheKey);
|
||||||
|
if (cachedResource != null) {
|
||||||
|
log.debug("从Redis缓存获取视频资源: resourceId={}", resourceId);
|
||||||
|
return (AiolResource) cachedResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 缓存未命中,从数据库查询
|
||||||
|
log.debug("Redis缓存未命中,从数据库查询视频资源: resourceId={}", resourceId);
|
||||||
|
AiolResource resource = resourceMapper.selectById(resourceId);
|
||||||
|
|
||||||
|
if (resource != null) {
|
||||||
|
// 3. 将资源信息存入Redis缓存
|
||||||
|
redisUtil.set(cacheKey, resource, RedisConst.VIDEO_RESOURCE_CACHE_EXPIRE);
|
||||||
|
log.debug("视频资源已缓存到Redis: resourceId={}, duration={}", resourceId, resource.getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取视频资源缓存失败: resourceId={}, error={}", resourceId, e.getMessage(), e);
|
||||||
|
// 缓存异常时,直接从数据库查询
|
||||||
|
return resourceMapper.selectById(resourceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> queryVideoProgress(String courseId, String sectionId, String userId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 查询用户在该章节的学习进度
|
||||||
|
QueryWrapper<AiolLearnProgress> progressQuery = new QueryWrapper<>();
|
||||||
|
progressQuery.eq("user_id", userId)
|
||||||
|
.eq("course_id", courseId)
|
||||||
|
.eq("section_id", sectionId);
|
||||||
|
|
||||||
|
AiolLearnProgress learnProgress = learnProgressMapper.selectOne(progressQuery);
|
||||||
|
|
||||||
|
// 2. 获取已学习时长
|
||||||
|
Integer learnedDuration = 0;
|
||||||
|
|
||||||
|
if (learnProgress != null) {
|
||||||
|
learnedDuration = learnProgress.getDuration() != null ? learnProgress.getDuration() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 从Redis缓存获取视频资源信息,如果没有则从数据库查询并缓存
|
||||||
|
String sourceType = EntityLinkConst.SourceType.COURSE_SECTION;
|
||||||
|
String targetType = EntityLinkConst.TargetType.RESOURCE;
|
||||||
|
|
||||||
|
List<String> resourceIds = entityLinkBizService.listTargetIds(sourceType, sectionId, targetType);
|
||||||
|
if (resourceIds.isEmpty()) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "未找到章节对应的视频资源");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String resourceId = resourceIds.get(0);
|
||||||
|
AiolResource resource = getVideoResourceFromCache(resourceId);
|
||||||
|
|
||||||
|
if (resource == null || resource.getDuration() == null) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "视频资源信息不完整");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer totalDuration = resource.getDuration();
|
||||||
|
|
||||||
|
// 6. 构建返回结果
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("learnedDuration", learnedDuration); // 已学习时长(秒)
|
||||||
|
result.put("totalDuration", totalDuration); // 视频总时长(秒)
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询视频学习进度失败: courseId={}, sectionId={}, userId={}, error={}",
|
||||||
|
courseId, sectionId, userId, e.getMessage(), e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "查询学习进度失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,4 +74,7 @@
|
|||||||
<appender-ref ref="FILE_HTML" />
|
<appender-ref ref="FILE_HTML" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
|
<!-- Aiol模块日志输出级别 -->
|
||||||
|
<logger name="org.jeecg.modules.aiol" level="DEBUG" />
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
@ -101,6 +101,11 @@ export const columns: BasicColumn[] = [
|
|||||||
align:"center",
|
align:"center",
|
||||||
dataIndex: 'question',
|
dataIndex: 'question',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '是否ai伴学模式',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'izAi'
|
||||||
|
},
|
||||||
];
|
];
|
||||||
//查询数据
|
//查询数据
|
||||||
export const searchFormSchema: FormSchema[] = [
|
export const searchFormSchema: FormSchema[] = [
|
||||||
@ -228,6 +233,11 @@ export const formSchema: FormSchema[] = [
|
|||||||
label: '常见问题',
|
label: '常见问题',
|
||||||
field: 'question',
|
field: 'question',
|
||||||
component: 'JEditor',
|
component: 'JEditor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '是否ai伴学模式',
|
||||||
|
field: 'izAi',
|
||||||
|
component: 'InputNumber',
|
||||||
},
|
},
|
||||||
// TODO 主键隐藏字段,目前写死为ID
|
// TODO 主键隐藏字段,目前写死为ID
|
||||||
{
|
{
|
||||||
@ -259,6 +269,7 @@ export const superQuerySchema = {
|
|||||||
maxEnroll: {title: '最大报名人数',order: 16,view: 'number', type: 'number',},
|
maxEnroll: {title: '最大报名人数',order: 16,view: 'number', type: 'number',},
|
||||||
status: {title: '状态',order: 17,view: 'number', type: 'number',dictCode: 'course_status',},
|
status: {title: '状态',order: 17,view: 'number', type: 'number',dictCode: 'course_status',},
|
||||||
question: {title: '常见问题',order: 18,view: 'umeditor', type: 'string',},
|
question: {title: '常见问题',order: 18,view: 'umeditor', type: 'string',},
|
||||||
|
izAi: {title: '是否ai伴学模式',order: 19,view: 'number', type: 'number',},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user