Compare commits

...

27 Commits

Author SHA1 Message Date
王卓煜
fd0c7da1b3 修复问答管理的标题查询与实际不符 2025-07-18 11:29:04 +08:00
王卓煜
80cf62e598 修复新建文章,上传图片报错 2025-07-15 14:34:27 +08:00
王卓煜
faf9226668 修复个人中心中“@我的”列表已删除的内容依然显示在列表中 2025-07-08 17:33:15 +08:00
王卓煜
154a7ab04d 修复个人中心笔记导入txt格式报错 2025-07-08 11:11:06 +08:00
王卓煜
fa40c24722 修复考试成绩窗口查询不支持模糊查询 2025-07-07 16:39:03 +08:00
王卓煜
7ad6da6f53 修复学员端,“文章”列表页,进入文章详情。对文章发表评论后,评论数量没有即时更新;对评论进行回复后,回复的数量没有在评论的数量统计即时更新,并且修复删除评论,评论数量不对 2025-07-04 13:37:28 +08:00
王卓煜
2a1c04ccb5 修复学员端,“文章”列表页,进入文章详情。对文章发表评论后,评论数量没有即时更新;对评论进行回复后,回复的数量没有在评论的数量统计即时更新 2025-07-04 13:31:28 +08:00
670788339
171ddfb89c studyIndex添加讲师头像 2025-06-23 10:04:23 +08:00
joshen
618b0b3f62 Merge remote-tracking branch 'yx/master-20250414-lyc' into release-20250328-master 2025-05-13 15:37:05 +08:00
670788339
5836b147c4 在线删除同步授课记录 2025-04-29 15:58:37 +08:00
670788339
fb1d11ebab 取消lastStudyTime更新条件 2025-04-21 15:18:48 +08:00
670788339
add1d6abb2 取消lastStudyTime更新条件 2025-04-21 13:59:06 +08:00
670788339
eafe17b99a studyindex 日志 2025-04-21 13:46:55 +08:00
670788339
d1b6573c25 studyindex调试 2025-04-21 11:40:22 +08:00
670788339
1039f80aec 调试 2025-04-18 11:15:36 +08:00
670788339
efc0605115 调试 2025-04-18 11:05:18 +08:00
670788339
d938425151 日志 2025-04-18 10:54:36 +08:00
670788339
00510562ca 日志 2025-04-18 10:40:50 +08:00
670788339
3e70e71e5a 合并影响的其他接口 2025-04-18 10:19:06 +08:00
670788339
24011a4470 合并影响的其他接口 2025-04-18 10:06:53 +08:00
670788339
8948165296 合并影响的其他接口 2025-04-18 09:49:59 +08:00
670788339
076b1828ad 合并 2025-04-18 09:16:47 +08:00
670788339
95312696f6 studyindex缓存获取duration 2025-04-17 13:36:30 +08:00
670788339
1aca002b8f 合并测试 2025-04-14 16:10:18 +08:00
670788339
50bdc39ce8 appendtime 于 study-video-time 合并 2025-04-14 14:08:01 +08:00
joshen
4fff780970 Merge branch '250408-bugfix-shl' into release-20250328-master
# Conflicts:
#	servers/boe-server-all/src/main/java/com/xboe/module/course/service/ICourseService.java
2025-04-09 18:16:07 +08:00
sunhonglai
47813ea565 增加面授课删除 2025-04-08 17:43:37 +08:00
15 changed files with 413 additions and 50 deletions

View File

@@ -98,7 +98,8 @@ public class ThirdApi {
private String syncOnLineScore;
@Value("${coursesuilt.updateOnLineStatua}")
private String updateOnLineStatua;
@Value("${coursesuilt.delOnLineById}")
private String delOnLineById;
//获取例外人员的id
public List<String> getUserId() {
@@ -398,4 +399,12 @@ public class ThirdApi {
public void syncExamStudyRecord(ExamStudyRecordParam param) {
externalRemoteClient.syncExamStudyRecord(param);
}
public void delOnLineById(CourseParam param, String token) {
log.info("---------------同步在线课删除 ------- param " + param);
String resp = Optional.ofNullable(
HttpRequest.post(delOnLineById).body(JSONUtil.toJsonStr(param)).header("token", token).execute()
.body()).orElseThrow(() -> new RuntimeException("token校验失败"));
log.info("-------delOnLineById = " + resp);
}
}

View File

@@ -886,7 +886,7 @@ public class CourseManageApi extends ApiBaseController{
* @return
*/
@PostMapping("/delete")
public JsonResponse<Boolean> delete(String id,Boolean erasable,String title,String remark){
public JsonResponse<Boolean> delete(String id,Boolean erasable,String title,String remark, HttpServletRequest request){
if(StringUtils.isBlank(id)){
return badRequest("参数错误");
}
@@ -901,6 +901,11 @@ public class CourseManageApi extends ApiBaseController{
try {
CurrentUser cu=getCurrent();
courseService.delete(id, erasable,cu.getAccountId(), cu.getName(), remark);
String token = request.getHeader("Xboe-Access-Token");
CourseParam param = new CourseParam();
param.setId(id);
thirdApi.delOnLineById(param,token);
return success(true);
} catch (Exception e) {
log.error("管理员删除课程错误",e);

View File

@@ -57,4 +57,8 @@ public class CourseTeacher extends IdBaseEntity {
@Transient
private Integer teacherType;
/**讲师头像*/
@Transient
private String photo;
}

View File

@@ -342,4 +342,5 @@ public interface ICourseService {
List<Course> findByIds(List<String> courseIds);
void deletedStudyResourceBatchByCourseIdAndType(String courseId,Integer courseType);
}

View File

@@ -1,5 +1,6 @@
package com.xboe.module.course.service.impl;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -20,6 +21,11 @@ import com.xboe.core.orm.*;
import com.xboe.school.study.dao.StudyCourseDao;
import com.xboe.school.study.entity.StudyCourse;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.hibernate.mapping.IdGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
@@ -116,6 +122,9 @@ public class CourseServiceImpl implements ICourseService {
@Autowired(required = false)
private IEventDataSender eventSender;
@Resource
RestHighLevelClient restHighLevelClient;
/**
@@ -855,12 +864,14 @@ public class CourseServiceImpl implements ICourseService {
log.error("未配置事件消息发送的实现");
}
}
// 删除ES数据
deletedStudyResourceBatchByCourseIdAndType(id,c.getType());
} else {
//彻底删除,课件设置为无课程状态
courseDao.setDeleted(id);
}
//记录删除日志信息
}
@Override
@@ -1987,5 +1998,17 @@ public class CourseServiceImpl implements ICourseService {
return courseDao.findListByHql("Select new Course(id,studys,score) from Course where id in(?1)", ids);
}
@Override
public void deletedStudyResourceBatchByCourseIdAndType(String courseId, Integer courseType) {
DeleteByQueryRequest request = new DeleteByQueryRequest("new_study_resource");
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(QueryBuilders.matchQuery("courseId", courseId));
boolQueryBuilder.must(QueryBuilders.matchQuery("courseType", courseType));
request.setQuery(boolQueryBuilder);
try {
restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -165,7 +165,7 @@ public class AloneExamServiceImpl implements IAloneExamService{
query.addFilter(FieldFilters.eq("aid",aea.getAid()));
}
if(StringUtils.isNotBlank(aea.getName())) {
query.addFilter(FieldFilters.eq("name",aea.getName()));
query.addFilter(FieldFilters.like("name", aea.getName()));
}
}
return dao.findPage(query.builder());
@@ -247,7 +247,7 @@ public class AloneExamServiceImpl implements IAloneExamService{
query.addFilter(FieldFilters.eq("a.aid",aea.getAid()));
}
if(StringUtils.isNotBlank(aea.getName())) {
query.addFilter(FieldFilters.eq("a.name",aea.getName()));
query.addFilter(FieldFilters.like("a.name", "%" + aea.getName() + "%"));
}
}
return aeDao.findPage(query.builder());

View File

@@ -75,6 +75,7 @@ public class CommentsServiceImpl implements ICommentsService{
c.setPraises(0);
c.setFavorites(0);
dao.save(c);
callback.increase(BoedxResourceType.toEnum(c.getObjType()),c.getObjId(), BoedxHitsField.Comments);
if(c.getParentRead()!=null && c.getParentRead()) {
dao.updateMultiFieldById(c.getParentId(),
UpdateBuilder.create("replys","replys+1",FieldUpdateType.EXPRESSION),
@@ -87,9 +88,11 @@ public class CommentsServiceImpl implements ICommentsService{
@Override
public void deleteReply(String id,String parentId) {
Comments comment=dao.get(id);
dao.deleteById(id);
dao.updateMultiFieldById(parentId, UpdateBuilder.create("replys","replys-1",FieldUpdateType.EXPRESSION));
//需要同时回调处理,修改对应的内容的评论数量
callback.reduce(BoedxResourceType.toEnum(comment.getObjType()),comment.getObjId(),BoedxHitsField.Comments);
}
@Override
@@ -198,7 +201,7 @@ public class CommentsServiceImpl implements ICommentsService{
PageList<CommentsDto> list = new PageList<>();
list.setList(new ArrayList<CommentsDto>());
QueryBuilder builder = QueryBuilder.from(CommentQa.class);
// builder.addFilter(FieldFilters.eq("deleted",false));
builder.addFilter(FieldFilters.eq("deleted",false));
// builder.addFilter(FieldFilters.ne("objType",4));
if(StringUtil.isNotBlank(toaid)){
builder.addFilter(FieldFilters.eq("toAid",toaid));

View File

@@ -86,7 +86,7 @@ public class QuestionServiceImpl implements IQuestionService {
// filters.add(FieldFilters.or(FieldFilters.like("title", questionDto.getKeyword()), FieldFilters.like("content", questionDto.getKeyword())));
List<IFieldFilter> iFieldFilters = new ArrayList<>();
iFieldFilters.add(FieldFilters.like("title", questionDto.getKeyword()));
iFieldFilters.add(FieldFilters.like("content", questionDto.getKeyword()));
// iFieldFilters.add(FieldFilters.like("content", questionDto.getKeyword()));
iFieldFilters.add(FieldFilters.like("sysCreateBy", questionDto.getKeyword()));
filters.add(FieldFilters.or(iFieldFilters));
}
@@ -213,6 +213,14 @@ public class QuestionServiceImpl implements IQuestionService {
@Override
public void deleteQuestions(String id) {
questionDao.setDeleted(id);
// 首先根据问题id查询出所有答案id
QueryBuilder builder = QueryBuilder.from(Answer.class);
builder.addFilter(FieldFilters.eq("qid",id));
List<Answer> answerList = answerDao.findList(builder.builder());
// 删除答案(逻辑删除)
for (Answer answer : answerList) {
answerDao.setDeleted(answer.getId());
}
}
/**
* 设置精华问题*/

View File

@@ -15,6 +15,7 @@ import com.xboe.constants.CacheName;
import com.xboe.module.course.vo.TeacherVo;
import com.xboe.module.usergroup.service.IUserGroupService;
import com.xboe.school.study.dao.StudyCourseDao;
import com.xboe.school.vo.StudyTimeVo;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -211,6 +212,7 @@ public class StudyCourseApi extends ApiBaseController{
ct.setRemark(t.getDescription());
ct.setSupplier(t.getSupplier());
ct.setTeacherType(t.getTeacherType());
ct.setPhoto(t.getPhoto());
}
if(redisTemplate.opsForValue().get(ct.getTeacherId())==null){
List<String>list=new ArrayList<>();
@@ -275,19 +277,6 @@ public class StudyCourseApi extends ApiBaseController{
continue;
}
// 查询redis上面的key并解析value获取到lastStudyTime
String lastActive = redisTemplate.opsForValue().get("studyContentId:" + item.getId() + ":last_active");
if (StringUtil.isNotBlank(lastActive)) {
String[] parts = lastActive.split("&");
if (parts.length == 2) {
int lastStudyTimeRedis = Integer.parseInt(parts[0]);
log.info("study-video-time-redis获取---lastStudyTimeRedis = " + lastStudyTimeRedis);
if(lastStudyTimeRedis>0){
item.setLastStudyTime(lastStudyTimeRedis);
}
}
}
BigDecimal lastStudyTime = new BigDecimal(item.getLastStudyTime());
BigDecimal duration = new BigDecimal(content.getDuration());
BigDecimal progress = lastStudyTime.divide(duration, 10, RoundingMode.HALF_UP);
@@ -625,7 +614,7 @@ public class StudyCourseApi extends ApiBaseController{
@Deprecated
@RequestMapping(value="/appendtime",method = {RequestMethod.GET,RequestMethod.POST})
public JsonResponse<String> appendTime(StudyTime studyTime, HttpServletRequest request){
if(StringUtils.isBlank(studyTime.getStudyId())){
return error("参数错误");
}
@@ -655,7 +644,91 @@ public class StudyCourseApi extends ApiBaseController{
return error("记录学习时长错误",e.getMessage());
}
}
/**
* appendtime 于 study-video-time 合并
* */
/*@RequestMapping(value="/updateStudyVideoTime1",method = {RequestMethod.GET,RequestMethod.POST})
public JsonResponse<String> updateStudyVideoTime1(StudyTimeVo studyTime, HttpServletRequest request){
// 0 study-video-time , 1 appendtime
if (studyTime.getType() == 0){
if(StringUtils.isBlank(studyTime.getItemId())){
return error("参数错误");
}
if(studyTime.getVideoTime()==null){
return error("无时间点");
}
//检查是否已存在
try {
studyService.updateLastTime(studyTime.getItemId(),studyTime.getVideoTime(), getCurrent().getAccountId());
if (studyTime.getContentId() != null && studyTime.getCourseId() != null && studyTime.getProgressVideo() != null){
contentService.updateProcessVideo(studyTime.getContentId(), studyTime.getCourseId(), studyTime.getProgressVideo());
}
return success("true");
}catch(Exception e) {
log.error("updateStudyVideoTime type =0 记录最后学习时间错误",e);
return error("updateStudyVideoTime type =0 记录最后学习时间失败 ",e.getMessage());
}
}else if(studyTime.getType() == 1){
if(StringUtils.isBlank(studyTime.getStudyId())){
return error("参数错误");
}
if(StringUtils.isBlank(studyTime.getCourseId())){
return error("未指定课程");
}
if(StringUtils.isBlank(studyTime.getContentId())){
return error("未指定资源内容");
}
String token = request.getHeader("Xboe-Access-Token");
if (StringUtils.isEmpty(token)) {
token = request.getHeader("token");
}
try {
studyService.updateStudyDuration(studyTime.getStudyId(),null,studyTime.getContentId(),studyTime.getVideoTime(),studyTime.getCourseId());
List<StudyCourse> allUserList = thirdApi.getStudyCourseList(studyTime.getStudyId() ,studyTime.getCourseId(), token);
log.info("updateStudyVideoTime type =1 在线课学习记录 = " + allUserList);
return success(studyTime.getId());
}catch(Exception e) {
log.error("updateStudyVideoTime type =1 记录学习时长错误",e);
return error("updateStudyVideoTime type =1 记录学习时长错误 ",e.getMessage());
}
}else{
return error("type不能为空");
}
}*/
@RequestMapping(value="/updateStudyVideoTime",method = {RequestMethod.GET,RequestMethod.POST})
public JsonResponse<String> updateStudyVideoTime(StudyTimeVo studyTime, HttpServletRequest request){
try {
if(StringUtils.isBlank(studyTime.getItemId())){
return error("参数错误");
}
if(studyTime.getVideoTime()==null){
return error("无时间点");
}
if(StringUtils.isBlank(studyTime.getStudyId())){
return error("参数错误");
}
if(StringUtils.isBlank(studyTime.getCourseId())){
return error("未指定课程");
}
if(StringUtils.isBlank(studyTime.getContentId())){
return error("未指定资源内容");
}
studyService.updateStudyDuration(studyTime.getStudyId(),studyTime.getItemId(),studyTime.getContentId(),studyTime.getVideoTime(),studyTime.getCourseId());
if (studyTime.getContentId() != null && studyTime.getCourseId() != null && studyTime.getProgressVideo() != null){
contentService.updateProcessVideo(studyTime.getContentId(), studyTime.getCourseId(), studyTime.getProgressVideo());
}
} catch (Exception e) {
log.error("updateStudyVideoTime",e);
return error("学习时长记录失败",e.getMessage(),null);
}
return success("true");
}
/**获取最后一次的学习内容*/
@GetMapping("/last-study")
public JsonResponse<Map<String,Object>> lastStudy(){

View File

@@ -1,5 +1,7 @@
package com.xboe.school.study.api;
import com.xboe.api.ThirdApi;
import com.xboe.school.study.entity.StudyCourse;
import com.xboe.school.study.service.IStudyService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
@@ -10,10 +12,13 @@ import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
/**
* @author by lyc
@@ -26,13 +31,15 @@ public class StudyCourseTask {
private final IStudyService studyService;
private final StringRedisTemplate redisTemplate;
@Resource
private ThirdApi thirdApi;
/**
* 定时任务
* 获取redis 中学习结束的数据更新入库
* */
@XxlJob("saveStudyCourseItemLastTime")
public void saveStudyCourseItemLastTime() {
@XxlJob("saveStudyCourseItemLastTime2")
public void saveStudyCourseItemLastTime2() {
// 1. 定义匹配模式匹配所有目标key
final String KEY_PATTERN = "studyContentId:*:last_active";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
@@ -59,7 +66,6 @@ public class StudyCourseTask {
String[] parts = redisKey.split(":");
if (parts.length < 2) continue;
String studyContentId = parts[1];
// 7. 获取存储的时间点(示例逻辑)
String redisValue = redisTemplate.opsForValue().get(redisKey);
if (redisValue == null) continue;
@@ -69,19 +75,15 @@ public class StudyCourseTask {
if (partValues.length >= 2){
timestamp = LocalDateTime.parse(partValues[1], formatter);
}
// 8. 更新数据库(调用已有服务方法)
studyService.updateStudyCourseItemLastTime(studyContentId, lastStudyTime, timestamp);
// 9. 删除Redis键原子操作
redisTemplate.delete(redisKey);
log.info("处理成功 key: {}, lastStudyTime: {}", redisKey, lastStudyTime);
} catch (Exception e) {
log.error("处理失败 key: {}", redisKey, e);
}
}
}
cursor.close();
} catch (Exception e) {
@@ -102,5 +104,159 @@ public class StudyCourseTask {
}
@XxlJob("saveStudyCourseItemLastTime")
public void saveStudyCourseItemLastTime() {
// 1. 定义匹配模式匹配所有目标key
final String KEY_PATTERN = "studyId:*:courseId:*:courseContentId:*:studyItemId:*";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
// 2. 使用SCAN安全遍历避免阻塞
ScanOptions options = ScanOptions.scanOptions()
.match(KEY_PATTERN)
.count(100) // 分页大小
.build();
try (RedisConnection connection = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection()) {
Cursor<byte[]> cursor = connection.scan(options);
// 3. 遍历处理符合条件的key
while (cursor.hasNext()) {
String redisKey = new String(cursor.next());
log.info("-定时任务 saveStudyCourseItemLastTime ---redisKey = " + redisKey);
// 4. 获取剩余TTL
Long ttl = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
// 5. 过滤条件:剩余时间 >= 29天23小时30分钟转换为秒
// 总需时间 = (30天 - 30分钟) = 29天23小时30分钟 = 2590200秒
// 5分钟 300秒 || 2592000 - 300 = 2591700
if (ttl <= 2590200) {
try {
// 6. 提取studyContentId
String[] parts = redisKey.split(":");
if (parts.length < 7) continue;
String studyId = parts[1];
String courseId = parts[3];
String courseContentId = parts[5];
String studyItemId = parts[7];
// 7. 获取存储的时间点(示例逻辑)
String redisValue = redisTemplate.opsForValue().get(redisKey);
log.info("-定时任务 saveStudyCourseItemLastTime ---redisValue = " + redisValue);
if (redisValue == null) continue;
String[] partValues = redisValue.split("&");
int studyVideoTtime = Integer.parseInt(partValues[0]);
int appendtime = Integer.parseInt(partValues[1]);
LocalDateTime timestamp = null;
if (partValues.length >= 2){
timestamp = LocalDateTime.parse(partValues[2], formatter);
}
// 8. 更新数据库(调用已有服务方法)
studyService.newAppendStudyDuration(studyId,null,courseContentId,appendtime,timestamp);
log.info("-定时任务 saveStudyCourseItemLastTime ---studyItemId = " + studyItemId);
if (studyItemId != null && !studyItemId.equals("null")){
log.info("-定时任务 saveStudyCourseItemLastTime --- boolean studyItemId = " + (studyItemId != null));
// 8. 更新数据库(调用已有服务方法)
studyService.updateStudyCourseItemLastTime(studyItemId, studyVideoTtime, timestamp);
}
List<StudyCourse> allUserList = thirdApi.getStudyCourseList(studyId , courseId, null);
log.info("处理成功 allUserList: {}", allUserList);
// 9. 删除Redis键原子操作
redisTemplate.delete(redisKey);
log.info("处理成功 key: {}, lastStudyTime: {}", redisKey, appendtime);
} catch (Exception e) {
log.error("处理失败 key: {}", redisKey, e);
}
}
}
cursor.close();
} catch (Exception e) {
log.error("定时任务执行异常", e);
}
}
@XxlJob("saveStudyCourseItemLastTime1")
public void saveStudyCourseItemLastTime1() {
// 定义需要处理的键模式集合
processKeys("studyContentId:*:last_active", this::handleLastActiveKey);
processKeys("studyId:*:courseId:*:courseContentId:*", this::handleDurationKey);
}
private void processKeys(String keyPattern, BiConsumer<String, String> keyHandler) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
ScanOptions options = ScanOptions.scanOptions()
.match(keyPattern)
.count(100)
.build();
try (RedisConnection connection = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection()) {
Cursor<byte[]> cursor = connection.scan(options);
while (cursor.hasNext()) {
String redisKey = new String(cursor.next());
Long ttl = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
if (ttl != null && ttl <= 2590200) {
try {
String redisValue = redisTemplate.opsForValue().get(redisKey);
if (redisValue != null) {
// 调用对应的处理方法
keyHandler.accept(redisKey, redisValue);
}
redisTemplate.delete(redisKey);
log.info("Key processed: {}", redisKey);
} catch (Exception e) {
log.error("Process failed [{}]", redisKey, e);
}
}
}
cursor.close();
} catch (Exception e) {
log.error("Key processing error: {}", keyPattern, e);
}
}
// 处理 last_active 类型键
private void handleLastActiveKey(String redisKey, String redisValue) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
String[] parts = redisKey.split(":");
String studyContentId = parts[1];
String[] values = redisValue.split("&");
int lastStudyTime = Integer.parseInt(values[0]);
LocalDateTime timestamp = values.length >= 2 ?
LocalDateTime.parse(values[1], formatter) : null;
studyService.updateStudyCourseItemLastTime(studyContentId, lastStudyTime, timestamp);
}
// 处理 duration 类型键
private void handleDurationKey(String redisKey, String redisValue) {
String[] parts = redisKey.split(":");
String studyId = parts[1];
String courseId = parts[3];
String courseContentId = parts[5];
String[] values = redisValue.split("&");
int duration = Integer.parseInt(values[0]);
LocalDateTime timestamp = values.length >= 2 ?
LocalDateTime.parse(values[1], DateTimeFormatter.ISO_LOCAL_DATE_TIME) : null;
studyService.newAppendStudyDuration(studyId, null, courseContentId, duration, timestamp);
// 保留第三方调用
List<StudyCourse> allUserList = thirdApi.getStudyCourseList(studyId, courseId, null);
log.info("Study records synced: {}", allUserList.size());
}
}

View File

@@ -101,4 +101,8 @@ public interface IStudyService {
List<StudyCourseItem> getList(String courseId, String contentId, String name, Integer status);
void updateStudyCourseItemLastTime(String studyContentId, int lastStudyTime, LocalDateTime timestamp);
void updateStudyDuration(String studyId,String studyItemId, String contentId, Integer videoTime,String courseId);
void newAppendStudyDuration(String studyId, String studyItemId, String courseContentId, int duration, LocalDateTime timestamp);
}

View File

@@ -116,34 +116,74 @@ public class StudyServiceImpl implements IStudyService{
//增加内容的学习时长
if(StringUtils.isNotBlank(studyItemId)) {
//直接根据id更新
// String hql="Update StudyCourseItem set studyDuration=studyDuration+"+duration+",status=(case when status<2 then 2 else status end) where id=?1";
// scItemDao.update(hql,studyItemId);
String sql="Update boe_study_course_item set study_duration=study_duration+"+duration+",status=(case when status<2 then 2 else status end) where id=?1";
scItemDao.sqlUpdate(sql,studyItemId);
//scItemDao.updateMultiFieldById(studyItemId, UpdateBuilder.create("studyDuration", "studyDuration+"+duration,FieldUpdateType.EXPRESSION));
}else {
//根据学习id和课程内容id更新
// scItemDao.update(UpdateBuilder.from(StudyCourseItem.class)
// .addUpdateField("studyDuration", "studyDuration+"+duration,FieldUpdateType.EXPRESSION)
// .addFilter(FieldFilters.eq("studyId", studyId))
// .addFilter(FieldFilters.eq("contentId", courseContentId))
// .builder());
//
// String hql="Update StudyCourseItem set studyDuration=studyDuration+"+duration+",status=(case when status<2 then 2 else status end) where studyId=?1 and contentId=?2";
// scItemDao.update(hql,studyId,courseContentId);
String sql="Update boe_study_course_item set study_duration=study_duration+"+duration+",status=(case when status<2 then 2 else status end) where study_id=?1 and content_id=?2";
scItemDao.sqlUpdate(sql,studyId,courseContentId);
}
//追加课程的学习时长
//scDao.updateMultiFieldById(studyId, UpdateBuilder.create("totalDuration", "totalDuration+"+duration,FieldUpdateType.EXPRESSION));
String sql="Update boe_study_course set total_duration=total_duration+"+duration+",status=(case when status<2 then 2 else status end),progress=(case when progress=0 then 1 else progress end),last_time = '"+LocalDateTime.now()+"' where id=?1";
scDao.sqlUpdate(sql,studyId);
}
@Override
@Transactional
public void newAppendStudyDuration(String studyId,String studyItemId,String courseContentId, int duration,LocalDateTime timestamp) {
//增加内容的学习时长
if(StringUtils.isNotBlank(studyItemId)) {
//直接根据id更新
String sql="Update boe_study_course_item set study_duration=study_duration+"+duration+",status=(case when status<2 then 2 else status end) where id=?1";
scItemDao.sqlUpdate(sql,studyItemId);
}else {
String sql="Update boe_study_course_item set study_duration=study_duration+"+duration+",status=(case when status<2 then 2 else status end) where study_id=?1 and content_id=?2";
scItemDao.sqlUpdate(sql,studyId,courseContentId);
}
String sql="Update boe_study_course set total_duration=total_duration+"+duration+",status=(case when status<2 then 2 else status end),progress=(case when progress=0 then 1 else progress end),last_time = '"+timestamp+"' where id=?1";
scDao.sqlUpdate(sql,studyId);
}
// 更新 前端传输已学习时长
@Override
public void updateStudyDuration(String studyId,String studyItemId,String courseContentId, Integer videoTime,String courseId) {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
String key = "studyId:" + studyId + ":courseId:" + courseId + ":courseContentId:" + courseContentId + ":studyItemId:" + studyItemId;
String currentValue = redisTemplate.opsForValue().get(key);
Integer lastDuration = 0;
Integer oldVideoTime = 0;
Integer sum = 10; // 原appendtime改为固定10秒调用一次接口
if (currentValue != null) {
String[] partValues = currentValue.split("&");
oldVideoTime = Integer.parseInt(partValues[0]);
lastDuration = Integer.parseInt(partValues[1]);
sum += lastDuration;
if(oldVideoTime > videoTime){
videoTime = oldVideoTime;// 取最大值最终入库
}
};
String value = videoTime + "&" + sum + "&" + now.format(formatter); // study_video_time & appendtime & time
log.info("-study-video-time-----value = " + value);
// 20250303 优化 多次更新改一次更新
// 更新Redis中的最后活跃时间带30秒过期
redisTemplate.opsForValue().set(
key,
value,
Duration.ofSeconds(2592000)
);
log.info("- 合并 updateStudyDuration -redis保存---value = " + value);
// Duration.ofDays(30) 也就是 2592000秒
}
@Override
@Transactional
public void appendStudyDuration(StudyTime st) {
@@ -166,7 +206,21 @@ public class StudyServiceImpl implements IStudyService{
@Override
public List<StudyCourseItem> findByStudyId(String studyId) {
return scItemDao.findList(OrderCondition.desc("lastTime"),FieldFilters.eq("studyId", studyId));
List<StudyCourseItem> list = scItemDao.findList(OrderCondition.desc("lastTime"),FieldFilters.eq("studyId", studyId));
for (StudyCourseItem item : list){
log.info("-- studyIndex -查询上次学习的是什么资源。mysql查询---------------- item = " + item);
String redisKey = "studyId:" + studyId + ":courseId:" + item.getCourseId() + ":courseContentId:" + item.getContentId() + ":studyItemId:" + item.getId();
log.info("-- studyIndex -查询上次学习的是什么资源。查询用户的学习情况---------------- redisKey = " + redisKey);
String redisValue = redisTemplate.opsForValue().get(redisKey);
log.info("-- studyIndex -查询上次学习的是什么资源。查询用户的学习情况---------------- redisValue = " + redisValue);
if (redisValue != null) {
String[] values = redisValue.split("&");
int duration = Integer.parseInt(values[0]);
item.setLastStudyTime(duration);
log.info("-- studyIndex -----set 结果---------------- LastStudyTime = " + item.getLastStudyTime());
}
}
return list;
}
@Override
@@ -357,6 +411,7 @@ public class StudyServiceImpl implements IStudyService{
log.info("-study-video-time-mysql保存---studyContentId = " + studyContentId);
}
@Override
public Map<String,Object> getLast(String aid) {
//按lastTime排序第一条,只是课件内容

View File

@@ -0,0 +1,19 @@
package com.xboe.school.vo;
import com.xboe.school.study.entity.StudyTime;
import lombok.Data;
/** appendtime 于 study-video-time 合并
* appendtime 参数 StudyTime
* study-video-time 参数 是 StudyTimeVo
*/
@Data
public class StudyTimeVo extends StudyTime {
private String itemId;
private Integer videoTime;
// private String contentId; // 已继承
// private String courseId; // 已继承
private Float progressVideo;
private Integer type; // 0 study-video-time , 1 appendtime
}

View File

@@ -60,6 +60,8 @@ public class SysUploaderApi extends ApiBaseController{
fileTypeSet.add("pptx");
fileTypeSet.add("pdf");
fileTypeSet.add("zip");
fileTypeSet.add("txt");
fileTypeSet.add("jpeg");
}
@RequestMapping(value = "/file/upload", method = RequestMethod.POST, produces = "application/json")

View File

@@ -73,4 +73,5 @@ coursesuilt:
updateOrSaveCourse: ${boe.domain}/manageApi/admin/teacherRecord/updateOrSaveCourse
syncCourseStudent: ${boe.domain}/manageApi/admin/teacherRecord/syncCourseStudent
syncOnLineScore: ${boe.domain}/manageApi/admin/teacherRecord/syncOnLineScore
updateOnLineStatua: ${boe.domain}/manageApi/admin/teacherRecord/updateOnLineStatua
updateOnLineStatua: ${boe.domain}/manageApi/admin/teacherRecord/updateOnLineStatua
delOnLineById: ${boe.domain}/manageApi/admin/teacherRecord/delOnLineById