数据统计

This commit is contained in:
2025-10-30 18:55:40 +08:00
parent 0935ec5ec5
commit a881f57e30
19 changed files with 1587 additions and 162 deletions

View File

@@ -5,6 +5,7 @@ import org.xyzh.common.dto.study.TbLearningRecord;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* @description 学习记录服务接口
@@ -110,4 +111,22 @@ public interface LearningRecordService {
* @since 2025-10-24
*/
ResultDomain<TbLearningRecord> getCourseLearningRecord(TbLearningRecord learningRecord);
// ----------------学习记录统计相关--------------------------------
/**
* @description 获取学习记录统计图表数据
* @return ResultDomain<Map<String, Object>> 图表数据(本周课程和文章的总学习时长)
* @author yslg
* @since 2025-10-30
*/
ResultDomain<Map<String, Object>> getStudyRecordsCharts();
/**
* @description 获取学习记录排行榜数据
* @return ResultDomain<Map<String, Object>> 排行榜数据(学习时长排行榜、课程排行榜、文章排行榜、任务完成排行榜)
* @author yslg
* @since 2025-10-30
*/
ResultDomain<Map<String, Object>> getStudyRecordsRankings();
}

View File

@@ -9,6 +9,7 @@ import org.xyzh.common.vo.TaskVO;
import org.xyzh.common.dto.study.TbTaskItem;
import java.util.List;
import java.util.Map;
/**
* @description 学习任务服务接口
@@ -216,4 +217,24 @@ public interface LearningTaskService {
* @since 2025-10-15
*/
ResultDomain<Boolean> removeTaskResource(String taskID, String resourceID);
// ----------------任务统计相关--------------------------------
/**
* @description 获取任务统计图表数据
* @param taskID 任务ID
* @return ResultDomain<Map<String, Object>> 图表数据(学习时长分布、学习进度分布)
* @author yslg
* @since 2025-10-30
*/
ResultDomain<Map<String, Object>> getTaskStatisticsCharts(String taskID);
/**
* @description 获取任务排行榜数据
* @param taskID 任务ID
* @return ResultDomain<Map<String, Object>> 排行榜数据(完成时间排行榜、学习时长排行榜)
* @author yslg
* @since 2025-10-30
*/
ResultDomain<Map<String, Object>> getTaskStatisticsRankings(String taskID);
}

View File

@@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Map;
/**
* @description 学习记录控制器
@@ -72,4 +74,22 @@ public class LearningRecordController {
return learningRecordService.getCourseLearningRecord(learningRecord);
}
/**
* 获取学习记录统计图表数据
* @return 图表数据(本周课程和文章的总学习时长)
*/
@GetMapping("/statistics/charts")
public ResultDomain<Map<String, Object>> getStudyRecordsCharts() {
return learningRecordService.getStudyRecordsCharts();
}
/**
* 获取学习记录排行榜数据
* @return 排行榜数据(学习时长排行榜、课程排行榜、文章排行榜、任务完成排行榜)
*/
@GetMapping("/statistics/rankings")
public ResultDomain<Map<String, Object>> getStudyRecordsRankings() {
return learningRecordService.getStudyRecordsRankings();
}
}

View File

@@ -160,6 +160,24 @@ public class LearningTaskController {
return learningTaskService.getUserProgress(userID);
}
/**
* 获取任务统计图表数据
* @param taskID 任务ID
* @return 包含学习时长分布和学习进度分布的Map数据
*/
@GetMapping("/{taskID}/statistics/charts")
public ResultDomain<Map<String, Object>> getTaskStatisticsCharts(@PathVariable("taskID") String taskID) {
return learningTaskService.getTaskStatisticsCharts(taskID);
}
/**
* 获取任务排行榜数据
* @param taskID 任务ID
* @return 包含完成时间排行榜和学习时长排行榜的Map数据
*/
@GetMapping("/{taskID}/statistics/rankings")
public ResultDomain<Map<String, Object>> getTaskStatisticsRankings(@PathVariable("taskID") String taskID) {
return learningTaskService.getTaskStatisticsRankings(taskID);
}
}

View File

@@ -5,8 +5,10 @@ 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.TbLearningRecord;
import org.xyzh.common.vo.UserDeptRoleVO;
import java.util.List;
import java.util.Map;
/**
* @description LearningRecordMapper.java文件描述 学习记录数据访问层
@@ -145,4 +147,51 @@ public interface LearningRecordMapper extends BaseMapper<TbLearningRecord> {
* @since 2025-10-15
*/
long countLearningRecords(@Param("filter") TbLearningRecord filter);
// ----------------学习记录统计相关--------------------------------
/**
* @description 获取本周课程和文章的总学习时长统计
* @param userDeptRoles 用户部门角色信息
* @return List<Map<String, Object>> 学习时长统计数据
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getWeeklyStudyDurationByType(@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
/**
* @description 获取本周学习时长排行榜
* @param userDeptRoles 用户部门角色信息
* @return List<Map<String, Object>> 学习时长排行榜
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getWeeklyStudyDurationRanking(@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
/**
* @description 获取本周学习课程排行榜
* @param userDeptRoles 用户部门角色信息
* @return List<Map<String, Object>> 课程学习排行榜
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getWeeklyCourseRanking(@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
/**
* @description 获取本周学习文章排行榜
* @param userDeptRoles 用户部门角色信息
* @return List<Map<String, Object>> 文章学习排行榜
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getWeeklyArticleRanking(@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
/**
* @description 获取本周任务完成最多的排行榜
* @param userDeptRoles 用户部门角色信息
* @return List<Map<String, Object>> 任务完成排行榜
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getWeeklyTaskCompletionRanking(@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
}

View File

@@ -8,6 +8,7 @@ import org.xyzh.common.dto.study.TbTaskUser;
import org.xyzh.common.vo.TaskItemVO;
import java.util.List;
import java.util.Map;
/**
* @description TaskUserMapper.java文件描述 任务用户数据访问层
@@ -179,4 +180,40 @@ public interface TaskUserMapper extends BaseMapper<TbTaskUser> {
* @since 2025-10-15
*/
long countTaskUsers(@Param("filter") TbTaskUser filter);
/**
* @description 获取任务学习时长分布数据
* @param taskId 任务ID
* @return List<Map<String, Object>> 学习时长分布数据
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getStudyDurationDistribution(@Param("taskId") String taskId);
/**
* @description 获取任务学习进度分布数据
* @param taskId 任务ID
* @return List<Map<String, Object>> 学习进度分布数据
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getStudyProgressDistribution(@Param("taskId") String taskId);
/**
* @description 获取任务完成时间排行榜前10名
* @param taskId 任务ID
* @return List<Map<String, Object>> 完成时间排行榜
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getCompletionTimeRanking(@Param("taskId") String taskId);
/**
* @description 获取任务学习时长排行榜前10名
* @param taskId 任务ID
* @return List<Map<String, Object>> 学习时长排行榜
* @author yslg
* @since 2025-10-30
*/
List<Map<String, Object>> getStudyDurationRanking(@Param("taskId") String taskId);
}

View File

@@ -2,7 +2,9 @@ package org.xyzh.study.service.impl;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,6 +17,7 @@ import org.xyzh.common.dto.study.TbTaskItem;
import org.xyzh.common.dto.study.TbTaskUser;
import org.xyzh.common.dto.user.TbSysUser;
import org.xyzh.common.vo.TaskItemVO;
import org.xyzh.common.vo.UserDeptRoleVO;
import org.xyzh.api.study.record.LearningRecordService;
import org.xyzh.study.mapper.LearningRecordMapper;
import org.xyzh.study.mapper.TaskItemMapper;
@@ -272,8 +275,57 @@ public class SCLearningRecordServiceImpl implements LearningRecordService {
}
}
@Override
public ResultDomain<Map<String, Object>> getStudyRecordsCharts() {
ResultDomain<Map<String, Object>> resultDomain = new ResultDomain<>();
try {
Map<String, Object> chartsData = new HashMap<>();
// 获取当前用户的部门角色信息
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
// 获取本周课程和文章的总学习时长统计
List<Map<String, Object>> durationByType = learningRecordMapper.getWeeklyStudyDurationByType(userDeptRoles);
chartsData.put("durationByType", durationByType);
resultDomain.success("获取学习记录图表数据成功", chartsData);
} catch (Exception e) {
logger.error("获取学习记录图表数据失败", e);
resultDomain.fail("获取学习记录图表数据失败:" + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<Map<String, Object>> getStudyRecordsRankings() {
ResultDomain<Map<String, Object>> resultDomain = new ResultDomain<>();
try {
Map<String, Object> rankingsData = new HashMap<>();
// 获取当前用户的部门角色信息
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
// 获取本周学习时长排行榜
List<Map<String, Object>> durationRanking = learningRecordMapper.getWeeklyStudyDurationRanking(userDeptRoles);
rankingsData.put("durationRanking", durationRanking);
// 获取本周学习课程排行榜
List<Map<String, Object>> courseRanking = learningRecordMapper.getWeeklyCourseRanking(userDeptRoles);
rankingsData.put("courseRanking", courseRanking);
// 获取本周学习文章排行榜
List<Map<String, Object>> articleRanking = learningRecordMapper.getWeeklyArticleRanking(userDeptRoles);
rankingsData.put("articleRanking", articleRanking);
// 获取本周任务完成最多的排行榜
List<Map<String, Object>> taskCompletionRanking = learningRecordMapper.getWeeklyTaskCompletionRanking(userDeptRoles);
rankingsData.put("taskCompletionRanking", taskCompletionRanking);
resultDomain.success("获取学习记录排行榜数据成功", rankingsData);
} catch (Exception e) {
logger.error("获取学习记录排行榜数据失败", e);
resultDomain.fail("获取学习记录排行榜数据失败:" + e.getMessage());
}
return resultDomain;
}
}

View File

@@ -3,6 +3,7 @@ package org.xyzh.study.service.impl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -690,4 +691,48 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
resultDomain.success("获取用户整体学习进度成功", taskVO);
return resultDomain;
}
@Override
public ResultDomain<Map<String, Object>> getTaskStatisticsCharts(String taskID) {
ResultDomain<Map<String, Object>> resultDomain = new ResultDomain<>();
try {
Map<String, Object> chartsData = new HashMap<>();
// 获取学习时长分布数据
List<Map<String, Object>> durationDistribution = taskUserMapper.getStudyDurationDistribution(taskID);
chartsData.put("durationDistribution", durationDistribution);
// 获取学习进度分布数据
List<Map<String, Object>> progressDistribution = taskUserMapper.getStudyProgressDistribution(taskID);
chartsData.put("progressDistribution", progressDistribution);
resultDomain.success("获取任务统计图表数据成功", chartsData);
} catch (Exception e) {
logger.error("获取任务统计图表数据失败", e);
resultDomain.fail("获取任务统计图表数据失败:" + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<Map<String, Object>> getTaskStatisticsRankings(String taskID) {
ResultDomain<Map<String, Object>> resultDomain = new ResultDomain<>();
try {
Map<String, Object> rankingsData = new HashMap<>();
// 获取完成时间排行榜前10名
List<Map<String, Object>> completionTimeRanking = taskUserMapper.getCompletionTimeRanking(taskID);
rankingsData.put("completionTimeRanking", completionTimeRanking);
// 获取学习时长排行榜前10名
List<Map<String, Object>> durationRanking = taskUserMapper.getStudyDurationRanking(taskID);
rankingsData.put("durationRanking", durationRanking);
resultDomain.success("获取任务排行榜数据成功", rankingsData);
} catch (Exception e) {
logger.error("获取任务排行榜数据失败", e);
resultDomain.fail("获取任务排行榜数据失败:" + e.getMessage());
}
return resultDomain;
}
}

View File

@@ -204,4 +204,157 @@
<include refid="Where_Clause" />
</select>
<!-- 获取本周课程和文章的总学习时长统计 -->
<select id="getWeeklyStudyDurationByType" resultType="map">
SELECT
CASE
WHEN lr.resource_type = 2 THEN '课程'
WHEN lr.resource_type = 1 THEN '文章'
ELSE '其他'
END AS resourceType,
COUNT(DISTINCT lr.user_id) AS userCount,
COALESCE(SUM(lr.duration), 0) AS totalDuration,
COUNT(*) AS studyCount
FROM tb_learning_record lr
INNER JOIN tb_sys_user u ON lr.user_id = u.id AND u.deleted = 0
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
INNER JOIN tb_sys_dept user_dept ON udr.dept_id = user_dept.dept_id AND user_dept.deleted = 0
INNER JOIN (
SELECT dept_id, dept_path FROM tb_sys_dept WHERE deleted = 0 AND dept_id IN (
<foreach collection="userDeptRoles" item="udr" separator=",">
#{udr.deptID}
</foreach>
)
) current_dept ON user_dept.dept_path LIKE CONCAT(current_dept.dept_path, '%')
</if>
WHERE lr.deleted = 0
AND lr.last_learn_time >= DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY)
AND lr.last_learn_time &lt; DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY), INTERVAL 7 DAY)
GROUP BY lr.resource_type
ORDER BY totalDuration DESC
</select>
<!-- 获取本周学习时长排行榜 -->
<select id="getWeeklyStudyDurationRanking" resultType="map">
SELECT
lr.user_id AS userId,
u.username AS username,
COALESCE(SUM(lr.duration), 0) AS totalDuration,
COUNT(DISTINCT lr.resource_id) AS resourceCount,
COUNT(*) AS studyCount
FROM tb_learning_record lr
INNER JOIN tb_sys_user u ON lr.user_id = u.id AND u.deleted = 0
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
INNER JOIN tb_sys_dept user_dept ON udr.dept_id = user_dept.dept_id AND user_dept.deleted = 0
INNER JOIN (
SELECT dept_id, dept_path FROM tb_sys_dept WHERE deleted = 0 AND dept_id IN (
<foreach collection="userDeptRoles" item="udr" separator=",">
#{udr.deptID}
</foreach>
)
) current_dept ON user_dept.dept_path LIKE CONCAT(current_dept.dept_path, '%')
</if>
WHERE lr.deleted = 0
AND lr.last_learn_time >= DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY)
AND lr.last_learn_time &lt; DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY), INTERVAL 7 DAY)
GROUP BY lr.user_id, u.username
ORDER BY totalDuration DESC
LIMIT 10
</select>
<!-- 获取本周学习课程排行榜 -->
<select id="getWeeklyCourseRanking" resultType="map">
SELECT
lr.resource_id AS resourceId,
c.name AS resourceName,
COUNT(DISTINCT lr.user_id) AS learnerCount,
COALESCE(SUM(lr.duration), 0) AS totalDuration,
COUNT(*) AS studyCount
FROM tb_learning_record lr
INNER JOIN tb_course c ON lr.resource_id = c.course_id AND c.deleted = 0
INNER JOIN tb_sys_user u ON lr.user_id = u.id AND u.deleted = 0
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
INNER JOIN tb_sys_dept user_dept ON udr.dept_id = user_dept.dept_id AND user_dept.deleted = 0
INNER JOIN (
SELECT dept_id, dept_path FROM tb_sys_dept WHERE deleted = 0 AND dept_id IN (
<foreach collection="userDeptRoles" item="udr" separator=",">
#{udr.deptID}
</foreach>
)
) current_dept ON user_dept.dept_path LIKE CONCAT(current_dept.dept_path, '%')
</if>
WHERE lr.deleted = 0
AND lr.resource_type = 2
AND lr.last_learn_time >= DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY)
AND lr.last_learn_time &lt; DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY), INTERVAL 7 DAY)
GROUP BY lr.resource_id, c.name
ORDER BY learnerCount DESC, totalDuration DESC
LIMIT 10
</select>
<!-- 获取本周学习文章排行榜 -->
<select id="getWeeklyArticleRanking" resultType="map">
SELECT
lr.resource_id AS resourceId,
r.title AS resourceName,
COUNT(DISTINCT lr.user_id) AS learnerCount,
COALESCE(SUM(lr.duration), 0) AS totalDuration,
COUNT(*) AS studyCount
FROM tb_learning_record lr
INNER JOIN tb_resource r ON lr.resource_id = r.resource_id AND r.deleted = 0
INNER JOIN tb_sys_user u ON lr.user_id = u.id AND u.deleted = 0
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
INNER JOIN tb_sys_dept user_dept ON udr.dept_id = user_dept.dept_id AND user_dept.deleted = 0
INNER JOIN (
SELECT dept_id, dept_path FROM tb_sys_dept WHERE deleted = 0 AND dept_id IN (
<foreach collection="userDeptRoles" item="udr" separator=",">
#{udr.deptID}
</foreach>
)
) current_dept ON user_dept.dept_path LIKE CONCAT(current_dept.dept_path, '%')
</if>
WHERE lr.deleted = 0
AND lr.resource_type = 1
AND lr.last_learn_time >= DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY)
AND lr.last_learn_time &lt; DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY), INTERVAL 7 DAY)
GROUP BY lr.resource_id, r.title
ORDER BY learnerCount DESC, totalDuration DESC
LIMIT 10
</select>
<!-- 获取本周任务完成最多的排行榜 -->
<select id="getWeeklyTaskCompletionRanking" resultType="map">
SELECT
tu.user_id AS userId,
u.username AS username,
COUNT(DISTINCT tu.task_id) AS completedTaskCount,
COALESCE(SUM(lr.duration), 0) AS totalDuration,
MAX(tu.complete_time) AS lastCompleteTime
FROM tb_task_user tu
INNER JOIN tb_sys_user u ON tu.user_id = u.id AND u.deleted = 0
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
INNER JOIN tb_sys_dept user_dept ON udr.dept_id = user_dept.dept_id AND user_dept.deleted = 0
INNER JOIN (
SELECT dept_id, dept_path FROM tb_sys_dept WHERE deleted = 0 AND dept_id IN (
<foreach collection="userDeptRoles" item="udr" separator=",">
#{udr.deptID}
</foreach>
)
) current_dept ON user_dept.dept_path LIKE CONCAT(current_dept.dept_path, '%')
</if>
LEFT JOIN tb_learning_record lr ON tu.user_id = lr.user_id AND tu.task_id = lr.task_id AND lr.deleted = 0
WHERE tu.deleted = 0
AND tu.status = 2
AND tu.complete_time >= DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY)
AND tu.complete_time &lt; DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFWEEK(CURDATE())-1 DAY), INTERVAL 7 DAY)
GROUP BY tu.user_id, u.username
ORDER BY completedTaskCount DESC, totalDuration DESC
LIMIT 10
</select>
</mapper>

View File

@@ -263,4 +263,84 @@
<include refid="Where_Clause" />
</select>
<!-- 获取任务学习时长分布数据 -->
<select id="getStudyDurationDistribution" resultType="map">
SELECT
CASE
WHEN total_duration &lt; 600 THEN '0-10分钟'
WHEN total_duration &lt; 1800 THEN '10-30分钟'
WHEN total_duration &lt; 3600 THEN '30-60分钟'
WHEN total_duration &lt; 7200 THEN '1-2小时'
WHEN total_duration &lt; 14400 THEN '2-4小时'
ELSE '4小时以上'
END AS durationRange,
COUNT(*) AS userCount
FROM (
SELECT
lh.user_id,
COALESCE(SUM(lh.duration), 0) AS total_duration
FROM tb_task_user tu
LEFT JOIN tb_learning_history lh ON tu.user_id = lh.user_id AND tu.task_id = lh.task_id AND lh.deleted = 0
WHERE tu.task_id = #{taskId} AND tu.deleted = 0
GROUP BY lh.user_id
) AS user_durations
GROUP BY durationRange
ORDER BY MIN(total_duration)
</select>
<!-- 获取任务学习进度分布数据 -->
<select id="getStudyProgressDistribution" resultType="map">
SELECT
CASE
WHEN progress &lt; 10 THEN '0-10%'
WHEN progress &lt; 20 THEN '10-20%'
WHEN progress &lt; 30 THEN '20-30%'
WHEN progress &lt; 40 THEN '30-40%'
WHEN progress &lt; 50 THEN '40-50%'
WHEN progress &lt; 60 THEN '50-60%'
WHEN progress &lt; 70 THEN '60-70%'
WHEN progress &lt; 80 THEN '70-80%'
WHEN progress &lt; 90 THEN '80-90%'
WHEN progress &lt; 100 THEN '90-100%'
ELSE '100%'
END AS progressRange,
COUNT(*) AS userCount
FROM tb_task_user
WHERE task_id = #{taskId} AND deleted = 0
GROUP BY progressRange
ORDER BY MIN(progress)
</select>
<!-- 获取任务完成时间排行榜前10名 -->
<select id="getCompletionTimeRanking" resultType="map">
SELECT
tu.user_id AS userId,
u.username AS username,
tu.complete_time AS completeTime,
TIMESTAMPDIFF(SECOND, tu.create_time, tu.complete_time) AS completionDuration
FROM tb_task_user tu
INNER JOIN tb_sys_user u ON tu.user_id = u.id AND u.deleted = 0
WHERE tu.task_id = #{taskId}
AND tu.status = 2
AND tu.complete_time IS NOT NULL
AND tu.deleted = 0
ORDER BY tu.complete_time ASC
LIMIT 10
</select>
<!-- 获取任务学习时长排行榜前10名 -->
<select id="getStudyDurationRanking" resultType="map">
SELECT
lh.user_id AS userId,
u.username AS username,
COALESCE(SUM(lh.duration), 0) AS totalDuration
FROM tb_task_user tu
INNER JOIN tb_sys_user u ON tu.user_id = u.id AND u.deleted = 0
LEFT JOIN tb_learning_history lh ON tu.user_id = lh.user_id AND tu.task_id = lh.task_id AND lh.deleted = 0
WHERE tu.task_id = #{taskId} AND tu.deleted = 0
GROUP BY lh.user_id, u.username
ORDER BY totalDuration DESC
LIMIT 10
</select>
</mapper>