pom.xml
@@ -42,6 +42,12 @@ <dependencies> <!-- caffeine --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.0.5</version> </dependency> <!-- rabbitmq --> <dependency> src/main/java/com/ycl/jxkg/config/CaffeineConfig.java
New file @@ -0,0 +1,24 @@ package com.ycl.jxkg.config; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class CaffeineConfig { @Bean public Cache<String, Object> caffeineCache() { return Caffeine.newBuilder() // 设置最后一次写入或访问后经过固定时间过期 .expireAfterWrite(600, TimeUnit.SECONDS) // 初始的缓存空间大小 .initialCapacity(100) // 缓存的最大条数 .maximumSize(1000) .build(); } } src/main/java/com/ycl/jxkg/domain/entity/StudyRecord.java
New file @@ -0,0 +1,24 @@ package com.ycl.jxkg.domain.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.util.Date; @Data @TableName("t_study_record") public class StudyRecord { @TableId(value = "id", type = IdType.AUTO) private Integer id; @TableField("student_id") private Integer studentId; @TableField("study_time") private Long studyTime; //上一次记录时间 @TableField("last_time") private Date lastTime; } src/main/java/com/ycl/jxkg/domain/query/WebSocketQuery.java
New file @@ -0,0 +1,9 @@ package com.ycl.jxkg.domain.query; import lombok.Data; @Data public class WebSocketQuery { private String commend; private Integer id; } src/main/java/com/ycl/jxkg/enums/WebsocketCommendEnum.java
@@ -14,6 +14,7 @@ DELAYED("delayed", "延长考试时间"), FORCE_SUBMIT("forceSubmit", "强制提交"), RECORD_STUDY_TIME("recordStudyTime", "记录学习时间"), ; private final String commend; src/main/java/com/ycl/jxkg/job/StudyRecordJob.java
New file @@ -0,0 +1,67 @@ package com.ycl.jxkg.job; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.github.benmanes.caffeine.cache.Cache; import com.ycl.jxkg.domain.entity.StudyRecord; import com.ycl.jxkg.mapper.StudyRecordMapper; import com.ycl.jxkg.service.StudyRecordService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; /** * @author:xp * @date:2024/7/1 11:06 */ @Component @RequiredArgsConstructor @Slf4j public class StudyRecordJob { @Autowired private Cache<String, Object> caffeineCache; private final StudyRecordMapper studyRecordMapper; private final StudyRecordService studyRecordService; @Scheduled(fixedRate = 1200000) // 20分钟执行一次 private void updateStudyRecord() { log.info("开始存学习时长"); List<StudyRecord> cacheList = new ArrayList<>(); // 取出所有缓存项 ConcurrentMap<String, Object> map = caffeineCache.asMap(); for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); if (key.startsWith("STUDENT_")) { StudyRecord studyRecord = (StudyRecord) entry.getValue(); cacheList.add(studyRecord); } } List<Integer> studentIds = cacheList.stream().map(StudyRecord::getStudentId).collect(Collectors.toList()); if (!CollectionUtils.isEmpty(studentIds)) { //数据库中已经存在的学生数据 QueryWrapper<StudyRecord> wrapper = new QueryWrapper<>(); wrapper.in("student_id", studentIds); List<StudyRecord> studyRecords = studyRecordMapper.selectList(wrapper); for (StudyRecord record : studyRecords) { for (StudyRecord cacheRecord : cacheList) { if (record.getStudentId().equals(cacheRecord.getStudentId())) { cacheRecord.setId(record.getId()); cacheRecord.setStudyTime(record.getStudyTime() + cacheRecord.getStudyTime()); } } } studyRecordService.saveOrUpdateBatch(cacheList); } caffeineCache.invalidateAll(map.keySet()); log.info("结束存学习时长"); } } src/main/java/com/ycl/jxkg/mapper/StudyRecordMapper.java
New file @@ -0,0 +1,28 @@ package com.ycl.jxkg.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ycl.jxkg.domain.entity.StudyRecord; import org.apache.ibatis.annotations.Mapper; import java.util.List; /** * 会议表 Mapper 接口 * * @author flq * @since 2024-06-17 */ @Mapper public interface StudyRecordMapper extends BaseMapper<StudyRecord> { /** * id查找 * @param * @return */ StudyRecord getByStudentId(Integer studentId); void updateBatch(List<StudyRecord> list); } src/main/java/com/ycl/jxkg/server/WebsocketServer.java
@@ -1,8 +1,16 @@ package com.ycl.jxkg.server; import com.alibaba.fastjson.JSONObject; import com.google.gson.JsonObject; import com.ycl.jxkg.domain.entity.Message; import com.ycl.jxkg.domain.query.WebSocketQuery; import com.ycl.jxkg.enums.WebsocketCommendEnum; import com.ycl.jxkg.service.EducationResourceService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpSession; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; @@ -23,6 +31,8 @@ @ServerEndpoint("/websocket/{userId}") public class WebsocketServer { @Autowired private EducationResourceService educationResourceService; /** * 线程安全的无序的集合 */ @@ -56,6 +66,12 @@ @OnMessage public void onMessage(String message) { WebSocketQuery webSocketQuery = JSONObject.parseObject(message, WebSocketQuery.class); String commend = webSocketQuery.getCommend(); Integer userId = webSocketQuery.getId(); if(WebsocketCommendEnum.RECORD_STUDY_TIME.getCommend().equals(commend)){ educationResourceService.recordTime(userId); } log.info("【WebSocket消息】收到客户端消息:" + message); } src/main/java/com/ycl/jxkg/service/EducationResourceService.java
@@ -37,4 +37,6 @@ * @return */ Result typeList(); Result recordTime(Integer userId); } src/main/java/com/ycl/jxkg/service/StudyRecordService.java
New file @@ -0,0 +1,20 @@ package com.ycl.jxkg.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ycl.jxkg.base.Result; import com.ycl.jxkg.domain.entity.Meet; import com.ycl.jxkg.domain.entity.StudyRecord; import com.ycl.jxkg.domain.form.MeetForm; import com.ycl.jxkg.domain.query.MeetQuery; import java.util.List; /** * 学习记录 服务类 * * @author flq * @since 2024-06-17 */ public interface StudyRecordService extends IService<StudyRecord> { } src/main/java/com/ycl/jxkg/service/impl/EducationResourceServiceImpl.java
@@ -1,20 +1,22 @@ package com.ycl.jxkg.service.impl; import com.alibaba.fastjson.JSON; import com.github.benmanes.caffeine.cache.Cache; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ycl.jxkg.base.Result; import com.ycl.jxkg.context.WebContext; import com.ycl.jxkg.domain.entity.EducationResource; import com.ycl.jxkg.domain.entity.Subject; import com.ycl.jxkg.domain.entity.StudyRecord; import com.ycl.jxkg.domain.vo.admin.education.EducationResourceVO; import com.ycl.jxkg.domain.vo.student.education.StudentOnlineVO; import com.ycl.jxkg.mapper.ClassesUserMapper; import com.ycl.jxkg.mapper.EducationResourceMapper; import com.ycl.jxkg.mapper.SubjectMapper; import com.ycl.jxkg.service.EducationResourceService; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -35,7 +37,8 @@ private final SubjectMapper subjectMapper; private final WebContext webContext; private final ClassesUserMapper classesUserMapper; @Autowired private Cache<String, Object> caffeineCache; @Override public Result add(EducationResourceVO form) { EducationResource educationResource = new EducationResource(); @@ -109,4 +112,27 @@ List<Subject> subjects = subjectMapper.allSubject(); return Result.ok(subjects); } //记录视频时长 @Override public Result recordTime(Integer userId) { if (userId ==null){ throw new RuntimeException("用户id为空"); } StudyRecord studyRecord = (StudyRecord) caffeineCache.getIfPresent("STUDENT_"+userId); if (studyRecord != null) { //存在缓存 Date lastTime = studyRecord.getLastTime(); Date now = new Date(); studyRecord.setStudyTime(studyRecord.getStudyTime()+(now.getTime()-lastTime.getTime())/1000); studyRecord.setLastTime(now); }else { //不存在缓存 studyRecord = new StudyRecord(); studyRecord.setStudentId(userId); studyRecord.setStudyTime(0L); studyRecord.setLastTime(new Date()); } caffeineCache.put("STUDENT_" + userId, studyRecord); return Result.ok(); } } src/main/java/com/ycl/jxkg/service/impl/StudyRecordServiceImpl.java
New file @@ -0,0 +1,20 @@ package com.ycl.jxkg.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ycl.jxkg.domain.entity.StudyRecord; import com.ycl.jxkg.mapper.StudyRecordMapper; import com.ycl.jxkg.service.StudyRecordService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; /** * 学习记录 服务实现类 * * @author flq * @since 2024-06-17 */ @Service @RequiredArgsConstructor public class StudyRecordServiceImpl extends ServiceImpl<StudyRecordMapper, StudyRecord> implements StudyRecordService { } src/main/resources/mapper/StudyRecordMapper.xml
New file @@ -0,0 +1,21 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ycl.jxkg.mapper.StudyRecordMapper"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.ycl.jxkg.domain.entity.StudyRecord"> <result column="id" property="id"/> <result column="student_id" property="studentId"/> <result column="study_time" property="studyTime"/> <result column="last_time" property="lastTime"/> </resultMap> <select id="getByStudentId" resultMap="BaseResultMap"> SELECT * from t_study_record WHERE student_id = #{studentId} </select> </mapper>