serv\web-学习历史修改

This commit is contained in:
2025-10-27 13:42:34 +08:00
parent 74880b429e
commit e50de4a277
31 changed files with 3997 additions and 64 deletions

View File

@@ -116,3 +116,78 @@ CREATE TABLE `tb_learning_statistics` (
KEY `idx_date` (`stat_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学习统计表';
-- 学习观看历史表(记录每次学习行为)
DROP TABLE IF EXISTS `tb_learning_history`;
CREATE TABLE `tb_learning_history` (
`id` VARCHAR(50) NOT NULL COMMENT 'ID',
`user_id` VARCHAR(50) NOT NULL COMMENT '用户ID',
`history_id` VARCHAR(50) DEFAULT NULL COMMENT '学习历史ID',
-- 学习对象
`resource_type` INT(4) NOT NULL COMMENT '资源类型1资源/新闻 2课程 3章节 4节点',
`resource_id` VARCHAR(50) DEFAULT NULL COMMENT '资源ID',
`course_id` VARCHAR(50) DEFAULT NULL COMMENT '课程ID',
`chapter_id` VARCHAR(50) DEFAULT NULL COMMENT '章节ID',
`node_id` VARCHAR(50) DEFAULT NULL COMMENT '节点ID',
`task_id` VARCHAR(50) DEFAULT NULL COMMENT '关联任务ID',
-- 学习时间
`start_time` TIMESTAMP NOT NULL COMMENT '开始学习时间',
`end_time` TIMESTAMP NULL DEFAULT NULL COMMENT '结束学习时间',
`duration` INT(11) DEFAULT 0 COMMENT '本次学习时长(秒)',
-- 学习进度
`start_progress` DECIMAL(5,2) DEFAULT 0.00 COMMENT '开始进度0-100',
`end_progress` DECIMAL(5,2) DEFAULT 0.00 COMMENT '结束进度0-100',
-- 设备信息(可选)
`device_type` VARCHAR(20) DEFAULT NULL COMMENT '设备类型web/mobile/app',
`ip_address` VARCHAR(50) DEFAULT NULL COMMENT 'IP地址',
`creator` VARCHAR(50) DEFAULT NULL COMMENT '创建者',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`delete_time` TIMESTAMP NULL DEFAULT NULL COMMENT '删除时间',
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`),
KEY `idx_user_time` (`user_id`, `start_time`),
KEY `idx_user_resource` (`user_id`, `resource_type`, `resource_id`),
KEY `idx_course` (`course_id`),
KEY `idx_task` (`task_id`),
KEY `idx_history` (`history_id`),
KEY `idx_start_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学习观看历史表';
-- 学习统计明细表(按天按资源统计)
DROP TABLE IF EXISTS `tb_learning_statistics_detail`;
CREATE TABLE `tb_learning_statistics_detail` (
`id` VARCHAR(50) NOT NULL COMMENT '统计ID',
`user_id` VARCHAR(50) NOT NULL COMMENT '用户ID',
`stat_date` DATE NOT NULL COMMENT '统计日期',
-- 学习对象
`resource_type` INT(4) NOT NULL COMMENT '资源类型1资源/新闻 2课程 3章节',
`resource_id` VARCHAR(50) DEFAULT NULL COMMENT '资源ID',
`course_id` VARCHAR(50) DEFAULT NULL COMMENT '课程ID',
`chapter_id` VARCHAR(50) DEFAULT NULL COMMENT '章节ID',
-- 统计数据
`total_duration` INT(11) DEFAULT 0 COMMENT '学习时长(秒)',
`learn_count` INT(11) DEFAULT 0 COMMENT '学习次数',
`is_complete` TINYINT(1) DEFAULT 0 COMMENT '是否完成',
`complete_time` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
`creator` VARCHAR(50) DEFAULT NULL COMMENT '创建者',
`updater` VARCHAR(50) DEFAULT NULL COMMENT '更新者',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` TIMESTAMP NULL DEFAULT NULL COMMENT '删除时间',
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_date_resource` (`user_id`, `stat_date`, `resource_type`, `resource_id`, `course_id`, `chapter_id`),
KEY `idx_user_date` (`user_id`, `stat_date`),
KEY `idx_date` (`stat_date`),
KEY `idx_resource` (`resource_type`, `resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学习统计明细表';

View File

@@ -0,0 +1,59 @@
package org.xyzh.api.study.history;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.dto.study.TbLearningHistory;
import java.util.List;
/**
* @description 学习历史API接口供其他模块调用
* @filename LearningHistoryAPI.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
public interface LearningHistoryAPI {
/**
* @description 记录学习历史
* @param learningHistory 学习历史
* @return ResultDomain<TbLearningHistory> 记录结果
* @author yslg
* @since 2025-10-27
*/
ResultDomain<TbLearningHistory> recordLearningHistory(TbLearningHistory learningHistory);
/**
* @description 批量记录学习历史
* @param historyList 学习历史列表
* @return ResultDomain<Boolean> 记录结果
* @author yslg
* @since 2025-10-27
*/
ResultDomain<Boolean> batchRecordLearningHistory(List<TbLearningHistory> historyList);
/**
* @description 简化记录方法 - 观看新闻/资源
* @param userId 用户ID
* @param resourceId 资源ID
* @param duration 学习时长(秒)
* @return ResultDomain<TbLearningHistory> 记录结果
* @author yslg
* @since 2025-10-27
*/
ResultDomain<TbLearningHistory> recordResourceView(String userId, String resourceId, Integer duration);
/**
* @description 简化记录方法 - 学习课程
* @param userId 用户ID
* @param courseId 课程ID
* @param chapterId 章节ID可选
* @param nodeId 节点ID可选
* @param duration 学习时长(秒)
* @return ResultDomain<TbLearningHistory> 记录结果
* @author yslg
* @since 2025-10-27
*/
ResultDomain<TbLearningHistory> recordCourseLearn(String userId, String courseId, String chapterId, String nodeId, Integer duration);
}

View File

@@ -0,0 +1,250 @@
package org.xyzh.common.dto.study;
import org.xyzh.common.dto.BaseDTO;
import java.math.BigDecimal;
import java.util.Date;
/**
* @description 学习观看历史表
* @filename TbLearningHistory.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
public class TbLearningHistory extends BaseDTO {
private static final long serialVersionUID = 1L;
/**
* @description 用户ID
*/
private String userID;
/**
* @description 学习会话ID
*/
private String historyID;
/**
* @description 资源类型1资源/新闻 2课程 3章节 4节点
*/
private Integer resourceType;
/**
* @description 资源ID
*/
private String resourceID;
/**
* @description 课程ID
*/
private String courseID;
/**
* @description 章节ID
*/
private String chapterID;
/**
* @description 节点ID
*/
private String nodeID;
/**
* @description 关联任务ID
*/
private String taskID;
/**
* @description 开始学习时间
*/
private Date startTime;
/**
* @description 结束学习时间
*/
private Date endTime;
/**
* @description 本次学习时长(秒)
*/
private Integer duration;
/**
* @description 开始进度0-100
*/
private BigDecimal startProgress;
/**
* @description 结束进度0-100
*/
private BigDecimal endProgress;
/**
* @description 设备类型web/mobile/app
*/
private String deviceType;
/**
* @description IP地址
*/
private String ipAddress;
/**
* @description 创建者
*/
private String creator;
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getHistoryID() {
return historyID;
}
public void setHistoryID(String historyID) {
this.historyID = historyID;
}
public Integer getResourceType() {
return resourceType;
}
public void setResourceType(Integer resourceType) {
this.resourceType = resourceType;
}
public String getResourceID() {
return resourceID;
}
public void setResourceID(String resourceID) {
this.resourceID = resourceID;
}
public String getCourseID() {
return courseID;
}
public void setCourseID(String courseID) {
this.courseID = courseID;
}
public String getChapterID() {
return chapterID;
}
public void setChapterID(String chapterID) {
this.chapterID = chapterID;
}
public String getNodeID() {
return nodeID;
}
public void setNodeID(String nodeID) {
this.nodeID = nodeID;
}
public String getTaskID() {
return taskID;
}
public void setTaskID(String taskID) {
this.taskID = taskID;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Integer getDuration() {
return duration;
}
public void setDuration(Integer duration) {
this.duration = duration;
}
public BigDecimal getStartProgress() {
return startProgress;
}
public void setStartProgress(BigDecimal startProgress) {
this.startProgress = startProgress;
}
public BigDecimal getEndProgress() {
return endProgress;
}
public void setEndProgress(BigDecimal endProgress) {
this.endProgress = endProgress;
}
public String getDeviceType() {
return deviceType;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
@Override
public String toString() {
return "TbLearningHistory{" +
"id=" + getID() +
", userID='" + userID + '\'' +
", historyID='" + historyID + '\'' +
", resourceType=" + resourceType +
", resourceID='" + resourceID + '\'' +
", courseID='" + courseID + '\'' +
", chapterID='" + chapterID + '\'' +
", nodeID='" + nodeID + '\'' +
", taskID='" + taskID + '\'' +
", startTime=" + startTime +
", endTime=" + endTime +
", duration=" + duration +
", startProgress=" + startProgress +
", endProgress=" + endProgress +
", deviceType='" + deviceType + '\'' +
", ipAddress='" + ipAddress + '\'' +
", creator='" + creator + '\'' +
", createTime=" + getCreateTime() +
'}';
}
}

View File

@@ -0,0 +1,194 @@
package org.xyzh.common.dto.study;
import org.xyzh.common.dto.BaseDTO;
import java.util.Date;
/**
* @description 学习统计明细表
* @filename TbLearningStatisticsDetail.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
public class TbLearningStatisticsDetail extends BaseDTO {
private static final long serialVersionUID = 1L;
/**
* @description 用户ID
*/
private String userID;
/**
* @description 统计日期
*/
private Date statDate;
/**
* @description 资源类型1资源/新闻 2课程 3章节
*/
private Integer resourceType;
/**
* @description 资源ID
*/
private String resourceID;
/**
* @description 课程ID
*/
private String courseID;
/**
* @description 章节ID
*/
private String chapterID;
/**
* @description 学习时长(秒)
*/
private Integer totalDuration;
/**
* @description 学习次数
*/
private Integer learnCount;
/**
* @description 是否完成
*/
private Boolean isComplete;
/**
* @description 完成时间
*/
private Date completeTime;
/**
* @description 创建者
*/
private String creator;
/**
* @description 更新者
*/
private String updater;
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public Date getStatDate() {
return statDate;
}
public void setStatDate(Date statDate) {
this.statDate = statDate;
}
public Integer getResourceType() {
return resourceType;
}
public void setResourceType(Integer resourceType) {
this.resourceType = resourceType;
}
public String getResourceID() {
return resourceID;
}
public void setResourceID(String resourceID) {
this.resourceID = resourceID;
}
public String getCourseID() {
return courseID;
}
public void setCourseID(String courseID) {
this.courseID = courseID;
}
public String getChapterID() {
return chapterID;
}
public void setChapterID(String chapterID) {
this.chapterID = chapterID;
}
public Integer getTotalDuration() {
return totalDuration;
}
public void setTotalDuration(Integer totalDuration) {
this.totalDuration = totalDuration;
}
public Integer getLearnCount() {
return learnCount;
}
public void setLearnCount(Integer learnCount) {
this.learnCount = learnCount;
}
public Boolean getIsComplete() {
return isComplete;
}
public void setIsComplete(Boolean isComplete) {
this.isComplete = isComplete;
}
public Date getCompleteTime() {
return completeTime;
}
public void setCompleteTime(Date completeTime) {
this.completeTime = completeTime;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public String getUpdater() {
return updater;
}
public void setUpdater(String updater) {
this.updater = updater;
}
@Override
public String toString() {
return "TbLearningStatisticsDetail{" +
"id=" + getID() +
", userID='" + userID + '\'' +
", statDate=" + statDate +
", resourceType=" + resourceType +
", resourceID='" + resourceID + '\'' +
", courseID='" + courseID + '\'' +
", chapterID='" + chapterID + '\'' +
", totalDuration=" + totalDuration +
", learnCount=" + learnCount +
", isComplete=" + isComplete +
", completeTime=" + completeTime +
", creator='" + creator + '\'' +
", updater='" + updater + '\'' +
", createTime=" + getCreateTime() +
", updateTime=" + getUpdateTime() +
'}';
}
}

View File

@@ -0,0 +1,347 @@
package org.xyzh.common.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* @description 学习历史响应VO
* @filename LearningHistoryVO.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
public class LearningHistoryVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* @description 历史记录ID
*/
private String id;
/**
* @description 用户ID
*/
private String userID;
/**
* @description 用户名称(关联查询)
*/
private String userName;
/**
* @description 学习会话ID
*/
private String sessionID;
/**
* @description 资源类型1资源/新闻 2课程 3章节 4节点
*/
private Integer resourceType;
/**
* @description 资源类型名称
*/
private String resourceTypeName;
/**
* @description 资源ID
*/
private String resourceID;
/**
* @description 资源标题(关联查询)
*/
private String resourceTitle;
/**
* @description 课程ID
*/
private String courseID;
/**
* @description 课程名称(关联查询)
*/
private String courseName;
/**
* @description 章节ID
*/
private String chapterID;
/**
* @description 章节名称(关联查询)
*/
private String chapterName;
/**
* @description 节点ID
*/
private String nodeID;
/**
* @description 节点名称(关联查询)
*/
private String nodeName;
/**
* @description 关联任务ID
*/
private String taskID;
/**
* @description 任务名称(关联查询)
*/
private String taskName;
/**
* @description 开始学习时间
*/
private Date startTime;
/**
* @description 结束学习时间
*/
private Date endTime;
/**
* @description 本次学习时长(秒)
*/
private Integer duration;
/**
* @description 学习时长格式化后1小时20分钟
*/
private String durationFormatted;
/**
* @description 开始进度0-100
*/
private BigDecimal startProgress;
/**
* @description 结束进度0-100
*/
private BigDecimal endProgress;
/**
* @description 设备类型
*/
private String deviceType;
/**
* @description 创建时间
*/
private Date createTime;
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getSessionID() {
return sessionID;
}
public void setSessionID(String sessionID) {
this.sessionID = sessionID;
}
public Integer getResourceType() {
return resourceType;
}
public void setResourceType(Integer resourceType) {
this.resourceType = resourceType;
}
public String getResourceTypeName() {
return resourceTypeName;
}
public void setResourceTypeName(String resourceTypeName) {
this.resourceTypeName = resourceTypeName;
}
public String getResourceID() {
return resourceID;
}
public void setResourceID(String resourceID) {
this.resourceID = resourceID;
}
public String getResourceTitle() {
return resourceTitle;
}
public void setResourceTitle(String resourceTitle) {
this.resourceTitle = resourceTitle;
}
public String getCourseID() {
return courseID;
}
public void setCourseID(String courseID) {
this.courseID = courseID;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getChapterID() {
return chapterID;
}
public void setChapterID(String chapterID) {
this.chapterID = chapterID;
}
public String getChapterName() {
return chapterName;
}
public void setChapterName(String chapterName) {
this.chapterName = chapterName;
}
public String getNodeID() {
return nodeID;
}
public void setNodeID(String nodeID) {
this.nodeID = nodeID;
}
public String getNodeName() {
return nodeName;
}
public void setNodeName(String nodeName) {
this.nodeName = nodeName;
}
public String getTaskID() {
return taskID;
}
public void setTaskID(String taskID) {
this.taskID = taskID;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Integer getDuration() {
return duration;
}
public void setDuration(Integer duration) {
this.duration = duration;
}
public String getDurationFormatted() {
return durationFormatted;
}
public void setDurationFormatted(String durationFormatted) {
this.durationFormatted = durationFormatted;
}
public BigDecimal getStartProgress() {
return startProgress;
}
public void setStartProgress(BigDecimal startProgress) {
this.startProgress = startProgress;
}
public BigDecimal getEndProgress() {
return endProgress;
}
public void setEndProgress(BigDecimal endProgress) {
this.endProgress = endProgress;
}
public String getDeviceType() {
return deviceType;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "LearningHistoryVO{" +
"id='" + id + '\'' +
", userID='" + userID + '\'' +
", userName='" + userName + '\'' +
", resourceType=" + resourceType +
", resourceTypeName='" + resourceTypeName + '\'' +
", resourceTitle='" + resourceTitle + '\'' +
", duration=" + duration +
", durationFormatted='" + durationFormatted + '\'' +
", startTime=" + startTime +
", endTime=" + endTime +
'}';
}
}

View File

@@ -0,0 +1,213 @@
package org.xyzh.common.vo;
import java.io.Serializable;
import java.util.Date;
/**
* @description 学习统计明细VO
* @filename LearningStatisticsDetailVO.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
public class LearningStatisticsDetailVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* @description 统计日期
*/
private Date statDate;
/**
* @description 资源类型1资源/新闻 2课程 3章节
*/
private Integer resourceType;
/**
* @description 资源类型名称
*/
private String resourceTypeName;
/**
* @description 资源ID
*/
private String resourceID;
/**
* @description 资源标题
*/
private String resourceTitle;
/**
* @description 课程ID
*/
private String courseID;
/**
* @description 课程名称
*/
private String courseName;
/**
* @description 章节ID
*/
private String chapterID;
/**
* @description 章节名称
*/
private String chapterName;
/**
* @description 学习时长(秒)
*/
private Integer totalDuration;
/**
* @description 学习时长(格式化)
*/
private String totalDurationFormatted;
/**
* @description 学习次数
*/
private Integer learnCount;
/**
* @description 是否完成
*/
private Boolean isComplete;
/**
* @description 完成时间
*/
private Date completeTime;
public Date getStatDate() {
return statDate;
}
public void setStatDate(Date statDate) {
this.statDate = statDate;
}
public Integer getResourceType() {
return resourceType;
}
public void setResourceType(Integer resourceType) {
this.resourceType = resourceType;
}
public String getResourceTypeName() {
return resourceTypeName;
}
public void setResourceTypeName(String resourceTypeName) {
this.resourceTypeName = resourceTypeName;
}
public String getResourceID() {
return resourceID;
}
public void setResourceID(String resourceID) {
this.resourceID = resourceID;
}
public String getResourceTitle() {
return resourceTitle;
}
public void setResourceTitle(String resourceTitle) {
this.resourceTitle = resourceTitle;
}
public String getCourseID() {
return courseID;
}
public void setCourseID(String courseID) {
this.courseID = courseID;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getChapterID() {
return chapterID;
}
public void setChapterID(String chapterID) {
this.chapterID = chapterID;
}
public String getChapterName() {
return chapterName;
}
public void setChapterName(String chapterName) {
this.chapterName = chapterName;
}
public Integer getTotalDuration() {
return totalDuration;
}
public void setTotalDuration(Integer totalDuration) {
this.totalDuration = totalDuration;
}
public String getTotalDurationFormatted() {
return totalDurationFormatted;
}
public void setTotalDurationFormatted(String totalDurationFormatted) {
this.totalDurationFormatted = totalDurationFormatted;
}
public Integer getLearnCount() {
return learnCount;
}
public void setLearnCount(Integer learnCount) {
this.learnCount = learnCount;
}
public Boolean getIsComplete() {
return isComplete;
}
public void setIsComplete(Boolean isComplete) {
this.isComplete = isComplete;
}
public Date getCompleteTime() {
return completeTime;
}
public void setCompleteTime(Date completeTime) {
this.completeTime = completeTime;
}
@Override
public String toString() {
return "LearningStatisticsDetailVO{" +
"statDate=" + statDate +
", resourceType=" + resourceType +
", resourceTypeName='" + resourceTypeName + '\'' +
", resourceTitle='" + resourceTitle + '\'' +
", totalDuration=" + totalDuration +
", totalDurationFormatted='" + totalDurationFormatted + '\'' +
", learnCount=" + learnCount +
", isComplete=" + isComplete +
'}';
}
}

View File

@@ -0,0 +1,176 @@
package org.xyzh.common.vo;
import java.io.Serializable;
import java.util.List;
/**
* @description 学习统计响应VO
* @filename LearningStatisticsVO.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
public class LearningStatisticsVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* @description 用户ID
*/
private String userID;
/**
* @description 统计周期day/week/month
*/
private String period;
/**
* @description 总学习时长(秒)
*/
private Integer totalDuration;
/**
* @description 总学习时长(格式化)
*/
private String totalDurationFormatted;
/**
* @description 学习天数
*/
private Integer learnDays;
/**
* @description 学习资源数量
*/
private Integer resourceCount;
/**
* @description 学习课程数量
*/
private Integer courseCount;
/**
* @description 学习次数
*/
private Integer learnCount;
/**
* @description 完成数量
*/
private Integer completeCount;
/**
* @description 平均每天学习时长(秒)
*/
private Integer avgDailyDuration;
/**
* @description 学习明细列表
*/
private List<LearningStatisticsDetailVO> details;
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getPeriod() {
return period;
}
public void setPeriod(String period) {
this.period = period;
}
public Integer getTotalDuration() {
return totalDuration;
}
public void setTotalDuration(Integer totalDuration) {
this.totalDuration = totalDuration;
}
public String getTotalDurationFormatted() {
return totalDurationFormatted;
}
public void setTotalDurationFormatted(String totalDurationFormatted) {
this.totalDurationFormatted = totalDurationFormatted;
}
public Integer getLearnDays() {
return learnDays;
}
public void setLearnDays(Integer learnDays) {
this.learnDays = learnDays;
}
public Integer getResourceCount() {
return resourceCount;
}
public void setResourceCount(Integer resourceCount) {
this.resourceCount = resourceCount;
}
public Integer getCourseCount() {
return courseCount;
}
public void setCourseCount(Integer courseCount) {
this.courseCount = courseCount;
}
public Integer getLearnCount() {
return learnCount;
}
public void setLearnCount(Integer learnCount) {
this.learnCount = learnCount;
}
public Integer getCompleteCount() {
return completeCount;
}
public void setCompleteCount(Integer completeCount) {
this.completeCount = completeCount;
}
public Integer getAvgDailyDuration() {
return avgDailyDuration;
}
public void setAvgDailyDuration(Integer avgDailyDuration) {
this.avgDailyDuration = avgDailyDuration;
}
public List<LearningStatisticsDetailVO> getDetails() {
return details;
}
public void setDetails(List<LearningStatisticsDetailVO> details) {
this.details = details;
}
@Override
public String toString() {
return "LearningStatisticsVO{" +
"userID='" + userID + '\'' +
", period='" + period + '\'' +
", totalDuration=" + totalDuration +
", totalDurationFormatted='" + totalDurationFormatted + '\'' +
", learnDays=" + learnDays +
", resourceCount=" + resourceCount +
", courseCount=" + courseCount +
", learnCount=" + learnCount +
", completeCount=" + completeCount +
", avgDailyDuration=" + avgDailyDuration +
'}';
}
}

View File

@@ -73,7 +73,7 @@ public class CrontabController {
* @since 2025-10-25
*/
@GetMapping("/task/{taskId}")
public ResultDomain<TbCrontabTask> getTaskById(@PathVariable String taskId) {
public ResultDomain<TbCrontabTask> getTaskById(@PathVariable(value = "taskId") String taskId) {
return crontabService.getTaskById(taskId);
}
@@ -111,7 +111,7 @@ public class CrontabController {
* @since 2025-10-25
*/
@PostMapping("/task/start/{taskId}")
public ResultDomain<TbCrontabTask> startTask(@PathVariable String taskId) {
public ResultDomain<TbCrontabTask> startTask(@PathVariable(value = "taskId") String taskId) {
return crontabService.startTask(taskId);
}
@@ -123,7 +123,7 @@ public class CrontabController {
* @since 2025-10-25
*/
@PostMapping("/task/pause/{taskId}")
public ResultDomain<TbCrontabTask> pauseTask(@PathVariable String taskId) {
public ResultDomain<TbCrontabTask> pauseTask(@PathVariable(value = "taskId") String taskId) {
return crontabService.pauseTask(taskId);
}
@@ -135,7 +135,7 @@ public class CrontabController {
* @since 2025-10-25
*/
@PostMapping("/task/execute/{taskId}")
public ResultDomain<TbCrontabTask> executeTaskOnce(@PathVariable String taskId) {
public ResultDomain<TbCrontabTask> executeTaskOnce(@PathVariable(value = "taskId") String taskId) {
return crontabService.executeTaskOnce(taskId);
}
@@ -161,7 +161,7 @@ public class CrontabController {
* @since 2025-10-25
*/
@GetMapping("/log/task/{taskId}")
public ResultDomain<TbCrontabLog> getLogsByTaskId(@PathVariable String taskId) {
public ResultDomain<TbCrontabLog> getLogsByTaskId(@PathVariable(value = "taskId") String taskId) {
return crontabService.getLogsByTaskId(taskId);
}
@@ -199,7 +199,7 @@ public class CrontabController {
* @since 2025-10-25
*/
@GetMapping("/log/{logId}")
public ResultDomain<TbCrontabLog> getLogById(@PathVariable String logId) {
public ResultDomain<TbCrontabLog> getLogById(@PathVariable(value = "logId") String logId) {
return crontabService.getLogById(logId);
}
@@ -211,7 +211,7 @@ public class CrontabController {
* @since 2025-10-25
*/
@DeleteMapping("/log/clean/{days}")
public ResultDomain<Integer> cleanLogs(@PathVariable Integer days) {
public ResultDomain<Integer> cleanLogs(@PathVariable(value = "days") Integer days) {
return crontabService.cleanLogs(days);
}

View File

@@ -100,5 +100,14 @@ public interface CrontabTaskMapper extends BaseMapper<TbCrontabTask> {
* @since 2025-10-25
*/
TbCrontabTask selectTaskByBeanAndMethod(@Param("beanName") String beanName, @Param("methodName") String methodName);
/**
* @description 查询任务总数
* @param filter 过滤条件
* @return int 任务总数
* @author yslg
* @since 2025-10-25
*/
int countSelectTask(@Param("filter") TbCrontabTask filter);
}

View File

@@ -8,6 +8,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xyzh.api.crontab.CrontabService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.crontab.TbCrontabTask;
import org.xyzh.common.dto.crontab.TbCrontabLog;
@@ -215,7 +216,14 @@ public class CrontabServiceImpl implements CrontabService {
}
List<TbCrontabTask> list = taskMapper.selectTaskPage(filter, pageParam);
resultDomain.success("查询成功", list);
int total = taskMapper.countSelectTask(filter);
PageDomain<TbCrontabTask> pageDomain = new PageDomain<>();
pageDomain.setDataList(list);
pageParam.setTotalElements(total);
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
pageDomain.setPageParam(pageParam);
resultDomain.success("查询成功", pageDomain);
} catch (Exception e) {
logger.error("分页查询定时任务异常: ", e);
resultDomain.fail("分页查询定时任务异常: " + e.getMessage());

View File

@@ -33,6 +33,33 @@
<!-- 查询条件 -->
<sql id="Base_Where_Clause">
<where>
deleted = 0
<if test="taskId != null and taskId != ''">
AND task_id = #{taskId}
</if>
<if test="taskName != null and taskName != ''">
AND task_name LIKE CONCAT('%', #{taskName}, '%')
</if>
<if test="taskGroup != null and taskGroup != ''">
AND task_group = #{taskGroup}
</if>
<if test="beanName != null and beanName != ''">
AND bean_name = #{beanName}
</if>
<if test="methodName != null and methodName != ''">
AND method_name = #{methodName}
</if>
<if test="status != null">
AND status = #{status}
</if>
<if test="deleted != null">
AND deleted = #{deleted}
</if>
</where>
</sql>
<sql id="Filter_Where_Clause">
<where>
<if test="filter != null">
<if test="filter.ID != null and filter.ID != ''">
@@ -144,7 +171,7 @@
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_task
<include refid="Base_Where_Clause" />
<include refid="Filter_Where_Clause" />
ORDER BY create_time DESC
</select>
@@ -153,7 +180,7 @@
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_task
<include refid="Base_Where_Clause" />
<include refid="Filter_Where_Clause" />
ORDER BY create_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
@@ -185,4 +212,11 @@
AND deleted = 0
LIMIT 1
</select>
<!-- countSelectTask -->
<select id="countSelectTask">
SELECT COUNT(1) FROM tb_crontab_task
<include refid="Filter_Where_Clause" />
</select>
</mapper>

View File

@@ -0,0 +1,218 @@
package org.xyzh.study.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.study.TbLearningHistory;
import org.xyzh.common.vo.LearningHistoryVO;
import org.xyzh.common.vo.LearningStatisticsVO;
import org.xyzh.study.service.SCLearningHistoryService;
import java.util.Date;
import java.util.List;
/**
* @description 学习历史控制器
* @filename LearningHistoryController.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
@RestController
@RequestMapping("/study/history")
public class LearningHistoryController {
private static final Logger logger = LoggerFactory.getLogger(LearningHistoryController.class);
@Autowired
private SCLearningHistoryService learningHistoryService;
/**
* 记录学习历史
* POST /study/history/record
*
* @param learningHistory 学习历史
* @return ResultDomain<TbLearningHistory> 记录结果
*/
@PostMapping("/record")
public ResultDomain<TbLearningHistory> recordLearningHistory(@RequestBody TbLearningHistory learningHistory) {
logger.info("记录学习历史,资源类型: {}, 资源ID: {}", learningHistory.getResourceType(), learningHistory.getResourceID());
return learningHistoryService.recordLearningHistory(learningHistory);
}
/**
* 批量记录学习历史
* POST /study/history/batch-record
*
* @param historyList 学习历史列表
* @return ResultDomain<Boolean> 记录结果
*/
@PostMapping("/batch-record")
public ResultDomain<Boolean> batchRecordLearningHistory(@RequestBody List<TbLearningHistory> historyList) {
logger.info("批量记录学习历史,数量: {}", historyList != null ? historyList.size() : 0);
return learningHistoryService.batchRecordLearningHistory(historyList);
}
/**
* 查询学习历史列表
* POST /study/history/list
*
* @param filter 过滤条件
* @return ResultDomain<List<LearningHistoryVO>> 学习历史列表
*/
@PostMapping("/list")
public ResultDomain<List<LearningHistoryVO>> getLearningHistories(@RequestBody TbLearningHistory filter) {
logger.info("查询学习历史列表用户ID: {}", filter != null ? filter.getUserID() : null);
return learningHistoryService.getLearningHistories(filter);
}
/**
* 分页查询学习历史
* POST /study/history/page
*
* @param pageRequest 分页查询请求
* @return ResultDomain<PageDomain<LearningHistoryVO>> 分页结果
*/
@PostMapping("/page")
public ResultDomain<PageDomain<LearningHistoryVO>> getLearningHistoriesPage(@RequestBody PageRequest<TbLearningHistory> pageRequest) {
logger.info("分页查询学习历史,页码: {}, 每页数量: {}",
pageRequest.getPageParam() != null ? pageRequest.getPageParam().getPageNumber() : null,
pageRequest.getPageParam() != null ? pageRequest.getPageParam().getPageSize() : null);
return learningHistoryService.getLearningHistoriesPage(pageRequest);
}
/**
* 根据ID查询学习历史
* GET /study/history/{id}
*
* @param id 历史记录ID
* @return ResultDomain<TbLearningHistory> 学习历史
*/
@GetMapping("/{id}")
public ResultDomain<TbLearningHistory> getLearningHistoryById(@PathVariable String id) {
logger.info("查询学习历史ID: {}", id);
return learningHistoryService.getLearningHistoryById(id);
}
/**
* 获取当前用户的学习历史
* POST /study/history/my-histories
*
* @param filter 过滤条件
* @return ResultDomain<List<LearningHistoryVO>> 学习历史列表
*/
@PostMapping("/my-histories")
public ResultDomain<List<LearningHistoryVO>> getCurrentUserLearningHistories(@RequestBody(required = false) TbLearningHistory filter) {
logger.info("查询当前用户学习历史");
return learningHistoryService.getCurrentUserLearningHistories(filter);
}
/**
* 获取当前用户最近的学习历史
* GET /study/history/recent
*
* @param limit 限制数量可选默认10
* @return ResultDomain<List<LearningHistoryVO>> 学习历史列表
*/
@GetMapping("/recent")
public ResultDomain<List<LearningHistoryVO>> getRecentLearningHistories(@RequestParam(required = false, defaultValue = "10") Integer limit) {
logger.info("查询最近学习历史,限制数量: {}", limit);
// 从当前登录用户获取
return learningHistoryService.getRecentLearningHistories(null, limit);
}
/**
* 获取用户学习统计(按时间范围)
* GET /study/history/statistics
*
* @param userId 用户ID
* @param startTime 开始时间(时间戳,毫秒)
* @param endTime 结束时间(时间戳,毫秒)
* @return ResultDomain<LearningStatisticsVO> 学习统计
*/
@GetMapping("/statistics")
public ResultDomain<LearningStatisticsVO> getUserLearningStatistics(
@RequestParam String userId,
@RequestParam Long startTime,
@RequestParam Long endTime) {
logger.info("查询学习统计用户ID: {}, 开始时间: {}, 结束时间: {}", userId, startTime, endTime);
return learningHistoryService.getUserLearningStatistics(
userId,
new Date(startTime),
new Date(endTime)
);
}
/**
* 获取用户学习统计(按周期)
* GET /study/history/statistics/{userId}/{periodType}
*
* @param userId 用户ID
* @param periodType 周期类型day/week/month
* @return ResultDomain<LearningStatisticsVO> 学习统计
*/
@GetMapping("/statistics/{userId}/{periodType}")
public ResultDomain<LearningStatisticsVO> getUserLearningStatisticsByPeriod(
@PathVariable String userId,
@PathVariable String periodType) {
logger.info("查询学习统计用户ID: {}, 周期类型: {}", userId, periodType);
return learningHistoryService.getUserLearningStatisticsByPeriod(userId, periodType);
}
/**
* 获取当前用户的学习统计
* GET /study/history/my-statistics/{periodType}
*
* @param periodType 周期类型day/week/month
* @return ResultDomain<LearningStatisticsVO> 学习统计
*/
@GetMapping("/my-statistics/{periodType}")
public ResultDomain<LearningStatisticsVO> getCurrentUserLearningStatistics(@PathVariable String periodType) {
logger.info("查询当前用户学习统计,周期类型: {}", periodType);
return learningHistoryService.getCurrentUserLearningStatistics(periodType);
}
/**
* 删除学习历史
* DELETE /study/history/{id}
*
* @param id 历史记录ID
* @return ResultDomain<Boolean> 删除结果
*/
@DeleteMapping("/{id}")
public ResultDomain<Boolean> deleteLearningHistory(@PathVariable String id) {
logger.info("删除学习历史ID: {}", id);
return learningHistoryService.deleteLearningHistory(id);
}
/**
* 批量删除学习历史
* DELETE /study/history/batch
*
* @param ids 历史记录ID列表
* @return ResultDomain<Boolean> 删除结果
*/
@DeleteMapping("/batch")
public ResultDomain<Boolean> batchDeleteLearningHistories(@RequestBody List<String> ids) {
logger.info("批量删除学习历史,数量: {}", ids != null ? ids.size() : 0);
return learningHistoryService.batchDeleteLearningHistories(ids);
}
/**
* 健康检查
* GET /study/history/health
*
* @return ResultDomain<String> 健康状态
*/
@GetMapping("/health")
public ResultDomain<String> health() {
ResultDomain<String> resultDomain = new ResultDomain<>();
resultDomain.success("学习历史服务运行正常", "OK");
return resultDomain;
}
}

View File

@@ -0,0 +1,214 @@
package org.xyzh.study.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.study.TbLearningHistory;
import org.xyzh.common.vo.LearningHistoryVO;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @description 学习观看历史数据访问层
* @filename LearningHistoryMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
@Mapper
public interface LearningHistoryMapper extends BaseMapper<TbLearningHistory> {
/**
* @description 插入学习历史记录
* @param learningHistory 学习历史
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int insertLearningHistory(TbLearningHistory learningHistory);
/**
* @description 更新学习历史记录
* @param learningHistory 学习历史
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int updateLearningHistory(TbLearningHistory learningHistory);
/**
* @description 批量插入学习历史记录
* @param historyList 学习历史列表
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int batchInsertLearningHistories(@Param("historyList") List<TbLearningHistory> historyList);
/**
* @description 根据ID查询学习历史
* @param id 历史记录ID
* @return TbLearningHistory 学习历史
* @author yslg
* @since 2025-10-27
*/
TbLearningHistory selectById(@Param("id") String id);
/**
* @description 根据用户ID查询学习历史列表
* @param userId 用户ID
* @return List<TbLearningHistory> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningHistory> selectByUserId(@Param("userId") String userId);
/**
* @description 根据条件查询学习历史列表
* @param filter 过滤条件
* @return List<TbLearningHistory> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningHistory> selectLearningHistories(TbLearningHistory filter);
/**
* @description 根据条件查询学习历史VO列表带关联信息
* @param filter 过滤条件
* @return List<LearningHistoryVO> 学习历史VO列表
* @author yslg
* @since 2025-10-27
*/
List<LearningHistoryVO> selectLearningHistoriesWithDetails(TbLearningHistory filter);
/**
* @description 分页查询学习历史
* @param filter 过滤条件
* @param pageParam 分页参数
* @return List<TbLearningHistory> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningHistory> selectLearningHistoriesPage(@Param("filter") TbLearningHistory filter, @Param("pageParam") PageParam pageParam);
/**
* @description 统计学习历史总数
* @param filter 过滤条件
* @return long 总数
* @author yslg
* @since 2025-10-27
*/
long countLearningHistories(@Param("filter") TbLearningHistory filter);
/**
* @description 根据时间段查询学习历史
* @param userId 用户ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return List<TbLearningHistory> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningHistory> selectByUserIdAndTimeRange(@Param("userId") String userId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
/**
* @description 根据时间段和资源类型查询学习历史
* @param userId 用户ID
* @param resourceType 资源类型
* @param startTime 开始时间
* @param endTime 结束时间
* @return List<TbLearningHistory> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningHistory> selectByUserIdAndResourceTypeAndTimeRange(
@Param("userId") String userId,
@Param("resourceType") Integer resourceType,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
/**
* @description 统计用户某时间段的学习时长
* @param userId 用户ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return Map<String, Object> 统计结果totalDuration, learnCount等
* @author yslg
* @since 2025-10-27
*/
Map<String, Object> selectLearningStatisticsByTimeRange(
@Param("userId") String userId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
/**
* @description 统计用户某时间段各资源类型的学习时长
* @param userId 用户ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return List<Map<String, Object>> 统计结果列表
* @author yslg
* @since 2025-10-27
*/
List<Map<String, Object>> selectLearningStatisticsByResourceType(
@Param("userId") String userId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
/**
* @description 获取用户学习的资源列表(按时间段)
* @param userId 用户ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return List<Map<String, Object>> 资源列表
* @author yslg
* @since 2025-10-27
*/
List<Map<String, Object>> selectLearnedResourcesByTimeRange(
@Param("userId") String userId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
/**
* @description 删除学习历史(软删除)
* @param id 历史记录ID
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int deleteLearningHistory(@Param("id") String id);
/**
* @description 批量删除学习历史(软删除)
* @param ids 历史记录ID列表
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int batchDeleteLearningHistories(@Param("ids") List<String> ids);
/**
* @description 根据会话ID查询学习历史
* @param sessionId 会话ID
* @return List<TbLearningHistory> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningHistory> selectBySessionId(@Param("sessionId") String sessionId);
/**
* @description 获取用户最近的学习历史
* @param userId 用户ID
* @param limit 限制数量
* @return List<TbLearningHistory> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningHistory> selectRecentByUserId(@Param("userId") String userId, @Param("limit") Integer limit);
}

View File

@@ -0,0 +1,145 @@
package org.xyzh.study.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.common.dto.study.TbLearningStatisticsDetail;
import org.xyzh.common.vo.LearningStatisticsDetailVO;
import java.util.Date;
import java.util.List;
/**
* @description 学习统计明细数据访问层
* @filename LearningStatisticsDetailMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
@Mapper
public interface LearningStatisticsDetailMapper extends BaseMapper<TbLearningStatisticsDetail> {
/**
* @description 插入统计明细
* @param detail 统计明细
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int insertStatisticsDetail(TbLearningStatisticsDetail detail);
/**
* @description 更新统计明细
* @param detail 统计明细
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int updateStatisticsDetail(TbLearningStatisticsDetail detail);
/**
* @description 插入或更新统计明细ON DUPLICATE KEY UPDATE
* @param detail 统计明细
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int insertOrUpdateStatisticsDetail(TbLearningStatisticsDetail detail);
/**
* @description 根据条件查询统计明细
* @param filter 过滤条件
* @return List<TbLearningStatisticsDetail> 统计明细列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningStatisticsDetail> selectStatisticsDetails(TbLearningStatisticsDetail filter);
/**
* @description 根据用户ID和日期查询统计明细
* @param userId 用户ID
* @param statDate 统计日期
* @return List<TbLearningStatisticsDetail> 统计明细列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningStatisticsDetail> selectByUserIdAndDate(
@Param("userId") String userId,
@Param("statDate") Date statDate);
/**
* @description 根据用户ID和日期范围查询统计明细
* @param userId 用户ID
* @param startDate 开始日期
* @param endDate 结束日期
* @return List<TbLearningStatisticsDetail> 统计明细列表
* @author yslg
* @since 2025-10-27
*/
List<TbLearningStatisticsDetail> selectByUserIdAndDateRange(
@Param("userId") String userId,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
/**
* @description 根据用户ID和日期范围查询统计明细VO带关联信息
* @param userId 用户ID
* @param startDate 开始日期
* @param endDate 结束日期
* @return List<LearningStatisticsDetailVO> 统计明细VO列表
* @author yslg
* @since 2025-10-27
*/
List<LearningStatisticsDetailVO> selectStatisticsDetailsWithInfo(
@Param("userId") String userId,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
/**
* @description 根据用户ID、资源类型和日期查询统计明细
* @param userId 用户ID
* @param resourceType 资源类型
* @param statDate 统计日期
* @return TbLearningStatisticsDetail 统计明细
* @author yslg
* @since 2025-10-27
*/
TbLearningStatisticsDetail selectByUserIdAndResourceTypeAndDate(
@Param("userId") String userId,
@Param("resourceType") Integer resourceType,
@Param("resourceId") String resourceId,
@Param("statDate") Date statDate);
/**
* @description 删除统计明细(软删除)
* @param id 明细ID
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int deleteStatisticsDetail(@Param("id") String id);
/**
* @description 批量删除统计明细(软删除)
* @param ids 明细ID列表
* @return int 影响行数
* @author yslg
* @since 2025-10-27
*/
int batchDeleteStatisticsDetails(@Param("ids") List<String> ids);
/**
* @description 按日期汇总用户学习统计
* @param userId 用户ID
* @param startDate 开始日期
* @param endDate 结束日期
* @return List<TbLearningStatisticsDetail> 汇总结果
* @author yslg
* @since 2025-10-27
*/
List<TbLearningStatisticsDetail> selectDailySummary(
@Param("userId") String userId,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
}

View File

@@ -0,0 +1,117 @@
package org.xyzh.study.service;
import org.xyzh.api.study.history.LearningHistoryAPI;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.study.TbLearningHistory;
import org.xyzh.common.vo.LearningHistoryVO;
import org.xyzh.common.vo.LearningStatisticsVO;
import java.util.Date;
import java.util.List;
/**
* @description 学习历史服务接口
* @filename SCLearningHistoryService.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
public interface SCLearningHistoryService extends LearningHistoryAPI {
/**
* @description 查询学习历史列表
* @param filter 过滤条件
* @return ResultDomain<List<LearningHistoryVO>> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
ResultDomain<List<LearningHistoryVO>> getLearningHistories(TbLearningHistory filter);
/**
* @description 分页查询学习历史
* @param pageRequest 分页查询请求
* @return ResultDomain<PageDomain<LearningHistoryVO>> 分页结果
* @author yslg
* @since 2025-10-27
*/
ResultDomain<PageDomain<LearningHistoryVO>> getLearningHistoriesPage(PageRequest<TbLearningHistory> pageRequest);
/**
* @description 根据ID查询学习历史
* @param id 历史记录ID
* @return ResultDomain<TbLearningHistory> 学习历史
* @author yslg
* @since 2025-10-27
*/
ResultDomain<TbLearningHistory> getLearningHistoryById(String id);
/**
* @description 获取用户某时间段的学习统计
* @param userId 用户ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return ResultDomain<LearningStatisticsVO> 学习统计
* @author yslg
* @since 2025-10-27
*/
ResultDomain<LearningStatisticsVO> getUserLearningStatistics(String userId, Date startTime, Date endTime);
/**
* @description 获取用户某时间段的学习统计(按周期)
* @param userId 用户ID
* @param periodType 周期类型day/week/month
* @return ResultDomain<LearningStatisticsVO> 学习统计
* @author yslg
* @since 2025-10-27
*/
ResultDomain<LearningStatisticsVO> getUserLearningStatisticsByPeriod(String userId, String periodType);
/**
* @description 获取当前用户的学习历史
* @param filter 过滤条件
* @return ResultDomain<List<LearningHistoryVO>> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
ResultDomain<List<LearningHistoryVO>> getCurrentUserLearningHistories(TbLearningHistory filter);
/**
* @description 获取当前用户的学习统计
* @param periodType 周期类型day/week/month
* @return ResultDomain<LearningStatisticsVO> 学习统计
* @author yslg
* @since 2025-10-27
*/
ResultDomain<LearningStatisticsVO> getCurrentUserLearningStatistics(String periodType);
/**
* @description 删除学习历史
* @param id 历史记录ID
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-10-27
*/
ResultDomain<Boolean> deleteLearningHistory(String id);
/**
* @description 批量删除学习历史
* @param ids 历史记录ID列表
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-10-27
*/
ResultDomain<Boolean> batchDeleteLearningHistories(List<String> ids);
/**
* @description 获取用户最近的学习历史
* @param userId 用户ID
* @param limit 限制数量
* @return ResultDomain<List<LearningHistoryVO>> 学习历史列表
* @author yslg
* @since 2025-10-27
*/
ResultDomain<List<LearningHistoryVO>> getRecentLearningHistories(String userId, Integer limit);
}

View File

@@ -0,0 +1,681 @@
package org.xyzh.study.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.enums.AchievementEventType;
import org.xyzh.common.core.event.AchievementEvent;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.study.TbLearningHistory;
import org.xyzh.common.dto.user.TbSysUser;
import org.xyzh.common.utils.IDUtils;
import org.xyzh.common.vo.LearningHistoryVO;
import org.xyzh.common.vo.LearningStatisticsDetailVO;
import org.xyzh.common.vo.LearningStatisticsVO;
import org.xyzh.study.mapper.LearningHistoryMapper;
import org.xyzh.study.service.SCLearningHistoryService;
import org.xyzh.system.utils.LoginUtil;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
* @description 学习历史服务实现类
* @filename SCLearningHistoryServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-10-27
*/
@Service
public class SCLearningHistoryServiceImpl implements SCLearningHistoryService {
private static final Logger logger = LoggerFactory.getLogger(SCLearningHistoryServiceImpl.class);
@Autowired
private LearningHistoryMapper learningHistoryMapper;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Override
@Transactional
public ResultDomain<TbLearningHistory> recordLearningHistory(TbLearningHistory learningHistory) {
ResultDomain<TbLearningHistory> resultDomain = new ResultDomain<>();
try {
// 参数校验
if (learningHistory.getUserID() == null || learningHistory.getUserID().isEmpty()) {
TbSysUser currentUser = LoginUtil.getCurrentUser();
if (currentUser == null) {
resultDomain.fail("用户未登录");
return resultDomain;
}
learningHistory.setUserID(currentUser.getID());
}
if (learningHistory.getResourceType() == null) {
resultDomain.fail("资源类型不能为空");
return resultDomain;
}
if (learningHistory.getStartTime() == null) {
learningHistory.setStartTime(new Date());
}
// 计算时长
if (learningHistory.getDuration() == null || learningHistory.getDuration() <= 0) {
if (learningHistory.getEndTime() != null) {
long duration = (learningHistory.getEndTime().getTime() - learningHistory.getStartTime().getTime()) / 1000;
learningHistory.setDuration((int) duration);
} else {
learningHistory.setDuration(0);
}
}
// 判断是插入还是更新
boolean isUpdate = false;
if (learningHistory.getHistoryID() != null && !learningHistory.getHistoryID().isEmpty()) {
isUpdate = true;
}
int result;
if (isUpdate) {
// 更新已有记录
result = learningHistoryMapper.updateLearningHistory(learningHistory);
if (result > 0) {
resultDomain.success("更新学习历史成功", learningHistory);
logger.info("更新学习历史成功historyID: {}, 用户ID: {}, 资源类型: {}, 时长: {}秒",
learningHistory.getHistoryID(), learningHistory.getUserID(),
learningHistory.getResourceType(), learningHistory.getDuration());
// 发布成就事件 - 学习时长更新
publishLearningTimeEvent(learningHistory);
} else {
resultDomain.fail("更新学习历史失败");
}
} else {
// 插入新记录
learningHistory.setID(IDUtils.generateID());
learningHistory.setHistoryID(IDUtils.generateID());
learningHistory.setCreateTime(new Date());
result = learningHistoryMapper.insertLearningHistory(learningHistory);
if (result > 0) {
resultDomain.success("记录学习历史成功", learningHistory);
logger.info("记录学习历史成功ID: {}, 用户ID: {}, 资源类型: {}, 时长: {}秒",
learningHistory.getID(), learningHistory.getUserID(),
learningHistory.getResourceType(), learningHistory.getDuration());
} else {
resultDomain.fail("记录学习历史失败");
}
}
} catch (Exception e) {
logger.error("记录学习历史异常", e);
resultDomain.fail("记录学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional
public ResultDomain<Boolean> batchRecordLearningHistory(List<TbLearningHistory> historyList) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
try {
if (historyList == null || historyList.isEmpty()) {
resultDomain.fail("学习历史列表不能为空");
return resultDomain;
}
TbSysUser currentUser = LoginUtil.getCurrentUser();
Date now = new Date();
for (TbLearningHistory learningHistory : historyList) {
if (learningHistory.getUserID() == null || learningHistory.getUserID().isEmpty()) {
if (currentUser == null) {
resultDomain.fail("用户未登录");
return resultDomain;
}
learningHistory.setUserID(currentUser.getID());
}
if (learningHistory.getStartTime() == null) {
learningHistory.setStartTime(now);
}
if (learningHistory.getID() == null || learningHistory.getID().isEmpty()) {
learningHistory.setID(IDUtils.generateID());
}
if (learningHistory.getCreateTime() == null) {
learningHistory.setCreateTime(now);
}
}
int result = learningHistoryMapper.batchInsertLearningHistories(historyList);
if (result > 0) {
resultDomain.success("批量记录学习历史成功", true);
logger.info("批量记录学习历史成功,数量: {}", result);
} else {
resultDomain.fail("批量记录学习历史失败");
}
} catch (Exception e) {
logger.error("批量记录学习历史异常", e);
resultDomain.fail("批量记录学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<List<LearningHistoryVO>> getLearningHistories(TbLearningHistory filter) {
ResultDomain<List<LearningHistoryVO>> resultDomain = new ResultDomain<>();
try {
List<LearningHistoryVO> historyVOList = learningHistoryMapper.selectLearningHistoriesWithDetails(filter);
// 格式化时长
formatDuration(historyVOList);
resultDomain.success("查询学习历史成功", historyVOList);
} catch (Exception e) {
logger.error("查询学习历史异常", e);
resultDomain.fail("查询学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<PageDomain<LearningHistoryVO>> getLearningHistoriesPage(PageRequest<TbLearningHistory> pageRequest) {
ResultDomain<PageDomain<LearningHistoryVO>> resultDomain = new ResultDomain<>();
try {
TbLearningHistory filter = pageRequest.getFilter();
if (filter == null) {
filter = new TbLearningHistory();
}
// 分页参数
PageParam pageParam = pageRequest.getPageParam();
pageParam.setOffset((pageParam.getPageNumber() - 1) * pageParam.getPageSize());
// 查询总数
long total = learningHistoryMapper.countLearningHistories(filter);
// 查询列表
List<TbLearningHistory> historyList = learningHistoryMapper.selectLearningHistoriesPage(filter, pageParam);
// 转换为VO
List<LearningHistoryVO> historyVOList = historyList.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
// 格式化时长
formatDuration(historyVOList);
// 构建分页结果
PageDomain<LearningHistoryVO> pageDomain = new PageDomain<>();
pageParam.setTotalElements(total);
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
pageDomain.setPageParam(pageParam);
pageDomain.setDataList(historyVOList);
resultDomain.success("分页查询学习历史成功", pageDomain);
} catch (Exception e) {
logger.error("分页查询学习历史异常", e);
resultDomain.fail("分页查询学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbLearningHistory> getLearningHistoryById(String id) {
ResultDomain<TbLearningHistory> resultDomain = new ResultDomain<>();
try {
if (id == null || id.isEmpty()) {
resultDomain.fail("历史记录ID不能为空");
return resultDomain;
}
TbLearningHistory learningHistory = learningHistoryMapper.selectById(id);
if (learningHistory != null) {
resultDomain.success("查询学习历史成功", learningHistory);
} else {
resultDomain.fail("学习历史不存在");
}
} catch (Exception e) {
logger.error("查询学习历史异常", e);
resultDomain.fail("查询学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<LearningStatisticsVO> getUserLearningStatistics(String userId, Date startTime, Date endTime) {
ResultDomain<LearningStatisticsVO> resultDomain = new ResultDomain<>();
try {
if (userId == null || userId.isEmpty()) {
resultDomain.fail("用户ID不能为空");
return resultDomain;
}
if (startTime == null || endTime == null) {
resultDomain.fail("开始时间和结束时间不能为空");
return resultDomain;
}
// 统计总体数据
Map<String, Object> statistics = learningHistoryMapper.selectLearningStatisticsByTimeRange(userId, startTime, endTime);
// 获取学习的资源列表
List<Map<String, Object>> learnedResources = learningHistoryMapper.selectLearnedResourcesByTimeRange(userId, startTime, endTime);
// 构建VO
LearningStatisticsVO statisticsVO = new LearningStatisticsVO();
statisticsVO.setUserID(userId);
if (statistics != null) {
statisticsVO.setTotalDuration(getIntValue(statistics, "totalDuration"));
statisticsVO.setLearnCount(getIntValue(statistics, "learnCount"));
statisticsVO.setLearnDays(getIntValue(statistics, "learnDays"));
statisticsVO.setResourceCount(getIntValue(statistics, "resourceCount"));
statisticsVO.setCourseCount(getIntValue(statistics, "courseCount"));
// 格式化时长
statisticsVO.setTotalDurationFormatted(formatDurationString(statisticsVO.getTotalDuration()));
// 计算平均每天学习时长
if (statisticsVO.getLearnDays() > 0) {
statisticsVO.setAvgDailyDuration(statisticsVO.getTotalDuration() / statisticsVO.getLearnDays());
}
}
resultDomain.success("查询学习统计成功", statisticsVO);
} catch (Exception e) {
logger.error("查询学习统计异常", e);
resultDomain.fail("查询学习统计异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<LearningStatisticsVO> getUserLearningStatisticsByPeriod(String userId, String periodType) {
ResultDomain<LearningStatisticsVO> resultDomain = new ResultDomain<>();
try {
if (userId == null || userId.isEmpty()) {
resultDomain.fail("用户ID不能为空");
return resultDomain;
}
// 计算时间范围
Date endTime = new Date();
Date startTime = calculateStartTime(endTime, periodType);
if (startTime == null) {
resultDomain.fail("无效的周期类型");
return resultDomain;
}
ResultDomain<LearningStatisticsVO> statistics = getUserLearningStatistics(userId, startTime, endTime);
if (statistics.isSuccess() && statistics.getData() != null) {
statistics.getData().setPeriod(periodType);
}
return statistics;
} catch (Exception e) {
logger.error("查询学习统计异常", e);
resultDomain.fail("查询学习统计异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<List<LearningHistoryVO>> getCurrentUserLearningHistories(TbLearningHistory filter) {
ResultDomain<List<LearningHistoryVO>> resultDomain = new ResultDomain<>();
try {
TbSysUser currentUser = LoginUtil.getCurrentUser();
if (currentUser == null) {
resultDomain.fail("用户未登录");
return resultDomain;
}
if (filter == null) {
filter = new TbLearningHistory();
}
filter.setUserID(currentUser.getID());
return getLearningHistories(filter);
} catch (Exception e) {
logger.error("查询当前用户学习历史异常", e);
resultDomain.fail("查询当前用户学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<LearningStatisticsVO> getCurrentUserLearningStatistics(String periodType) {
ResultDomain<LearningStatisticsVO> resultDomain = new ResultDomain<>();
try {
TbSysUser currentUser = LoginUtil.getCurrentUser();
if (currentUser == null) {
resultDomain.fail("用户未登录");
return resultDomain;
}
return getUserLearningStatisticsByPeriod(currentUser.getID(), periodType);
} catch (Exception e) {
logger.error("查询当前用户学习统计异常", e);
resultDomain.fail("查询当前用户学习统计异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional
public ResultDomain<Boolean> deleteLearningHistory(String id) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
try {
if (id == null || id.isEmpty()) {
resultDomain.fail("历史记录ID不能为空");
return resultDomain;
}
int result = learningHistoryMapper.deleteLearningHistory(id);
if (result > 0) {
resultDomain.success("删除学习历史成功", true);
} else {
resultDomain.fail("删除学习历史失败");
}
} catch (Exception e) {
logger.error("删除学习历史异常", e);
resultDomain.fail("删除学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional
public ResultDomain<Boolean> batchDeleteLearningHistories(List<String> ids) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
try {
if (ids == null || ids.isEmpty()) {
resultDomain.fail("历史记录ID列表不能为空");
return resultDomain;
}
int result = learningHistoryMapper.batchDeleteLearningHistories(ids);
if (result > 0) {
resultDomain.success("批量删除学习历史成功", true);
} else {
resultDomain.fail("批量删除学习历史失败");
}
} catch (Exception e) {
logger.error("批量删除学习历史异常", e);
resultDomain.fail("批量删除学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<List<LearningHistoryVO>> getRecentLearningHistories(String userId, Integer limit) {
ResultDomain<List<LearningHistoryVO>> resultDomain = new ResultDomain<>();
try {
if (userId == null || userId.isEmpty()) {
resultDomain.fail("用户ID不能为空");
return resultDomain;
}
if (limit == null || limit <= 0) {
limit = 10;
}
List<TbLearningHistory> historyList = learningHistoryMapper.selectRecentByUserId(userId, limit);
List<LearningHistoryVO> historyVOList = historyList.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
formatDuration(historyVOList);
resultDomain.success("查询最近学习历史成功", historyVOList);
} catch (Exception e) {
logger.error("查询最近学习历史异常", e);
resultDomain.fail("查询最近学习历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbLearningHistory> recordResourceView(String userId, String resourceId, Integer duration) {
ResultDomain<TbLearningHistory> resultDomain = new ResultDomain<>();
try {
TbLearningHistory learningHistory = new TbLearningHistory();
learningHistory.setUserID(userId);
learningHistory.setResourceType(1); // 1=资源/新闻
learningHistory.setResourceID(resourceId);
learningHistory.setDuration(duration);
learningHistory.setStartTime(new Date());
return recordLearningHistory(learningHistory);
} catch (Exception e) {
logger.error("记录资源观看历史异常", e);
resultDomain.fail("记录资源观看历史异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbLearningHistory> recordCourseLearn(String userId, String courseId, String chapterId, String nodeId, Integer duration) {
ResultDomain<TbLearningHistory> resultDomain = new ResultDomain<>();
try {
TbLearningHistory learningHistory = new TbLearningHistory();
learningHistory.setUserID(userId);
// 根据提供的信息确定资源类型
if (nodeId != null && !nodeId.isEmpty()) {
learningHistory.setResourceType(4); // 4=节点
learningHistory.setNodeID(nodeId);
} else if (chapterId != null && !chapterId.isEmpty()) {
learningHistory.setResourceType(3); // 3=章节
learningHistory.setChapterID(chapterId);
} else {
learningHistory.setResourceType(2); // 2=课程
}
learningHistory.setCourseID(courseId);
learningHistory.setChapterID(chapterId);
learningHistory.setNodeID(nodeId);
learningHistory.setDuration(duration);
learningHistory.setStartTime(new Date());
return recordLearningHistory(learningHistory);
} catch (Exception e) {
logger.error("记录课程学习历史异常", e);
resultDomain.fail("记录课程学习历史异常: " + e.getMessage());
}
return resultDomain;
}
// ==================== 私有辅助方法 ====================
/**
* 发布学习时长更新事件
*/
private void publishLearningTimeEvent(TbLearningHistory learningHistory) {
try {
// 构建成就事件
AchievementEvent event = AchievementEvent.builder(
learningHistory.getUserID(),
AchievementEventType.LEARNING_TIME_UPDATED
)
.value(learningHistory.getDuration()) // 学习时长(秒)
.extra("resourceType", learningHistory.getResourceType())
.extra("resourceID", learningHistory.getResourceID())
.extra("courseID", learningHistory.getCourseID())
.extra("historyID", learningHistory.getHistoryID())
.build();
// 发布事件(异步处理)
eventPublisher.publishEvent(event);
logger.debug("发布学习时长更新事件用户ID: {}, 时长: {}秒",
learningHistory.getUserID(), learningHistory.getDuration());
} catch (Exception e) {
// 成就事件发布失败不应该影响主业务
logger.error("发布学习时长事件失败", e);
}
}
/**
* 转换实体为VO
*/
private LearningHistoryVO convertToVO(TbLearningHistory entity) {
LearningHistoryVO vo = new LearningHistoryVO();
BeanUtils.copyProperties(entity, vo);
// 设置资源类型名称
if (entity.getResourceType() != null) {
vo.setResourceTypeName(getResourceTypeName(entity.getResourceType()));
}
return vo;
}
/**
* 格式化时长列表
*/
private void formatDuration(List<LearningHistoryVO> historyVOList) {
if (historyVOList != null) {
for (LearningHistoryVO vo : historyVOList) {
if (vo.getDuration() != null) {
vo.setDurationFormatted(formatDurationString(vo.getDuration()));
}
}
}
}
/**
* 格式化时长(秒转为可读字符串)
*/
private String formatDurationString(Integer seconds) {
if (seconds == null || seconds == 0) {
return "0秒";
}
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
StringBuilder sb = new StringBuilder();
if (hours > 0) {
sb.append(hours).append("小时");
}
if (minutes > 0) {
sb.append(minutes).append("分钟");
}
if (secs > 0 || sb.length() == 0) {
sb.append(secs).append("");
}
return sb.toString();
}
/**
* 获取资源类型名称
*/
private String getResourceTypeName(Integer resourceType) {
switch (resourceType) {
case 1:
return "资源/新闻";
case 2:
return "课程";
case 3:
return "章节";
case 4:
return "节点";
default:
return "未知";
}
}
/**
* 计算开始时间
*/
private Date calculateStartTime(Date endTime, String periodType) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(endTime);
if ("day".equalsIgnoreCase(periodType)) {
calendar.add(Calendar.DAY_OF_MONTH, -1);
} else if ("week".equalsIgnoreCase(periodType)) {
calendar.add(Calendar.WEEK_OF_YEAR, -1);
} else if ("month".equalsIgnoreCase(periodType)) {
calendar.add(Calendar.MONTH, -1);
} else {
return null;
}
return calendar.getTime();
}
/**
* 从Map中获取Integer值
*/
private Integer getIntValue(Map<String, Object> map, String key) {
Object value = map.get(key);
if (value == null) {
return 0;
}
if (value instanceof Integer) {
return (Integer) value;
}
if (value instanceof Long) {
return ((Long) value).intValue();
}
if (value instanceof BigDecimal) {
return ((BigDecimal) value).intValue();
}
return 0;
}
}

View File

@@ -0,0 +1,334 @@
<?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="org.xyzh.study.mapper.LearningHistoryMapper">
<!-- 基础结果映射 -->
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.study.TbLearningHistory">
<id column="id" property="id" jdbcType="VARCHAR"/>
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
<result column="history_id" property="historyID" jdbcType="VARCHAR"/>
<result column="resource_type" property="resourceType" jdbcType="INTEGER"/>
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
<result column="course_id" property="courseID" jdbcType="VARCHAR"/>
<result column="chapter_id" property="chapterID" jdbcType="VARCHAR"/>
<result column="node_id" property="nodeID" jdbcType="VARCHAR"/>
<result column="task_id" property="taskID" jdbcType="VARCHAR"/>
<result column="start_time" property="startTime" jdbcType="TIMESTAMP"/>
<result column="end_time" property="endTime" jdbcType="TIMESTAMP"/>
<result column="duration" property="duration" jdbcType="INTEGER"/>
<result column="start_progress" property="startProgress" jdbcType="DECIMAL"/>
<result column="end_progress" property="endProgress" jdbcType="DECIMAL"/>
<result column="device_type" property="deviceType" jdbcType="VARCHAR"/>
<result column="ip_address" property="ipAddress" jdbcType="VARCHAR"/>
<result column="creator" property="creator" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
</resultMap>
<!-- VO结果映射带关联信息 -->
<resultMap id="VOResultMap" type="org.xyzh.common.vo.LearningHistoryVO">
<id column="id" property="id" jdbcType="VARCHAR"/>
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
<result column="user_name" property="userName" jdbcType="VARCHAR"/>
<result column="history_id" property="historyID" jdbcType="VARCHAR"/>
<result column="resource_type" property="resourceType" jdbcType="INTEGER"/>
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
<result column="resource_title" property="resourceTitle" jdbcType="VARCHAR"/>
<result column="course_id" property="courseID" jdbcType="VARCHAR"/>
<result column="course_name" property="courseName" jdbcType="VARCHAR"/>
<result column="chapter_id" property="chapterID" jdbcType="VARCHAR"/>
<result column="chapter_name" property="chapterName" jdbcType="VARCHAR"/>
<result column="node_id" property="nodeID" jdbcType="VARCHAR"/>
<result column="node_name" property="nodeName" jdbcType="VARCHAR"/>
<result column="task_id" property="taskID" jdbcType="VARCHAR"/>
<result column="task_name" property="taskName" jdbcType="VARCHAR"/>
<result column="start_time" property="startTime" jdbcType="TIMESTAMP"/>
<result column="end_time" property="endTime" jdbcType="TIMESTAMP"/>
<result column="duration" property="duration" jdbcType="INTEGER"/>
<result column="start_progress" property="startProgress" jdbcType="DECIMAL"/>
<result column="end_progress" property="endProgress" jdbcType="DECIMAL"/>
<result column="device_type" property="deviceType" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
</resultMap>
<!-- 基础字段 -->
<sql id="Base_Column_List">
id, user_id, history_id, resource_type, resource_id, course_id, chapter_id, node_id, task_id,
start_time, end_time, duration, start_progress, end_progress, device_type, ip_address,
creator, create_time, deleted
</sql>
<!-- 通用条件 -->
<sql id="Where_Clause">
<where>
deleted = 0
<if test="userID != null and userID != ''">
AND user_id = #{userID}
</if>
<if test="historyID != null and historyID != ''">
AND history_id = #{historyID}
</if>
<if test="resourceType != null">
AND resource_type = #{resourceType}
</if>
<if test="resourceID != null and resourceID != ''">
AND resource_id = #{resourceID}
</if>
<if test="courseID != null and courseID != ''">
AND course_id = #{courseID}
</if>
<if test="chapterID != null and chapterID != ''">
AND chapter_id = #{chapterID}
</if>
<if test="nodeID != null and nodeID != ''">
AND node_id = #{nodeID}
</if>
<if test="taskID != null and taskID != ''">
AND task_id = #{taskID}
</if>
<if test="deviceType != null and deviceType != ''">
AND device_type = #{deviceType}
</if>
</where>
</sql>
<!-- 插入学习历史 -->
<insert id="insertLearningHistory">
INSERT INTO tb_learning_history (
id, user_id, history_id, resource_type, resource_id, course_id, chapter_id, node_id, task_id,
start_time, end_time, duration, start_progress, end_progress, device_type, ip_address,
creator, create_time, deleted
) VALUES (
#{id}, #{userID}, #{historyID}, #{resourceType}, #{resourceID}, #{courseID}, #{chapterID}, #{nodeID}, #{taskID},
#{startTime}, #{endTime}, #{duration}, #{startProgress}, #{endProgress}, #{deviceType}, #{ipAddress},
#{creator}, #{createTime}, 0
)
</insert>
<!-- 更新学习历史 -->
<update id="updateLearningHistory">
UPDATE tb_learning_history
<set>
<if test="endTime != null">
end_time = #{endTime},
</if>
<if test="duration != null">
duration = #{duration},
</if>
<if test="startProgress != null">
start_progress = #{startProgress},
</if>
<if test="endProgress != null">
end_progress = #{endProgress},
</if>
<if test="deviceType != null">
device_type = #{deviceType},
</if>
<if test="ipAddress != null">
ip_address = #{ipAddress},
</if>
</set>
WHERE history_id = #{historyID} AND deleted = 0
</update>
<!-- 批量插入学习历史 -->
<insert id="batchInsertLearningHistories">
INSERT INTO tb_learning_history (
id, user_id, history_id, resource_type, resource_id, course_id, chapter_id, node_id, task_id,
start_time, end_time, duration, start_progress, end_progress, device_type, ip_address,
creator, create_time, deleted
) VALUES
<foreach collection="historyList" item="item" separator=",">
(
#{item.id}, #{item.userID}, #{item.historyID}, #{item.resourceType}, #{item.resourceID},
#{item.courseID}, #{item.chapterID}, #{item.nodeID}, #{item.taskID},
#{item.startTime}, #{item.endTime}, #{item.duration}, #{item.startProgress}, #{item.endProgress},
#{item.deviceType}, #{item.ipAddress}, #{item.creator}, #{item.createTime}, 0
)
</foreach>
</insert>
<!-- 根据ID查询 -->
<select id="selectById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_history
WHERE id = #{id} AND deleted = 0
</select>
<!-- 根据用户ID查询 -->
<select id="selectByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_history
WHERE user_id = #{userId} AND deleted = 0
ORDER BY start_time DESC, create_time DESC
</select>
<!-- 根据条件查询学习历史列表 -->
<select id="selectLearningHistories" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_history
<include refid="Where_Clause"/>
ORDER BY start_time DESC, create_time DESC
</select>
<!-- 根据条件查询学习历史VO列表带关联信息 -->
<select id="selectLearningHistoriesWithDetails" resultMap="VOResultMap">
SELECT
lh.id,
lh.user_id,
u.username as user_name,
lh.history_id,
lh.resource_type,
lh.resource_id,
CASE
WHEN lh.resource_type = 1 THEN r.title
WHEN lh.resource_type = 2 THEN c.name
WHEN lh.resource_type = 3 THEN ch.name
WHEN lh.resource_type = 4 THEN n.name
END as resource_title,
lh.course_id,
c.name as course_name,
lh.chapter_id,
ch.name as chapter_name,
lh.node_id,
n.name as node_name,
lh.task_id,
t.name as task_name,
lh.start_time,
lh.end_time,
lh.duration,
lh.start_progress,
lh.end_progress,
lh.device_type,
lh.create_time
FROM tb_learning_history lh
LEFT JOIN tb_sys_user u ON lh.user_id = u.id
LEFT JOIN tb_resource r ON lh.resource_type = 1 AND lh.resource_id = r.resource_id
LEFT JOIN tb_course c ON lh.course_id = c.course_id
LEFT JOIN tb_course_chapter ch ON lh.chapter_id = ch.chapter_id
LEFT JOIN tb_course_node n ON lh.node_id = n.node_id
LEFT JOIN tb_learning_task t ON lh.task_id = t.task_id
<include refid="Where_Clause"/>
ORDER BY lh.start_time DESC, lh.create_time DESC
</select>
<!-- 分页查询学习历史 -->
<select id="selectLearningHistoriesPage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_history
<include refid="Where_Clause"/>
ORDER BY start_time DESC, create_time DESC
LIMIT #{pageParam.offset}, #{pageParam.pageSize}
</select>
<!-- 统计学习历史总数 -->
<select id="countLearningHistories" resultType="long">
SELECT COUNT(*)
FROM tb_learning_history
<include refid="Where_Clause"/>
</select>
<!-- 根据时间段查询学习历史 -->
<select id="selectByUserIdAndTimeRange" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_history
WHERE user_id = #{userId}
AND start_time BETWEEN #{startTime} AND #{endTime}
AND deleted = 0
ORDER BY start_time DESC
</select>
<!-- 根据时间段和资源类型查询学习历史 -->
<select id="selectByUserIdAndResourceTypeAndTimeRange" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_history
WHERE user_id = #{userId}
AND resource_type = #{resourceType}
AND start_time BETWEEN #{startTime} AND #{endTime}
AND deleted = 0
ORDER BY start_time DESC
</select>
<!-- 统计用户某时间段的学习时长 -->
<select id="selectLearningStatisticsByTimeRange" resultType="map">
SELECT
SUM(duration) as totalDuration,
COUNT(*) as learnCount,
COUNT(DISTINCT DATE(start_time)) as learnDays,
COUNT(DISTINCT CASE WHEN resource_type = 1 THEN resource_id END) as resourceCount,
COUNT(DISTINCT CASE WHEN resource_type = 2 THEN course_id END) as courseCount
FROM tb_learning_history
WHERE user_id = #{userId}
AND start_time BETWEEN #{startTime} AND #{endTime}
AND deleted = 0
</select>
<!-- 统计用户某时间段各资源类型的学习时长 -->
<select id="selectLearningStatisticsByResourceType" resultType="map">
SELECT
resource_type as resourceType,
SUM(duration) as totalDuration,
COUNT(*) as learnCount,
COUNT(DISTINCT resource_id) as resourceCount
FROM tb_learning_history
WHERE user_id = #{userId}
AND start_time BETWEEN #{startTime} AND #{endTime}
AND deleted = 0
GROUP BY resource_type
ORDER BY resource_type
</select>
<!-- 获取用户学习的资源列表(按时间段) -->
<select id="selectLearnedResourcesByTimeRange" resultType="map">
SELECT
resource_type as resourceType,
resource_id as resourceId,
course_id as courseId,
chapter_id as chapterId,
node_id as nodeId,
SUM(duration) as totalDuration,
COUNT(*) as learnCount,
MAX(start_time) as lastLearnTime
FROM tb_learning_history
WHERE user_id = #{userId}
AND start_time BETWEEN #{startTime} AND #{endTime}
AND deleted = 0
GROUP BY resource_type, resource_id, course_id, chapter_id, node_id
ORDER BY lastLearnTime DESC
</select>
<!-- 删除学习历史(软删除) -->
<update id="deleteLearningHistory">
UPDATE tb_learning_history
SET deleted = 1, delete_time = NOW()
WHERE id = #{id}
</update>
<!-- 批量删除学习历史(软删除) -->
<update id="batchDeleteLearningHistories">
UPDATE tb_learning_history
SET deleted = 1, delete_time = NOW()
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 根据会话ID查询学习历史 -->
<select id="selectByhistoryID" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_history
WHERE history_id = #{historyID} AND deleted = 0
ORDER BY start_time DESC
</select>
<!-- 获取用户最近的学习历史 -->
<select id="selectRecentByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_history
WHERE user_id = #{userId} AND deleted = 0
ORDER BY start_time DESC, create_time DESC
LIMIT #{limit}
</select>
</mapper>

View File

@@ -0,0 +1,246 @@
<?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="org.xyzh.study.mapper.LearningStatisticsDetailMapper">
<!-- 基础结果映射 -->
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.study.TbLearningStatisticsDetail">
<id column="id" property="id" jdbcType="VARCHAR"/>
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
<result column="stat_date" property="statDate" jdbcType="DATE"/>
<result column="resource_type" property="resourceType" jdbcType="INTEGER"/>
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
<result column="course_id" property="courseID" jdbcType="VARCHAR"/>
<result column="chapter_id" property="chapterID" jdbcType="VARCHAR"/>
<result column="total_duration" property="totalDuration" jdbcType="INTEGER"/>
<result column="learn_count" property="learnCount" jdbcType="INTEGER"/>
<result column="is_complete" property="isComplete" jdbcType="BOOLEAN"/>
<result column="complete_time" property="completeTime" jdbcType="TIMESTAMP"/>
<result column="creator" property="creator" jdbcType="VARCHAR"/>
<result column="updater" property="updater" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
</resultMap>
<!-- VO结果映射带关联信息 -->
<resultMap id="VOResultMap" type="org.xyzh.common.vo.LearningStatisticsDetailVO">
<result column="stat_date" property="statDate" jdbcType="DATE"/>
<result column="resource_type" property="resourceType" jdbcType="INTEGER"/>
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
<result column="resource_title" property="resourceTitle" jdbcType="VARCHAR"/>
<result column="course_id" property="courseID" jdbcType="VARCHAR"/>
<result column="course_name" property="courseName" jdbcType="VARCHAR"/>
<result column="chapter_id" property="chapterID" jdbcType="VARCHAR"/>
<result column="chapter_name" property="chapterName" jdbcType="VARCHAR"/>
<result column="total_duration" property="totalDuration" jdbcType="INTEGER"/>
<result column="learn_count" property="learnCount" jdbcType="INTEGER"/>
<result column="is_complete" property="isComplete" jdbcType="BOOLEAN"/>
<result column="complete_time" property="completeTime" jdbcType="TIMESTAMP"/>
</resultMap>
<!-- 基础字段 -->
<sql id="Base_Column_List">
id, user_id, stat_date, resource_type, resource_id, course_id, chapter_id,
total_duration, learn_count, is_complete, complete_time,
creator, updater, create_time, update_time, deleted
</sql>
<!-- 通用条件 -->
<sql id="Where_Clause">
<where>
deleted = 0
<if test="userID != null and userID != ''">
AND user_id = #{userID}
</if>
<if test="statDate != null">
AND stat_date = #{statDate}
</if>
<if test="resourceType != null">
AND resource_type = #{resourceType}
</if>
<if test="resourceID != null and resourceID != ''">
AND resource_id = #{resourceID}
</if>
<if test="courseID != null and courseID != ''">
AND course_id = #{courseID}
</if>
<if test="chapterID != null and chapterID != ''">
AND chapter_id = #{chapterID}
</if>
<if test="isComplete != null">
AND is_complete = #{isComplete}
</if>
</where>
</sql>
<!-- 插入统计明细 -->
<insert id="insertStatisticsDetail">
INSERT INTO tb_learning_statistics_detail (
id, user_id, stat_date, resource_type, resource_id, course_id, chapter_id,
total_duration, learn_count, is_complete, complete_time,
creator, create_time, deleted
) VALUES (
#{id}, #{userID}, #{statDate}, #{resourceType}, #{resourceID}, #{courseID}, #{chapterID},
#{totalDuration}, #{learnCount}, #{isComplete}, #{completeTime},
#{creator}, #{createTime}, 0
)
</insert>
<!-- 更新统计明细 -->
<update id="updateStatisticsDetail">
UPDATE tb_learning_statistics_detail
<set>
<if test="totalDuration != null">
total_duration = #{totalDuration},
</if>
<if test="learnCount != null">
learn_count = #{learnCount},
</if>
<if test="isComplete != null">
is_complete = #{isComplete},
</if>
<if test="completeTime != null">
complete_time = #{completeTime},
</if>
<if test="updater != null">
updater = #{updater},
</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>
<!-- 插入或更新统计明细ON DUPLICATE KEY UPDATE -->
<insert id="insertOrUpdateStatisticsDetail">
INSERT INTO tb_learning_statistics_detail (
id, user_id, stat_date, resource_type, resource_id, course_id, chapter_id,
total_duration, learn_count, is_complete, complete_time,
creator, create_time, deleted
) VALUES (
#{id}, #{userID}, #{statDate}, #{resourceType}, #{resourceID}, #{courseID}, #{chapterID},
#{totalDuration}, #{learnCount}, #{isComplete}, #{completeTime},
#{creator}, NOW(), 0
)
ON DUPLICATE KEY UPDATE
total_duration = total_duration + #{totalDuration},
learn_count = learn_count + #{learnCount},
is_complete = #{isComplete},
complete_time = #{completeTime},
updater = #{updater},
update_time = NOW()
</insert>
<!-- 根据条件查询统计明细 -->
<select id="selectStatisticsDetails" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_statistics_detail
<include refid="Where_Clause"/>
ORDER BY stat_date DESC, create_time DESC
</select>
<!-- 根据用户ID和日期查询统计明细 -->
<select id="selectByUserIdAndDate" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_statistics_detail
WHERE user_id = #{userId}
AND stat_date = #{statDate}
AND deleted = 0
ORDER BY resource_type, total_duration DESC
</select>
<!-- 根据用户ID和日期范围查询统计明细 -->
<select id="selectByUserIdAndDateRange" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_statistics_detail
WHERE user_id = #{userId}
AND stat_date BETWEEN #{startDate} AND #{endDate}
AND deleted = 0
ORDER BY stat_date DESC, resource_type, total_duration DESC
</select>
<!-- 根据用户ID和日期范围查询统计明细VO带关联信息 -->
<select id="selectStatisticsDetailsWithInfo" resultMap="VOResultMap">
SELECT
sd.stat_date,
sd.resource_type,
sd.resource_id,
CASE
WHEN sd.resource_type = 1 THEN r.title
WHEN sd.resource_type = 2 THEN c.name
WHEN sd.resource_type = 3 THEN ch.name
END as resource_title,
sd.course_id,
c.name as course_name,
sd.chapter_id,
ch.name as chapter_name,
sd.total_duration,
sd.learn_count,
sd.is_complete,
sd.complete_time
FROM tb_learning_statistics_detail sd
LEFT JOIN tb_resource r ON sd.resource_type = 1 AND sd.resource_id = r.resource_id
LEFT JOIN tb_course c ON sd.course_id = c.course_id
LEFT JOIN tb_course_chapter ch ON sd.chapter_id = ch.chapter_id
WHERE sd.user_id = #{userId}
AND sd.stat_date BETWEEN #{startDate} AND #{endDate}
AND sd.deleted = 0
ORDER BY sd.stat_date DESC, sd.total_duration DESC
</select>
<!-- 根据用户ID、资源类型和日期查询统计明细 -->
<select id="selectByUserIdAndResourceTypeAndDate" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM tb_learning_statistics_detail
WHERE user_id = #{userId}
AND resource_type = #{resourceType}
AND resource_id = #{resourceId}
AND stat_date = #{statDate}
AND deleted = 0
LIMIT 1
</select>
<!-- 删除统计明细(软删除) -->
<update id="deleteStatisticsDetail">
UPDATE tb_learning_statistics_detail
SET deleted = 1, delete_time = NOW()
WHERE id = #{id}
</update>
<!-- 批量删除统计明细(软删除) -->
<update id="batchDeleteStatisticsDetails">
UPDATE tb_learning_statistics_detail
SET deleted = 1, delete_time = NOW()
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 按日期汇总用户学习统计 -->
<select id="selectDailySummary" resultMap="BaseResultMap">
SELECT
NULL as id,
user_id,
stat_date,
NULL as resource_type,
NULL as resource_id,
NULL as course_id,
NULL as chapter_id,
SUM(total_duration) as total_duration,
SUM(learn_count) as learn_count,
NULL as is_complete,
NULL as complete_time,
NULL as creator,
NULL as updater,
MIN(create_time) as create_time,
MAX(update_time) as update_time,
0 as deleted
FROM tb_learning_statistics_detail
WHERE user_id = #{userId}
AND stat_date BETWEEN #{startDate} AND #{endDate}
AND deleted = 0
GROUP BY user_id, stat_date
ORDER BY stat_date DESC
</select>
</mapper>