feat:导出报名记录接口、资源学习列表接口

This commit is contained in:
CHINAMI-GR51T7H\EDY
2025-11-25 20:57:47 +08:00
parent a694a379f4
commit 72ebb761f5
12 changed files with 417 additions and 36 deletions

View File

@@ -1,14 +1,6 @@
package com.xboe.data.outside; package com.xboe.data.outside;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import com.boe.feign.api.serverall.entity.UserData; import com.boe.feign.api.serverall.entity.UserData;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.xboe.core.SysConstant; import com.xboe.core.SysConstant;
@@ -16,9 +8,17 @@ import com.xboe.core.api.TokenProxy;
import com.xboe.core.utils.OkHttpUtil; import com.xboe.core.utils.OkHttpUtil;
import com.xboe.data.dto.AudienceUser; import com.xboe.data.dto.AudienceUser;
import com.xboe.data.dto.UserOrgIds; import com.xboe.data.dto.UserOrgIds;
import com.xboe.module.usergroup.dao.UserGroupItemDao;
import com.xboe.system.organization.service.IOrganizationService;
import com.xboe.system.user.service.IUserService;
import com.xboe.system.user.vo.UserSimpleVo; import com.xboe.system.user.vo.UserSimpleVo;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@Service @Service
@Slf4j @Slf4j
@@ -30,6 +30,15 @@ public class OutSideDataServiceImpl implements IOutSideDataService {
@Autowired @Autowired
private OkHttpUtil okHttpUtil; private OkHttpUtil okHttpUtil;
@Autowired
private UserGroupItemDao userGroupItemDao;
@Autowired
private IUserService userService;
@Autowired
private IOrganizationService organizationService;
private String getNodeText(JsonNode jn) { private String getNodeText(JsonNode jn) {
if(jn!=null) { if(jn!=null) {
@@ -402,7 +411,6 @@ public class OutSideDataServiceImpl implements IOutSideDataService {
return uids; return uids;
} }
@Override @Override
public void updateUser(String aid, String avatar, String sign) { public void updateUser(String aid, String avatar, String sign) {

View File

@@ -9,11 +9,7 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Opt; import cn.hutool.core.lang.Opt;
import com.boe.feign.api.serverall.entity.UserData; import com.boe.feign.api.serverall.entity.UserData;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.xboe.api.ThirdApi; import com.xboe.api.ThirdApi;
import com.xboe.core.orm.FieldFilters;
import com.xboe.core.orm.IFieldFilter;
import com.xboe.core.orm.QueryBuilder;
import com.xboe.data.outside.IOutSideDataService; import com.xboe.data.outside.IOutSideDataService;
import com.xboe.module.course.entity.CourseTag; import com.xboe.module.course.entity.CourseTag;
import com.xboe.module.course.service.*; import com.xboe.module.course.service.*;

View File

@@ -1,11 +1,9 @@
package com.xboe.module.interaction.api; package com.xboe.module.interaction.api;
import java.time.ZoneId;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.xboe.common.utils.StringUtil; import com.xboe.common.utils.StringUtil;
import com.xboe.core.CurrentUser;
import com.xboe.module.interaction.dto.*; import com.xboe.module.interaction.dto.*;
import com.xboe.module.qa.entity.Question; import com.xboe.module.qa.entity.Question;
import com.xboe.module.qa.service.IQuestionService; import com.xboe.module.qa.service.IQuestionService;
@@ -25,8 +23,6 @@ import com.xboe.module.interaction.service.IFavoritesService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.persistence.Cacheable;
/** /**
* 收藏处理 * 收藏处理
* *

View File

@@ -25,10 +25,7 @@ import com.xboe.module.teacher.service.ITeacherService;
import com.xboe.module.usergroup.service.IUserGroupService; import com.xboe.module.usergroup.service.IUserGroupService;
import com.xboe.school.study.dto.*; import com.xboe.school.study.dto.*;
import com.xboe.school.study.entity.*; import com.xboe.school.study.entity.*;
import com.xboe.school.study.service.IStudyCourseService; import com.xboe.school.study.service.*;
import com.xboe.school.study.service.IStudyExamService;
import com.xboe.school.study.service.IStudyService;
import com.xboe.school.study.service.IStudySignupService;
import com.xboe.school.vo.StudyTimeVo; import com.xboe.school.vo.StudyTimeVo;
import com.xboe.system.organization.service.IOrganizationService; import com.xboe.system.organization.service.IOrganizationService;
import com.xboe.system.user.entity.User; import com.xboe.system.user.entity.User;
@@ -48,6 +45,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream; import java.io.OutputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.net.URLEncoder;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -92,6 +90,9 @@ public class StudyCourseApi extends ApiBaseController{
@Autowired @Autowired
private IUserGroupService userGroupService; private IUserGroupService userGroupService;
@Autowired
private ICourseSignService courseSignService;
@Autowired @Autowired
StringRedisTemplate redisTemplate; StringRedisTemplate redisTemplate;
@Resource @Resource
@@ -112,6 +113,152 @@ public class StudyCourseApi extends ApiBaseController{
@Autowired @Autowired
private EntityManager entityManager; private EntityManager entityManager;
/**
* 用于查询资源学习列表
*
* @param pager 分页参数
* @param courseId 25.11.25新增
*
*/
@RequestMapping(value = "/pagelist-resource", method = {RequestMethod.GET, RequestMethod.POST})
public JsonResponse<PageList<CourseFinishCountDto>> findPageResource(Pagination pager, String courseId) {
if (StringUtil.isBlank(courseId)) {
return error("课程id不能为空");
}
try {
PageList<CourseFinishCountDto> courseFinishCountDTOs = service.findPageResource(pager.getPageIndex(), pager.getPageSize(), courseId);
return success(courseFinishCountDTOs);
} catch (Exception e) {
log.error("查询课程学习记录错误:{}", e.getMessage());
return error("查询失败", e.getMessage());
}
}
/**
* 导出课程报名记录
* 数据维度:(单门课程、多个学生)
* 25.11.21新增
*/
@RequestMapping(value = "/export-signup", method = {RequestMethod.GET, RequestMethod.POST})
public void exportSignup(CourseSignDto queryDto, HttpServletResponse response) {
// 定义输出流
OutputStream os;
try {
// 1. 参数校验必填条件courseId非空
if (StringUtils.isBlank(queryDto.getCourseId())) {
throw new RuntimeException("课程ID不能为空");
}
// 2. 定义Excel表头映射
LinkedHashMap<String, String> excelHeadMap = new LinkedHashMap<>();
excelHeadMap.put("课程名称", "courseName");
excelHeadMap.put("姓名", "name");
excelHeadMap.put("工号", "userNo");
excelHeadMap.put("部门", "departName");
excelHeadMap.put("报名方式", "signType");
excelHeadMap.put("报名时间", "signTime");
// 3. 调用Service根据筛选条件查询报名记录
List<StudySignup> signRecordList = courseSignService.querySignRecords(queryDto);
// 用Set去重避免重复查询同一用户
Set<String> userIds = new HashSet<>();
// 3.1 从报名记录中提取「用户ID」
if (signRecordList != null && !signRecordList.isEmpty()) {
for (StudySignup signup : signRecordList) {
if (signup.getAid() != null && StringUtil.isNotBlank(signup.getAid())) {
userIds.add(signup.getAid());
}
}
}
List<UserSimpleVo> userList = null;
if (!userIds.isEmpty()) {
// 优先调用 findByIds(),如果不报错则尝试获取结果
userList = outsideService.findByIds(new ArrayList<>(userIds));
// 若 findByIds() 执行成功但返回空查不到数据则从Redis中获取数据
if (userList == null || userList.isEmpty()) {
// 初始化userList
userList = new ArrayList<>();
// 遍历每个用户ID逐个从Redis查询
for (String aid : userIds) {
UserSimpleVo resultVo = new UserSimpleVo();
resultVo.setAid(aid); // 绑定当前用户ID用于后续匹配
// 1. 从Redis查询用户信息
User userInfo = userService.get(aid);
if (userInfo != null) {
// 2. 填工号取userInfo的userNo
resultVo.setCode(StringUtils.isNotBlank(userInfo.getUserNo())
? userInfo.getUserNo()
: "");
// 3. 填部门通过departId查部门名称
String departName = organizationService.getName(userInfo.getDepartId());
resultVo.setOrgInfo(StringUtils.isNotBlank(departName)
? departName
: "");
log.info("【导出课程报名记录】aid={} 从redis查询到用户信息", aid);
} else {
// Redis没查到 → 设默认值
resultVo.setCode("");
resultVo.setOrgInfo("");
log.warn("【导出课程报名记录】aid={} 从redis未查询到任何用户信息", aid);
}
// 加入结果列表
userList.add(resultVo);
}
}
}
// 3.3 转换成 Map方便快速匹配
Map<String, UserSimpleVo> userInfoMap = new HashMap<>();
if (userList != null) {
for (UserSimpleVo user : userList) {
// key=用户关联ID
userInfoMap.put(user.getAid(), user);
}
}
// 4. 原始数据→导出DTO格式化处理状态转中文、日期转字符串等
List<CourseSignDto> exportDtoList = new ArrayList<>();
if (signRecordList != null) {
for (StudySignup studySignup : signRecordList) {
CourseSignDto exportDto = new CourseSignDto();
// 4.1 直接赋值(无需格式化)
exportDto.setCourseName(studySignup.getCourseName());
exportDto.setName(studySignup.getName());
exportDto.setSignTime(studySignup.getSignTime());
// 4.2 格式化:报名状态(枚举值→中文描述)
Integer signStatus = studySignup.getSignType();
if (Objects.equals(signStatus, 1)) {
exportDto.setSignType("自主报名");
} else if (Objects.equals(signStatus, 2)) {
exportDto.setSignType("手动加入");
} else {
exportDto.setSignType("");
}
// 4.3 从 UserSimpleVo 中获取工号、部门
UserSimpleVo user = userInfoMap.get(studySignup.getAid());
if (user != null) {
exportDto.setUserNo(user.getCode());
exportDto.setDepartName(user.getOrgInfo());
} else {
// 空值默认值(不变)
exportDto.setUserNo("");
exportDto.setDepartName("");
}
exportDtoList.add(exportDto);
}
}
// 5. 设置下载响应头(处理中文文件名乱码)
response.setContentType("application/octet-stream");
String fileName = URLEncoder.encode("SignUpRecord.xlsx", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
// 6. 调用Excel工具类导出
os = response.getOutputStream();
ExportsExcelSenderUtil.export(excelHeadMap, exportDtoList, os, "yyyy-MM-dd HH:mm:ss");
} catch (Exception e) {
log.error("导出课程学习记录错误:{}", e.getMessage());
}
}
/** /**
* 分页查询当前用户的课程学习记录(学生维度) * 分页查询当前用户的课程学习记录(学生维度)
* *

View File

@@ -0,0 +1,9 @@
package com.xboe.school.study.dao;
import com.xboe.core.orm.BaseDao;
import com.xboe.school.study.entity.StudySignup;
import org.springframework.stereotype.Repository;
@Repository
public class CourseSignDao extends BaseDao<StudySignup> {
}

View File

@@ -0,0 +1,74 @@
package com.xboe.school.study.dao;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.xboe.core.orm.BaseDao;
import com.xboe.school.study.dto.CourseFinishCountDto;
import com.xboe.school.study.entity.StudyCourse;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class CourseStatDao extends BaseDao<StudyCourse> {
// 查当前页DTO列表参数偏移量、每页条数、courseId
public List<CourseFinishCountDto> findFinishCountPage(int startIndex, int pageSize, String courseId) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ")
// 课程名和DTO字段对应
.append("c.content_name AS contentName, ")
// 完成人数(去重统计)
.append("COUNT(DISTINCT c.aid) AS finishCount ")
// 你的课程表名(必改!)
.append("FROM boe_study_course_item c ")
.append("WHERE 1=1 ")
// 条件:已完成
.append("AND c.status = 9 ");
List<Object> params = new ArrayList<>();
// courseId非空则过滤参数化防注入
if (StringUtils.isNotBlank(courseId)) {
sql.append("AND c.course_id = ? ");
params.add(courseId);
}
// 分组+排序+分页聚合函数必须分组排序参考第一个代码的desc id
sql.append("GROUP BY c.content_id, c.content_name ")
.append("ORDER BY c.content_id DESC ")
.append("LIMIT ?, ?"); // MySQL分页偏移量每页条数
// 补充分页参数顺序startIndex → pageSize
params.add(startIndex);
params.add(pageSize);
// 执行SQL得到Object[]列表([0]courseName, [1]finishCount
List<Object[]> resultList = sqlFindList(sql.toString(), params.toArray());
// 手动封装DTO避免null值导致前端报错
List<CourseFinishCountDto> dtoList = new ArrayList<>();
for (Object[] objs : resultList) {
CourseFinishCountDto dto = new CourseFinishCountDto();
dto.setContentName(objs[0] != null ? (String) objs[0] : "");
dto.setFinishCount(objs[1] != null ? ((Number) objs[1]).intValue() : 0);
dtoList.add(dto);
}
return dtoList;
}
// 查总条数返回int类型匹配PageList的count字段
public int findFinishCountTotal(String courseId) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ")
// 总条数=去重后的人数和GROUP BY对应
.append("COUNT(DISTINCT c.content_id) ")
.append("FROM boe_study_course_item c ")
.append("WHERE 1=1 ")
// 条件:已完成
.append("AND c.status = 9 ");
List<Object> params = new ArrayList<>();
// courseId非空则过滤参数化防注入
if (StringUtils.isNotBlank(courseId)) {
sql.append("AND c.course_id = ? ");
params.add(courseId);
}
// 用sqlCount替代sqlFindObject直接返回int类型结果
// sqlCount会执行SQL并返回COUNT的结果无需手动转换Object
return this.sqlCount(sql.toString(), params.toArray());
}
}

View File

@@ -0,0 +1,18 @@
package com.xboe.school.study.dto;
import lombok.Data;
@Data
public class CourseFinishCountDto {
/**
* 资源名称
*/
private String contentName;
/**
* 完成人数(数据库 count 统计得出)
*/
private Integer finishCount;
}

View File

@@ -0,0 +1,49 @@
package com.xboe.school.study.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 课程报名记录查询筛选DTO
*/
@Data
public class CourseSignDto {
/**
* 课程ID
*/
private String courseId;
/**
* 可选筛选条件(前端筛选时传值,不筛选则留空)
* 姓名筛选(模糊查询)
*/
private String name;
/**
* 报名状态1.自主报名 2.手动加入)
*/
private String signType;
/**
* 课程名称
*/
private String courseName;
/**
* 工号
*/
private String userNo;
/**
* 部门
*/
private String departName;
/**
* 报名时间
*/
private LocalDateTime signTime;
}

View File

@@ -0,0 +1,16 @@
package com.xboe.school.study.service;
import com.xboe.school.study.dto.CourseSignDto;
import com.xboe.school.study.entity.StudySignup;
import java.util.List;
public interface ICourseSignService {
/**
* 根据筛选条件查询课程报名记录
*/
List<StudySignup> querySignRecords(CourseSignDto queryDto);
}

View File

@@ -2,10 +2,7 @@ package com.xboe.school.study.service;
import com.xboe.common.OrderCondition; import com.xboe.common.OrderCondition;
import com.xboe.common.PageList; import com.xboe.common.PageList;
import com.xboe.school.study.dto.CourseStudyItem; import com.xboe.school.study.dto.*;
import com.xboe.school.study.dto.StudyCourseNameDto;
import com.xboe.school.study.dto.StudyCourseQuery;
import com.xboe.school.study.dto.StudyCourseSimple;
import com.xboe.school.study.entity.StudyCourse; import com.xboe.school.study.entity.StudyCourse;
import com.xboe.school.study.entity.StudyCourseItem; import com.xboe.school.study.entity.StudyCourseItem;
import com.xboe.school.study.entity.StudySignup; import com.xboe.school.study.entity.StudySignup;
@@ -67,6 +64,14 @@ public interface IStudyCourseService {
*/ */
PageList<StudyCourse> findPage(int pageIndex,int pageSize,StudyCourse sc,OrderCondition oc,Boolean isFinish); PageList<StudyCourse> findPage(int pageIndex,int pageSize,StudyCourse sc,OrderCondition oc,Boolean isFinish);
/**
* 分页查询课程的资源名称以及资源学习完成人数
* @param pageIndex 页码
* @param pageSize 每页数据条数
* @param courseId 课程id
*/
PageList<CourseFinishCountDto> findPageResource(int pageIndex, int pageSize, String courseId);
/** /**
* 热度榜 * 热度榜
* */ * */

View File

@@ -0,0 +1,48 @@
package com.xboe.school.study.service.impl;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.xboe.core.orm.FieldFilters;
import com.xboe.core.orm.QueryBuilder;
import com.xboe.school.study.dao.CourseSignDao;
import com.xboe.school.study.dto.CourseSignDto;
import com.xboe.school.study.entity.StudySignup;
import com.xboe.school.study.service.ICourseSignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CourseSignServiceImpl implements ICourseSignService {
@Autowired
CourseSignDao coursesignDao;
/**
* 根据筛选条件查询课程报名记录
*
* @param queryDto 查询条件载体
* @return 课程报名记录
*/
@Override
public List<StudySignup> querySignRecords(CourseSignDto queryDto) {
QueryBuilder query = QueryBuilder.from(StudySignup.class);
if (queryDto != null) {
// 先判断查询条件载体是否为空,避免空指针异常
// 1. 姓名模糊查询(字符串类型:用 isNotBlank 判断非空+非空白)
if (StringUtils.isNotBlank(queryDto.getName())) {
query.addFilter(FieldFilters.like("name", queryDto.getName()));
}
// 2. 课程ID精确查询字符串类型ID通常是精确匹配
if (StringUtils.isNotBlank(queryDto.getCourseId())) {
query.addFilter(FieldFilters.eq("courseId", queryDto.getCourseId()));
}
// 3. 课程类型精确查询
if (queryDto.getSignType() != null) {
query.addFilter(FieldFilters.eq("courseType", queryDto.getSignType()));
}
}
return coursesignDao.findList(query.builder());
}
}

View File

@@ -10,10 +10,7 @@ import com.xboe.module.course.dao.CourseDao;
import com.xboe.module.course.entity.Course; import com.xboe.module.course.entity.Course;
import com.xboe.module.course.service.ICourseStudySearch; import com.xboe.module.course.service.ICourseStudySearch;
import com.xboe.school.study.dao.*; import com.xboe.school.study.dao.*;
import com.xboe.school.study.dto.CourseStudyItem; import com.xboe.school.study.dto.*;
import com.xboe.school.study.dto.StudyCourseNameDto;
import com.xboe.school.study.dto.StudyCourseQuery;
import com.xboe.school.study.dto.StudyCourseSimple;
import com.xboe.school.study.entity.StudyCourse; import com.xboe.school.study.entity.StudyCourse;
import com.xboe.school.study.entity.StudyCourseItem; import com.xboe.school.study.entity.StudyCourseItem;
import com.xboe.school.study.entity.StudySignup; import com.xboe.school.study.entity.StudySignup;
@@ -85,6 +82,9 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
@Autowired @Autowired
StudyTimeDao studyTimeDao; StudyTimeDao studyTimeDao;
@Autowired
CourseStatDao courseStatDao;
@Autowired @Autowired
CourseDao courseDao; CourseDao courseDao;
@@ -230,7 +230,22 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
} }
return studyCoursePageList; return studyCoursePageList;
} }
@Override
public PageList<CourseFinishCountDto> findPageResource(int pageIndex, int pageSize, String courseId) {
// 1. 手动计算分页偏移量(数据库分页必需)
// pageIndex<1时设为0避免数据库LIMIT偏移量为负数
int startIndex = (pageIndex < 1) ? 0 : (pageIndex - 1) * pageSize;
// 2. 调用Dao层查当前页数据传入偏移量、每页条数、courseId条件
List<CourseFinishCountDto> dtoList = courseStatDao.findFinishCountPage(startIndex, pageSize, courseId);
// 3. 调用Dao层查总条数对应PageList的count字段用int类型和PageList一致
int totalCount = courseStatDao.findFinishCountTotal(courseId);
// 4. 按PageList构造函数创建对象只传list和count
PageList<CourseFinishCountDto> pageList = new PageList<>(dtoList, totalCount);
// 5. 设置pageSize覆盖默认10确保总页数计算正确
pageList.setPageSize(pageSize);
// 6. 返回totalPage会在前端调用getTotalPages()时自动计算
return pageList;
}
@Override @Override
public List<StudyCourseNameDto> studyCounts(int num) { public List<StudyCourseNameDto> studyCounts(int num) {
// QueryBuilder builder = QueryBuilder.from(StudyCourse.class); // QueryBuilder builder = QueryBuilder.from(StudyCourse.class);