Compare commits

...

37 Commits

Author SHA1 Message Date
miaowenbo
50c7cb4dba 导出课程学习记录添加用户、状态、时间等查询字段支持 2025-11-26 11:13:56 +08:00
吴季分
f3e2908b41 修正重定向地址 2025-11-26 10:54:54 +08:00
CHINAMI-GR51T7H\EDY
72ebb761f5 feat:导出报名记录接口、资源学习列表接口 2025-11-25 20:57:47 +08:00
吴季分
a694a379f4 Merge branch 'rediect' into 251114-feature-course-online 2025-11-25 14:34:24 +08:00
吴季分
6fa71c125d 二维码扫描重定向 2025-11-25 14:33:26 +08:00
miaowenbo
b9e1125145 feat:
1.开始结束时间均传入的情况,实现筛选逻辑(startTime >= 查询开始时间 AND startTime <= 查询结束时间) OR (finishTime >= 查询开始时间 AND finishTime <= 查询结束时间),这样兼容查询结束时间为空值的情况,因为学员课程未结束时没有结束时间
2.和前端沟通组件中有通过组件实现今年以来等筛选逻辑,去掉对应逻辑和枚举类
2025-11-25 14:05:29 +08:00
miaowenbo
2145e5d17e feat:
1.导出学习记录接口修改工号字段映射,调用用户中心取code字段,本地库取userNo字段
2.修改分页查询课程学习记录接口,原有分页接口只支持查询当前登录用户,增加同逻辑的课程维度分页接口
3.增加学习时长时间筛选字段与枚举(学习时长筛选逻辑还未完成)
2025-11-24 19:52:08 +08:00
miaowenbo
2df7e9061c feat:
1.添加根据课程id得到对应学习记录(包括考试记录)接口
2.修改学习记录中的分数根据考试设置为最新/最高分数
2025-11-24 17:42:17 +08:00
liu.zixi
a579317829 fix: page接口增加出参字段isPermission;
page接口显示顺序
2025-11-24 16:11:56 +08:00
liu.zixi
2c8732b2f1 fix: controller层代码修正 2025-11-24 15:39:26 +08:00
miaowenbo
c2eb338e95 feat:学习进度导出接口添加入参校验,同时添加部门、学习时长字段获取逻辑 2025-11-24 14:18:59 +08:00
liu.zixi
8e65d0a0b0 fix: 课程导出问题修正 2025-11-24 14:11:13 +08:00
liu.zixi
6f6fb8e00b fix: 课程导出问题修正
继续尝试修正文件名错误的问题
2025-11-24 14:05:44 +08:00
liu.zixi
c5bcf8fa57 fix: 课程导出问题修正
修正文件名错误的问题
2025-11-24 13:45:35 +08:00
liu.zixi
eafac59c8f fix: 课程导出问题修正
排查空指针问题修正
2025-11-24 13:35:16 +08:00
liu.zixi
0cb788451f fix: 课程导出问题修正
排查空指针问题修正
2025-11-24 13:28:46 +08:00
liu.zixi
518c1c3d27 fix: 课程列表dao层报错修正
排查空指针问题修正
2025-11-24 12:08:12 +08:00
liu.zixi
3858ec18fc fix: 课程列表dao层报错修正
ResultSet修正
2025-11-24 11:54:54 +08:00
liu.zixi
f1daa2bc58 fix: 课程列表dao层报错修正
修复timestamp和localDateTime的兼容
2025-11-24 11:49:27 +08:00
liu.zixi
5f0ab85384 fix: 课程列表dao层报错修正
修复id数据类型的问题
2025-11-24 11:40:10 +08:00
liu.zixi
1bc239d7a4 fix: 课程列表dao层报错继续修正 2025-11-24 11:28:30 +08:00
liu.zixi
ea4c1248a3 fix: 课程列表dao层报错继续修正 2025-11-24 11:22:48 +08:00
liu.zixi
94b237cb4e fix: 课程列表dao层报错修正 2025-11-24 11:02:57 +08:00
liu.zixi
f0145da738 feat: 课程列表SQL版
完善了所有查询条件,并完善导出接口
2025-11-23 18:00:37 +08:00
liu.zixi
b28387a2c6 feat: 课程列表SQL版
完善了所有查询条件,并完善导出接口
2025-11-23 18:00:19 +08:00
liu.zixi
9f685004ab Merge remote-tracking branch 'origin/251114-feature-course-online' into 251114-feature-course-online 2025-11-21 09:00:51 +08:00
miaowenbo
a53c00a928 feat:添加根据课程id获取对应考试记录接口 2025-11-20 20:33:02 +08:00
miaowenbo
cf87dfbb8c feat:
1.忽略项目参考文档内容
2.修改excel导出工具类,支持导出动态列excel
3.补全导出课程学习记录接口初版(还缺少部分字段)
4.添加列表查询课程学习记录方法
2025-11-20 20:30:52 +08:00
miaowenbo
d1b04dfba7 feat:添加启动成功提示 2025-11-20 20:16:32 +08:00
liu.zixi
4a516f58bd feat: 课程列表相关接口
完善了“查看置顶课程列表”的接口出参格式
2025-11-20 17:27:29 +08:00
joshen
f7513d0480 Merge branch 'release-20250328-master' into 251114-feature-course-online 2025-11-20 17:24:06 +08:00
liu.zixi
59fa24b83e feat: 课程列表相关接口
完善了“查看置顶课程列表”、“设置置顶”、“置顶排序”、“导出课程列表”api层、service层逻辑
2025-11-20 16:08:57 +08:00
liu.zixi
0ebd710293 feat: 课程列表相关接口
增加了“查看置顶课程列表”、“设置置顶”、“置顶排序”、“导出课程列表”接口,部分未完成
2025-11-20 13:13:33 +08:00
liu.zixi
74c8a1383d 课程列表管理端新版 2025-11-19 21:54:30 +08:00
吴季分
e419b9cd0c Merge remote-tracking branch 'origin/251114-feature-course-online' into 251114-feature-course-online 2025-11-18 09:39:20 +08:00
吴季分
1c151711b2 Merge remote-tracking branch 'origin/251114-feature-course-online' into 251114-feature-course-online
# Conflicts:
#	servers/boe-server-course/pom.xml
#	servers/boe-server-old/pom.xml
#	servers/boe-server-task/pom.xml
#	servers/modify-221027/pom.xml
#	servers/modify-user/pom.xml
#	servers/org-user-sync/pom.xml
2025-11-17 10:20:52 +08:00
hui
767631201d 已经独立出去不在这里维护的模块 2024-11-18 11:36:42 +08:00
33 changed files with 2518 additions and 339 deletions

1
.gitignore vendored
View File

@@ -48,3 +48,4 @@ nbdist/
!*/build/*.xml
*.pid
doc/

View File

@@ -1,5 +1,6 @@
package com.xboe;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.system.ApplicationPid;
@@ -23,11 +24,13 @@ import java.io.IOException;
@EnableScheduling
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.boe.feign.api.*.remote", "com.xboe.api"})
@Slf4j
public class BoeServerAllApplication {
public static void main(String[] args) {
System.setProperty("jasypt.encryptor.password", "jasypt");
SpringApplication.run(BoeServerAllApplication.class, args);
log.info("== BOE启动成功 ==");
}
@PostConstruct

View File

@@ -34,6 +34,7 @@ public class UrlSecurityFilterImpl implements IUrlSecurityFilter{
noLoginUrls.add("/xboe/m/exam/alone-extend/save");
noLoginUrls.add("/xboe/m/course/manage/test");
noLoginUrls.add("/xboe/m/course/manage/redirectDetail");
}
@Override

View File

@@ -1,14 +1,6 @@
package com.xboe.data.outside;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
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.ObjectMapper;
import com.xboe.core.SysConstant;
@@ -16,9 +8,17 @@ import com.xboe.core.api.TokenProxy;
import com.xboe.core.utils.OkHttpUtil;
import com.xboe.data.dto.AudienceUser;
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 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
@Slf4j
@@ -30,7 +30,16 @@ public class OutSideDataServiceImpl implements IOutSideDataService {
@Autowired
private OkHttpUtil okHttpUtil;
@Autowired
private UserGroupItemDao userGroupItemDao;
@Autowired
private IUserService userService;
@Autowired
private IOrganizationService organizationService;
private String getNodeText(JsonNode jn) {
if(jn!=null) {
String str = jn.asText();
@@ -402,8 +411,7 @@ public class OutSideDataServiceImpl implements IOutSideDataService {
return uids;
}
@Override
@Override
public void updateUser(String aid, String avatar, String sign) {
String token = TokenProxy.getToken(request);

View File

@@ -0,0 +1,32 @@
package com.xboe.enums;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
/**
* 课程创建来源
* boe_course.create_from
*/
@Getter
public enum CourseCreateFromEnum {
ADMIN("admin", "管理端"),
TEACHER("teacher", "教师端")
;
private final String code;
private final String label;
CourseCreateFromEnum(String code, String label) {
this.code = code;
this.label = label;
}
public static CourseCreateFromEnum getByCode(String code) {
return Arrays.stream(values()).filter(item -> StringUtils.equals(item.code, code)).findFirst().orElse(ADMIN);
}
}

View File

@@ -0,0 +1,29 @@
package com.xboe.enums;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
@Getter
public enum CourseStatusEnum {
STATUS_NONE(1, "-"),
STATUS_SUBMIT(2, "审核中"),
STATUS_AUDIT_NOPASS(3, "审核驳回"),
STATUS_AUDIT_FINISH(5, "审核通过")
;
private final int code;
private final String label;
CourseStatusEnum(int code, String label) {
this.code = code;
this.label = label;
}
public static CourseStatusEnum getByCode(Integer code) {
return Arrays.stream(values()).filter(item -> Objects.equals(item.code, code)).findFirst().orElse(STATUS_NONE);
}
}

View File

@@ -9,11 +9,7 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Opt;
import com.boe.feign.api.serverall.entity.UserData;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonProcessingException;
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.module.course.entity.CourseTag;
import com.xboe.module.course.service.*;
@@ -66,7 +62,7 @@ public class CourseFullTextApi extends ApiBaseController{
@Autowired
ICourseTagService courseTagService;
@Resource
IStudyCourseService IStudyCourseService;
IStudyCourseService IStudyCourseService;
@Autowired
ThirdApi thirdApi;

View File

@@ -1,5 +1,6 @@
package com.xboe.module.course.api;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.stream.Collectors;
@@ -7,15 +8,20 @@ import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import cn.hutool.http.Header;
import cn.hutool.http.useragent.UserAgentUtil;
import com.boe.feign.api.infrastructure.entity.CommonSearchVo;
import com.boe.feign.api.infrastructure.entity.Dict;
import com.xboe.api.ThirdApi;
import com.xboe.module.course.dto.*;
import com.xboe.module.course.entity.*;
import com.xboe.module.course.service.*;
import com.xboe.module.course.vo.CoursePageVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -55,6 +61,9 @@ public class CourseManageApi extends ApiBaseController{
@Resource
private ICourseService courseService;
@Autowired
private ICoursePageService coursePageService;
@Autowired
IOutSideDataService outsideService;
@@ -92,6 +101,18 @@ public class CourseManageApi extends ApiBaseController{
IDataUserSyncService userSyncService;
@Resource
private ThirdApi thirdApi;
@Value("${boe.pcPageUrl}")
private String pcPageUrl;
@Value("${boe.h5PageUrl}")
private String h5PageUrl;
@Value("${boe.h5LoginUrl}")
private String h5LoginUrl;
@Value("${boe.pcLoginUrl}")
private String pcLoginUrl;
// @PostMapping("/test")
// public JsonResponse<PageList<Course>> findTest(Pagination pager,CourseQueryDto dto){
@@ -99,6 +120,60 @@ public class CourseManageApi extends ApiBaseController{
// PageList<Course> coursePageList = courseService.findPage(pager.getPageIndex(), pager.getPageSize(),dto);
// return success(coursePageList);
// }
/**
* 新-管理端 课程列表
* @return
*/
@PostMapping("/page")
public JsonResponse<PageList<CoursePageVo>> managePage(@RequestBody CoursePageQueryDTO coursePageQueryDTO) {
// 管理端查询时不需要传入当前用户信息
return success(coursePageService.pageQuery(null, coursePageQueryDTO));
}
/**
* 当前用户是否在管理端显示置顶相关功能
* @return
*/
@GetMapping("/show-settop")
public JsonResponse<Boolean> showSetTop() {
CurrentUser currentUser = getCurrent();
return success(coursePageService.showSetTop(currentUser));
}
/**
* 新-管理端 置顶课程列表
* @return
*/
@GetMapping("/topList")
public JsonResponse<List<CoursePageVo>> topList() {
return success(coursePageService.topList());
}
/**
* 新-管理端 置顶课程列表排序修改
* 对整个置顶列表进行重排序
* @param changedList
* @return
*/
@PostMapping("/top-sortchange")
public JsonResponse<List<CoursePageVo>> topListSortChange(@RequestBody List<CoursePageVo> changedList) {
ServiceResponse<List<CoursePageVo>> serviceResponse = coursePageService.topListSortChange(changedList);
if (!serviceResponse.isSuccess()) {
return error(serviceResponse.getMessage());
}
return success(serviceResponse.getData());
}
/**
* 新-管理端 课程列表导出
* @param coursePageQueryDTO
* @param response
*/
@GetMapping("/export")
public void manageExport(CoursePageQueryDTO coursePageQueryDTO, HttpServletResponse response) {
coursePageService.exportCourseList(coursePageQueryDTO, response);
}
/**
* 管理列表的查询
@@ -855,6 +930,7 @@ public class CourseManageApi extends ApiBaseController{
*/
@PostMapping("/top")
public JsonResponse<Boolean> setTop(String ids,String title, Boolean top){
// lzx这个ids实际上只有一个id。。。
if(StringUtils.isBlank(ids)){
return badRequest("参数错误");
}
@@ -863,8 +939,14 @@ public class CourseManageApi extends ApiBaseController{
}
try {
courseService.setTop(ids, top,getCurrent().getAccountId(), title,"");
return success(true);
// courseService.setTop(ids, top,getCurrent().getAccountId(), title,"");
// return success(true);
// 调整置顶逻辑
ServiceResponse<Boolean> serviceResponse = coursePageService.top(ids, top);
if (!serviceResponse.isSuccess()) {
return error(serviceResponse.getMessage());
}
return success(serviceResponse.getData());
} catch (Exception e) {
log.error("课程设置置顶错误",e);
return error("设置置顶失败",e.getMessage(),false);
@@ -1226,4 +1308,40 @@ public class CourseManageApi extends ApiBaseController{
courseService.saveTip(aid);
return success(true);
}
/**
* 扫描二维码重定向
* @param courseId 课程id
* @param request
* @param response
* @throws IOException
*/
@GetMapping("/redirectDetail")
public void redirectDetail(
@NotNull Long courseId,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
boolean isMobile = UserAgentUtil.parse(request.getHeader(Header.USER_AGENT.toString())).isMobile();
String baseUrl = isMobile ? h5PageUrl : pcPageUrl;
String loginUrl = isMobile ? h5LoginUrl : pcLoginUrl;
CurrentUser currentUser;
try {
currentUser = getCurrent();
} catch (Exception e) {
log.warn("获取当前用户信息异常跳转至登录页。课程ID: {}", courseId, e);
response.sendRedirect(loginUrl);
return;
}
if (currentUser == null) {
log.info("用户未登录跳转至登录页。课程ID: {}", courseId);
response.sendRedirect(loginUrl);
return;
}
log.info("跳转到课程详情页课程ID: {}, 用户ID: {}", courseId, currentUser.getAccountId());
response.sendRedirect(baseUrl + courseId);
}
}

View File

@@ -1,11 +1,16 @@
package com.xboe.module.course.dao;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import com.xboe.module.course.dto.CoursePageQueryDTO;
import com.xboe.module.course.vo.CoursePageVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Repository;
import com.xboe.common.OrderCondition;
@@ -17,8 +22,14 @@ import com.xboe.core.orm.QueryBuilder;
import com.xboe.module.course.dto.RankingDto;
import com.xboe.module.course.entity.Course;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
@Repository
public class CourseDao extends BaseDao<Course> {
@PersistenceContext
private EntityManager entityManager;
/**
* 课程分页 搜索查询
* */
@@ -70,4 +81,379 @@ public class CourseDao extends BaseDao<Course> {
}
return rs;
}
/**
* 课程查询
* 使用原生sql查询
* 入参在sql语句中用:name的方式传入
*
* @param queryDTO 页面的查询入参
* @param isSystemAdmin 是否是系统管理员
* @param orgIds 组织id列表
* @param currentAccountId 当前用户id
* @param pageQuery 是否分页查询
* @return 课程列表
*/
public List<CoursePageVo> queryCourse(CoursePageQueryDTO queryDTO,
boolean isSystemAdmin, List<String> orgIds, String currentAccountId,
boolean pageQuery) {
// select字段
StringBuilder builder = new StringBuilder("select ");
builder.append("c.id,");
builder.append("c.name,");
builder.append("c.cover_img AS coverImg,");
builder.append("c.sys_type1 AS sysType1,");
builder.append("c.sys_type2 AS sysType2,");
builder.append("c.sys_type3 AS sysType3,");
builder.append("c.res_owner1 AS resOwner1,");
builder.append("c.res_owner2 AS resOwner2,");
builder.append("c.res_owner3 AS resOwner3,");
builder.append("c.sys_create_by AS sysCreateBy,");
builder.append("c.create_from AS createFrom,");
builder.append("c.sys_create_time AS sysCreateTime,");
builder.append("c.for_users AS forUsers,");
builder.append("c.status,");
builder.append("c.published,");
builder.append("c.publish_time AS publishTime,");
builder.append("COALESCE(stu.studys, 0) AS studys,");
builder.append("COALESCE(grd.score, 0.0) AS score,");
builder.append("COALESCE(cc.duration_sum, 0) AS courseDuration,");
builder.append("c.enabled,");
builder.append("c.open_course AS openCourse,");
builder.append("c.is_top AS isTop,");
builder.append("COALESCE(tch.teacher_names, '') AS teacherName,");
builder.append("c.sort_weight AS sortWeight");
// 拼接FROM及查询条件语句
appendFrom(builder, queryDTO, isSystemAdmin, orgIds, currentAccountId);
// 排序语句
appendOrder(builder, queryDTO);
Query query = entityManager.createNativeQuery(builder.toString());
setQueryParams(query, queryDTO, isSystemAdmin, orgIds, currentAccountId, pageQuery);
List<Object[]> resultList = query.getResultList();
List<CoursePageVo> coursePageVos = new ArrayList<>();
for (Object[] row : resultList) {
CoursePageVo vo = new CoursePageVo();
// 防止BigInteger为null的情况
BigInteger id = (BigInteger) row[0];
if (id != null) {
vo.setId(id.toString());
}
vo.setName((String) row[1]);
vo.setCoverImg((String) row[2]);
vo.setSysType1((String) row[3]);
vo.setSysType2((String) row[4]);
vo.setSysType3((String) row[5]);
vo.setResOwner1((String) row[6]);
vo.setResOwner2((String) row[7]);
vo.setResOwner3((String) row[8]);
vo.setSysCreateBy((String) row[9]);
vo.setCreateFrom((String) row[10]);
// 增加对Timestamp和LocalDateTime的兼容性防止Timestamp为null的情况
Timestamp sysCreateTimestamp = (Timestamp) row[11];
if (sysCreateTimestamp != null) {
vo.setSysCreateTime(sysCreateTimestamp.toLocalDateTime());
}
vo.setForUsers((String) row[12]);
vo.setStatus((Integer) row[13]);
vo.setPublished((Boolean) row[14]);
// 增加对Timestamp和LocalDateTime的兼容性防止Timestamp为null的情况
Timestamp publishTimestamp = (Timestamp) row[15];
if (publishTimestamp != null) {
vo.setPublishTime(publishTimestamp.toLocalDateTime());
}
// 防止Number为null的情况
Number studysNum = (Number) row[16];
if (studysNum != null) {
vo.setStudys(studysNum.intValue());
} else {
vo.setStudys(0);
}
Number scoreNum = (Number) row[17];
if (scoreNum != null) {
vo.setScore(scoreNum.floatValue());
} else {
vo.setScore(0.0f);
}
Number durationNum = (Number) row[18];
if (durationNum != null) {
vo.setCourseDuration(durationNum.longValue());
} else {
vo.setCourseDuration(0L);
}
vo.setEnabled((Boolean) row[19]);
vo.setOpenCourse((Integer) row[20]);
vo.setIsTop((Boolean) row[21]);
vo.setTeacherName((String) row[22]);
vo.setSortWeight((Integer) row[23]);
coursePageVos.add(vo);
}
return coursePageVos;
}
public long countCourse(CoursePageQueryDTO queryDTO,
boolean isSystemAdmin, List<String> orgIds, String currentAccountId) {
// select count
StringBuilder builder = new StringBuilder("select count(*)");
// 拼接FROM及查询条件语句
appendFrom(builder, queryDTO, isSystemAdmin, orgIds, currentAccountId);
// 排序语句
appendOrder(builder, queryDTO);
Query query = entityManager.createNativeQuery(builder.toString());
setQueryParams(query, queryDTO, isSystemAdmin, orgIds, currentAccountId, false);
Number count = (Number) query.getSingleResult();
return count.longValue();
}
/**
* 拼接FROM及查询条件语句
*
* @param builder
* @param queryDTO
* @param isSystemAdmin
* @param orgIds
* @param currentAccountId
*/
private void appendFrom(StringBuilder builder, CoursePageQueryDTO queryDTO,
boolean isSystemAdmin, List<String> orgIds, String currentAccountId) {
// 开头判断课程培训时间的两个参数是否不为null
boolean filterLearningTime = queryDTO.getLearningTimeStart() != null && queryDTO.getLearningTimeEnd() != null;
builder.append(" FROM boe_course c");
// 聚合教师姓名(仅未删除的教师)
builder.append(System.lineSeparator());
builder.append("LEFT JOIN (SELECT course_id, GROUP_CONCAT(teacher_name ORDER BY id SEPARATOR ',') AS teacher_names FROM boe_course_teacher GROUP BY course_id) tch ON c.id = tch.course_id");
// 学习人数聚合(满足时间条件的学习记录,且学习记录有效)
builder.append(System.lineSeparator());
builder.append("LEFT JOIN (SELECT course_id, COUNT(*) AS studys FROM boe_study_course");
if (filterLearningTime) {
builder.append(" WHERE (add_time >= :learningTimeStart AND add_time <= :learningTimeEnd) OR (finish_time >= :learningTimeStart AND finish_time <= :learningTimeEnd)");
}
builder.append(" GROUP BY course_id) stu ON c.id = stu.course_id");
// 评分聚合(在时间区间内的有效打分)
builder.append(System.lineSeparator());
builder.append("LEFT JOIN (SELECT course_id, AVG(scores) AS score FROM boe_grade");
if (filterLearningTime) {
builder.append(" WHERE sys_create_time >= :learningTimeStart AND sys_create_time <= :learningTimeEnd");
}
builder.append(" GROUP BY course_id) grd ON c.id = grd.course_id");
// 课件聚合
builder.append(System.lineSeparator());
builder.append("LEFT JOIN (SELECT course_id, SUM(duration) AS duration_sum FROM boe_course_content WHERE deleted = 0 GROUP BY course_id) cc ON c.id = cc.course_id");
// where条件
// 第一个条件deleted = 0
builder.append(System.lineSeparator());
builder.append("WHERE c.deleted = 0");
// 数据权限筛选:系统管理员可查看所有课程,非系统管理员只能看到自己创建的课程或所属组织的课程
if (!isSystemAdmin) {
if (orgIds != null && !orgIds.isEmpty() && StringUtils.isNotBlank(currentAccountId)) {
builder.append(System.lineSeparator());
builder.append("AND (c.sys_create_aid = :currentAccountId OR c.org_id IN (:orgIds))");
} else if (orgIds != null && !orgIds.isEmpty()) {
builder.append(System.lineSeparator());
builder.append("AND c.org_id IN (:orgIds)");
} else if (StringUtils.isNotBlank(currentAccountId)) {
builder.append(System.lineSeparator());
builder.append("AND c.sys_create_aid = :currentAccountId");
}
}
// 简单查询条件(展开前)
if (StringUtils.isNotBlank(queryDTO.getName())) {
builder.append(System.lineSeparator());
builder.append("AND c.name LIKE CONCAT('%', :name, '%')");
}
if (StringUtils.isNotBlank(queryDTO.getSysType1())) {
builder.append(System.lineSeparator());
builder.append("AND c.sys_type1 = :sysType1");
}
if (StringUtils.isNotBlank(queryDTO.getSysType2())) {
builder.append(System.lineSeparator());
builder.append("AND c.sys_type2 = :sysType2");
}
if (StringUtils.isNotBlank(queryDTO.getSysType3())) {
builder.append(System.lineSeparator());
builder.append("AND c.sys_type3 = :sysType3");
}
if (StringUtils.isNotBlank(queryDTO.getStatus())) {
builder.append(System.lineSeparator());
builder.append("AND c.status = :status");
}
if (queryDTO.getPublish() != null) {
builder.append(System.lineSeparator());
builder.append("AND c.published = :publish");
}
// 时间筛选逻辑:只有当两个时间参数都提供时才启用学习记录存在性校验
if (filterLearningTime) {
builder.append(System.lineSeparator());
builder.append("AND (:learningTimeStart IS NULL OR :learningTimeEnd IS NULL OR EXISTS (SELECT 1 FROM boe_study_course sc WHERE sc.course_id = c.id AND sc.finish_time IS NOT NULL AND sc.add_time >= :learningTimeStart AND sc.finish_time <= :learningTimeEnd))");
}
// 授课教师
if (StringUtils.isNotBlank(queryDTO.getTeacherName())) {
builder.append(System.lineSeparator());
builder.append("AND tch.teacher_names LIKE CONCAT('%', :teacherName, '%')");
}
// 展开后条件
if (queryDTO.getEnabled() != null) {
builder.append(System.lineSeparator());
builder.append("AND c.enabled = :enabled");
}
if (queryDTO.getOpenCourse() != null) {
builder.append(System.lineSeparator());
builder.append("AND c.open_course = :openCourse");
}
if (StringUtils.isNotBlank(queryDTO.getResOwner1())) {
builder.append(System.lineSeparator());
builder.append("AND c.res_owner1 = :resOwner1");
}
if (StringUtils.isNotBlank(queryDTO.getResOwner2())) {
builder.append(System.lineSeparator());
builder.append("AND c.res_owner2 = :resOwner2");
}
if (StringUtils.isNotBlank(queryDTO.getResOwner3())) {
builder.append(System.lineSeparator());
builder.append("AND c.res_owner3 = :resOwner3");
}
if (StringUtils.isNotBlank(queryDTO.getCreateUser())) {
builder.append(System.lineSeparator());
builder.append("AND c.sys_create_by LIKE CONCAT('%', :createUser, '%')");
}
if (StringUtils.isNotBlank(queryDTO.getCreateFrom())) {
builder.append(System.lineSeparator());
builder.append("AND c.create_from = :createFrom");
}
}
/**
* 拼接ORDER BY语句
* @param builder
* @param queryDTO
*/
private void appendOrder(StringBuilder builder, CoursePageQueryDTO queryDTO) {
builder.append(System.lineSeparator());
builder.append("ORDER BY ");
// 页面选择的排序字段处理
String orderField = queryDTO.getOrderField();
// 小驼峰转MySQL字段
String orderFieldSql = camelToUnderline(orderField);
Boolean orderAsc = queryDTO.getOrderAsc();
if (StringUtils.isNotBlank(orderField)) {
// 排序逻辑
String orderAscStr = orderAsc == null || orderAsc ? "ASC" : "DESC";
// 多字段排序: sysType resOwner
if (StringUtils.equals(orderField, "sysType") || StringUtils.equals(orderField, "resOwner")) {
for (int i = 1; i <= 3; i++) {
builder.append("c.").append(orderFieldSql).append(i).append(" ").append(orderAscStr).append(", ");
}
} else if (StringUtils.equals(orderField, "studys")) {
builder.append("COALESCE(stu.studys, 0) ").append(orderAscStr).append(", ");
} else if (StringUtils.equals(orderField, "score")) {
builder.append("COALESCE(grd.score, 0) ").append(orderAscStr).append(", ");
} else if (StringUtils.equals(orderField, "courseDuration")) {
builder.append("COALESCE(cc.duration_sum, 0) ").append(orderAscStr).append(", ");
} else {
builder.append("c.").append(orderFieldSql).append(" ").append(orderAscStr).append(", ");
}
}
builder.append("c.is_top DESC, c.sort_weight ASC, c.sys_create_time DESC");
}
private void setQueryParams(Query query,
CoursePageQueryDTO queryDTO,
boolean isSystemAdmin, List<String> orgIds, String currentAccountId,
boolean pageQuery) {
boolean filterLearningTime = queryDTO.getLearningTimeStart() != null && queryDTO.getLearningTimeEnd() != null;
// 数据权限
if (!isSystemAdmin) {
if (orgIds != null && !orgIds.isEmpty()) {
query.setParameter("orgIds", orgIds);
}
if (StringUtils.isNotBlank(currentAccountId)) {
query.setParameter("currentAccountId", currentAccountId);
}
}
if (StringUtils.isNotBlank(queryDTO.getName())) {
query.setParameter("name", queryDTO.getName());
}
if (StringUtils.isNotBlank(queryDTO.getSysType1())) {
query.setParameter("sysType1", queryDTO.getSysType1());
}
if (StringUtils.isNotBlank(queryDTO.getSysType2())) {
query.setParameter("sysType2", queryDTO.getSysType2());
}
if (StringUtils.isNotBlank(queryDTO.getSysType3())) {
query.setParameter("sysType3", queryDTO.getSysType3());
}
if (StringUtils.isNotBlank(queryDTO.getStatus())) {
query.setParameter("status", queryDTO.getStatus());
}
if (queryDTO.getPublish() != null) {
query.setParameter("publish", queryDTO.getPublish());
}
if (filterLearningTime) {
query.setParameter("learningTimeStart", queryDTO.getLearningTimeStart());
query.setParameter("learningTimeEnd", queryDTO.getLearningTimeEnd());
}
if (StringUtils.isNotBlank(queryDTO.getTeacherName())) {
query.setParameter("teacherName", queryDTO.getTeacherName());
}
if (queryDTO.getEnabled() != null) {
query.setParameter("enabled", queryDTO.getEnabled());
}
if (queryDTO.getOpenCourse() != null) {
query.setParameter("openCourse", queryDTO.getOpenCourse());
}
if (StringUtils.isNotBlank(queryDTO.getResOwner1())) {
query.setParameter("resOwner1", queryDTO.getResOwner1());
}
if (StringUtils.isNotBlank(queryDTO.getResOwner2())) {
query.setParameter("resOwner2", queryDTO.getResOwner2());
}
if (StringUtils.isNotBlank(queryDTO.getResOwner3())) {
query.setParameter("resOwner3", queryDTO.getResOwner3());
}
if (StringUtils.isNotBlank(queryDTO.getCreateUser())) {
query.setParameter("createUser", queryDTO.getCreateUser());
}
if (StringUtils.isNotBlank(queryDTO.getCreateFrom())) {
query.setParameter("createFrom", queryDTO.getCreateFrom());
}
if (pageQuery) {
// 设置OFFSET和LIMIT
query.setFirstResult((queryDTO.getPageIndex() - 1) * queryDTO.getPageSize());
query.setMaxResults(queryDTO.getPageSize());
}
}
/**
* 将小驼峰命名转换为下划线命名
* @param camelCase 小驼峰命名字符串
* @return 下划线命名字符串
*/
public String camelToUnderline(String camelCase) {
if (StringUtils.isBlank(camelCase)) {
return camelCase;
}
StringBuilder result = new StringBuilder();
result.append(Character.toLowerCase(camelCase.charAt(0)));
for (int i = 1; i < camelCase.length(); i++) {
char ch = camelCase.charAt(i);
if (Character.isUpperCase(ch)) {
result.append("_");
result.append(Character.toLowerCase(ch));
} else {
result.append(ch);
}
}
return result.toString();
}
}

View File

@@ -0,0 +1,75 @@
package com.xboe.module.course.dto;
import lombok.Data;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
@Data
public class CoursePageQueryDTO {
/**课程名称*/
private String name;
/**资源归属一级*/
private String resOwner1;
/**资源归属二级*/
private String resOwner2;
/**资源归属三级*/
private String resOwner3;
/**是否发布,无就是全部*/
private Boolean publish;
/**创建人*/
private String createUser;
/**课程分类的一级*/
private String sysType1;
/**课程分类的二级*/
private String sysType2;
/**课程分类的三级*/
private String sysType3;
/**授课教师*/
private String teacherName;
/**培训时间筛选类型*/
private String learningTimeType;
/**培训时间-左区间*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime learningTimeStart;
/**培训时间-右区间*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime learningTimeEnd;
/**审核状态*/
private String status;
/**启用状态*/
private Boolean enabled;
/**是否公开课*/
private Integer openCourse;
/**创建来源*/
private String createFrom;
/**页码*/
private Integer pageIndex;
/**每页数量*/
private Integer pageSize;
/**排序字段*/
private String orderField;
/**排序顺序*/
private Boolean orderAsc;
}

View File

@@ -0,0 +1,52 @@
package com.xboe.module.course.dto;
/**
* 服务层返回
* @param <T>
*/
public class ServiceResponse<T> {
private boolean success;
private String message;
private T data;
public static <V> ServiceResponse<V> success(V data) {
ServiceResponse<V> response = new ServiceResponse<>();
response.setSuccess(true);
response.setData(data);
return response;
}
public static <V> ServiceResponse<V> failure(String message) {
ServiceResponse<V> response = new ServiceResponse<>();
response.setSuccess(false);
response.setMessage(message);
return response;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}

View File

@@ -406,6 +406,26 @@ public class Course extends BaseEntity {
@Transient
private Boolean isTip;
/**
* 课程时长(秒)
*/
@Column(name = "course_duration")
private Long courseDuration;
/**
* 排序权重
*/
@Column(name = "sort_weight")
private Integer sortWeight;
/**
* 创建来源
* teacher-教师端
* admin-管理员端
*/
@Column(name = "create_from")
private String createFrom;
public Course(String id,String name,String summary,String coverImg,String sysCreateAid,String sysCreateBy,Integer type,LocalDateTime publishTime){
super.setId(id);
this.name=name;

View File

@@ -0,0 +1,59 @@
package com.xboe.module.course.service;
import com.xboe.common.PageList;
import com.xboe.core.CurrentUser;
import com.xboe.module.course.dto.CoursePageQueryDTO;
import com.xboe.module.course.dto.ServiceResponse;
import com.xboe.module.course.vo.CoursePageVo;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
public interface ICoursePageService {
/**
* 分页查询
* @param currentUser 教师端传入此选项
* @param coursePageQueryDTO
* @return
*/
PageList<CoursePageVo> pageQuery(CurrentUser currentUser, CoursePageQueryDTO coursePageQueryDTO);
/**
* 当前用户是否展示置顶相关功能
* @param currentUser
* @return
*/
boolean showSetTop(CurrentUser currentUser);
/**
* 置顶列表
* @return
*/
List<CoursePageVo> topList();
/**
* 置顶/取消置顶
* @param courseId
* @param top true代表执行置顶操作false代表执行取消置顶操作
* @return
*/
@Transactional
ServiceResponse<Boolean> top(String courseId, boolean top);
/**
* 置顶列表排序修改
* @param topList
* @return
*/
@Transactional
ServiceResponse<List<CoursePageVo>> topListSortChange(List<CoursePageVo> topList);
/**
* 导出课程列表
* @param coursePageQueryDTO
* @param response
*/
void exportCourseList(CoursePageQueryDTO coursePageQueryDTO, HttpServletResponse response);
}

View File

@@ -0,0 +1,443 @@
package com.xboe.module.course.service.impl;
import com.xboe.common.OrderCondition;
import com.xboe.common.PageList;
import com.xboe.common.exception.AppException;
import com.xboe.core.CurrentUser;
import com.xboe.core.orm.FieldFilters;
import com.xboe.core.orm.IFieldFilter;
import com.xboe.core.orm.QueryBuilder;
import com.xboe.core.orm.UpdateBuilder;
import com.xboe.data.dto.UserOrgIds;
import com.xboe.data.outside.IOutSideDataService;
import com.xboe.enums.CourseCreateFromEnum;
import com.xboe.enums.CourseStatusEnum;
import com.xboe.module.course.dao.CourseDao;
import com.xboe.module.course.dao.CourseTeacherDao;
import com.xboe.module.course.dto.CoursePageQueryDTO;
import com.xboe.module.course.dto.ServiceResponse;
import com.xboe.module.course.entity.Course;
import com.xboe.module.course.entity.CourseTeacher;
import com.xboe.module.course.service.ICourseFullTextSearch;
import com.xboe.module.course.service.ICoursePageService;
import com.xboe.module.course.vo.CoursePageVo;
import com.xboe.module.excel.ExportsExcelSenderUtil;
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.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class CoursePageServiceImpl implements ICoursePageService {
@Resource
private CourseDao courseDao;
@Resource
private CourseTeacherDao courseTeacherDao;
@Autowired
private IOutSideDataService outSideDataService;
@Autowired(required = false)
private ICourseFullTextSearch fullTextSearch;
@Resource
private PublishCourseUtil publishUtil;
@Override
public PageList<CoursePageVo> pageQuery(CurrentUser currentUser, CoursePageQueryDTO coursePageQueryDTO) {
/*
* 1. 前置权限过滤
* 权限说明:管理员端 可查看本人创建的课程及被授权的课程,其他课程不可见,被赋予查看权的用户可直接引用可查看的课程;
* 教师端 当前用户创建的课程及具有管理权、查看权的课程清单
*/
UserOrgIds userOrgIds = outSideDataService.getOrgIds();
List<String> orgIds = userOrgIds.getIds();
boolean isSystemAdmin = userOrgIds.getPermissions().containsKey(UserOrgIds.IsSystemAdminKey)
&& userOrgIds.getPermissions().get(UserOrgIds.IsSystemAdminKey);
String currentAccountId = currentUser == null ? null : currentUser.getAccountId();
// 第一版废弃
// // 构建查询条件
// List<IFieldFilter> filters = new ArrayList<>();
//
// // 自动添加过滤已删除
// filters.add(FieldFilters.eq("c.deleted", false));
//
// // 添加权限过滤条件
// if (!isSystemAdmin) {
// // 非系统管理员需要进行权限过滤
// List<IFieldFilter> permissionFilters = new ArrayList<>();
//
// // 添加组织权限过滤
// if (orgIds != null && !orgIds.isEmpty()) {
// permissionFilters.add(FieldFilters.in("c.orgId", orgIds));
// }
//
// // 添加创建人过滤(可以看到自己创建的课程)
// if (StringUtils.isNotBlank(currentAccountId)) {
// permissionFilters.add(FieldFilters.eq("c.sysCreateAid", currentAccountId));
// }
//
// // 如果有权限条件则添加OR条件
// if (!permissionFilters.isEmpty()) {
// filters.add(FieldFilters.or(permissionFilters));
// }
// }
// filters.addAll(createFilters(coursePageQueryDTO));
//
// QueryBuilder query = QueryBuilder.from(Course.class.getSimpleName() + " c").addFilters(filters);
// // 处理排序
// handleOrder(query, coursePageQueryDTO.getOrderField(), coursePageQueryDTO.getOrderAsc());
//
// // 设置分页参数
// int pageIndex = coursePageQueryDTO.getPageIndex() != null ? coursePageQueryDTO.getPageIndex() : 0;
// int pageSize = coursePageQueryDTO.getPageSize() != null ? coursePageQueryDTO.getPageSize() : 10;
// query.setPageIndex(pageIndex);
// query.setPageSize(pageSize);
//
// // 执行查询
// PageList<Course> coursePageList = courseDao.findPage(query.builder());
//
// // 转换为CoursePageVo
// PageList<CoursePageVo> result = new PageList<>();
// result.setCount(coursePageList.getCount());
// result.setPageSize(coursePageList.getPageSize());
//
// // 子查询根据课程id查询课程下的教师
// List<String> courseIds = coursePageList.getList().stream()
// .map(Course::getId)
// .collect(Collectors.toList());
// List<CourseTeacher> courseTeacherList = getCourseTeacherList(courseIds);
// List<CoursePageVo> voList = coursePageList.getList().stream()
// .map(c -> convertToVo(c, courseTeacherList))
// .collect(Collectors.toList());
// result.setList(voList);
//
// return result;
// 第二版
long total = courseDao.countCourse(coursePageQueryDTO, isSystemAdmin, orgIds, currentAccountId);
List<CoursePageVo> voList = courseDao.queryCourse(coursePageQueryDTO, isSystemAdmin, orgIds, currentAccountId, true);
PageList<CoursePageVo> result = new PageList<>();
result.setCount((int) total);
result.setPageSize(coursePageQueryDTO.getPageSize());
result.setList(voList);
return result;
}
@Override
public boolean showSetTop(CurrentUser currentUser) {
UserOrgIds userOrgIds = outSideDataService.getOrgIds();
return userOrgIds.getPermissions().containsKey(UserOrgIds.IsSystemAdminKey)
&& userOrgIds.getPermissions().get(UserOrgIds.IsSystemAdminKey);
}
@Override
public List<CoursePageVo> topList() {
// 构建查询条件
IFieldFilter fieldFilter = FieldFilters.eq("isTop", true);
List<Course> courseList = courseDao.findList(fieldFilter);
// 如果有值,查询教师数据
List<CourseTeacher> courseTeacherList;
if (!courseList.isEmpty()) {
List<String> courseIds = courseList.stream()
.map(Course::getId)
.collect(Collectors.toList());
courseTeacherList = getCourseTeacherList(courseIds);
} else {
courseTeacherList = new ArrayList<>();
}
return courseList.stream()
.map(c -> convertToVo(c, courseTeacherList))
.sorted(Comparator.comparing(CoursePageVo::getSortWeight)) // 按照sortWeight字段进行排序
.collect(Collectors.toList());
}
@Override
public ServiceResponse<Boolean> top(String courseId, boolean top) {
// 1. 查询课程数据
Course course = courseDao.get(courseId);
if (top) {
// 2. 执行置顶操作
// 2.1 目前课程是否已置顶
if (course.getIsTop() != null && course.getIsTop()) {
// 错误提示:已置顶
return ServiceResponse.failure("已置顶");
}
// 2.2 查看置顶列表数量
IFieldFilter fieldFilter = FieldFilters.eq("isTop", true);
int topCount = courseDao.count(fieldFilter);
if (topCount >= 10) {
return ServiceResponse.failure("最多只能置顶10个课程");
}
// 2.3 设置置顶
courseDao.updateMultiFieldById(courseId,
UpdateBuilder.create("isTop", top),
UpdateBuilder.create("topTime", LocalDateTime.now()));
} else {
// 3. 取消置顶
// 3.1 课程是否已置顶
if (course.getIsTop() == null || !course.getIsTop()) {
return ServiceResponse.failure("未置顶");
}
// 3.2 取消置顶
course.setIsTop(false);
course.setSortWeight(9999);
courseDao.updateMultiFieldById(courseId,
UpdateBuilder.create("isTop", top),
UpdateBuilder.create("topTime", null),
UpdateBuilder.create("sortWeight", 9999));
}
// ES同步
if (this.fullTextSearch != null) {
Object fullId = courseDao.findField("fullTextId", FieldFilters.eq("id", courseId));
if (fullId != null) {
publishUtil.updateFieldByDocId((String) fullId, "isTop", top ? 1 : 0);
}
}
return ServiceResponse.success(true);
}
@Override
public ServiceResponse<List<CoursePageVo>> topListSortChange(List<CoursePageVo> topList) {
// 1. 按sortWeight升序排序
topList.sort(Comparator.comparingInt(CoursePageVo::getSortWeight));
// 2. 更新
for (CoursePageVo vo : topList) {
courseDao.updateMultiFieldById(vo.getId(),
UpdateBuilder.create("sortWeight", vo.getSortWeight()));
}
return ServiceResponse.success(topList);
}
@Override
public void exportCourseList(CoursePageQueryDTO coursePageQueryDTO, HttpServletResponse response) {
/*
* 1. 前置权限过滤
* 权限说明:管理员端 可查看本人创建的课程及被授权的课程,其他课程不可见,被赋予查看权的用户可直接引用可查看的课程;
* 教师端 当前用户创建的课程及具有管理权、查看权的课程清单
*/
UserOrgIds userOrgIds = outSideDataService.getOrgIds();
List<String> orgIds = userOrgIds.getIds();
boolean isSystemAdmin = userOrgIds.getPermissions().containsKey(UserOrgIds.IsSystemAdminKey)
&& userOrgIds.getPermissions().get(UserOrgIds.IsSystemAdminKey);
List<CoursePageVo> courseList = courseDao.queryCourse(coursePageQueryDTO, isSystemAdmin, orgIds, null, false);
// 导出
LinkedHashMap<String, String> exportMap = new LinkedHashMap<>();
exportMap.put("课程名称", "name");
exportMap.put("课程分类", "sysType");
exportMap.put("授课教师", "teacherName");
exportMap.put("课程时长", "courseDuration");
exportMap.put("学习人数", "studys");
exportMap.put("课程评分", "score");
exportMap.put("审核状态", "status");
exportMap.put("发布状态", "published");
exportMap.put("启停用状态", "enabled");
exportMap.put("公开课", "openCourse");
exportMap.put("资源归属", "resOwner");
exportMap.put("创建人", "sysCreateBy");
exportMap.put("创建来源", "createFrom");
exportMap.put("创建时间", "sysCreateTime");
List<Map<String, Object>> dataList = new ArrayList<>();
if (courseList != null && !courseList.isEmpty()) {
// TODO 查询sysType和resOwner
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
for (CoursePageVo coursePageVo : courseList) {
Map<String, Object> map = new HashMap<>();
map.put("name", coursePageVo.getName());
map.put("sysType", coursePageVo.getSysType1()); // FIXME 三级分类用/拼接
map.put("teacherName", coursePageVo.getTeacherName());
// 课程时长:秒转分
map.put("courseDuration", coursePageVo.getCourseDuration() / 60);
map.put("studys", coursePageVo.getStudys());
map.put("score", coursePageVo.getScore());
map.put("status", CourseStatusEnum.getByCode(coursePageVo.getStatus()).getLabel());
map.put("published", coursePageVo.getPublished() == null || !coursePageVo.getPublished() ? "未发布" : "已发布");
map.put("enabled", coursePageVo.getEnabled() == null || coursePageVo.getEnabled() ? "停用" : "启用");
map.put("openCourse", coursePageVo.getOpenCourse() == null || coursePageVo.getOpenCourse() == 0 ? "" : "");
map.put("resOwner", coursePageVo.getResOwner1()); // FIXME 三级分类用/拼接
map.put("sysCreateBy", coursePageVo.getSysCreateBy());
map.put("createFrom", CourseCreateFromEnum.getByCode(coursePageVo.getCreateFrom()).getLabel());
map.put("sysCreateTime", formatter.format(coursePageVo.getSysCreateTime()));
dataList.add(map);
}
}
// output
try (OutputStream out = response.getOutputStream()) {
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment;filename=course.xlsx");
ExportsExcelSenderUtil.exportDynamic(exportMap, dataList, out, null);
} catch (Exception e) {
throw new AppException("导出课程列表发生异常", e);
}
}
/**
* 生成过滤条件
*
* @param dto
* @return
*/
private List<IFieldFilter> createFilters(CoursePageQueryDTO dto) {
List<IFieldFilter> filters = new ArrayList<>();
if (StringUtils.isNotBlank(dto.getName())) {
filters.add(FieldFilters.like("c.name", dto.getName()));
}
if (StringUtils.isNotBlank(dto.getCreateUser())) {
filters.add(FieldFilters.like("c.sysCreateBy", dto.getCreateUser()));
}
if (StringUtils.isNotBlank(dto.getResOwner3())) {
filters.add(FieldFilters.eq("c.resOwner3", dto.getResOwner3()));
}
if (StringUtils.isNotBlank(dto.getResOwner2())) {
filters.add(FieldFilters.eq("c.resOwner2", dto.getResOwner2()));
}
if (StringUtils.isNotBlank(dto.getResOwner1())) {
filters.add(FieldFilters.eq("c.resOwner1", dto.getResOwner1()));
}
if (dto.getPublish() != null) {
filters.add(FieldFilters.eq("c.published", dto.getPublish()));
}
// 状态查询
if (StringUtils.isNotBlank(dto.getStatus())) {
filters.add(FieldFilters.eq("c.status", Integer.valueOf(dto.getStatus())));
}
// 系统分类查询
if (StringUtils.isNotBlank(dto.getSysType3())) {
filters.add(FieldFilters.eq("c.sysType3", dto.getSysType3()));
}
if (StringUtils.isNotBlank(dto.getSysType2())) {
filters.add(FieldFilters.eq("c.sysType2", dto.getSysType2()));
}
if (StringUtils.isNotBlank(dto.getSysType1())) {
filters.add(FieldFilters.eq("c.sysType1", dto.getSysType1()));
}
// 是否启用
if (dto.getEnabled() != null) {
filters.add(FieldFilters.eq("c.enabled", dto.getEnabled()));
}
// 时间范围查询(待定)
return filters;
}
private List<CourseTeacher> getCourseTeacherList(List<String> courseIds) {
return courseTeacherDao.findList(OrderCondition.desc("courseId"), FieldFilters.in("courseId", courseIds));
}
/**
* 将Course转换为CoursePageVo
* @param course
* @return
*/
private CoursePageVo convertToVo(Course course, List<CourseTeacher> courseTeacherList) {
CoursePageVo vo = new CoursePageVo();
vo.setId(course.getId());
vo.setName(course.getName());
vo.setCoverImg(course.getCoverImg());
vo.setSysType1(course.getSysType1());
vo.setSysType2(course.getSysType2());
vo.setSysType3(course.getSysType3());
vo.setResOwner1(course.getResOwner1());
vo.setResOwner2(course.getResOwner2());
vo.setResOwner3(course.getResOwner3());
vo.setSysCreateBy(course.getSysCreateBy());
vo.setCreateFrom(course.getCreateFrom());
vo.setSysCreateTime(course.getSysCreateTime());
vo.setForUsers(course.getForUsers());
vo.setStatus(course.getStatus());
// auditType 需要从其他地方获取,这里暂时设置为默认值
vo.setAuditType(1);
vo.setPublished(course.getPublished());
vo.setStudys(course.getStudys());
vo.setScore(course.getScore());
// courseDuration 需要计算,这里暂时设置为默认值
vo.setCourseDuration(course.getCourseDuration());
vo.setEnabled(course.getEnabled());
vo.setSortWeight(course.getSortWeight());
// 获取教师名称
List<String> teacherNames = courseTeacherList.stream()
.filter(ct -> StringUtils.equals(ct.getCourseId(), course.getId()))
.map(CourseTeacher::getTeacherName)
.collect(Collectors.toList());
if (!teacherNames.isEmpty()) {
vo.setTeacherName(String.join(",", teacherNames));
}
return vo;
}
private void handleOrder(QueryBuilder query, String orderField, Boolean orderAsc) {
if (StringUtils.isNotBlank(orderField)) {
boolean isAsc = orderAsc == null || orderAsc;
// 1. 多字段排序
if (StringUtils.equals(orderField, "sysType")) {
if (isAsc) {
query.addOrder(OrderCondition.asc("c.sysType1"));
query.addOrder(OrderCondition.asc("c.sysType2"));
query.addOrder(OrderCondition.asc("c.sysType3"));
} else {
query.addOrder(OrderCondition.desc("c.sysType1"));
query.addOrder(OrderCondition.desc("c.sysType2"));
query.addOrder(OrderCondition.desc("c.sysType3"));
}
} else if (StringUtils.equals(orderField, "resOwner")) {
if (isAsc) {
query.addOrder(OrderCondition.asc("c.resOwner1"));
query.addOrder(OrderCondition.asc("c.resOwner2"));
query.addOrder(OrderCondition.asc("c.resOwner3"));
} else {
query.addOrder(OrderCondition.desc("c.resOwner1"));
query.addOrder(OrderCondition.desc("c.resOwner2"));
query.addOrder(OrderCondition.desc("c.resOwner3"));
}
} else {
if (isAsc) {
query.addOrder(OrderCondition.asc("c." + orderField));
} else {
query.addOrder(OrderCondition.desc("c." + orderField));
}
}
} else {
OrderCondition isTop = OrderCondition.desc("c.isTop");
query.addOrder(isTop);
OrderCondition sortWeightOc = OrderCondition.asc("c.sortWeight");
query.addOrder(sortWeightOc);
OrderCondition createTimeOc = OrderCondition.desc("c.sysCreateTime");
query.addOrder(createTimeOc);
}
}
}

View File

@@ -0,0 +1,160 @@
package com.xboe.module.course.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Data
public class CoursePageVo {
/**
* 课程ID
*/
private String id;
/**
* 课程名称
*/
private String name;
/**
* 课程封面图片地址
*/
private String coverImg;
/**
* 课程分类一级
*/
private String sysType1;
/**
* 课程分类二级
*/
private String sysType2;
/**
* 课程分类三级
*/
private String sysType3;
/**
* 资源归属一级
*/
private String resOwner1;
/**
* 资源归属二级
*/
private String resOwner2;
/**
* 资源归属三级
*/
private String resOwner3;
/**
* 创建人
*/
private String sysCreateBy;
/**
* 创建来源
* teacher-教师端
* admin-管理员端
*/
private String createFrom;
/**
* 创建时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime sysCreateTime;
/**
* 目标人群
*/
private String forUsers;
/**
* 审核状态
* 1-无审核状态
* 2-审核中
* 3-审核驳回
* 5-审核通过
*/
private Integer status;
/**
* 课程审核类型
* 1-课程创建
* 2-课程编辑
* 3-课程启用
* 4-课程停用
* 5-撤回申请
*/
private Integer auditType;
/**
* 发布状态
* false-未发布
* true-已发布
*/
private Boolean published;
/**
* 发布时间
*/
private LocalDateTime publishTime;
/**
* 学习人数
*/
private Integer studys;
/**
* 课程评分
*/
private Float score;
/**
* 课程时长(秒)
*/
private Long courseDuration;
/**
* 启用状态
* false-停用
* true-启用
*/
private Boolean enabled;
/**
* 授课教师
*/
private String teacherName;
/**
* 是否公开课
*/
private Integer openCourse;
/**
* 是否置顶
*/
private Boolean isTop;
/**
* 排序权重
*/
private Integer sortWeight;
/**
* 是否权限课程
* 默认为true
* TODO 在线课优化二期会对此字段进行其他的赋值操作
*/
private Boolean isPermission = true;
}

View File

@@ -1,31 +1,21 @@
package com.xboe.module.excel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* 导出excel工具类
@@ -34,148 +24,6 @@ import org.springframework.stereotype.Component;
@Component
public class ExportsExcelSenderUtil {
// @Resource
// ITeacherService teacherService;
// @Resource
// ICourseService courseService;
//
//
// private HSSFWorkbook workbook;
//
// /**
// * 教师导出
// * */
// public void teacherExports(Integer pageIndex, Integer pageSize, Teacher entity, OrderCondition order){
//// 创建excel对象
// try {
// workbook = new HSSFWorkbook();
//// 创建工作表 参数 表名
// HSSFSheet sheet = workbook.createSheet("教师信息");
//// 创建标题行
// HSSFRow row = sheet.createRow(0);
// String[] title = {"姓名","部门", "创建时间", "修改时间", "授课时长", "在职状态"};
//// 单元格对象 标题行
// HSSFCell cell = null;
// CellStyle cellStyle = this.cellStyle(workbook);
// for (int i = 0; i < title.length; i++) {
// cell = row.createCell(i);
// cell.setCellValue(title[i]);
// cell.setCellStyle(cellStyle);
// sheet.setColumnWidth(i,20*256);
// }
//// 数据行
// PageList<Teacher> pageList = teacherService.query(pageIndex, pageSize, entity, order);
// List<Teacher> list = pageList.getList();
// for (int i = 0; i < list.size(); i++) {
//// 遍历一次创建一行
// HSSFRow row1 = sheet.createRow(i + 1);
//// 给行内单元格放对应数据
// row1.createCell(0).setCellValue(list.get(i).getName());
// row1.createCell(1).setCellValue(list.get(i).getDepartId());
// row1.createCell(2).setCellValue(String.valueOf(list.get(i).getSysCreateTime()));
// row1.createCell(3).setCellValue(String.valueOf(list.get(i).getSysUpdateTime()));
// row1.createCell(4).setCellValue(list.get(i).getTeaching());
// if(list.get(i).getWaitStatus().equals("0")){
// row1.createCell(5).setCellValue("成功");
// }else{
// row1.createCell(5).setCellValue("失败");
// }
//
// }
// String configValue = SysConstant.getConfigValue(BaseConstant.CONFIG_UPLOAD_FILES_TEMPPATH);
// File file = new File(configValue + "/teacher.xls");
// FileOutputStream fileOutputStream = new FileOutputStream(file);
// workbook.write(fileOutputStream);
// workbook.close();
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// /**
// * 课程导出
// * */
// public void courseExports(Pagination pager, CourseQueryDto dto){
// try {
// workbook = new HSSFWorkbook();
//// 创建工作表 参数 表名
// HSSFSheet sheet = workbook.createSheet("课程信息");
//// 创建标题行
// HSSFRow row = sheet.createRow(0);
// String[] title = {"序号","名称", "内容分类", "资源归属", "授课方式", "状态","是否发布"};
//// 单元格对象 标题行
// HSSFCell cell = null;
// CellStyle cellStyle = this.cellStyle(workbook);
// for (int i = 0; i < title.length; i++) {
// cell = row.createCell(i);
// cell.setCellValue(title[i]);
// cell.setCellStyle(cellStyle);
// sheet.setColumnWidth(i,20*256);
// }
//// 数据行
// PageList<Course> page = courseService.findPage(pager.getPageIndex(), pager.getPageSize(), dto);
// List<Course> list = page.getList();
// for (int i = 0; i < list.size(); i++) {
//// 遍历一次创建一行
// HSSFRow row1 = sheet.createRow(i + 1);
//// 给行内单元格放对应数据
// row1.createCell(0).setCellValue(i+1);
// row1.createCell(1).setCellValue(list.get(i).getName());
// row1.createCell(2).setCellValue(list.get(i).getSysType1()+"/"+list.get(i).getSysType2()+"/"+list.get(i).getSysType3());
// row1.createCell(3).setCellValue(list.get(i).getResOwner1()+"/"+list.get(i).getResOwner2()+"/"+list.get(i).getResOwner3());
// if(list.get(i).getType()==20){
// row1.createCell(4).setCellValue("在线课(录播)");
// }
// if(list.get(i).getType()==10){
// row1.createCell(4).setCellValue("微课");
// }
// if(list.get(i).getStatus()==1){
// row1.createCell(5).setCellValue("未提交");
// }
// if(list.get(i).getStatus()==2){
// row1.createCell(5).setCellValue("已提交");
// }
// if(list.get(i).getStatus()==3){
// row1.createCell(5).setCellValue("审核未通过");
// }
// if(list.get(i).getStatus()==5){
// row1.createCell(5).setCellValue("审核完成");
// }
// if(list.get(i).getPublished()){
// row1.createCell(6).setCellValue("已发布");
// }else{
// row1.createCell(6).setCellValue("未发布");
// }
//
// }
// String configValue = SysConstant.getConfigValue(BaseConstant.CONFIG_UPLOAD_FILES_TEMPPATH);
// File file = new File(configValue + "/course.xls");
// FileOutputStream fileOutputStream = new FileOutputStream(file);
// workbook.write(fileOutputStream);
// workbook.close();
// } catch (Exception e) {
// e.printStackTrace();
// }
//
// }
//
// /**
// * 标题行样式
// * */
// private CellStyle cellStyle(HSSFWorkbook workbook){
//
//// 构建字体
// HSSFFont font = workbook.createFont();
//// 加粗
// font.setBold(true);
//// 字号
// font.setFontHeightInPoints((short)10);
//// 创建样式对象
// CellStyle cellStyle = workbook.createCellStyle();
// cellStyle.setFont(font);
//// 设置文字居中
// cellStyle.setAlignment(HorizontalAlignment.CENTER);
// return cellStyle;
// }
/**
* 判断是否为空
* @param obj
@@ -323,10 +171,106 @@ public class ExportsExcelSenderUtil {
}
/**
* 导出动态列Excel
*
* @param kvMap 自定义标题属性,key:标题 value:属性
* @param dataList 数据列表每个元素是一个Mapkey为属性名value为值
* @param out 输出流
* @param dateFormat 日期格式 默认为yyyy-MM-dd HH:mm:ss
*/
public static void exportDynamic(LinkedHashMap<String, String> kvMap, List<Map<String, Object>> dataList, OutputStream out, String dateFormat) {
HSSFWorkbook wb = null;
try {
dateFormat = !ExportsExcelSenderUtil.isEmpty(dateFormat) ? dateFormat : "yyyy-MM-dd HH:mm:ss";
wb = new HSSFWorkbook();
// 表头样式
HSSFCellStyle titleStyle = wb.createCellStyle();
titleStyle.setBorderTop(BorderStyle.THIN);
titleStyle.setBorderLeft(BorderStyle.THIN);
titleStyle.setBorderBottom(BorderStyle.THIN);
titleStyle.setBorderRight(BorderStyle.THIN);
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleStyle.setWrapText(true);
// 设置字体样式
HSSFFont titleFont = wb.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 15);
titleStyle.setFont(titleFont);
// 内容样式
HSSFCellStyle contentStyle = wb.createCellStyle();
contentStyle.setBorderTop(BorderStyle.THIN);
contentStyle.setBorderLeft(BorderStyle.THIN);
contentStyle.setBorderBottom(BorderStyle.THIN);
contentStyle.setBorderRight(BorderStyle.THIN);
contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
contentStyle.setAlignment(HorizontalAlignment.CENTER);
contentStyle.setWrapText(true);
// 创建工作表
HSSFSheet sheet = wb.createSheet();
HSSFRow headerRow = sheet.createRow(0);
headerRow.setHeightInPoints(33);
// 设置表头
Set<String> headerKeys = kvMap.keySet();
int columnIndex = 0;
for (String header : headerKeys) {
sheet.setColumnWidth(columnIndex, 60 * 80);
HSSFCell headerCell = headerRow.createCell(columnIndex);
headerCell.setCellValue(header);
headerCell.setCellStyle(titleStyle);
columnIndex++;
}
// 填充数据
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> rowData = dataList.get(i);
HSSFRow row = sheet.createRow(i + 1);
row.setHeightInPoints(33);
columnIndex = 0;
for (String header : headerKeys) {
HSSFCell cell = row.createCell(columnIndex);
String propertyName = kvMap.get(header); // 获取属性名
Object cellValue = rowData.get(propertyName);
if (cellValue instanceof LocalDateTime) {
LocalDateTime date = (LocalDateTime) cellValue;
cellValue = DateTimeFormatter.ofPattern(dateFormat).format(date);
} else if (cellValue instanceof Date) {
Date date = (Date) cellValue;
cellValue = new SimpleDateFormat(dateFormat).format(date);
}
String valueStr = !ExportsExcelSenderUtil.isEmpty(cellValue) ? cellValue.toString() : "";
cell.setCellValue(valueStr);
cell.setCellStyle(contentStyle);
columnIndex++;
}
}
try {
wb.write(out);
wb.close();
} catch (IOException e) {
// 日志记录
}
} catch (Exception e) {
try {
if (wb != null) {
wb.write(out);
wb.close();
}
} catch (IOException e1) {
// 日志记录
}
throw e;
}
}
}

View File

@@ -1,11 +1,9 @@
package com.xboe.module.interaction.api;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
import com.xboe.common.utils.StringUtil;
import com.xboe.core.CurrentUser;
import com.xboe.module.interaction.dto.*;
import com.xboe.module.qa.entity.Question;
import com.xboe.module.qa.service.IQuestionService;
@@ -25,8 +23,6 @@ import com.xboe.module.interaction.service.IFavoritesService;
import lombok.extern.slf4j.Slf4j;
import javax.persistence.Cacheable;
/**
* 收藏处理
*
@@ -44,7 +40,7 @@ public class FavoritesApi extends ApiBaseController {
ICourseTeacherService ctService;
@Autowired
IStudyCourseService studyCourseService;
IStudyCourseService studyCourseService;
@Autowired
IQuestionService questionService;

View File

@@ -1,68 +1,57 @@
package com.xboe.school.study.api;
import cn.hutool.core.collection.CollectionUtil;
import com.boe.feign.api.infrastructure.entity.CommonSearchVo;
import com.boe.feign.api.infrastructure.entity.Dict;
import com.xboe.api.ThirdApi;
import com.xboe.common.OrderCondition;
import com.xboe.common.PageList;
import com.xboe.common.Pagination;
import com.xboe.common.utils.StringUtil;
import com.xboe.constants.CacheName;
import com.xboe.core.CurrentUser;
import com.xboe.core.JsonResponse;
import com.xboe.core.api.ApiBaseController;
import com.xboe.data.outside.IOutSideDataService;
import com.xboe.module.course.entity.*;
import com.xboe.module.course.service.ICourseContentService;
import com.xboe.module.course.service.ICourseSectionService;
import com.xboe.module.course.service.ICourseService;
import com.xboe.module.course.service.ICourseTagService;
import com.xboe.module.course.vo.TeacherVo;
import com.xboe.module.excel.ExportsExcelSenderUtil;
import com.xboe.module.teacher.entity.Teacher;
import com.xboe.module.teacher.service.ITeacherService;
import com.xboe.module.usergroup.service.IUserGroupService;
import com.xboe.school.study.dto.*;
import com.xboe.school.study.entity.*;
import com.xboe.school.study.service.*;
import com.xboe.school.vo.StudyTimeVo;
import com.xboe.system.organization.service.IOrganizationService;
import com.xboe.system.user.entity.User;
import com.xboe.system.user.service.IUserService;
import com.xboe.system.user.vo.UserSimpleVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.nacos.shaded.com.google.common.util.concurrent.RateLimiter;
import com.boe.feign.api.infrastructure.entity.CommonSearchVo;
import com.boe.feign.api.infrastructure.entity.Dict;
import com.xboe.api.ThirdApi;
import com.xboe.constants.CacheName;
import com.xboe.module.course.entity.*;
import com.xboe.module.course.service.ICourseTagService;
import com.xboe.module.course.vo.TeacherVo;
import com.xboe.module.usergroup.service.IUserGroupService;
import com.xboe.school.study.dao.StudyCourseDao;
import com.xboe.school.vo.StudyTimeVo;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.xboe.common.OrderCondition;
import com.xboe.common.PageList;
import com.xboe.common.Pagination;
import com.xboe.common.utils.StringUtil;
import com.xboe.core.CurrentUser;
import com.xboe.core.JsonResponse;
import com.xboe.core.api.ApiBaseController;
import com.xboe.module.course.service.ICourseContentService;
import com.xboe.module.course.service.ICourseSectionService;
import com.xboe.module.course.service.ICourseService;
import com.xboe.module.teacher.entity.Teacher;
import com.xboe.module.teacher.service.ITeacherService;
import com.xboe.school.study.dto.CourseStudyItem;
import com.xboe.school.study.dto.CourseStudyQuery;
import com.xboe.school.study.dto.StudyContentDto;
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.StudyCourseItem;
import com.xboe.school.study.entity.StudySignup;
import com.xboe.school.study.entity.StudyTime;
import com.xboe.school.study.service.IStudyCourseService;
import com.xboe.school.study.service.IStudyService;
import com.xboe.school.study.service.IStudySignupService;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* 课程学习的内容
*/
@@ -80,6 +69,9 @@ public class StudyCourseApi extends ApiBaseController{
@Autowired
ICourseService courseService;
@Autowired
IStudyExamService studyExamService;
@Resource
private ThirdApi thirdApi;
@@ -98,57 +90,410 @@ public class StudyCourseApi extends ApiBaseController{
@Autowired
private IUserGroupService userGroupService;
@Autowired
private ICourseSignService courseSignService;
@Autowired
StringRedisTemplate redisTemplate;
@Resource
private ICourseTagService courseTagService;
@Resource
private IOutSideDataService outsideService;
@Resource
private IUserService userService;
@Resource
private IOrganizationService organizationService;
/**
* 用于查询课程的学习记录
* @param pager
* @param sc
* @return
* 用于避免JPA查询后修改entity实体字段自动更新到数据库
*/
@Autowired
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());
}
}
/**
* 分页查询当前用户的课程学习记录(学生维度)
*
* @param pager 分页参数
* @param sc 课程学习记录查询参数
* @return 课程学习记录分页集合
*/
@RequestMapping(value="/pagelist",method = {RequestMethod.GET,RequestMethod.POST})
public JsonResponse<PageList<StudyCourse>> findPage(Pagination pager,StudyCourse sc,Boolean isFinish){
// 默认查询当前登录用户aid
sc.setAid(getCurrent().getAccountId());
try {
PageList<StudyCourse> rs=service.findPage(pager.getPageIndex(),pager.getPageSize(), sc,OrderCondition.desc("id"),isFinish);
//查询出图片
// List<String> ids=new ArrayList<String>();
// for(StudyCourse scourse:rs.getList()) {
// ids.add(scourse.getCourseId());
// }
// Map<String,Object> maps = courseService.findImages(ids);
// for(StudyCourse scourse:rs.getList()) {
// if(maps.containsKey(scourse.getCourseId())) {
// scourse.setCourseImage((String)maps.get(scourse.getCourseId()));
// }
// }
return success(rs);
}catch(Exception e) {
log.error("查询课程学习记录错误",e.getMessage());
return error("查询失败",e.getMessage());
log.error("查询课程学习记录错误{}", e.getMessage());
return error("查询失败", e.getMessage());
}
}
/**
* 分页查询当前课程的学习记录(课程维度)
*
* @param pager 分页参数
* @param sc 课程学习记录查询参数
* @return 课程学习记录分页集合
*/
@RequestMapping(value = "/pagelistEx", method = {RequestMethod.GET, RequestMethod.POST})
public JsonResponse<PageList<StudyCourse>> findPageEx(Pagination pager, StudyCourse sc, Boolean isFinish) {
// 空值校验
if (StringUtils.isBlank(sc.getCourseId())) {
return error("课程id不能为空");
}
try {
PageList<StudyCourse> rs = service.findPage(pager.getPageIndex(), pager.getPageSize(), sc, OrderCondition.desc("id"), isFinish);
return success(rs);
} catch (Exception e) {
log.error("【分页查询当前课程的学习记录(课程维度)】错误{}", e.getMessage());
return error("查询失败", e.getMessage());
}
}
/**
* 导出课程学习记录
* @return
* 数据维度:(单门课程、多个学生)
* 25.11.20新增
*
* @param sc 课程学习记录查询参数
*/
@RequestMapping(value="/export",method = {RequestMethod.GET,RequestMethod.POST})
public JsonResponse<Boolean> export(String courseId,Integer type){
if(type==null) {
return badRequest("未指定导出类型");
public void export(StudyCourse sc, HttpServletResponse response) {
// 入参校验
if (StringUtils.isBlank(sc.getCourseId())) {
log.error("【导出课程学习记录】课程id不能为空");
return;
}
//type 1表导出汇总2表导出详细
String courseId = sc.getCourseId();
// 定义输出流
OutputStream outputStream = null;
try {
return success(true);
}catch(Exception e) {
log.error("导出课程学习记录错误",e.getMessage());
return error("导出课程学习记录失败",e.getMessage());
outputStream = response.getOutputStream();
LinkedHashMap<String, String> exportMap = new LinkedHashMap<>();
// 1.拼接固定表头(在线课名称,姓名,工号,部门,学习开始时间,学习结束时间,学习时长(分),学习状态,学习进度)
exportMap.put("在线课名称", "在线课名称");
exportMap.put("姓名", "姓名");
exportMap.put("工号", "工号");
exportMap.put("部门", "部门");
exportMap.put("学习开始时间", "学习开始时间");
exportMap.put("学习结束时间", "学习结束时间");
exportMap.put("学习时长(分)", "学习时长(分)");
exportMap.put("学习状态", "学习状态");
exportMap.put("学习进度", "学习进度");
// 2.查询课程的所有考试答卷信息拼接动态表头XXX考试成绩
// 注意:这里的考试信息每个学生每门课程只有一条,实际数据可能多次考试,因此根据考试配置中的(最高分/最新数据)获取对应列表数据即可(本次新增接口)
List<StudyExam> studyExams = studyExamService.getByCourseId(courseId);
// courseExam和studyExam表都有testName字段查看现有逻辑后选择studyExam表的testName字段作为考试名称
// 因为需求没提因此查询按默认id排序
// 查询学习记录接口,学习记录默认存储获取最高/最新的分数,备用
List<StudyCourseItem> studyCourseItems = service.getStudyCourseItemByCourseId(courseId);
// 判断非空则拼接表头
// 用于处理考试名称重复的情况
if (studyExams != null && !studyExams.isEmpty()) {
// 使用testId作为唯一标识符来区分不同的考试实例
Map<String, StudyExam> uniqueExams = new HashMap<>();
for (StudyExam studyExam : studyExams) {
// 因为后续要赋值,这里先让实体脱离持久化上下文,避免赋值后自动更新到数据库
entityManager.detach(studyExam);
String testId = studyExam.getTestId();
String examName = studyExam.getTestName();
// 如果还没有这个testId的记录则添加
if (!uniqueExams.containsKey(testId)) {
// 处理空考试名称
if (examName == null || examName.trim().isEmpty()) {
examName = "";
studyExam.setTestName(examName);
}
uniqueExams.put(testId, studyExam);
}
}
// 对唯一考试进行命名处理,解决同名问题
Map<String, Integer> testNameCountMap = new HashMap<>();
for (StudyExam studyExam : uniqueExams.values()) {
String examName = studyExam.getTestName();
// 处理重复名称
if (testNameCountMap.containsKey(examName)) {
// 如果已经存在该名称,则计数+1并添加后缀
Integer count = testNameCountMap.get(examName);
count++;
testNameCountMap.put(examName, count);
examName = examName + count;
// 赋值给原始对象
studyExam.setTestName(examName);
} else {
// 第一次出现该名称
testNameCountMap.put(examName, 1);
}
exportMap.put(examName + "考试成绩", examName);
}
}
// 3.查询课程学习记录信息并拼接导出信息
List<StudyCourse> studyCourses = service.findList(sc, null, null);
// 通过studyCourses中的人员id集合(去重),调用用户中心接口获取人员信息,填充部门字段
List<String> userIds = studyCourses.stream().map(StudyCourse::getAid).filter(Objects::nonNull).collect(Collectors.toList());
if (!userIds.isEmpty()) {
// 调用用户中心接口
List<UserSimpleVo> userSimpleVos = outsideService.findByIds(userIds);
if (userSimpleVos != null && !userSimpleVos.isEmpty()) {
for (UserSimpleVo userSimpleVo : userSimpleVos) {
// 填充部门字段
for (StudyCourse studyCourse1 : studyCourses) {
if (userSimpleVo.getAid().equals(studyCourse1.getAid())) {
studyCourse1.setOrgInfo(userSimpleVo.getOrgInfo());
// 取code为工号
studyCourse1.setUserNo(userSimpleVo.getCode());
}
}
}
} else {
log.error("【导出课程学习记录】用户信息查询失败查询boe人员表作为兜底方案");
// 和需求沟通后查询用户中心失败情况查询boe的人员表作为兜底方案如果仍然查询不到则不填充继续导出其他字段
for (String userId : userIds) {
// 用户信息也是redis获取的
User userInfo = userService.get(userId);
log.info("【导出课程学习记录】查询boe人员表用户id{}", userId);
if (userInfo != null) {
// 填充部门字段
for (StudyCourse studyCourse1 : studyCourses) {
if (Objects.equals(userId, studyCourse1.getAid())) {
log.info("【导出课程学习记录】查询boe人员表机构id{}", userInfo.getDepartId());
// 和技术沟通后确认这里机构名称是redis获取的获取不到返回null因此正常遍历没有效率和空值问题
studyCourse1.setOrgInfo(organizationService.getName(userInfo.getDepartId()));
// 取userNo为工号
studyCourse1.setUserNo(userInfo.getUserNo());
}
}
} else {
log.error("【导出课程学习记录】用户信息查询boe人员表失败用户id{}", userId);
}
}
}
}
// 将课程学习记录与考试信息拼接为map
List<Map<String, Object>> dataList = studyCourses.stream().map(studyCourse1 -> {
Map<String, Object> map = new HashMap<>();
// 拼接课程学习记录信息
map.put("在线课名称", studyCourse1.getCourseName());
map.put("姓名", studyCourse1.getAname());
map.put("工号", studyCourse1.getUserNo());
// 部门信息需要额外获取,暂时留空
map.put("部门", studyCourse1.getOrgInfo());
// 这个开始时间已经弃置了,不过先用再说(有值)
map.put("学习开始时间", studyCourse1.getStartTime());
// 结束时间为空的,说明还没学习结束(有值)
map.put("学习结束时间", studyCourse1.getFinishTime());
// 学习时长学习总时间是秒现在要求的学习时间是分钟向上取整1秒算1分钟60秒算1分钟61秒算2分钟
map.put("学习时长(分)", studyCourse1.getTotalDuration() == null ? null : (int) Math.ceil(studyCourse1.getTotalDuration() / 60.0));
// 学习状态需要转换
String statusText = "";
if (studyCourse1.getStatus() != null) {
switch (studyCourse1.getStatus()) {
case StudyCourse.STATUS_NOSTUDY:
statusText = "未开始学习";
break;
case StudyCourse.STATUS_STUDYING:
statusText = "学习中";
break;
case StudyCourse.STATUS_ABORTED:
statusText = "已终止";
break;
case StudyCourse.STATUS_FINISH:
statusText = "学习完成";
break;
default:
statusText = "";
}
}
map.put("学习状态", statusText);
// 学习进度
map.put("学习进度", studyCourse1.getProgress() != null ? studyCourse1.getProgress() + "%" : "");
// 按考试顺序拼接考试信息
if (studyExams != null && !studyExams.isEmpty()) {
// 获取当前学生该课程的所有考试成绩
List<StudyExam> studentExams = studyExams.stream().filter(studyExam -> Objects.equals(studyExam.getStudyId(), studyCourse1.getId())).collect(Collectors.toList());
// 修改为使用testId作为键而不是testName
Map<String, StudyExam> examMap = studentExams.stream().collect(Collectors.toMap(StudyExam::getTestId, Function.identity(), (e1, e2) -> e1));
// 按顺序添加考试成绩使用testId匹配
for (StudyExam studyExam : studyExams) {
String testId = studyExam.getTestId();
String examName = studyExam.getTestName();
String contentId = studyExam.getContentId();
if (examMap.containsKey(testId)) {
// 这里不直接用考试成绩,因为有多次考试
// 使用contentId+aid组合筛选学习记录数据获取成绩因为学习记录中的成绩默认按考试设置最新/最高分的方式计算
Optional<StudyCourseItem> studyCourseItemOptional = studyCourseItems.stream().filter(studyCourseItem -> Objects.equals(studyCourseItem.getContentId(), contentId) && Objects.equals(studyCourseItem.getAid(), studyCourse1.getAid())).findFirst();
// 如果存在成绩,填写成绩,否则填入空值
if (studyCourseItemOptional.isPresent()) {
map.put(examName, studyCourseItemOptional.get().getScore());
} else {
map.put(examName, "");
}
} else {
// 当前学生未进行的考试,填入空值
map.put(examName, "");
}
}
}
return map;
}).collect(Collectors.toList());
// 4.拼接消息类型和响应头信息
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment;filename=StudyCourseRecord.xlsx");
// 5.调用动态列excel导出接口
ExportsExcelSenderUtil.exportDynamic(exportMap, dataList, outputStream, "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

@@ -1,18 +1,16 @@
package com.xboe.school.study.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xboe.core.SysConstant;
import com.xboe.core.orm.IdEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xboe.core.SysConstant;
import com.xboe.core.orm.IdEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/*
* 课程学习表,课程图片通过对应图片的api获取,这里不做存储。
@@ -83,8 +81,9 @@ public class StudyCourse extends IdEntity{
/*
* 开始学习时间,报名和学习是一体的,此字段为后续报名学习不致的情况,当前这种情况没有
* */
@Deprecated
* 25.11.21修改:经生产数据查证本字段实际未废弃,因此继续使用
*/
// @Deprecated
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Column(name = "start_time")
private LocalDateTime startTime;
@@ -105,8 +104,9 @@ public class StudyCourse extends IdEntity{
/*
* 学习总时间,秒,没有小数的情况,所以直接使用整数类型
* 这一项计算时间,在二期中已经不在使用,学习时长已经移到统计服务中,单独的课程不再记录
* 25.11.21修改:经生产数据查证本字段实际未废弃,因此继续使用
*/
@Deprecated
// @Deprecated
@Column(name = "total_duration")
private Integer totalDuration;
@@ -142,9 +142,51 @@ public class StudyCourse extends IdEntity{
* */
@Column(name = "remark",length = 200)
private String remark;
/**
* 机构信息,多级使用/分隔
* 25.11.24新增
*/
@Transient
private String orgInfo;
@Transient
private String courseImage;
/**
* 工号
* 25.11.24新增
*/
@Transient
private String userNo;
/**
* 部门
* 25.11.24新增
*/
@Transient
private String departName;
/**
* 根据考试设置的最新/最高成绩
* 25.11.24新增
*/
@Transient
private String bestScore;
/**
* 查询开始学习时间yyyy-MM-dd
* 25.11.24新增
*/
@Transient
private String queryStartTime;
/**
* 查询完成学习时间yyyy-MM-dd
* 25.11.24新增
*/
@Transient
private String queryFinishTime;
}

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

@@ -1,17 +1,14 @@
package com.xboe.school.study.service;
import java.util.List;
import com.xboe.common.OrderCondition;
import com.xboe.common.PageList;
import com.xboe.school.study.dto.CourseStudyItem;
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.dto.*;
import com.xboe.school.study.entity.StudyCourse;
import com.xboe.school.study.entity.StudyCourseItem;
import com.xboe.school.study.entity.StudySignup;
import java.util.List;
public interface IStudyCourseService {
/**
@@ -46,15 +43,35 @@ public interface IStudyCourseService {
PageList<StudyCourse> findByES(int pageIndex,int pageSize) throws Exception;
/**
* 分页查询课程学习记录,用户的课程学习记录
* @param pageIndex
* @param pageSize
* @param sc
* @param oc
* @return
* 列表查询课程学习记录,用户的课程学习记录
* 25.11.20新增
*
* @param sc 课程学习表查询字段
* @param oc 排序字段
* @param isFinish 是否已完成
* @return 学习记录信息集合
*/
List<StudyCourse> findList(StudyCourse sc, OrderCondition oc, Boolean isFinish);
/**
* 分页查询课程学习记录
* @param pageIndex 页码
* @param pageSize 每页数量
* @param sc 课程学习记录查询参数
* @param oc 排序字段
* @param isFinish 是否已完成
* @return 课程学习记录分页集合
*/
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);
/**
* 热度榜
* */
@@ -105,4 +122,15 @@ public interface IStudyCourseService {
List<CourseStudyItem> findByCourseAndUsers(String courseId,List<String> aids) throws Exception;
List<StudyCourse> getCourseId(String userId);
/**
* 根据课程id得到对应课程学习记录包括考试记录
* 这个接口从study_course_item表获取这个表的score考试分数字段保存或更新时是根据考试设置最新/最高分的
* 25.11.24新增
*
* @param courseId 课程id
* @return 考试记录集合
*/
List<StudyCourseItem> getStudyCourseItemByCourseId(String courseId);
}

View File

@@ -1,9 +1,9 @@
package com.xboe.school.study.service;
import java.util.List;
import com.xboe.school.study.entity.StudyExam;
import java.util.List;
public interface IStudyExamService {
/**
@@ -30,5 +30,14 @@ public interface IStudyExamService {
*/
List<StudyExam> getByStudyIdAndContentId(String studyId,String contentId);
/**
* 根据课程id得到对应的考试记录
* 25.11.20新增
*
* @param courseId 课程id
* @return 考试记录集合
*/
List<StudyExam> getByCourseId(String courseId);
void correctStstus(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

@@ -1,48 +1,39 @@
package com.xboe.school.study.service.impl;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.xboe.common.OrderCondition;
import com.xboe.common.PageList;
import com.xboe.core.orm.FieldFilters;
import com.xboe.core.orm.FieldUpdateType;
import com.xboe.core.orm.QueryBuilder;
import com.xboe.core.orm.UpdateBuilder;
import com.xboe.core.orm.*;
import com.xboe.core.utils.ConvertUtil;
import com.xboe.data.outside.IOutSideDataService;
import com.xboe.module.course.dao.CourseContentDao;
import com.xboe.module.course.dao.CourseDao;
import com.xboe.module.course.entity.Course;
import com.xboe.module.course.service.ICourseStudySearch;
import com.xboe.school.study.dao.StudyAssessDao;
import com.xboe.school.study.dao.StudyCourseDao;
import com.xboe.school.study.dao.StudyCourseItemDao;
import com.xboe.school.study.dao.StudyExamDao;
import com.xboe.school.study.dao.StudyHomeWorkDao;
import com.xboe.school.study.dao.StudyScoreDao;
import com.xboe.school.study.dao.StudySignupDao;
import com.xboe.school.study.dao.StudyTimeDao;
import com.xboe.school.study.dto.CourseStudyItem;
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.dao.*;
import com.xboe.school.study.dto.*;
import com.xboe.school.study.entity.StudyCourse;
import com.xboe.school.study.entity.StudyCourseItem;
import com.xboe.school.study.entity.StudySignup;
import com.xboe.school.study.service.IStudyCourseService;
import com.xboe.school.study.service.IStudySignupService;
import com.xboe.system.organization.service.IOrganizationService;
import com.xboe.system.user.entity.User;
import com.xboe.system.user.service.IUserService;
import com.xboe.system.user.vo.UserSimpleVo;
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.transaction.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j
@Service
@@ -55,6 +46,15 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
@Autowired
IStudySignupService signupService;
@Autowired
IOutSideDataService outsideService;
@Autowired
IUserService userService;
@Autowired
IOrganizationService organizationService;
@Autowired
StudyCourseDao studyCourseDao;
@@ -82,6 +82,9 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
@Autowired
StudyTimeDao studyTimeDao;
@Autowired
CourseStatDao courseStatDao;
@Autowired
CourseDao courseDao;
@@ -101,6 +104,16 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
return null;
}
/**
* 分页查询课程学习记录
*
* @param pageIndex 页码
* @param pageSize 每页数量
* @param sc 课程学习记录查询参数
* @param oc 排序字段
* @param isFinish 是否已完成
* @return 课程学习记录分页集合
*/
@Override
public PageList<StudyCourse> findPage(int pageIndex, int pageSize, StudyCourse sc, OrderCondition oc,Boolean isFinish) {
QueryBuilder query=QueryBuilder.from(StudyCourse.class);
@@ -132,17 +145,107 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
if(StringUtils.isNotBlank(sc.getAid())) {
query.addFilter(FieldFilters.eq("aid", sc.getAid()));
}
}
if(isFinish!=null) {
if(isFinish) {
query.addFilter(FieldFilters.eq("status",9));
}else {
query.addFilter(FieldFilters.lt("status",9));
// 25.11.25新增:添加基于时间的查询条件
if (sc.getQueryStartTime() != null && sc.getQueryFinishTime() != null) {
// 开始结束时间均传入的情况,实现筛选逻辑
// (startTime >= 查询开始时间 AND startTime <= 查询结束时间) OR (finishTime >= 查询开始时间 AND finishTime <= 查询结束时间)
// 这样兼容查询结束时间为空值的情况,因为学员课程未结束时没有结束时间
LocalDate startDate = LocalDate.parse(sc.getQueryStartTime());
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDate finishDate = LocalDate.parse(sc.getQueryFinishTime());
LocalDateTime finishDateTime = finishDate.atTime(LocalTime.MAX);
// 筛选开始时间
IFieldFilter startTimeInRange = FieldFilters.and(FieldFilters.ge("startTime", startDateTime), FieldFilters.le("startTime", finishDateTime));
// 筛选结束时间
IFieldFilter finishTimeInRange = FieldFilters.and(FieldFilters.ge("finishTime", startDateTime), FieldFilters.le("finishTime", finishDateTime));
// OR条件查询
query.addFilter(FieldFilters.or(startTimeInRange, finishTimeInRange));
} else {
// 只输出单个参数情况
// 筛选开始时间
if (sc.getQueryStartTime() != null) {
LocalDate startDate = LocalDate.parse(sc.getQueryStartTime());
LocalDateTime startDateTime = startDate.atStartOfDay();
query.addFilter(FieldFilters.ge("startTime", startDateTime));
}
// 筛选结束时间
if (sc.getQueryFinishTime() != null) {
LocalDate finishDate = LocalDate.parse(sc.getQueryFinishTime());
LocalDateTime finishDateTime = finishDate.atTime(LocalTime.MAX);
query.addFilter(FieldFilters.le("finishTime", finishDateTime));
}
}
}
return studyCourseDao.findPage(query.builder());
// 原有查询是否结束逻辑
if (isFinish != null) {
if (isFinish) {
query.addFilter(FieldFilters.eq("status", 9));
} else {
query.addFilter(FieldFilters.lt("status", 9));
}
}
PageList<StudyCourse> studyCoursePageList = studyCourseDao.findPage(query.builder());
// 25.11.24新增:添加工号和部门字段,并增加学习时长枚举筛选
// 根据当前查询数据的aid补全用户工号和部门字段
List<StudyCourse> studyCourses = studyCoursePageList.getList();
List<String> userIds = studyCourses.stream().map(StudyCourse::getAid).collect(Collectors.toList());
// 这块和StudyCourseApi的export方法一致但是因为两种查询方式返回对象不同暂时没有整合为独立方法复用
if (!userIds.isEmpty()) {
// 调用用户中心接口
List<UserSimpleVo> userSimpleVos = outsideService.findByIds(userIds);
if (userSimpleVos != null && !userSimpleVos.isEmpty()) {
for (UserSimpleVo userSimpleVo : userSimpleVos) {
// 填充部门字段
for (StudyCourse studyCourse1 : studyCourses) {
if (userSimpleVo.getAid().equals(studyCourse1.getAid())) {
studyCourse1.setOrgInfo(userSimpleVo.getOrgInfo());
// 取code为工号
studyCourse1.setUserNo(userSimpleVo.getCode());
}
}
}
} else {
log.error("【分页查询课程学习记录】用户信息查询失败查询boe人员表作为兜底方案");
// 和需求沟通后查询用户中心失败情况查询boe的人员表作为兜底方案如果仍然查询不到则不填充继续导出其他字段
for (String userId : userIds) {
// 用户信息也是redis获取的
User userInfo = userService.get(userId);
log.info("【分页查询课程学习记录】查询boe人员表用户id{}", userId);
if (userInfo != null) {
// 填充部门字段
for (StudyCourse studyCourse1 : studyCourses) {
if (Objects.equals(userId, studyCourse1.getAid())) {
log.info("【分页查询课程学习记录】查询boe人员表机构id{}", userInfo.getDepartId());
// 和技术沟通后确认这里机构名称是redis获取的获取不到返回null因此正常遍历没有效率和空值问题
studyCourse1.setOrgInfo(organizationService.getName(userInfo.getDepartId()));
// 取userNo为工号
studyCourse1.setUserNo(userInfo.getUserNo());
}
}
} else {
log.error("【分页查询课程学习记录】用户信息查询boe人员表失败用户id{}", userId);
}
}
}
}
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
public List<StudyCourseNameDto> studyCounts(int num) {
// QueryBuilder builder = QueryBuilder.from(StudyCourse.class);
@@ -674,6 +777,76 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
return rs;
}
@Override
public List<StudyCourse> findList(StudyCourse sc, OrderCondition oc, Boolean isFinish) {
QueryBuilder query = QueryBuilder.from(StudyCourse.class);
if (oc == null) {
oc = OrderCondition.desc("id");
}
query.addOrder(oc);
if (sc != null) {
if (StringUtils.isNotBlank(sc.getCourseName())) {
query.addFilter(FieldFilters.like("courseName", sc.getCourseName()));
}
if (StringUtils.isNotBlank(sc.getAname())) {
query.addFilter(FieldFilters.like("aname", sc.getAname()));
}
if (sc.getStatus() != null) {
query.addFilter(FieldFilters.eq("status", sc.getStatus()));
}
if (sc.getCourseType() != null) {
query.addFilter(FieldFilters.eq("courseType", sc.getCourseType()));
}
if (StringUtils.isNotBlank(sc.getCourseId())) {
query.addFilter(FieldFilters.eq("courseId", sc.getCourseId()));
}
if (sc.getStartTime() != null) {
query.addFilter(FieldFilters.eq("startTime", sc.getStartTime()));
}
if (StringUtils.isNotBlank(sc.getAid())) {
query.addFilter(FieldFilters.eq("aid", sc.getAid()));
}
// 25.11.25新增:添加基于时间的查询条件
if (sc.getQueryStartTime() != null && sc.getQueryFinishTime() != null) {
// 开始结束时间均传入的情况,实现筛选逻辑
// (startTime >= 查询开始时间 AND startTime <= 查询结束时间) OR (finishTime >= 查询开始时间 AND finishTime <= 查询结束时间)
// 这样兼容查询结束时间为空值的情况,因为学员课程未结束时没有结束时间
LocalDate startDate = LocalDate.parse(sc.getQueryStartTime());
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDate finishDate = LocalDate.parse(sc.getQueryFinishTime());
LocalDateTime finishDateTime = finishDate.atTime(LocalTime.MAX);
// 筛选开始时间
IFieldFilter startTimeInRange = FieldFilters.and(FieldFilters.ge("startTime", startDateTime), FieldFilters.le("startTime", finishDateTime));
// 筛选结束时间
IFieldFilter finishTimeInRange = FieldFilters.and(FieldFilters.ge("finishTime", startDateTime), FieldFilters.le("finishTime", finishDateTime));
// OR条件查询
query.addFilter(FieldFilters.or(startTimeInRange, finishTimeInRange));
} else {
// 只输出单个参数情况
// 筛选开始时间
if (sc.getQueryStartTime() != null) {
LocalDate startDate = LocalDate.parse(sc.getQueryStartTime());
LocalDateTime startDateTime = startDate.atStartOfDay();
query.addFilter(FieldFilters.ge("startTime", startDateTime));
}
// 筛选结束时间
if (sc.getQueryFinishTime() != null) {
LocalDate finishDate = LocalDate.parse(sc.getQueryFinishTime());
LocalDateTime finishDateTime = finishDate.atTime(LocalTime.MAX);
query.addFilter(FieldFilters.le("finishTime", finishDateTime));
}
}
}
if (isFinish != null) {
if (isFinish) {
query.addFilter(FieldFilters.eq("status", 9));
} else {
query.addFilter(FieldFilters.lt("status", 9));
}
}
return studyCourseDao.findList(query.builder());
}
@Override
public List<CourseStudyItem> findByCourseAndUsers(String courseId, List<String> aids) throws Exception {
@@ -719,4 +892,17 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
return studyCourseDao.findList(FieldFilters.in("id", ids));
}
/**
* 根据课程id得到对应考试记录
* 这个接口从study_course_item表获取这个表的score分数字段更新时是根据考试设置最新/最高分的
* 25.11.24新增
*
* @param courseId 课程id
* @return 考试记录集合
*/
@Override
public List<StudyCourseItem> getStudyCourseItemByCourseId(String courseId) {
return studyCourseItemDao.findList(FieldFilters.eq("courseId", courseId));
}
}

View File

@@ -22,7 +22,6 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.transaction.Transactional;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
@@ -159,6 +158,18 @@ public class StudyExamServiceImpl implements IStudyExamService{
return dao.findList(OrderCondition.desc("id"),FieldFilters.eq("studyId", studyId),FieldFilters.eq("contentId", contentId));
}
/**
* 根据课程id得到对应的考试记录
* 25.11.20新增
*
* @param courseId 课程id
* @return 考试记录集合
*/
@Override
public List<StudyExam> getByCourseId(String courseId) {
return dao.findList(OrderCondition.desc("id"), FieldFilters.eq("courseId", courseId));
}
@Override
@Transactional
public void correctStstus(String courseId) {

View File

@@ -1,5 +1,10 @@
boe:
domain: http://192.168.0.253
domain-name: https://u-pre.boe.com
pcPageUrl: ${boe.domain-name}/pc/course/studyindex?id=
h5PageUrl: ${boe.domain-name}/mobile/pages/study/courseStudy?id=
pcLoginUrl: ${boe.domain-name}/web/
h5LoginUrl: ${boe.domain-name}/m/loginuser
spring:
servlet:
multipart:

View File

@@ -128,7 +128,11 @@ jasypt:
boe:
domain: http://10.251.132.75
domain-name: https://u-pre.boe.com
pcPageUrl: ${boe.domain-name}/pc/course/studyindex?id=
h5PageUrl: ${boe.domain-name}/mobile/pages/study/courseStudy?id=
pcLoginUrl: ${boe.domain-name}/web/
h5LoginUrl: ${boe.domain-name}/m/loginuser
ok:
http:
connect-timeout: 30

View File

@@ -139,4 +139,12 @@ aop-log-record:
password: admin
elasticsearch:
host: 10.251.88.218
port: 9200
port: 9200
boe:
domain: http://192.168.0.253
domain-name: https://u.boe.com
pcPageUrl: ${boe.domain-name}/pc/course/studyindex?id=
h5PageUrl: ${boe.domain-name}/mobile/pages/study/courseStudy?id=
pcLoginUrl: ${boe.domain-name}/web/
h5LoginUrl: ${boe.domain-name}/m/loginuser

View File

@@ -153,7 +153,11 @@ jasypt:
boe:
domain: http://10.251.186.27
domain-name: https://u-pre.boe.com
pcPageUrl: ${boe.domain-name}/pc/course/studyindex?id=
h5PageUrl: ${boe.domain-name}/mobile/pages/study/courseStudy?id=
pcLoginUrl: ${boe.domain-name}/web/
h5LoginUrl: ${boe.domain-name}/m/loginuser
ok:
http:
connect-timeout: 30