serv\web-学习历史修改
This commit is contained in:
@@ -116,3 +116,78 @@ CREATE TABLE `tb_learning_statistics` (
|
|||||||
KEY `idx_date` (`stat_date`)
|
KEY `idx_date` (`stat_date`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学习统计表';
|
) 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='学习统计明细表';
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ public class CrontabController {
|
|||||||
* @since 2025-10-25
|
* @since 2025-10-25
|
||||||
*/
|
*/
|
||||||
@GetMapping("/task/{taskId}")
|
@GetMapping("/task/{taskId}")
|
||||||
public ResultDomain<TbCrontabTask> getTaskById(@PathVariable String taskId) {
|
public ResultDomain<TbCrontabTask> getTaskById(@PathVariable(value = "taskId") String taskId) {
|
||||||
return crontabService.getTaskById(taskId);
|
return crontabService.getTaskById(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ public class CrontabController {
|
|||||||
* @since 2025-10-25
|
* @since 2025-10-25
|
||||||
*/
|
*/
|
||||||
@PostMapping("/task/start/{taskId}")
|
@PostMapping("/task/start/{taskId}")
|
||||||
public ResultDomain<TbCrontabTask> startTask(@PathVariable String taskId) {
|
public ResultDomain<TbCrontabTask> startTask(@PathVariable(value = "taskId") String taskId) {
|
||||||
return crontabService.startTask(taskId);
|
return crontabService.startTask(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ public class CrontabController {
|
|||||||
* @since 2025-10-25
|
* @since 2025-10-25
|
||||||
*/
|
*/
|
||||||
@PostMapping("/task/pause/{taskId}")
|
@PostMapping("/task/pause/{taskId}")
|
||||||
public ResultDomain<TbCrontabTask> pauseTask(@PathVariable String taskId) {
|
public ResultDomain<TbCrontabTask> pauseTask(@PathVariable(value = "taskId") String taskId) {
|
||||||
return crontabService.pauseTask(taskId);
|
return crontabService.pauseTask(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ public class CrontabController {
|
|||||||
* @since 2025-10-25
|
* @since 2025-10-25
|
||||||
*/
|
*/
|
||||||
@PostMapping("/task/execute/{taskId}")
|
@PostMapping("/task/execute/{taskId}")
|
||||||
public ResultDomain<TbCrontabTask> executeTaskOnce(@PathVariable String taskId) {
|
public ResultDomain<TbCrontabTask> executeTaskOnce(@PathVariable(value = "taskId") String taskId) {
|
||||||
return crontabService.executeTaskOnce(taskId);
|
return crontabService.executeTaskOnce(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ public class CrontabController {
|
|||||||
* @since 2025-10-25
|
* @since 2025-10-25
|
||||||
*/
|
*/
|
||||||
@GetMapping("/log/task/{taskId}")
|
@GetMapping("/log/task/{taskId}")
|
||||||
public ResultDomain<TbCrontabLog> getLogsByTaskId(@PathVariable String taskId) {
|
public ResultDomain<TbCrontabLog> getLogsByTaskId(@PathVariable(value = "taskId") String taskId) {
|
||||||
return crontabService.getLogsByTaskId(taskId);
|
return crontabService.getLogsByTaskId(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ public class CrontabController {
|
|||||||
* @since 2025-10-25
|
* @since 2025-10-25
|
||||||
*/
|
*/
|
||||||
@GetMapping("/log/{logId}")
|
@GetMapping("/log/{logId}")
|
||||||
public ResultDomain<TbCrontabLog> getLogById(@PathVariable String logId) {
|
public ResultDomain<TbCrontabLog> getLogById(@PathVariable(value = "logId") String logId) {
|
||||||
return crontabService.getLogById(logId);
|
return crontabService.getLogById(logId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ public class CrontabController {
|
|||||||
* @since 2025-10-25
|
* @since 2025-10-25
|
||||||
*/
|
*/
|
||||||
@DeleteMapping("/log/clean/{days}")
|
@DeleteMapping("/log/clean/{days}")
|
||||||
public ResultDomain<Integer> cleanLogs(@PathVariable Integer days) {
|
public ResultDomain<Integer> cleanLogs(@PathVariable(value = "days") Integer days) {
|
||||||
return crontabService.cleanLogs(days);
|
return crontabService.cleanLogs(days);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,5 +100,14 @@ public interface CrontabTaskMapper extends BaseMapper<TbCrontabTask> {
|
|||||||
* @since 2025-10-25
|
* @since 2025-10-25
|
||||||
*/
|
*/
|
||||||
TbCrontabTask selectTaskByBeanAndMethod(@Param("beanName") String beanName, @Param("methodName") String methodName);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.xyzh.api.crontab.CrontabService;
|
import org.xyzh.api.crontab.CrontabService;
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
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.core.page.PageParam;
|
||||||
import org.xyzh.common.dto.crontab.TbCrontabTask;
|
import org.xyzh.common.dto.crontab.TbCrontabTask;
|
||||||
import org.xyzh.common.dto.crontab.TbCrontabLog;
|
import org.xyzh.common.dto.crontab.TbCrontabLog;
|
||||||
@@ -215,7 +216,14 @@ public class CrontabServiceImpl implements CrontabService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<TbCrontabTask> list = taskMapper.selectTaskPage(filter, pageParam);
|
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) {
|
} catch (Exception e) {
|
||||||
logger.error("分页查询定时任务异常: ", e);
|
logger.error("分页查询定时任务异常: ", e);
|
||||||
resultDomain.fail("分页查询定时任务异常: " + e.getMessage());
|
resultDomain.fail("分页查询定时任务异常: " + e.getMessage());
|
||||||
|
|||||||
@@ -33,6 +33,33 @@
|
|||||||
|
|
||||||
<!-- 查询条件 -->
|
<!-- 查询条件 -->
|
||||||
<sql id="Base_Where_Clause">
|
<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>
|
<where>
|
||||||
<if test="filter != null">
|
<if test="filter != null">
|
||||||
<if test="filter.ID != null and filter.ID != ''">
|
<if test="filter.ID != null and filter.ID != ''">
|
||||||
@@ -144,7 +171,7 @@
|
|||||||
SELECT
|
SELECT
|
||||||
<include refid="Base_Column_List" />
|
<include refid="Base_Column_List" />
|
||||||
FROM tb_crontab_task
|
FROM tb_crontab_task
|
||||||
<include refid="Base_Where_Clause" />
|
<include refid="Filter_Where_Clause" />
|
||||||
ORDER BY create_time DESC
|
ORDER BY create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@@ -153,7 +180,7 @@
|
|||||||
SELECT
|
SELECT
|
||||||
<include refid="Base_Column_List" />
|
<include refid="Base_Column_List" />
|
||||||
FROM tb_crontab_task
|
FROM tb_crontab_task
|
||||||
<include refid="Base_Where_Clause" />
|
<include refid="Filter_Where_Clause" />
|
||||||
ORDER BY create_time DESC
|
ORDER BY create_time DESC
|
||||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||||
</select>
|
</select>
|
||||||
@@ -185,4 +212,11 @@
|
|||||||
AND deleted = 0
|
AND deleted = 0
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- countSelectTask -->
|
||||||
|
|
||||||
|
<select id="countSelectTask">
|
||||||
|
SELECT COUNT(1) FROM tb_crontab_task
|
||||||
|
<include refid="Filter_Where_Clause" />
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -9,3 +9,4 @@ export { courseApi } from './course';
|
|||||||
export { learningTaskApi } from './learning-task';
|
export { learningTaskApi } from './learning-task';
|
||||||
export { learningRecordApi } from './learning-record';
|
export { learningRecordApi } from './learning-record';
|
||||||
export { learningPlanApi } from './learning-plan';
|
export { learningPlanApi } from './learning-plan';
|
||||||
|
export { learningHistoryApi } from './learning-history';
|
||||||
|
|||||||
204
schoolNewsWeb/src/apis/study/learning-history.ts
Normal file
204
schoolNewsWeb/src/apis/study/learning-history.ts
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
/**
|
||||||
|
* @description 学习历史相关API
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-10-27
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { api } from '@/apis/index';
|
||||||
|
import type {
|
||||||
|
TbLearningHistory,
|
||||||
|
LearningHistoryVO,
|
||||||
|
LearningStatisticsVO,
|
||||||
|
ResultDomain,
|
||||||
|
PageDomain,
|
||||||
|
PageRequest
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学习历史API服务
|
||||||
|
*/
|
||||||
|
export const learningHistoryApi = {
|
||||||
|
/**
|
||||||
|
* 记录学习历史
|
||||||
|
* @param learningHistory 学习历史数据
|
||||||
|
* @returns Promise<ResultDomain<TbLearningHistory>>
|
||||||
|
*/
|
||||||
|
async recordLearningHistory(learningHistory: TbLearningHistory): Promise<ResultDomain<TbLearningHistory>> {
|
||||||
|
const response = await api.post<TbLearningHistory>('/study/history/record', learningHistory, {
|
||||||
|
showLoading: false // 禁用 loading 动画,避免影响用户体验
|
||||||
|
} as any);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量记录学习历史
|
||||||
|
* @param historyList 学习历史列表
|
||||||
|
* @returns Promise<ResultDomain<boolean>>
|
||||||
|
*/
|
||||||
|
async batchRecordLearningHistory(historyList: TbLearningHistory[]): Promise<ResultDomain<boolean>> {
|
||||||
|
const response = await api.post<boolean>('/study/history/batch-record', historyList);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询学习历史列表
|
||||||
|
* @param filter 过滤条件
|
||||||
|
* @returns Promise<ResultDomain<LearningHistoryVO[]>>
|
||||||
|
*/
|
||||||
|
async getLearningHistories(filter?: Partial<TbLearningHistory>): Promise<ResultDomain<LearningHistoryVO[]>> {
|
||||||
|
const response = await api.post<LearningHistoryVO[]>('/study/history/list', filter || {});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询学习历史
|
||||||
|
* @param pageRequest 分页查询请求
|
||||||
|
* @returns Promise<ResultDomain<PageDomain<LearningHistoryVO>>>
|
||||||
|
*/
|
||||||
|
async getLearningHistoriesPage(pageRequest: PageRequest<TbLearningHistory>): Promise<ResultDomain<PageDomain<LearningHistoryVO>>> {
|
||||||
|
const response = await api.post<PageDomain<LearningHistoryVO>>('/study/history/page', pageRequest);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询学习历史
|
||||||
|
* @param id 历史记录ID
|
||||||
|
* @returns Promise<ResultDomain<TbLearningHistory>>
|
||||||
|
*/
|
||||||
|
async getLearningHistoryById(id: string): Promise<ResultDomain<TbLearningHistory>> {
|
||||||
|
const response = await api.get<TbLearningHistory>(`/study/history/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户的学习历史
|
||||||
|
* @param filter 过滤条件
|
||||||
|
* @returns Promise<ResultDomain<LearningHistoryVO[]>>
|
||||||
|
*/
|
||||||
|
async getCurrentUserLearningHistories(filter?: Partial<TbLearningHistory>): Promise<ResultDomain<LearningHistoryVO[]>> {
|
||||||
|
const response = await api.post<LearningHistoryVO[]>('/study/history/my-histories', filter || {});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户最近的学习历史
|
||||||
|
* @param limit 限制数量(默认10)
|
||||||
|
* @returns Promise<ResultDomain<LearningHistoryVO[]>>
|
||||||
|
*/
|
||||||
|
async getRecentLearningHistories(limit = 10): Promise<ResultDomain<LearningHistoryVO[]>> {
|
||||||
|
const response = await api.get<LearningHistoryVO[]>(`/study/history/recent?limit=${limit}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户学习统计(按时间范围)
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param startTime 开始时间(Date对象)
|
||||||
|
* @param endTime 结束时间(Date对象)
|
||||||
|
* @returns Promise<ResultDomain<LearningStatisticsVO>>
|
||||||
|
*/
|
||||||
|
async getUserLearningStatistics(userId: string, startTime: Date, endTime: Date): Promise<ResultDomain<LearningStatisticsVO>> {
|
||||||
|
const response = await api.get<LearningStatisticsVO>(
|
||||||
|
`/study/history/statistics?userId=${userId}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户学习统计(按周期)
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param periodType 周期类型(day/week/month)
|
||||||
|
* @returns Promise<ResultDomain<LearningStatisticsVO>>
|
||||||
|
*/
|
||||||
|
async getUserLearningStatisticsByPeriod(userId: string, periodType: 'day' | 'week' | 'month'): Promise<ResultDomain<LearningStatisticsVO>> {
|
||||||
|
const response = await api.get<LearningStatisticsVO>(`/study/history/statistics/${userId}/${periodType}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户的学习统计
|
||||||
|
* @param periodType 周期类型(day/week/month)
|
||||||
|
* @returns Promise<ResultDomain<LearningStatisticsVO>>
|
||||||
|
*/
|
||||||
|
async getCurrentUserLearningStatistics(periodType: 'day' | 'week' | 'month'): Promise<ResultDomain<LearningStatisticsVO>> {
|
||||||
|
const response = await api.get<LearningStatisticsVO>(`/study/history/my-statistics/${periodType}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除学习历史
|
||||||
|
* @param id 历史记录ID
|
||||||
|
* @returns Promise<ResultDomain<boolean>>
|
||||||
|
*/
|
||||||
|
async deleteLearningHistory(id: string): Promise<ResultDomain<boolean>> {
|
||||||
|
const response = await api.delete<boolean>(`/study/history/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除学习历史
|
||||||
|
* @param ids 历史记录ID列表
|
||||||
|
* @returns Promise<ResultDomain<boolean>>
|
||||||
|
*/
|
||||||
|
async batchDeleteLearningHistories(ids: string[]): Promise<ResultDomain<boolean>> {
|
||||||
|
const response = await api.delete<boolean>('/study/history/batch', ids);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化记录方法 - 观看新闻/资源
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param resourceId 资源ID
|
||||||
|
* @param duration 学习时长(秒)
|
||||||
|
* @returns Promise<ResultDomain<TbLearningHistory>>
|
||||||
|
*/
|
||||||
|
async recordResourceView(userId: string, resourceId: string, duration: number): Promise<ResultDomain<TbLearningHistory>> {
|
||||||
|
const learningHistory: TbLearningHistory = {
|
||||||
|
userID: userId,
|
||||||
|
resourceType: 1, // 1资源/新闻
|
||||||
|
resourceID: resourceId,
|
||||||
|
duration: duration,
|
||||||
|
deviceType: 'web'
|
||||||
|
};
|
||||||
|
return this.recordLearningHistory(learningHistory);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化记录方法 - 学习课程
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param courseId 课程ID
|
||||||
|
* @param chapterId 章节ID(可选)
|
||||||
|
* @param nodeId 节点ID(可选)
|
||||||
|
* @param duration 学习时长(秒)
|
||||||
|
* @returns Promise<ResultDomain<TbLearningHistory>>
|
||||||
|
*/
|
||||||
|
async recordCourseLearn(
|
||||||
|
userId: string,
|
||||||
|
courseId: string,
|
||||||
|
chapterId?: string,
|
||||||
|
nodeId?: string,
|
||||||
|
duration?: number
|
||||||
|
): Promise<ResultDomain<TbLearningHistory>> {
|
||||||
|
const learningHistory: TbLearningHistory = {
|
||||||
|
userID: userId,
|
||||||
|
resourceType: nodeId ? 4 : (chapterId ? 3 : 2), // 2课程 3章节 4节点
|
||||||
|
resourceID: nodeId || chapterId || courseId,
|
||||||
|
courseID: courseId,
|
||||||
|
chapterID: chapterId,
|
||||||
|
nodeID: nodeId,
|
||||||
|
duration: duration || 0,
|
||||||
|
deviceType: 'web'
|
||||||
|
};
|
||||||
|
return this.recordLearningHistory(learningHistory);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 健康检查
|
||||||
|
* @returns Promise<ResultDomain<string>>
|
||||||
|
*/
|
||||||
|
async health(): Promise<ResultDomain<string>> {
|
||||||
|
const response = await api.get<string>('/study/history/health');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@@ -25,9 +25,9 @@ export interface BaseDTO {
|
|||||||
*/
|
*/
|
||||||
export interface PageParam {
|
export interface PageParam {
|
||||||
/** 当前页码 */
|
/** 当前页码 */
|
||||||
page: number;
|
pageNumber: number;
|
||||||
/** 每页条数 */
|
/** 每页条数 */
|
||||||
size: number;
|
pageSize: number;
|
||||||
|
|
||||||
/** 总页数 */
|
/** 总页数 */
|
||||||
totalPages?: number;
|
totalPages?: number;
|
||||||
|
|||||||
@@ -331,3 +331,187 @@ export interface LearningRecordStatistics {
|
|||||||
/** 完成任务数 */
|
/** 完成任务数 */
|
||||||
taskCount?: number;
|
taskCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学习观看历史表
|
||||||
|
*/
|
||||||
|
export interface TbLearningHistory extends BaseDTO {
|
||||||
|
/** 用户ID */
|
||||||
|
userID?: string;
|
||||||
|
/** 学习会话ID */
|
||||||
|
historyID?: string;
|
||||||
|
/** 资源类型(1资源/新闻 2课程 3章节 4节点) */
|
||||||
|
resourceType?: number;
|
||||||
|
/** 资源ID */
|
||||||
|
resourceID?: string;
|
||||||
|
/** 课程ID */
|
||||||
|
courseID?: string;
|
||||||
|
/** 章节ID */
|
||||||
|
chapterID?: string;
|
||||||
|
/** 节点ID */
|
||||||
|
nodeID?: string;
|
||||||
|
/** 关联任务ID */
|
||||||
|
taskID?: string;
|
||||||
|
/** 开始学习时间 */
|
||||||
|
startTime?: string;
|
||||||
|
/** 结束学习时间 */
|
||||||
|
endTime?: string;
|
||||||
|
/** 本次学习时长(秒) */
|
||||||
|
duration?: number;
|
||||||
|
/** 开始进度(0-100) */
|
||||||
|
startProgress?: number;
|
||||||
|
/** 结束进度(0-100) */
|
||||||
|
endProgress?: number;
|
||||||
|
/** 设备类型(web/mobile/app) */
|
||||||
|
deviceType?: string;
|
||||||
|
/** IP地址 */
|
||||||
|
ipAddress?: string;
|
||||||
|
/** 创建者 */
|
||||||
|
creator?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学习统计明细表
|
||||||
|
*/
|
||||||
|
export interface TbLearningStatisticsDetail extends BaseDTO {
|
||||||
|
/** 用户ID */
|
||||||
|
userID?: string;
|
||||||
|
/** 统计日期 */
|
||||||
|
statDate?: string;
|
||||||
|
/** 资源类型(1资源/新闻 2课程 3章节) */
|
||||||
|
resourceType?: number;
|
||||||
|
/** 资源ID */
|
||||||
|
resourceID?: string;
|
||||||
|
/** 课程ID */
|
||||||
|
courseID?: string;
|
||||||
|
/** 章节ID */
|
||||||
|
chapterID?: string;
|
||||||
|
/** 学习时长(秒) */
|
||||||
|
totalDuration?: number;
|
||||||
|
/** 学习次数 */
|
||||||
|
learnCount?: number;
|
||||||
|
/** 是否完成 */
|
||||||
|
isComplete?: boolean;
|
||||||
|
/** 完成时间 */
|
||||||
|
completeTime?: string;
|
||||||
|
/** 创建者 */
|
||||||
|
creator?: string;
|
||||||
|
/** 更新者 */
|
||||||
|
updater?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学习历史响应VO
|
||||||
|
*/
|
||||||
|
export interface LearningHistoryVO {
|
||||||
|
/** 历史记录ID */
|
||||||
|
id?: string;
|
||||||
|
/** 用户ID */
|
||||||
|
userID?: string;
|
||||||
|
/** 用户名称(关联查询) */
|
||||||
|
userName?: string;
|
||||||
|
/** 学习会话ID */
|
||||||
|
sessionID?: string;
|
||||||
|
/** 资源类型(1资源/新闻 2课程 3章节 4节点) */
|
||||||
|
resourceType?: number;
|
||||||
|
/** 资源类型名称 */
|
||||||
|
resourceTypeName?: string;
|
||||||
|
/** 资源ID */
|
||||||
|
resourceID?: string;
|
||||||
|
/** 资源标题(关联查询) */
|
||||||
|
resourceTitle?: string;
|
||||||
|
/** 课程ID */
|
||||||
|
courseID?: string;
|
||||||
|
/** 课程名称(关联查询) */
|
||||||
|
courseName?: string;
|
||||||
|
/** 章节ID */
|
||||||
|
chapterID?: string;
|
||||||
|
/** 章节名称(关联查询) */
|
||||||
|
chapterName?: string;
|
||||||
|
/** 节点ID */
|
||||||
|
nodeID?: string;
|
||||||
|
/** 节点名称(关联查询) */
|
||||||
|
nodeName?: string;
|
||||||
|
/** 关联任务ID */
|
||||||
|
taskID?: string;
|
||||||
|
/** 任务名称(关联查询) */
|
||||||
|
taskName?: string;
|
||||||
|
/** 开始学习时间 */
|
||||||
|
startTime?: string;
|
||||||
|
/** 结束学习时间 */
|
||||||
|
endTime?: string;
|
||||||
|
/** 本次学习时长(秒) */
|
||||||
|
duration?: number;
|
||||||
|
/** 学习时长(格式化后,如:1小时20分钟) */
|
||||||
|
durationFormatted?: string;
|
||||||
|
/** 开始进度(0-100) */
|
||||||
|
startProgress?: number;
|
||||||
|
/** 结束进度(0-100) */
|
||||||
|
endProgress?: number;
|
||||||
|
/** 设备类型 */
|
||||||
|
deviceType?: string;
|
||||||
|
/** 创建时间 */
|
||||||
|
createTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学习统计明细VO
|
||||||
|
*/
|
||||||
|
export interface LearningStatisticsDetailVO {
|
||||||
|
/** 统计日期 */
|
||||||
|
statDate?: string;
|
||||||
|
/** 资源类型(1资源/新闻 2课程 3章节) */
|
||||||
|
resourceType?: number;
|
||||||
|
/** 资源类型名称 */
|
||||||
|
resourceTypeName?: string;
|
||||||
|
/** 资源ID */
|
||||||
|
resourceID?: string;
|
||||||
|
/** 资源标题 */
|
||||||
|
resourceTitle?: string;
|
||||||
|
/** 课程ID */
|
||||||
|
courseID?: string;
|
||||||
|
/** 课程名称 */
|
||||||
|
courseName?: string;
|
||||||
|
/** 章节ID */
|
||||||
|
chapterID?: string;
|
||||||
|
/** 章节名称 */
|
||||||
|
chapterName?: string;
|
||||||
|
/** 学习时长(秒) */
|
||||||
|
totalDuration?: number;
|
||||||
|
/** 学习时长(格式化) */
|
||||||
|
totalDurationFormatted?: string;
|
||||||
|
/** 学习次数 */
|
||||||
|
learnCount?: number;
|
||||||
|
/** 是否完成 */
|
||||||
|
isComplete?: boolean;
|
||||||
|
/** 完成时间 */
|
||||||
|
completeTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学习统计响应VO
|
||||||
|
*/
|
||||||
|
export interface LearningStatisticsVO {
|
||||||
|
/** 用户ID */
|
||||||
|
userID?: string;
|
||||||
|
/** 统计周期(day/week/month) */
|
||||||
|
period?: string;
|
||||||
|
/** 总学习时长(秒) */
|
||||||
|
totalDuration?: number;
|
||||||
|
/** 总学习时长(格式化) */
|
||||||
|
totalDurationFormatted?: string;
|
||||||
|
/** 学习天数 */
|
||||||
|
learnDays?: number;
|
||||||
|
/** 学习资源数量 */
|
||||||
|
resourceCount?: number;
|
||||||
|
/** 学习课程数量 */
|
||||||
|
courseCount?: number;
|
||||||
|
/** 学习次数 */
|
||||||
|
learnCount?: number;
|
||||||
|
/** 完成数量 */
|
||||||
|
completeCount?: number;
|
||||||
|
/** 平均每天学习时长(秒) */
|
||||||
|
avgDailyDuration?: number;
|
||||||
|
/** 学习明细列表 */
|
||||||
|
details?: LearningStatisticsDetailVO[];
|
||||||
|
}
|
||||||
@@ -492,7 +492,7 @@ async function handleViewUsers(row: Achievement) {
|
|||||||
try {
|
try {
|
||||||
achieversLoading.value = true;
|
achieversLoading.value = true;
|
||||||
const result = await achievementApi.getRecentAchievers(
|
const result = await achievementApi.getRecentAchievers(
|
||||||
{ page: 1, size: 100 },
|
{ pageNumber: 1, pageSize: 100 },
|
||||||
{ achievementID: row.achievementID }
|
{ achievementID: row.achievementID }
|
||||||
);
|
);
|
||||||
achieverList.value = result.dataList || [];
|
achieverList.value = result.dataList || [];
|
||||||
|
|||||||
@@ -103,8 +103,8 @@
|
|||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div class="pagination-wrapper" v-if="total > 0">
|
<div class="pagination-wrapper" v-if="total > 0">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pageParam.page"
|
v-model:current-page="pageParam.pageNumber"
|
||||||
v-model:page-size="pageParam.size"
|
v-model:page-size="pageParam.pageSize"
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
:total="total"
|
:total="total"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
@@ -238,8 +238,8 @@ const searchForm = reactive({
|
|||||||
|
|
||||||
// 分页参数
|
// 分页参数
|
||||||
const pageParam = reactive<PageParam>({
|
const pageParam = reactive<PageParam>({
|
||||||
page: 1,
|
pageNumber: 1,
|
||||||
size: 20
|
pageSize: 20
|
||||||
});
|
});
|
||||||
|
|
||||||
// 对话框状态
|
// 对话框状态
|
||||||
@@ -277,7 +277,7 @@ const loadLogList = async () => {
|
|||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadLogList();
|
loadLogList();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,19 +286,19 @@ const handleReset = () => {
|
|||||||
searchForm.taskName = '';
|
searchForm.taskName = '';
|
||||||
searchForm.taskGroup = '';
|
searchForm.taskGroup = '';
|
||||||
searchForm.executeStatus = undefined;
|
searchForm.executeStatus = undefined;
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadLogList();
|
loadLogList();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 分页变化
|
// 分页变化
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
pageParam.page = page;
|
pageParam.pageNumber = page;
|
||||||
loadLogList();
|
loadLogList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSizeChange = (size: number) => {
|
const handleSizeChange = (size: number) => {
|
||||||
pageParam.size = size;
|
pageParam.pageSize = size;
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadLogList();
|
loadLogList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -155,8 +155,8 @@
|
|||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div class="pagination-wrapper" v-if="total > 0">
|
<div class="pagination-wrapper" v-if="total > 0">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pageParam.page"
|
v-model:current-page="pageParam.pageNumber"
|
||||||
v-model:page-size="pageParam.size"
|
v-model:page-size="pageParam.pageSize"
|
||||||
:page-sizes="[9, 18, 36]"
|
:page-sizes="[9, 18, 36]"
|
||||||
:total="total"
|
:total="total"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
@@ -287,8 +287,8 @@ const searchForm = reactive({
|
|||||||
|
|
||||||
// 分页参数
|
// 分页参数
|
||||||
const pageParam = reactive<PageParam>({
|
const pageParam = reactive<PageParam>({
|
||||||
page: 1,
|
pageNumber: 1,
|
||||||
size: 9
|
pageSize: 10
|
||||||
});
|
});
|
||||||
|
|
||||||
// 对话框状态
|
// 对话框状态
|
||||||
@@ -341,7 +341,7 @@ const loadCrawlerList = async () => {
|
|||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadCrawlerList();
|
loadCrawlerList();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -349,19 +349,19 @@ const handleSearch = () => {
|
|||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
searchForm.taskName = '';
|
searchForm.taskName = '';
|
||||||
searchForm.status = undefined;
|
searchForm.status = undefined;
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadCrawlerList();
|
loadCrawlerList();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 分页变化
|
// 分页变化
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
pageParam.page = page;
|
pageParam.pageNumber = page;
|
||||||
loadCrawlerList();
|
loadCrawlerList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSizeChange = (size: number) => {
|
const handleSizeChange = (size: number) => {
|
||||||
pageParam.size = size;
|
pageParam.pageSize = size;
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadCrawlerList();
|
loadCrawlerList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -128,8 +128,8 @@
|
|||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div class="pagination-wrapper" v-if="total > 0">
|
<div class="pagination-wrapper" v-if="total > 0">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pageParam.page"
|
v-model:current-page="pageParam.pageNumber"
|
||||||
v-model:page-size="pageParam.size"
|
v-model:page-size="pageParam.pageSize"
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
:total="total"
|
:total="total"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
@@ -264,8 +264,8 @@ const searchForm = reactive({
|
|||||||
|
|
||||||
// 分页参数
|
// 分页参数
|
||||||
const pageParam = reactive<PageParam>({
|
const pageParam = reactive<PageParam>({
|
||||||
page: 1,
|
pageNumber: 1,
|
||||||
size: 20
|
pageSize: 20
|
||||||
});
|
});
|
||||||
|
|
||||||
// 对话框状态
|
// 对话框状态
|
||||||
@@ -316,7 +316,7 @@ const loadTaskList = async () => {
|
|||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadTaskList();
|
loadTaskList();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -325,19 +325,19 @@ const handleReset = () => {
|
|||||||
searchForm.taskName = '';
|
searchForm.taskName = '';
|
||||||
searchForm.taskGroup = '';
|
searchForm.taskGroup = '';
|
||||||
searchForm.status = undefined;
|
searchForm.status = undefined;
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadTaskList();
|
loadTaskList();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 分页变化
|
// 分页变化
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
pageParam.page = page;
|
pageParam.pageNumber = page;
|
||||||
loadTaskList();
|
loadTaskList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSizeChange = (size: number) => {
|
const handleSizeChange = (size: number) => {
|
||||||
pageParam.size = size;
|
pageParam.pageSize = size;
|
||||||
pageParam.page = 1;
|
pageParam.pageNumber = 1;
|
||||||
loadTaskList();
|
loadTaskList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pageParam.page"
|
v-model:current-page="pageParam.pageNumber"
|
||||||
v-model:page-size="pageParam.size"
|
v-model:page-size="pageParam.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@@ -76,8 +76,8 @@ import { ArticleStatus } from '@/types/enums';
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchKeyword = ref('');
|
const searchKeyword = ref('');
|
||||||
const pageParam = ref<PageParam>({
|
const pageParam = ref<PageParam>({
|
||||||
page: 1,
|
pageNumber: 1,
|
||||||
size: 10
|
pageSize: 10
|
||||||
});
|
});
|
||||||
const filter = ref<ResourceSearchParams>({
|
const filter = ref<ResourceSearchParams>({
|
||||||
keyword: searchKeyword.value
|
keyword: searchKeyword.value
|
||||||
@@ -232,12 +232,12 @@ function getActionButtonText(status: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleSizeChange(val: number) {
|
function handleSizeChange(val: number) {
|
||||||
pageParam.value.size = val;
|
pageParam.value.pageSize = val;
|
||||||
loadArticles();
|
loadArticles();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCurrentChange(val: number) {
|
function handleCurrentChange(val: number) {
|
||||||
pageParam.value.page = val;
|
pageParam.value.pageNumber = val;
|
||||||
loadArticles();
|
loadArticles();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -98,9 +98,9 @@ import { useRouter, useRoute } from 'vue-router';
|
|||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { ArrowLeft } from '@element-plus/icons-vue';
|
import { ArrowLeft } from '@element-plus/icons-vue';
|
||||||
import { resourceApi } from '@/apis/resource';
|
import { resourceApi } from '@/apis/resource';
|
||||||
import { learningRecordApi } from '@/apis/study';
|
import { learningRecordApi, learningHistoryApi } from '@/apis/study';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import type { ResourceCategory, Resource, ResourceVO, LearningRecord } from '@/types';
|
import type { ResourceCategory, Resource, ResourceVO, LearningRecord, TbLearningHistory } from '@/types';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ArticleShowView'
|
name: 'ArticleShowView'
|
||||||
@@ -154,6 +154,11 @@ const totalVideos = ref(0); // 视频总数
|
|||||||
const completedVideos = ref<Set<number>>(new Set()); // 已完成的视频索引
|
const completedVideos = ref<Set<number>>(new Set()); // 已完成的视频索引
|
||||||
const userInfo = computed(() => store.getters['auth/user']);
|
const userInfo = computed(() => store.getters['auth/user']);
|
||||||
|
|
||||||
|
// 学习历史记录相关
|
||||||
|
const learningHistory = ref<TbLearningHistory | null>(null);
|
||||||
|
const historyStartTime = ref(0);
|
||||||
|
const historyTimer = ref<number | null>(null);
|
||||||
|
|
||||||
// 当前显示的文章数据
|
// 当前显示的文章数据
|
||||||
const currentArticleData = computed(() => {
|
const currentArticleData = computed(() => {
|
||||||
// Dialog 模式使用传入的 articleData
|
// Dialog 模式使用传入的 articleData
|
||||||
@@ -182,13 +187,18 @@ onMounted(() => {
|
|||||||
// 如果传入了 articleData,则不需要从路由加载
|
// 如果传入了 articleData,则不需要从路由加载
|
||||||
if (props.articleData && Object.keys(props.articleData).length > 0) {
|
if (props.articleData && Object.keys(props.articleData).length > 0) {
|
||||||
loadedArticleData.value = props.articleData;
|
loadedArticleData.value = props.articleData;
|
||||||
// 即使传入了数据,也要监听视频(如果有taskId)
|
|
||||||
if (taskId || route.query.taskId) {
|
const resourceID = props.articleData.resourceID;
|
||||||
const resourceID = props.articleData.resourceID;
|
if (resourceID) {
|
||||||
if (resourceID) {
|
// 创建学习历史记录(每次进入都创建新记录)
|
||||||
|
createHistoryRecord(resourceID);
|
||||||
|
|
||||||
|
// 如果有taskId,还要创建学习记录
|
||||||
|
if (taskId || route.query.taskId) {
|
||||||
loadLearningRecord(resourceID);
|
loadLearningRecord(resourceID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化视频监听
|
// 初始化视频监听
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -215,12 +225,34 @@ onBeforeUnmount(() => {
|
|||||||
saveLearningProgress();
|
saveLearningProgress();
|
||||||
}
|
}
|
||||||
stopLearningTimer();
|
stopLearningTimer();
|
||||||
|
|
||||||
|
// 保存学习历史记录
|
||||||
|
if (learningHistory.value) {
|
||||||
|
saveHistoryRecord();
|
||||||
|
}
|
||||||
|
stopHistoryTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听 articleData 变化(用于 ResourceArticle 切换文章)
|
// 监听 articleData 变化(用于 ResourceArticle 切换文章)
|
||||||
watch(() => props.articleData, (newData) => {
|
watch(() => props.articleData, async (newData, oldData) => {
|
||||||
if (!props.asDialog && newData && Object.keys(newData).length > 0) {
|
if (!props.asDialog) {
|
||||||
loadedArticleData.value = newData;
|
// 如果从有数据变成null,或者切换到新文章,都需要保存当前历史
|
||||||
|
if (learningHistory.value && (oldData && (!newData || oldData.resourceID !== newData.resourceID))) {
|
||||||
|
await saveHistoryRecord();
|
||||||
|
stopHistoryTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载新文章数据
|
||||||
|
if (newData && Object.keys(newData).length > 0) {
|
||||||
|
loadedArticleData.value = newData;
|
||||||
|
|
||||||
|
// 为新文章创建学习历史记录
|
||||||
|
const resourceID = newData.resourceID;
|
||||||
|
if (resourceID) {
|
||||||
|
await createHistoryRecord(resourceID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 重新初始化视频监听
|
// 重新初始化视频监听
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -243,6 +275,9 @@ async function loadArticle(resourceID: string) {
|
|||||||
// 增加浏览次数
|
// 增加浏览次数
|
||||||
await resourceApi.incrementViewCount(resourceID);
|
await resourceApi.incrementViewCount(resourceID);
|
||||||
|
|
||||||
|
// 创建学习历史记录(每次进入都创建新记录)
|
||||||
|
await createHistoryRecord(resourceID);
|
||||||
|
|
||||||
// 等待 DOM 更新后监听视频(增加延迟确保 DOM 完全渲染)
|
// 等待 DOM 更新后监听视频(增加延迟确保 DOM 完全渲染)
|
||||||
await nextTick();
|
await nextTick();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -472,6 +507,81 @@ function handleVideoEnded(videoIndex: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 学习历史记录功能 ====================
|
||||||
|
|
||||||
|
// 创建学习历史记录
|
||||||
|
async function createHistoryRecord(resourceID: string) {
|
||||||
|
if (!userInfo.value?.id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await learningHistoryApi.recordResourceView(
|
||||||
|
userInfo.value.id,
|
||||||
|
resourceID,
|
||||||
|
0 // 初始时长为0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.success && res.data) {
|
||||||
|
learningHistory.value = res.data;
|
||||||
|
console.log('✅ 学习历史记录创建成功:', learningHistory.value);
|
||||||
|
|
||||||
|
// 开始计时
|
||||||
|
startHistoryTimer();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 创建学习历史记录失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始学习历史计时
|
||||||
|
function startHistoryTimer() {
|
||||||
|
historyStartTime.value = Date.now();
|
||||||
|
|
||||||
|
// 每30秒保存一次学习历史
|
||||||
|
historyTimer.value = window.setInterval(() => {
|
||||||
|
saveHistoryRecord();
|
||||||
|
}, 30000); // 30秒
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止学习历史计时
|
||||||
|
function stopHistoryTimer() {
|
||||||
|
if (historyTimer.value) {
|
||||||
|
clearInterval(historyTimer.value);
|
||||||
|
historyTimer.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存学习历史记录
|
||||||
|
async function saveHistoryRecord() {
|
||||||
|
if (!userInfo.value?.id || !learningHistory.value) return;
|
||||||
|
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const duration = Math.floor((currentTime - historyStartTime.value) / 1000); // 秒
|
||||||
|
|
||||||
|
// 如果时长太短(小于1秒),不保存
|
||||||
|
if (duration < 1) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedHistory: TbLearningHistory = {
|
||||||
|
...learningHistory.value,
|
||||||
|
duration: (learningHistory.value.duration || 0) + duration,
|
||||||
|
endTime: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用API更新学习历史
|
||||||
|
const res = await learningHistoryApi.recordLearningHistory(updatedHistory);
|
||||||
|
|
||||||
|
if (res.success && res.data) {
|
||||||
|
learningHistory.value = res.data;
|
||||||
|
console.log(`💾 学习历史已保存 - 累计时长: ${learningHistory.value.duration}秒`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置开始时间
|
||||||
|
historyStartTime.value = currentTime;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 保存学习历史失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化日期(简单格式:YYYY-MM-DD)
|
// 格式化日期(简单格式:YYYY-MM-DD)
|
||||||
function formatDateSimple(date: string | Date): string {
|
function formatDateSimple(date: string | Date): string {
|
||||||
if (!date) return '';
|
if (!date) return '';
|
||||||
@@ -486,6 +596,12 @@ function formatDateSimple(date: string | Date): string {
|
|||||||
|
|
||||||
// 关闭处理
|
// 关闭处理
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
|
// 非Dialog模式下关闭时保存学习历史
|
||||||
|
if (!props.asDialog && learningHistory.value) {
|
||||||
|
saveHistoryRecord();
|
||||||
|
stopHistoryTimer();
|
||||||
|
}
|
||||||
|
|
||||||
if (props.asDialog) {
|
if (props.asDialog) {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,11 +221,11 @@ import {
|
|||||||
} from '@element-plus/icons-vue';
|
} from '@element-plus/icons-vue';
|
||||||
import { ArticleShowView } from '@/views/article';
|
import { ArticleShowView } from '@/views/article';
|
||||||
import { courseApi } from '@/apis/study';
|
import { courseApi } from '@/apis/study';
|
||||||
import { learningRecordApi } from '@/apis/study';
|
import { learningRecordApi, learningHistoryApi } from '@/apis/study';
|
||||||
import { resourceApi } from '@/apis/resource';
|
import { resourceApi } from '@/apis/resource';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
import type { CourseVO, LearningRecord } from '@/types';
|
import type { CourseVO, LearningRecord, TbLearningHistory } from '@/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
courseId: string;
|
courseId: string;
|
||||||
@@ -269,6 +269,11 @@ const previousNodeKey = ref<string | null>(null);
|
|||||||
const totalRichTextVideos = ref(0);
|
const totalRichTextVideos = ref(0);
|
||||||
const completedRichTextVideos = ref<Set<number>>(new Set());
|
const completedRichTextVideos = ref<Set<number>>(new Set());
|
||||||
|
|
||||||
|
// 学习历史记录
|
||||||
|
const learningHistory = ref<TbLearningHistory | null>(null);
|
||||||
|
const historyStartTime = ref<number>(0);
|
||||||
|
const historyTimer = ref<number | null>(null);
|
||||||
|
|
||||||
// 当前节点
|
// 当前节点
|
||||||
const currentNode = computed(() => {
|
const currentNode = computed(() => {
|
||||||
if (!courseVO.value || !courseVO.value.courseChapters) return null;
|
if (!courseVO.value || !courseVO.value.courseChapters) return null;
|
||||||
@@ -346,9 +351,18 @@ watch(() => [props.chapterIndex, props.nodeIndex], () => {
|
|||||||
loadNodeContent();
|
loadNodeContent();
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
watch(currentNode, () => {
|
watch(currentNode, async () => {
|
||||||
|
// 保存上一个节点的学习历史记录
|
||||||
|
if (learningHistory.value) {
|
||||||
|
await saveHistoryRecord();
|
||||||
|
stopHistoryTimer();
|
||||||
|
}
|
||||||
|
|
||||||
if (currentNode.value) {
|
if (currentNode.value) {
|
||||||
loadNodeContent();
|
loadNodeContent();
|
||||||
|
|
||||||
|
// 为新节点创建学习历史记录
|
||||||
|
await createHistoryRecord();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -359,6 +373,12 @@ onMounted(() => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
stopLearningTimer();
|
stopLearningTimer();
|
||||||
saveLearningProgress();
|
saveLearningProgress();
|
||||||
|
|
||||||
|
// 保存学习历史记录
|
||||||
|
if (learningHistory.value) {
|
||||||
|
saveHistoryRecord();
|
||||||
|
}
|
||||||
|
stopHistoryTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载课程
|
// 加载课程
|
||||||
@@ -807,9 +827,97 @@ function toggleSidebar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 返回
|
// 返回
|
||||||
|
// ==================== 学习历史记录功能 ====================
|
||||||
|
|
||||||
|
// 创建学习历史记录
|
||||||
|
async function createHistoryRecord() {
|
||||||
|
if (!userInfo.value?.id || !courseVO.value || !currentNode.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const chapterVO = courseVO.value.courseChapters[currentChapterIndex.value];
|
||||||
|
const chapter = chapterVO?.chapter;
|
||||||
|
const node = currentNode.value;
|
||||||
|
|
||||||
|
const res = await learningHistoryApi.recordCourseLearn(
|
||||||
|
userInfo.value.id,
|
||||||
|
props.courseId,
|
||||||
|
chapter?.chapterID,
|
||||||
|
node.nodeID,
|
||||||
|
0 // 初始时长为0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.success && res.data) {
|
||||||
|
learningHistory.value = res.data;
|
||||||
|
console.log('✅ 课程学习历史记录创建成功:', learningHistory.value);
|
||||||
|
|
||||||
|
// 开始计时
|
||||||
|
startHistoryTimer();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 创建课程学习历史记录失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始学习历史计时
|
||||||
|
function startHistoryTimer() {
|
||||||
|
historyStartTime.value = Date.now();
|
||||||
|
|
||||||
|
// 每30秒保存一次学习历史
|
||||||
|
historyTimer.value = window.setInterval(() => {
|
||||||
|
saveHistoryRecord();
|
||||||
|
}, 30000); // 30秒
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止学习历史计时
|
||||||
|
function stopHistoryTimer() {
|
||||||
|
if (historyTimer.value) {
|
||||||
|
clearInterval(historyTimer.value);
|
||||||
|
historyTimer.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存学习历史记录
|
||||||
|
async function saveHistoryRecord() {
|
||||||
|
if (!userInfo.value?.id || !learningHistory.value) return;
|
||||||
|
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const duration = Math.floor((currentTime - historyStartTime.value) / 1000); // 秒
|
||||||
|
|
||||||
|
// 如果时长太短(小于1秒),不保存
|
||||||
|
if (duration < 1) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedHistory: TbLearningHistory = {
|
||||||
|
...learningHistory.value,
|
||||||
|
duration: (learningHistory.value.duration || 0) + duration,
|
||||||
|
endTime: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用API更新学习历史
|
||||||
|
const res = await learningHistoryApi.recordLearningHistory(updatedHistory);
|
||||||
|
|
||||||
|
if (res.success && res.data) {
|
||||||
|
learningHistory.value = res.data;
|
||||||
|
console.log(`💾 课程学习历史已保存 - 累计时长: ${learningHistory.value.duration}秒 - 节点: ${currentNode.value?.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置开始时间
|
||||||
|
historyStartTime.value = currentTime;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 保存课程学习历史失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
stopLearningTimer();
|
stopLearningTimer();
|
||||||
saveLearningProgress();
|
saveLearningProgress();
|
||||||
|
|
||||||
|
// 保存学习历史记录
|
||||||
|
if (learningHistory.value) {
|
||||||
|
saveHistoryRecord();
|
||||||
|
}
|
||||||
|
stopHistoryTimer();
|
||||||
|
|
||||||
emit('back');
|
emit('back');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
@category-change="handleCategoryChange"
|
@category-change="handleCategoryChange"
|
||||||
/>
|
/>
|
||||||
<ResourceList
|
<ResourceList
|
||||||
v-show="!showArticle"
|
v-if="!showArticle"
|
||||||
ref="resourceListRef"
|
ref="resourceListRef"
|
||||||
:category-id="currentCategoryId"
|
:category-id="currentCategoryId"
|
||||||
:search-keyword="searchKeyword"
|
:search-keyword="searchKeyword"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
@list-updated="handleListUpdated"
|
@list-updated="handleListUpdated"
|
||||||
/>
|
/>
|
||||||
<ResourceArticle
|
<ResourceArticle
|
||||||
v-show="showArticle"
|
v-if="showArticle"
|
||||||
:resource-id="currentResourceId"
|
:resource-id="currentResourceId"
|
||||||
:category-id="currentCategoryId"
|
:category-id="currentCategoryId"
|
||||||
:resource-list="resourceList"
|
:resource-list="resourceList"
|
||||||
|
|||||||
@@ -80,8 +80,8 @@
|
|||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div v-if="total > 0" class="pagination-container">
|
<div v-if="total > 0" class="pagination-container">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pageParam.page"
|
v-model:current-page="pageParam.pageNumber"
|
||||||
v-model:page-size="pageParam.size"
|
v-model:page-size="pageParam.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
:page-sizes="[6, 12, 24, 48]"
|
:page-sizes="[6, 12, 24, 48]"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
@@ -117,8 +117,8 @@ const total = ref(0);
|
|||||||
|
|
||||||
// 分页参数
|
// 分页参数
|
||||||
const pageParam = ref<PageParam>({
|
const pageParam = ref<PageParam>({
|
||||||
page: 1,
|
pageNumber: 1,
|
||||||
size: 6
|
pageSize: 6
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user