mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/per-boe/java-servers.git
synced 2025-12-10 03:16:48 +08:00
Compare commits
26 Commits
251208-fea
...
SZX-1194-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e7e529f74 | ||
|
|
d6dcbdfcac | ||
|
|
44f4309930 | ||
|
|
64a3b26e77 | ||
|
|
9f4475dd05 | ||
|
|
ec3d8c57ac | ||
|
|
aa9b24de1f | ||
|
|
4450e1b13a | ||
|
|
7efd586fdc | ||
|
|
385c3d1472 | ||
|
|
e3c94c97d2 | ||
|
|
186fc6e56f | ||
|
|
3cbfccf806 | ||
|
|
e513b08205 | ||
|
|
2191db1c95 | ||
|
|
6e2ffc9eaf | ||
|
|
6a33194818 | ||
|
|
5942a7dcd4 | ||
|
|
3ddc9d58f0 | ||
|
|
8112aea110 | ||
|
|
e8b31f4216 | ||
|
|
e83f3adb94 | ||
|
|
a14639283e | ||
|
|
8d9f400a7a | ||
|
|
933e7a018a | ||
|
|
4de8556802 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -48,4 +48,3 @@ nbdist/
|
||||
!*/build/*.xml
|
||||
|
||||
*.pid
|
||||
doc/
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -24,13 +23,11 @@ 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
|
||||
|
||||
@@ -34,7 +34,6 @@ 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
|
||||
|
||||
@@ -60,6 +60,9 @@ public class ThirdApi {
|
||||
@Value("${orgTree.orgChildTreeList}")
|
||||
private String orgChildTreeListUrl;
|
||||
|
||||
@Value("${userBasic.getUserBasicInfo}")
|
||||
private String getUserBasicInfo;
|
||||
|
||||
@Autowired
|
||||
UserDao userDao;
|
||||
|
||||
@@ -366,6 +369,62 @@ public class ThirdApi {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 虽然当前已存在接口查询用户基本信息,目前仅仅包括用户名、工号、用户ID
|
||||
*/
|
||||
public List<UserBasicInfoVo> getUserBasicInfoByWorkNums(List<String> workNums) {
|
||||
|
||||
UserBasicInfoDto userBasicInfoDto = new UserBasicInfoDto();
|
||||
userBasicInfoDto.setWorkNums(workNums);
|
||||
Response<List<UserAccount>> response = userRemoteClient.getUserBasicInfo(userBasicInfoDto);
|
||||
String respStr = JSON.toJSONString(response);
|
||||
|
||||
UserBasicInfoResultVo userBasicInfoResult = JSONUtil.parseObj(respStr).toBean(UserBasicInfoResultVo.class);
|
||||
List<UserBasicInfoVo> basicInfos = userBasicInfoResult.getResult();
|
||||
return basicInfos;
|
||||
|
||||
}
|
||||
|
||||
public List<UserBasicInfoVo> getUserBasicInfoByWorkNums2(String workNum, String token) {
|
||||
try {
|
||||
// 参数校验
|
||||
if (StringUtils.isBlank(workNum)) {
|
||||
log.warn("getUserBasicInfoByWorkNums2 workNum为空");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 构建URL参数,将单个workNum作为列表参数传递
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("workNums", Collections.singletonList(workNum));
|
||||
|
||||
log.info("getUserBasicInfoByWorkNums2 请求参数: workNum={}, url={}", workNum, getUserBasicInfo);
|
||||
|
||||
// 发送HTTP GET请求
|
||||
String responseStr = HttpRequest.get(getUserBasicInfo)
|
||||
.form(params)
|
||||
.header("token", token)
|
||||
.execute()
|
||||
.body();
|
||||
|
||||
log.info("getUserBasicInfoByWorkNums2 响应结果: {}", responseStr);
|
||||
|
||||
// 解析响应
|
||||
UserBasicInfoResultVo resultVo = JSONUtil.parseObj(responseStr).toBean(UserBasicInfoResultVo.class);
|
||||
log.info("getUserBasicInfoByWorkNums2 解析结果: {}", resultVo);
|
||||
if (resultVo != null && resultVo.getStatus() == 200 && resultVo.getResult() != null) {
|
||||
return resultVo.getResult();
|
||||
} else {
|
||||
log.error("getUserBasicInfoByWorkNums2 请求失败: status={}, message={}",
|
||||
resultVo != null ? resultVo.getStatus() : "null",
|
||||
resultVo != null ? resultVo.getMessage() : "响应为空");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("getUserBasicInfoByWorkNums2 HTTP请求异常: workNum={}", workNum, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateOrSaveCourse(CourseParam param, String token){
|
||||
log.info("---------------准备同步在线课到讲师管理完毕 ------- param " + param);
|
||||
String resp = Optional.ofNullable(
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.xboe.api.vo;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Data
|
||||
@Slf4j
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserBasicInfoResultVo {
|
||||
|
||||
private String error;
|
||||
private String message;
|
||||
private String permissions;
|
||||
private List<UserBasicInfoVo> result;
|
||||
private int status;
|
||||
private Date timestamp;
|
||||
|
||||
public UserBasicInfoResultVo success() {
|
||||
if (this.status != 200) {
|
||||
log.error("获取用户基本信息失败----{}", JSONUtil.toJsonPrettyStr(this));
|
||||
return null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.xboe.api.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UserBasicInfoVo {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 用户名。
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 工号。
|
||||
*/
|
||||
private String workNum;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.xboe.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 整体系统MySQL数据库schema配置
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "mysql.schema")
|
||||
@Data
|
||||
public class MySqlSchemaProperties {
|
||||
|
||||
/**
|
||||
* 用户中心数据库schema
|
||||
*/
|
||||
private String userCenterSchema;
|
||||
}
|
||||
@@ -65,6 +65,24 @@ public class ThreadPoolConfig {
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步存会话数据线程池
|
||||
* @return
|
||||
*/
|
||||
@Bean(name = "esChatExecutor")
|
||||
public ThreadPoolTaskExecutor esChatExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(10);
|
||||
executor.setMaxPoolSize(500);
|
||||
executor.setQueueCapacity(10);
|
||||
executor.setThreadNamePrefix("es-chat-");
|
||||
executor.setKeepAliveSeconds(300);
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean(name = "customDispatcher")
|
||||
public Dispatcher customDispatcher(@Qualifier("eventStreamExecutor") ThreadPoolTaskExecutor eventStreamExecutor) {
|
||||
return new Dispatcher(eventStreamExecutor.getThreadPoolExecutor());
|
||||
|
||||
@@ -7,4 +7,8 @@ public class CaseAiConstants {
|
||||
public static final String CASE_DOC_UPLOAD_INTERFACE_NAME = "文档上传";
|
||||
|
||||
public static final String CASE_DOC_DELETE_INTERFACE_NAME = "文档删除";
|
||||
|
||||
public static final String CHAT_SYS_ERR_MSG = "服务繁忙,请稍后再试。";
|
||||
|
||||
public static final String CHAT_NET_ERR_MSG = "网络异常,请稍后再试。";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
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;
|
||||
@@ -8,17 +16,9 @@ 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.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -30,16 +30,7 @@ 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();
|
||||
@@ -411,7 +402,8 @@ public class OutSideDataServiceImpl implements IOutSideDataService {
|
||||
return uids;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public void updateUser(String aid, String avatar, String sign) {
|
||||
|
||||
String token = TokenProxy.getToken(request);
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,6 @@ import com.xboe.module.boecase.service.ICaseAiChatService;
|
||||
import com.xboe.module.boecase.service.ICaseAiPermissionService;
|
||||
import com.xboe.module.boecase.service.IElasticSearchIndexService;
|
||||
import com.xboe.module.boecase.vo.CaseAiMessageVo;
|
||||
import com.xboe.module.excel.ExportsExcelSenderUtil;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -17,18 +15,16 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI对话管理API
|
||||
*/
|
||||
@Slf4j
|
||||
@Slf4j(topic = "caseAiChatLogger")
|
||||
@RestController
|
||||
@RequestMapping(value = "/xboe/m/boe/case/ai")
|
||||
public class CaseAiChatApi extends ApiBaseController {
|
||||
@@ -64,6 +60,26 @@ public class CaseAiChatApi extends ApiBaseController {
|
||||
return caseAiChatService.chat(caseAiChatDto, getCurrent());
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止当前聊天输出
|
||||
* @param conversationId 会话ID
|
||||
* @return 是否成功停止
|
||||
*/
|
||||
@PostMapping("/stop")
|
||||
public JsonResponse<Boolean> stopChat(@RequestParam String conversationId) {
|
||||
try {
|
||||
boolean result = caseAiChatService.stopChatOutput(conversationId);
|
||||
if (result) {
|
||||
return success(true, "成功停止输出");
|
||||
} else {
|
||||
return success(false, "未找到对应的会话或会话已结束");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("停止聊天输出异常", e);
|
||||
return error("停止输出失败", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据conversationId查看会话内消息记录
|
||||
* @param conversationId 会话ID
|
||||
@@ -90,32 +106,10 @@ public class CaseAiChatApi extends ApiBaseController {
|
||||
public void downloadConversationExcel(@RequestParam String startTime,
|
||||
@RequestParam String endTime,
|
||||
HttpServletResponse response) {
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
LocalDateTime start = LocalDateTime.parse(startTime, formatter);
|
||||
LocalDateTime end = LocalDateTime.parse(endTime, formatter);
|
||||
|
||||
// TODO: 这里需要修改为实际返回数据的方法
|
||||
caseAiChatService.downloadConversationExcel(start, end);
|
||||
|
||||
response.setContentType("application/vnd.ms-excel");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=conversations.xls");
|
||||
|
||||
// 示例数据,实际应该从Service获取
|
||||
LinkedHashMap<String, String> headers = new LinkedHashMap<>();
|
||||
headers.put("会话ID", "conversationId");
|
||||
headers.put("会话名称", "conversationName");
|
||||
headers.put("用户", "user");
|
||||
headers.put("开始时间", "startTime");
|
||||
headers.put("会话时长", "duration");
|
||||
|
||||
List<ConversationExcelVo> dataList = new ArrayList<>();
|
||||
// 这里应该填充实际数据
|
||||
|
||||
ExportsExcelSenderUtil.export(headers, dataList, response.getOutputStream(), "yyyy-MM-dd HH:mm:ss");
|
||||
} catch (Exception e) {
|
||||
log.error("导出会话记录为Excel异常", e);
|
||||
}
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
LocalDate startDate = LocalDate.parse(startTime, formatter);
|
||||
LocalDate endDate = LocalDate.parse(endTime, formatter);
|
||||
caseAiChatService.getConversationExcel(startDate.atStartOfDay(), endDate.atTime(23, 59, 59), response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,16 +165,4 @@ public class CaseAiChatApi extends ApiBaseController {
|
||||
}
|
||||
return error("创建失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于Excel导出的VO类
|
||||
*/
|
||||
@Data
|
||||
static class ConversationExcelVo {
|
||||
private String conversationId;
|
||||
private String conversationName;
|
||||
private String user;
|
||||
private String startTime;
|
||||
private String duration;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@ public class CaseAiDocumentAsyncHandler {
|
||||
|
||||
private final AtomicInteger currentTaskCount = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* 限流,默认QPS 40
|
||||
*/
|
||||
private final TokenBucketRateLimiter rateLimiter = new TokenBucketRateLimiter(40);
|
||||
|
||||
@Autowired
|
||||
@Qualifier("aiDocExecutor")
|
||||
private ThreadPoolTaskExecutor aiDocExecutor;
|
||||
@@ -27,7 +32,7 @@ public class CaseAiDocumentAsyncHandler {
|
||||
public void process(CaseDocumentLogOptTypeEnum optTypeEnum, Cases... caseList) {
|
||||
for (Cases cases : caseList) {
|
||||
// 控制并发数量
|
||||
while (currentTaskCount.get() >= 15) {
|
||||
while (currentTaskCount.get() >= 5) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -39,8 +44,13 @@ public class CaseAiDocumentAsyncHandler {
|
||||
currentTaskCount.incrementAndGet();
|
||||
|
||||
aiDocExecutor.submit(() -> {
|
||||
processCases(cases, optTypeEnum);
|
||||
currentTaskCount.decrementAndGet();
|
||||
try {
|
||||
// 限流
|
||||
rateLimiter.acquire();
|
||||
processCases(cases, optTypeEnum);
|
||||
} finally {
|
||||
currentTaskCount.decrementAndGet();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.xboe.module.boecase.async;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 令牌桶限流算法实现
|
||||
*/
|
||||
public class TokenBucketRateLimiter {
|
||||
|
||||
private final double permitsPerSecond; // 每秒生成的令牌数(即 TPS)
|
||||
private final AtomicLong nextFreeTicketMicros = new AtomicLong(0); // 下一个令牌可用的时间(微秒)
|
||||
private final AtomicLong storedPermits = new AtomicLong(0); // 当前桶中存储的令牌数(本简化版不支持突发,可省略)
|
||||
private static final long MICROSECONDS_PER_SECOND = 1_000_000L;
|
||||
|
||||
public TokenBucketRateLimiter(double permitsPerSecond) {
|
||||
this.permitsPerSecond = permitsPerSecond;
|
||||
this.nextFreeTicketMicros.set(System.nanoTime() / 1000); // 初始化为当前时间(微秒)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个令牌,阻塞直到可用
|
||||
*/
|
||||
public void acquire() {
|
||||
long waitMicros = reserve(1);
|
||||
if (waitMicros > 0) {
|
||||
try {
|
||||
long waitNanos = waitMicros * 1000; // 转为纳秒
|
||||
TimeUnit.NANOSECONDS.sleep(waitNanos);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预留 1 个令牌,返回需要等待的微秒数
|
||||
*/
|
||||
private long reserve(int permits) {
|
||||
long nowMicros = System.nanoTime() / 1000;
|
||||
long nextFreeTicket = nextFreeTicketMicros.get();
|
||||
long waitMicros = Math.max(0, nextFreeTicket - nowMicros);
|
||||
|
||||
long newNextFreeTicket = nowMicros + waitMicros + (long) (permits * MICROSECONDS_PER_SECOND / permitsPerSecond);
|
||||
while (!nextFreeTicketMicros.compareAndSet(nextFreeTicket, newNextFreeTicket)) {
|
||||
// CAS 失败,说明其他线程修改了时间,重试
|
||||
nowMicros = System.nanoTime() / 1000;
|
||||
nextFreeTicket = nextFreeTicketMicros.get();
|
||||
waitMicros = Math.max(0, nextFreeTicket - nowMicros);
|
||||
newNextFreeTicket = nowMicros + waitMicros + (long) (permits * MICROSECONDS_PER_SECOND / permitsPerSecond);
|
||||
}
|
||||
|
||||
return waitMicros;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.xboe.module.boecase.entity.CaseAiConversations;
|
||||
import com.xboe.module.boecase.vo.CaseAiMessageVo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@@ -16,6 +17,7 @@ public interface ICaseAiChatService {
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param caseAiChatDto
|
||||
* @param currentUser
|
||||
* @return
|
||||
@@ -24,7 +26,8 @@ public interface ICaseAiChatService {
|
||||
|
||||
/**
|
||||
* 创建新的AI对话会话
|
||||
* @param userId 用户ID
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param conversationName 对话名称
|
||||
* @return 创建的会话信息
|
||||
*/
|
||||
@@ -32,6 +35,7 @@ public interface ICaseAiChatService {
|
||||
|
||||
/**
|
||||
* 根据conversationId查看会话内消息记录
|
||||
*
|
||||
* @param conversationId 会话ID
|
||||
* @return 消息记录列表
|
||||
*/
|
||||
@@ -41,6 +45,23 @@ public interface ICaseAiChatService {
|
||||
* 导出会话记录为Excel
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param response
|
||||
*/
|
||||
void getConversationExcel(LocalDateTime startTime, LocalDateTime endTime, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 导出会话记录为Excel
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
*/
|
||||
void downloadConversationExcel(LocalDateTime startTime, LocalDateTime endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止当前聊天输出
|
||||
*
|
||||
* @param conversationId 会话ID
|
||||
* @return 是否成功停止
|
||||
*/
|
||||
boolean stopChatOutput(String conversationId);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
@EnableConfigurationProperties({CaseAiProperties.class})
|
||||
@Service
|
||||
@Slf4j
|
||||
@Slf4j(topic = "caseAiChatLogger")
|
||||
public class AiAccessTokenServiceImpl implements IAiAccessTokenService {
|
||||
|
||||
private static final String ACCESS_TOKEN_CACHE_KEY = "case_ai_access_token";
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.xboe.module.boecase.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.xboe.constants.CaseAiConstants;
|
||||
import com.xboe.core.CurrentUser;
|
||||
import com.xboe.core.orm.FieldFilters;
|
||||
import com.xboe.enums.CaseAiChatStatusEnum;
|
||||
@@ -22,13 +23,12 @@ import com.xboe.module.boecase.entity.AiChatConversationData;
|
||||
import com.xboe.module.boecase.vo.ConversationExcelVo;
|
||||
import com.xboe.system.organization.vo.OrgSimpleVo;
|
||||
import com.xboe.system.user.service.IUserService;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import okhttp3.sse.EventSource;
|
||||
import okhttp3.sse.EventSourceListener;
|
||||
import okhttp3.sse.EventSources;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
@@ -40,30 +40,21 @@ import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.index.IndexResponse;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -71,16 +62,21 @@ import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@EnableConfigurationProperties({CaseAiProperties.class})
|
||||
@Service
|
||||
@Slf4j
|
||||
@Slf4j(topic = "caseAiChatLogger")
|
||||
public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
|
||||
@Autowired
|
||||
private CaseAiProperties caseAiProperties;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("esChatExecutor")
|
||||
private ThreadPoolTaskExecutor esChatExecutor;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("customDispatcher")
|
||||
private Dispatcher dispatcher;
|
||||
@@ -102,6 +98,9 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
|
||||
@Autowired
|
||||
private CasesDao casesDao;
|
||||
|
||||
// 用于存储会话ID与EventSource的映射关系,以便能够中断特定会话
|
||||
private final Map<String, EventSource> conversationEventSourceMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -114,7 +113,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
conversationId = getOrCreateConversationId(caseAiChatDto, currentUser);
|
||||
} catch (Exception e) {
|
||||
log.error("获取会话ID失败", e);
|
||||
errMessage(sseEmitter, "服务繁忙,请稍后再试。");
|
||||
errMessage(sseEmitter, null, CaseAiConstants.CHAT_SYS_ERR_MSG);
|
||||
sseEmitter.complete();
|
||||
return sseEmitter;
|
||||
}
|
||||
@@ -124,6 +123,13 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
|
||||
// 3. 构建请求参数
|
||||
String userId = currentUser.getCode();
|
||||
|
||||
// 6. 用于收集对话数据的容器
|
||||
AiChatConversationData conversationData = new AiChatConversationData();
|
||||
conversationData.setQuery(caseAiChatDto.getQuery());
|
||||
conversationData.setConversationId(conversationId);
|
||||
conversationData.setUserId(userId);
|
||||
|
||||
String kId = caseAiProperties.getCaseKnowledgeId();
|
||||
JSONObject chatParam = new JSONObject();
|
||||
chatParam.put("userId", userId);
|
||||
@@ -154,10 +160,21 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
String accessToken;
|
||||
try {
|
||||
accessToken = aiAccessTokenService.getAccessToken();
|
||||
if (org.apache.commons.lang3.StringUtils.isBlank(accessToken)) {
|
||||
errMessage(sseEmitter, conversationId, CaseAiConstants.CHAT_SYS_ERR_MSG);
|
||||
// 先响应给前端
|
||||
sseEmitter.complete();
|
||||
conversationData.appendAnswer(CaseAiConstants.CHAT_SYS_ERR_MSG);
|
||||
saveConversationData(conversationData);
|
||||
return sseEmitter;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取access_token失败", e);
|
||||
errMessage(sseEmitter, "服务繁忙,请稍后再试。");
|
||||
errMessage(sseEmitter, conversationId, CaseAiConstants.CHAT_SYS_ERR_MSG);
|
||||
// 先响应给前端
|
||||
sseEmitter.complete();
|
||||
conversationData.appendAnswer(CaseAiConstants.CHAT_SYS_ERR_MSG);
|
||||
saveConversationData(conversationData);
|
||||
return sseEmitter;
|
||||
}
|
||||
String apiCode = caseAiProperties.getChatApiCode();
|
||||
@@ -168,26 +185,62 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
RequestBody bodyRequestBody = RequestBody.create(chatParamStr, MediaType.parse("application/json"));
|
||||
builder.post(bodyRequestBody);
|
||||
Request request = builder.build();
|
||||
|
||||
|
||||
// 6. 用于收集对话数据的容器
|
||||
AiChatConversationData conversationData = new AiChatConversationData();
|
||||
conversationData.setQuery(caseAiChatDto.getQuery());
|
||||
conversationData.setConversationId(conversationId);
|
||||
conversationData.setUserId(userId);
|
||||
|
||||
// 7. 创建事件监听器
|
||||
EventSourceListener listener = new EventSourceListener() {
|
||||
@Override
|
||||
public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
|
||||
// 检查contentType
|
||||
String contentType = response.header("Content-Type");
|
||||
if (contentType == null || !contentType.contains("text/event-stream")) {
|
||||
// 服务器返回的不是SSE流,需要额外处理
|
||||
log.error("调用接口 [{}] 返回的Content-Type不是text/event-stream,实际ContentType: {}", request.url(), contentType);
|
||||
String sseContent;
|
||||
try {
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody == null) {
|
||||
sseContent = CaseAiConstants.CHAT_SYS_ERR_MSG;
|
||||
} else {
|
||||
String responseBodyStr = responseBody.string();
|
||||
log.error("调用 [{}] 返回值: {}", request.url(), responseBodyStr);
|
||||
// 判断是否为json
|
||||
if (contentType != null && contentType.contains("application/json")) {
|
||||
JSONObject responseData = JSONObject.parseObject(responseBodyStr);
|
||||
if (responseData.containsKey("message") && StringUtils.isNotBlank(responseData.getString("message"))) {
|
||||
sseContent = responseData.getString("message");
|
||||
} else {
|
||||
sseContent = CaseAiConstants.CHAT_SYS_ERR_MSG;
|
||||
}
|
||||
} else {
|
||||
sseContent = CaseAiConstants.CHAT_SYS_ERR_MSG;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("解析接口响应失败", e);
|
||||
// 处理失败的情况
|
||||
sseContent = CaseAiConstants.CHAT_SYS_ERR_MSG;
|
||||
}
|
||||
|
||||
errMessage(sseEmitter, conversationId, sseContent);
|
||||
sseEmitter.complete();
|
||||
conversationData.appendAnswer(sseContent);
|
||||
saveConversationData(conversationData);
|
||||
// 关闭eventSource
|
||||
eventSource.cancel();
|
||||
return;
|
||||
}
|
||||
log.info("调用接口 [{}] 接口开始监听", request.url());
|
||||
// 将EventSource存储到Map中,以便后续可以中断
|
||||
conversationEventSourceMap.put(conversationId, eventSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(@NotNull EventSource eventSource) {
|
||||
log.info("调用接口 [{}] 接口关闭", request.url());
|
||||
// 对话完成,保存到ES
|
||||
elasticSearchIndexService.createData(conversationData);
|
||||
saveConversationData(conversationData);
|
||||
// 从Map中移除已完成的会话
|
||||
conversationEventSourceMap.remove(conversationId);
|
||||
sseEmitter.complete();
|
||||
}
|
||||
|
||||
@@ -224,7 +277,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
} else {
|
||||
// 异常问题,取message内容
|
||||
String message = jsonData.getString("message");
|
||||
errMessage(sseEmitter, message);
|
||||
errMessage(sseEmitter, conversationId, message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -246,20 +299,45 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable e, @Nullable Response response) {
|
||||
log.error("调用接口 [{}] 接口异常", request.url(), e);
|
||||
|
||||
// 如果是 content-type 错误,尝试作为普通 HTTP 请求处理
|
||||
if (e instanceof IllegalStateException && e.getMessage() != null && e.getMessage().contains("Invalid content-type")) {
|
||||
log.warn("服务器返回的 Content-Type 不是 text/event-stream,尝试作为普通 HTTP 请求处理");
|
||||
handleAsRegularHttpRequest(request, sseEmitter, conversationData);
|
||||
return;
|
||||
}
|
||||
|
||||
// 只要有异常,必打日志
|
||||
if (e != null) {
|
||||
sseEmitter.completeWithError(e);
|
||||
log.error("调用接口 [{}] 时发生错误,捕获到异常", request.url(), e);
|
||||
} else {
|
||||
sseEmitter.completeWithError(new RuntimeException("调用接口异常, 异常未捕获"));
|
||||
log.error("调用接口 [{}] 时发生错误,未捕获到异常", request.url());
|
||||
}
|
||||
String errorMessage = CaseAiConstants.CHAT_SYS_ERR_MSG;
|
||||
// 优先处理错误响应
|
||||
if (response != null) {
|
||||
try {
|
||||
log.error("调用接口 [{}] 时发生错误,响应码: {}", request.url(), response.code());
|
||||
if (response.body() != null) {
|
||||
String body = response.body().string();
|
||||
log.error("调用接口 [{}] 时的错误响应内容: {}", request.url(), body);
|
||||
// 将错误内容发送至SseEmitter
|
||||
if (StringUtils.contains(response.header("Content-Type"), "application/json")) {
|
||||
// json解析
|
||||
JSONObject jsonData = JSONObject.parseObject(body);
|
||||
if (jsonData.containsKey("message") && StringUtils.isNotBlank(jsonData.getString("message"))) {
|
||||
errorMessage = jsonData.getString("message");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
log.error("解析异常请求时错误", ex);
|
||||
}
|
||||
} else if (e != null) {
|
||||
if (isTimeoutException(e)) {
|
||||
errorMessage = CaseAiConstants.CHAT_NET_ERR_MSG;
|
||||
}
|
||||
}
|
||||
|
||||
errMessage(sseEmitter, conversationId, errorMessage);
|
||||
sseEmitter.complete();
|
||||
// 从Map中移除失败的会话
|
||||
conversationEventSourceMap.remove(conversationId);
|
||||
// 即使失败,也要将已有的对话数据保存到ES
|
||||
conversationData.appendAnswer(errorMessage);
|
||||
saveConversationData(conversationData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -359,92 +437,24 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
return elasticSearchIndexService.queryData(conversationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getConversationExcel(LocalDateTime startTime, LocalDateTime endTime, HttpServletResponse response) {
|
||||
Workbook workbook = getChatMessageExcel(startTime, endTime);
|
||||
// 写入response.getOutputStream()
|
||||
try (OutputStream out = response.getOutputStream()) {
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=chat_message.xlsx");
|
||||
workbook.write(out);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
log.error("导出Excel异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downloadConversationExcel(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
// 1. 根据startTime和endTime,查询在这个时间区间内的CaseAiConversations数据
|
||||
List<CaseAiConversations> conversations = caseAiConversationsDao.getGenericDao().findList(
|
||||
CaseAiConversations.class,
|
||||
FieldFilters.ge("sysCreateTime", startTime),
|
||||
FieldFilters.le("sysCreateTime", endTime)
|
||||
);
|
||||
|
||||
// 准备Excel数据
|
||||
List<ConversationExcelVo> excelDataList = new ArrayList<>();
|
||||
|
||||
// 2. 遍历这组数据,根据aiConversationId从es中查询数据(可调用getConversationMessages()方法)
|
||||
for (CaseAiConversations conversation : conversations) {
|
||||
String aiConversationId = conversation.getAiConversationId();
|
||||
String conversationName = conversation.getConversationName();
|
||||
String conversationUser = conversation.getConversationUser();
|
||||
|
||||
List<CaseAiMessageVo> messages = getConversationMessages(aiConversationId);
|
||||
|
||||
// 计算会话时长
|
||||
long duration = 0; // 默认为0,如果需要精确计算,需要从消息中提取时间信息
|
||||
|
||||
// 3. 写入Excel,包括每个会话的用户,会话标题,会话内的问答记录,每次对话时长等
|
||||
ConversationExcelVo excelData = new ConversationExcelVo();
|
||||
excelData.setConversationId(aiConversationId);
|
||||
excelData.setConversationName(conversationName);
|
||||
excelData.setUser(conversationUser);
|
||||
excelData.setMessages(messages);
|
||||
|
||||
excelDataList.add(excelData);
|
||||
}
|
||||
|
||||
// 写入Excel文件
|
||||
Workbook workbook = new XSSFWorkbook();
|
||||
Sheet sheet = workbook.createSheet("AI会话数据");
|
||||
// 标题行
|
||||
Row headerRow = sheet.createRow(0);
|
||||
headerRow.createCell(0).setCellValue("会话ID");
|
||||
headerRow.createCell(1).setCellValue("会话名称");
|
||||
headerRow.createCell(2).setCellValue("用户");
|
||||
headerRow.createCell(3).setCellValue("提问");
|
||||
headerRow.createCell(4).setCellValue("回答");
|
||||
headerRow.createCell(5).setCellValue("开始时间");
|
||||
headerRow.createCell(6).setCellValue("问答时长(秒)");
|
||||
|
||||
// 内容行
|
||||
if (!excelDataList.isEmpty()) {
|
||||
int rowNum = 1; // 从第二行开始写入数据
|
||||
for (ConversationExcelVo excelData : excelDataList) {
|
||||
List<CaseAiMessageVo> messages = excelData.getMessages();
|
||||
|
||||
if (messages != null && !messages.isEmpty()) {
|
||||
// 记录起始行号,用于后续合并单元格
|
||||
int startRow = rowNum;
|
||||
|
||||
// 遍历每个消息
|
||||
for (CaseAiMessageVo message : messages) {
|
||||
Row row = sheet.createRow(rowNum++);
|
||||
// 填充每行数据
|
||||
row.createCell(0).setCellValue(excelData.getConversationId());
|
||||
row.createCell(1).setCellValue(excelData.getConversationName());
|
||||
row.createCell(2).setCellValue(excelData.getUser());
|
||||
row.createCell(3).setCellValue(message.getQuery() != null ? message.getQuery() : "");
|
||||
row.createCell(4).setCellValue(message.getAnswer() != null ? message.getAnswer() : "");
|
||||
row.createCell(5).setCellValue(""); // 开始时间字段暂留空
|
||||
row.createCell(6).setCellValue(message.getDurationSeconds() != null ? message.getDurationSeconds() : 0);
|
||||
}
|
||||
|
||||
// 合并单元格(会话ID、会话名称、用户三列)
|
||||
// 参数说明:起始行号,结束行号,起始列号,结束列号
|
||||
if (rowNum > startRow + 1) { // 只有当有多行时才合并
|
||||
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 0, 0));
|
||||
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 1, 1));
|
||||
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 2, 2));
|
||||
}
|
||||
} else {
|
||||
// 如果没有消息,则仍然创建一行显示基本信息
|
||||
Row row = sheet.createRow(rowNum++);
|
||||
row.createCell(0).setCellValue(excelData.getConversationId());
|
||||
row.createCell(1).setCellValue(excelData.getConversationName());
|
||||
row.createCell(2).setCellValue(excelData.getUser());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. 创建Excel文件并保存
|
||||
Workbook workbook = getChatMessageExcel(startTime, endTime);
|
||||
// 创建Excel文件并保存
|
||||
if (caseAiProperties.isAiChatDataSendEmail()) {
|
||||
// TODO 发送邮件附件
|
||||
} else {
|
||||
@@ -471,9 +481,13 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
|
||||
/**
|
||||
* 从 ES 数据中解析消息对象
|
||||
* 已迁移
|
||||
* @see IElasticSearchIndexService
|
||||
*
|
||||
* @param sourceMap ES数据
|
||||
* @return 消息对象
|
||||
*/
|
||||
@Deprecated
|
||||
private CaseAiMessageVo parseMessageFromES(Map<String, Object> sourceMap) {
|
||||
try {
|
||||
CaseAiMessageVo messageVo = new CaseAiMessageVo();
|
||||
@@ -595,6 +609,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
/**
|
||||
* 处理文件引用(原方法,保留用于数据收集)
|
||||
*/
|
||||
@Deprecated
|
||||
private void handleFileRefer(JSONObject responseData, AiChatConversationData conversationData) {
|
||||
try {
|
||||
JSONObject fileRefer = responseData.getJSONObject("fileRefer");
|
||||
@@ -682,7 +697,9 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
|
||||
/**
|
||||
* 当 SSE 失败时,作为普通 HTTP 请求处理
|
||||
* 不再使用
|
||||
*/
|
||||
@Deprecated
|
||||
private void handleAsRegularHttpRequest(Request request, SseEmitter sseEmitter, AiChatConversationData conversationData) {
|
||||
try {
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
@@ -699,7 +716,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
// 将响应内容原封不动地推送到 SseEmitter
|
||||
JSONObject responseData = JSONObject.parseObject(responseBody);
|
||||
if (responseBody.contains("message")) {
|
||||
errMessage(sseEmitter, responseData.getString("message"));
|
||||
errMessage(sseEmitter, conversationData.getConversationId(), responseData.getString("message"));
|
||||
sseEmitter.complete();
|
||||
return;
|
||||
}
|
||||
@@ -715,15 +732,182 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
||||
}
|
||||
}
|
||||
|
||||
private void errMessage(SseEmitter sseEmitter, String message) {
|
||||
/**
|
||||
* 发送错误信息
|
||||
*/
|
||||
private void errMessage(SseEmitter sseEmitter, String conversationId, String message) {
|
||||
JSONObject conversationData = new JSONObject();
|
||||
conversationData.put("conversationId", conversationId);
|
||||
conversationData.put("content", "");
|
||||
conversationData.put("status", 0);
|
||||
|
||||
JSONObject jsonData = new JSONObject();
|
||||
jsonData.put("status", 1);
|
||||
jsonData.put("content", message);
|
||||
|
||||
JSONObject finishData = new JSONObject();
|
||||
jsonData.put("status", 4);
|
||||
jsonData.put("content", "");
|
||||
try {
|
||||
sseEmitter.send(conversationData.toJSONString());
|
||||
sseEmitter.send(jsonData.toJSONString());
|
||||
sseEmitter.send(finishData.toJSONString());
|
||||
} catch (IOException e) {
|
||||
log.error("发送错误信息异常", e);
|
||||
sseEmitter.completeWithError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopChatOutput(String conversationId) {
|
||||
EventSource eventSource = conversationEventSourceMap.get(conversationId);
|
||||
if (eventSource != null) {
|
||||
try {
|
||||
// 取消事件源,中断连接
|
||||
eventSource.cancel();
|
||||
// 注意:cancel()会触发onFailure回调,在onFailure中会清理资源
|
||||
log.info("成功发送停止会话 {} 的指令", conversationId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("停止会话 {} 输出时发生异常", conversationId, e);
|
||||
// 即使出现异常,也从Map中移除,避免内存泄漏
|
||||
conversationEventSourceMap.remove(conversationId);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log.warn("未找到会话 {} 对应的事件源,可能已经完成或不存在", conversationId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Throwable是否为超时类异常
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
private boolean isTimeoutException(@Nullable Throwable e) {
|
||||
if (e == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ConnectException SocketTimeoutException
|
||||
if (e instanceof java.net.ConnectException || e instanceof java.net.SocketTimeoutException) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 可能是包装后的异常,递归检查 cause
|
||||
Throwable cause = e.getCause();
|
||||
while (cause != null) {
|
||||
if (cause instanceof java.net.ConnectException || cause instanceof java.net.SocketTimeoutException) {
|
||||
return true;
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
|
||||
// 有些情况下 OkHttp 会抛出 IOException 并包含 "timeout" 字样
|
||||
if (e instanceof java.io.IOException) {
|
||||
String msg = e.getMessage();
|
||||
if (msg != null && msg.toLowerCase().contains("timeout")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Workbook getChatMessageExcel(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
// 1. 根据startTime和endTime,查询在这个时间区间内的CaseAiConversations数据
|
||||
List<CaseAiConversations> conversations = caseAiConversationsDao.getGenericDao().findList(
|
||||
CaseAiConversations.class,
|
||||
FieldFilters.ge("sysCreateTime", startTime),
|
||||
FieldFilters.le("sysCreateTime", endTime)
|
||||
);
|
||||
|
||||
// 准备Excel数据
|
||||
List<ConversationExcelVo> excelDataList = new ArrayList<>();
|
||||
|
||||
// 2. 遍历这组数据,根据aiConversationId从es中查询数据(可调用getConversationMessages()方法)
|
||||
for (CaseAiConversations conversation : conversations) {
|
||||
String aiConversationId = conversation.getAiConversationId();
|
||||
String conversationName = conversation.getConversationName();
|
||||
String conversationUser = conversation.getConversationUser();
|
||||
|
||||
List<CaseAiMessageVo> messages = getConversationMessages(aiConversationId);
|
||||
|
||||
// 计算会话时长
|
||||
long duration = 0; // 默认为0,如果需要精确计算,需要从消息中提取时间信息
|
||||
|
||||
// 3. 写入Excel,包括每个会话的用户,会话标题,会话内的问答记录,每次对话时长等
|
||||
ConversationExcelVo excelData = new ConversationExcelVo();
|
||||
excelData.setConversationId(aiConversationId);
|
||||
excelData.setConversationName(conversationName);
|
||||
excelData.setUser(conversationUser);
|
||||
excelData.setMessages(messages);
|
||||
|
||||
excelDataList.add(excelData);
|
||||
}
|
||||
|
||||
// 写入Excel文件
|
||||
Workbook workbook = new XSSFWorkbook();
|
||||
Sheet sheet = workbook.createSheet("AI会话数据");
|
||||
// 标题行
|
||||
Row headerRow = sheet.createRow(0);
|
||||
headerRow.createCell(0).setCellValue("会话ID");
|
||||
headerRow.createCell(1).setCellValue("会话名称");
|
||||
headerRow.createCell(2).setCellValue("用户");
|
||||
headerRow.createCell(3).setCellValue("提问");
|
||||
headerRow.createCell(4).setCellValue("回答");
|
||||
headerRow.createCell(5).setCellValue("开始时间");
|
||||
headerRow.createCell(6).setCellValue("问答时长(秒)");
|
||||
|
||||
// 内容行
|
||||
if (!excelDataList.isEmpty()) {
|
||||
int rowNum = 1; // 从第二行开始写入数据
|
||||
for (ConversationExcelVo excelData : excelDataList) {
|
||||
List<CaseAiMessageVo> messages = excelData.getMessages();
|
||||
|
||||
if (messages != null && !messages.isEmpty()) {
|
||||
// 记录起始行号,用于后续合并单元格
|
||||
int startRow = rowNum;
|
||||
|
||||
// 遍历每个消息
|
||||
for (CaseAiMessageVo message : messages) {
|
||||
Row row = sheet.createRow(rowNum++);
|
||||
// 填充每行数据
|
||||
row.createCell(0).setCellValue(excelData.getConversationId());
|
||||
row.createCell(1).setCellValue(excelData.getConversationName());
|
||||
row.createCell(2).setCellValue(excelData.getUser());
|
||||
row.createCell(3).setCellValue(message.getQuery() != null ? message.getQuery() : "");
|
||||
row.createCell(4).setCellValue(message.getAnswer() != null ? message.getAnswer() : "");
|
||||
row.createCell(5).setCellValue(""); // 开始时间字段暂留空
|
||||
row.createCell(6).setCellValue(message.getDurationSeconds() != null ? message.getDurationSeconds() : 0);
|
||||
}
|
||||
|
||||
// 合并单元格(会话ID、会话名称、用户三列)
|
||||
// 参数说明:起始行号,结束行号,起始列号,结束列号
|
||||
if (rowNum > startRow + 1) { // 只有当有多行时才合并
|
||||
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 0, 0));
|
||||
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 1, 1));
|
||||
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 2, 2));
|
||||
}
|
||||
} else {
|
||||
// 如果没有消息,则仍然创建一行显示基本信息
|
||||
Row row = sheet.createRow(rowNum++);
|
||||
row.createCell(0).setCellValue(excelData.getConversationId());
|
||||
row.createCell(1).setCellValue(excelData.getConversationName());
|
||||
row.createCell(2).setCellValue(excelData.getUser());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return workbook;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步存储会话数据
|
||||
* @param conversationData
|
||||
*/
|
||||
private void saveConversationData(AiChatConversationData conversationData) {
|
||||
esChatExecutor.execute(() -> elasticSearchIndexService.createData(conversationData));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ public class ElasticSearchIndexServiceImpl implements IElasticSearchIndexService
|
||||
messageVo.setStartTime(LocalDateTime.parse(startTimeStr));
|
||||
}
|
||||
if (sourceMap.containsKey("durationSeconds")) {
|
||||
messageVo.setDurationSeconds((Long) sourceMap.get("durationSeconds"));
|
||||
messageVo.setDurationSeconds((Integer) sourceMap.get("durationSeconds"));
|
||||
}
|
||||
|
||||
// 解析 suggestions
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 旧案例上传
|
||||
@@ -46,6 +47,7 @@ public class CaseUploadTask {
|
||||
|
||||
@XxlJob("oldDataUploadJob")
|
||||
public void oldDataUploadJob() {
|
||||
String currentLastId = null;
|
||||
try {
|
||||
// log.info("开始执行旧案例上传任务");
|
||||
|
||||
@@ -61,6 +63,7 @@ public class CaseUploadTask {
|
||||
// log.info("没有需要处理的案例数据");
|
||||
return;
|
||||
}
|
||||
currentLastId = casesToProcess.get(casesToProcess.size() - 1).getId();
|
||||
|
||||
// 批量检查这些案例是否已在CaseDocumentLog中存在记录,提升性能
|
||||
List<String> caseIds = new ArrayList<>();
|
||||
@@ -76,18 +79,37 @@ public class CaseUploadTask {
|
||||
// 过滤出未在CaseDocumentLog中存在的案例
|
||||
List<Cases> casesList = new ArrayList<>();
|
||||
for (Cases cases : casesToProcess) {
|
||||
boolean exists = false;
|
||||
for (CaseDocumentLog log : existingLogs) {
|
||||
if (cases.getId().equals(log.getCaseId())
|
||||
&& StringUtils.equals(log.getRequestUrl(), CaseAiConstants.CASE_DOC_UPLOAD_INTERFACE_NAME)
|
||||
&& Objects.equals(log.getRunStatus(), CaseDocumentLogRunStatusEnum.COMPLETED.getCode())
|
||||
&& Objects.equals(log.getOptStatus(), CaseDocumentLogOptStatusEnum.SUCCESS.getCode())
|
||||
&& Objects.equals(log.getRunStatus(), CaseDocumentLogCaseStatusEnum.SUCCESS.getCode())) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
// boolean exists = false;
|
||||
// for (CaseDocumentLog log : existingLogs) {
|
||||
// if (cases.getId().equals(log.getCaseId())
|
||||
// && StringUtils.equals(log.getRequestUrl(), CaseAiConstants.CASE_DOC_UPLOAD_INTERFACE_NAME)
|
||||
// && Objects.equals(log.getRunStatus(), CaseDocumentLogRunStatusEnum.COMPLETED.getCode())
|
||||
// && Objects.equals(log.getOptStatus(), CaseDocumentLogOptStatusEnum.SUCCESS.getCode())
|
||||
// && Objects.equals(log.getRunStatus(), CaseDocumentLogCaseStatusEnum.SUCCESS.getCode())) {
|
||||
// exists = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (!exists) {
|
||||
// casesList.add(cases);
|
||||
// }
|
||||
List<CaseDocumentLog> thisCaseLogs = existingLogs.stream()
|
||||
.filter(log -> cases.getId().equals(log.getCaseId()))
|
||||
.collect(Collectors.toList());
|
||||
if (thisCaseLogs == null || thisCaseLogs.isEmpty()) {
|
||||
casesList.add(cases);
|
||||
} else if (thisCaseLogs.stream()
|
||||
.noneMatch(caseLog -> {
|
||||
// 1. 是否存在已上传完成的案例
|
||||
boolean hasCompleted = StringUtils.equals(caseLog.getRequestUrl(), CaseAiConstants.CASE_DOC_UPLOAD_INTERFACE_NAME)
|
||||
&& Objects.equals(caseLog.getRunStatus(), CaseDocumentLogRunStatusEnum.COMPLETED.getCode())
|
||||
&& Objects.equals(caseLog.getOptStatus(), CaseDocumentLogOptStatusEnum.SUCCESS.getCode())
|
||||
&& Objects.equals(caseLog.getRunStatus(), CaseDocumentLogCaseStatusEnum.SUCCESS.getCode());
|
||||
// 2. 是否存在上传中的案例
|
||||
boolean hasUploading = StringUtils.equals(caseLog.getRequestUrl(), CaseAiConstants.CASE_DOC_UPLOAD_INTERFACE_NAME)
|
||||
&& Objects.equals(caseLog.getRunStatus(), CaseDocumentLogRunStatusEnum.RUNNING.getCode());
|
||||
return hasCompleted || hasUploading;
|
||||
})) {
|
||||
casesList.add(cases);
|
||||
}
|
||||
}
|
||||
@@ -98,17 +120,18 @@ public class CaseUploadTask {
|
||||
// 调用异步处理方法
|
||||
caseAiDocumentAsyncHandler.process(CaseDocumentLogOptTypeEnum.CREATE, casesList.toArray(new Cases[0]));
|
||||
|
||||
// 将当前处理的最后一条数据ID存入Redis
|
||||
String currentLastId = casesList.get(casesList.size() - 1).getId();
|
||||
stringRedisTemplate.opsForValue().set(CASE_UPLOAD_LAST_ID_KEY, currentLastId);
|
||||
log.info("已处理案例,最后一条记录ID已更新为: {}", currentLastId);
|
||||
} else {
|
||||
log.info("没有新的案例需要处理");
|
||||
}
|
||||
// 将当前处理的最后一条数据ID存入Redis
|
||||
|
||||
// log.info("旧案例上传任务执行完成");
|
||||
} catch (Exception e) {
|
||||
log.error("执行旧案例上传任务时发生异常", e);
|
||||
} finally {
|
||||
if (currentLastId != null) {
|
||||
fixOnLastCase(currentLastId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,4 +160,9 @@ public class CaseUploadTask {
|
||||
|
||||
return casesDao.findList(queryBuilder.builder());
|
||||
}
|
||||
|
||||
private void fixOnLastCase(String currentLastId) {
|
||||
stringRedisTemplate.opsForValue().set(CASE_UPLOAD_LAST_ID_KEY, currentLastId);
|
||||
log.info("已处理案例,最后一条记录ID已更新为: {}", currentLastId);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class CaseAiMessageVo {
|
||||
/**
|
||||
* 会话时长(秒)
|
||||
*/
|
||||
private Long durationSeconds;
|
||||
private Integer durationSeconds;
|
||||
|
||||
/**
|
||||
* 案例引用列表
|
||||
|
||||
@@ -9,7 +9,11 @@ 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.*;
|
||||
@@ -62,7 +66,7 @@ public class CourseFullTextApi extends ApiBaseController{
|
||||
@Autowired
|
||||
ICourseTagService courseTagService;
|
||||
@Resource
|
||||
IStudyCourseService IStudyCourseService;
|
||||
IStudyCourseService IStudyCourseService;
|
||||
|
||||
@Autowired
|
||||
ThirdApi thirdApi;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.xboe.module.course.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -8,20 +7,16 @@ 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.api.vo.UserBasicInfoVo;
|
||||
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.collections4.CollectionUtils;
|
||||
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;
|
||||
@@ -61,9 +56,6 @@ public class CourseManageApi extends ApiBaseController{
|
||||
|
||||
@Resource
|
||||
private ICourseService courseService;
|
||||
|
||||
@Autowired
|
||||
private ICoursePageService coursePageService;
|
||||
|
||||
@Autowired
|
||||
IOutSideDataService outsideService;
|
||||
@@ -102,88 +94,12 @@ public class CourseManageApi extends ApiBaseController{
|
||||
@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){
|
||||
// //dto.setOrgAid("7003708665807110150");
|
||||
// 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新-教师端 我开发的课程
|
||||
* @param coursePageQueryDTO
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/develop_page")
|
||||
public JsonResponse<PageList<CoursePageVo>> teacherPage(@RequestBody CoursePageQueryDTO coursePageQueryDTO) {
|
||||
return success(coursePageService.pageQuery(getCurrent(), 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理列表的查询
|
||||
@@ -549,7 +465,7 @@ public class CourseManageApi extends ApiBaseController{
|
||||
|
||||
@PostMapping("/submit")
|
||||
@AutoLog(module = "课程",action = "提交课程",info = "")
|
||||
public JsonResponse<CourseFullDto> submitCourseFull(@RequestBody CourseFullDto dto){
|
||||
public JsonResponse<CourseFullDto> submitCourseFull(HttpServletRequest request, @RequestBody CourseFullDto dto){
|
||||
if(dto.getCourse()==null){
|
||||
return badRequest("无课程信息");
|
||||
}
|
||||
@@ -670,7 +586,10 @@ public class CourseManageApi extends ApiBaseController{
|
||||
dto.getCourse().getOrgName(),
|
||||
dto.getCourse().getSysCreateBy(),dto.getCourse().getName());
|
||||
//邮件发送
|
||||
String email=dto.getAuditUser().getEmail();
|
||||
String email= this.getEmail(dto.getAuditUser().getCode(), request);
|
||||
if (StringUtils.isBlank( email)) {
|
||||
email=dto.getAuditUser().getEmail();
|
||||
}
|
||||
if(!isLocalDevelopment()) {
|
||||
//只是非开发模式下才可以发送
|
||||
service.sendMail(email,"《"+dto.getCourse().getName()+"》课程审核提醒", htmlEmail,"数字化学习平台");
|
||||
@@ -688,7 +607,39 @@ public class CourseManageApi extends ApiBaseController{
|
||||
return error("提交课程处理失败",e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getEmail(String code, HttpServletRequest request) {
|
||||
|
||||
String token = request.getHeader("Xboe-Access-Token");
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
token = request.getHeader("token");
|
||||
}
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
token = request.getHeader("x-access-token");
|
||||
}
|
||||
|
||||
log.info("审批获取邮箱新 code:{}", code);
|
||||
if (StringUtils.isBlank( code)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
log.info("审批获取邮箱 workNums:{}", code);
|
||||
List<UserBasicInfoVo> userBasicInfoVoList = thirdApi.getUserBasicInfoByWorkNums2(code, token);
|
||||
log.info("审批获取邮箱 userBasicInfoVoList:{}", userBasicInfoVoList);
|
||||
if (CollectionUtils.isEmpty(userBasicInfoVoList)) {
|
||||
return null;
|
||||
}
|
||||
String email = userBasicInfoVoList.get(0).getEmail();
|
||||
log.info("审批获取邮箱 userBasicInfoVoList.get(0).getEmail():{}", email);
|
||||
|
||||
|
||||
return email;
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户邮箱错误",e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String createEmailHtml(String name,String orgId, String orgName,String createBy,String courseName) throws Exception {
|
||||
StringBuffer htmlMsg=new StringBuffer("<div style=\"line-height:30px;border:2px solid #2990ca;padding:20px\">");
|
||||
|
||||
@@ -799,6 +750,10 @@ public class CourseManageApi extends ApiBaseController{
|
||||
//邮件发送
|
||||
if(!isLocalDevelopment()) {
|
||||
//只是非高度环境上才会发送
|
||||
String newEmail = getEmail(ucode,request);
|
||||
if (StringUtils.isNotBlank(newEmail)) {
|
||||
email = newEmail;
|
||||
}
|
||||
service.sendMail(email,"《"+course.getName()+"》课程审核提醒",htmlEmail,"数字化学习平台");
|
||||
}
|
||||
|
||||
@@ -940,7 +895,6 @@ 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("参数错误");
|
||||
}
|
||||
@@ -949,14 +903,8 @@ public class CourseManageApi extends ApiBaseController{
|
||||
}
|
||||
|
||||
try {
|
||||
// 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());
|
||||
courseService.setTop(ids, top,getCurrent().getAccountId(), title,"");
|
||||
return success(true);
|
||||
} catch (Exception e) {
|
||||
log.error("课程设置置顶错误",e);
|
||||
return error("设置置顶失败",e.getMessage(),false);
|
||||
@@ -1318,40 +1266,4 @@ 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: {}", courseId);
|
||||
response.sendRedirect(baseUrl + courseId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,21 +490,17 @@ public class CoursePortalApi extends ApiBaseController{
|
||||
courseStudyVo.setContentName(s.getName()+"--"+c.getContentName());
|
||||
courseStudyVo.setStatus(1);
|
||||
courseStudyVo.setStudyDuration(0);
|
||||
// 学习时长默认为0
|
||||
courseStudyVo.setLearningDuration(0);
|
||||
courseStudyVos.add(courseStudyVo);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!courseStudyVos.isEmpty()) {
|
||||
// DecimalFormat decimalFormat = new DecimalFormat("#.#");
|
||||
if(courseStudyVos!=null && !courseStudyVos.isEmpty()){
|
||||
for (StudyCourseItem study:studyItem) {
|
||||
for (CourseStudyVo cv:courseStudyVos) {
|
||||
if(study.getContentId().equals(cv.getContentId())){
|
||||
// 25.11.26标记 这就能硬set么,这俩是一个值么
|
||||
|
||||
cv.setStudyDuration(study.getProgress());
|
||||
// 25.11.26新增,添加学习时长字段
|
||||
// 数据库存储的秒,前端会转为分钟
|
||||
cv.setLearningDuration(study.getStudyDuration());
|
||||
//针对考试,文档一类
|
||||
if(study.getStatus()==null) {
|
||||
if(study.getProgress()!=null){
|
||||
@@ -515,7 +511,16 @@ public class CoursePortalApi extends ApiBaseController{
|
||||
//音视频
|
||||
}else{
|
||||
cv.setStatus(study.getStatus());
|
||||
|
||||
}
|
||||
|
||||
|
||||
// if(study.getStudyDuration()!=null){
|
||||
//// Float duration= Float.valueOf(study.getProgress());
|
||||
//// duration=duration/60;
|
||||
//
|
||||
// cv.setStudyDuration();
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,88 +528,6 @@ public class CoursePortalApi extends ApiBaseController{
|
||||
|
||||
return success(courseStudyVos);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取课程章节学习进度
|
||||
* 25.11.26新增
|
||||
*
|
||||
* @param pager 分页参数
|
||||
* @param courseId 课程id
|
||||
* @param aid 用户id
|
||||
* @return 课程内容学习进度集合
|
||||
*/
|
||||
@GetMapping("/detail-study-page")
|
||||
public JsonResponse<PageList<CourseStudyVo>> detailStudyPage(Pagination pager, String courseId, String aid) {
|
||||
if (StringUtil.isBlank(courseId)) {
|
||||
return badRequest("参数异常,未指定课程");
|
||||
}
|
||||
if (StringUtil.isBlank(aid)) {
|
||||
return badRequest("参数异常,未指定用户");
|
||||
}
|
||||
PageList<CourseStudyVo> courseStudyVoPage = new PageList<>();
|
||||
List<CourseStudyVo> courseStudyVos = new ArrayList<>();
|
||||
// 课程章节
|
||||
List<CourseSection> sectionlist = sectionService.getByCourseId(courseId);
|
||||
// 获取课程章节id集合备用
|
||||
List<String> sectionIdList = sectionlist.stream().map(CourseSection::getId).collect(Collectors.toList());
|
||||
if (sectionIdList.isEmpty()) {
|
||||
log.error("【分页获取课程章节学习进度】课程章节为空");
|
||||
return success(courseStudyVoPage);
|
||||
}
|
||||
// 课程内容(分页),只查询满足课程章节id条件的数据
|
||||
PageList<CourseContent> courseContentPageList = contentService.getByCourseIdPage(pager.getPageIndex(), pager.getPageSize(), courseId, sectionIdList);
|
||||
List<CourseContent> courseContentlist = courseContentPageList.getList();
|
||||
// 查出课程当前人学习进度
|
||||
StudyCourse studyCourse = studyCourseService.findByCourseIdAndAid(courseId, aid);
|
||||
// 查出对应章节学习进度
|
||||
List<StudyCourseItem> studyItem = studyCourseService.findStudyItem(studyCourse.getId(), aid);
|
||||
// 先遍历课程章节
|
||||
// 然后遍历课程内容
|
||||
for (CourseContent courseContent : courseContentlist) {
|
||||
CourseStudyVo courseStudyVo = new CourseStudyVo();
|
||||
courseStudyVo.setContentId(courseContent.getId());
|
||||
for (CourseSection s : sectionlist) {
|
||||
if (courseContent.getCsectionId().equals(s.getId())) {
|
||||
courseStudyVo.setContentName(s.getName() + "--" + courseContent.getContentName());
|
||||
}
|
||||
}
|
||||
courseStudyVo.setStatus(1);
|
||||
courseStudyVo.setStudyDuration(0);
|
||||
// 学习时长默认为0
|
||||
courseStudyVo.setLearningDuration(0);
|
||||
courseStudyVos.add(courseStudyVo);
|
||||
}
|
||||
if (!courseStudyVos.isEmpty()) {
|
||||
for (StudyCourseItem study : studyItem) {
|
||||
for (CourseStudyVo cv : courseStudyVos) {
|
||||
if (study.getContentId().equals(cv.getContentId())) {
|
||||
// 25.11.26标记 这就能硬set么,这俩是一个值么
|
||||
cv.setStudyDuration(study.getProgress());
|
||||
// 25.11.26新增,添加学习时长字段
|
||||
// 数据库存储的秒,前端会转为分钟
|
||||
cv.setLearningDuration(study.getStudyDuration());
|
||||
//针对考试,文档一类
|
||||
if (study.getStatus() == null) {
|
||||
if (study.getProgress() != null) {
|
||||
if (study.getProgress() == 100) {
|
||||
cv.setStatus(9);
|
||||
}
|
||||
}
|
||||
//音视频
|
||||
} else {
|
||||
cv.setStatus(study.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 拼接为分页格式
|
||||
courseStudyVoPage.setList(courseStudyVos);
|
||||
courseStudyVoPage.setCount(courseContentPageList.getCount());
|
||||
courseStudyVoPage.setPageSize(courseContentPageList.getPageSize());
|
||||
return success(courseStudyVoPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据课程id查出对应的教师id
|
||||
* */
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
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.Arrays;
|
||||
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;
|
||||
@@ -23,14 +17,8 @@ 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;
|
||||
/**
|
||||
* 课程分页 搜索查询
|
||||
* */
|
||||
@@ -82,404 +70,4 @@ 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, String userCenterSchema) {
|
||||
// select字段
|
||||
StringBuilder builder = new StringBuilder("select ");
|
||||
builder.append("c.id,");
|
||||
builder.append("c.name,");
|
||||
builder.append("c.type,");
|
||||
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.org_id AS orgId,");
|
||||
builder.append("org.org_name AS orgName,");
|
||||
builder.append("org.org_name_path AS orgFullName,");
|
||||
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,");
|
||||
builder.append("c.sys_update_time AS sysUpdateTime");
|
||||
// 拼接FROM及查询条件语句
|
||||
appendFrom(builder, queryDTO, isSystemAdmin, orgIds, currentAccountId, userCenterSchema);
|
||||
// 排序语句
|
||||
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.setType((Integer) row[2]);
|
||||
vo.setCoverImg((String) row[3]);
|
||||
vo.setSysType1((String) row[4]);
|
||||
vo.setSysType2((String) row[5]);
|
||||
vo.setSysType3((String) row[6]);
|
||||
vo.setOrgId((String) row[7]);
|
||||
vo.setOrgName((String) row[8]);
|
||||
vo.setOrgFullName((String) row[9]);
|
||||
vo.setSysCreateBy((String) row[10]);
|
||||
vo.setCreateFrom((String) row[11]);
|
||||
|
||||
// 增加对Timestamp和LocalDateTime的兼容性,防止Timestamp为null的情况
|
||||
Timestamp sysCreateTimestamp = (Timestamp) row[12];
|
||||
if (sysCreateTimestamp != null) {
|
||||
vo.setSysCreateTime(sysCreateTimestamp.toLocalDateTime());
|
||||
}
|
||||
|
||||
vo.setForUsers((String) row[13]);
|
||||
vo.setStatus((Integer) row[14]);
|
||||
vo.setPublished((Boolean) row[15]);
|
||||
|
||||
// 增加对Timestamp和LocalDateTime的兼容性,防止Timestamp为null的情况
|
||||
Timestamp publishTimestamp = (Timestamp) row[16];
|
||||
if (publishTimestamp != null) {
|
||||
vo.setPublishTime(publishTimestamp.toLocalDateTime());
|
||||
}
|
||||
|
||||
// 防止Number为null的情况
|
||||
Number studysNum = (Number) row[17];
|
||||
if (studysNum != null) {
|
||||
vo.setStudys(studysNum.intValue());
|
||||
} else {
|
||||
vo.setStudys(0);
|
||||
}
|
||||
|
||||
Number scoreNum = (Number) row[18];
|
||||
if (scoreNum != null) {
|
||||
vo.setScore(scoreNum.floatValue());
|
||||
} else {
|
||||
vo.setScore(0.0f);
|
||||
}
|
||||
|
||||
Number durationNum = (Number) row[19];
|
||||
if (durationNum != null) {
|
||||
vo.setCourseDuration(durationNum.longValue());
|
||||
} else {
|
||||
vo.setCourseDuration(0L);
|
||||
}
|
||||
|
||||
vo.setEnabled((Boolean) row[20]);
|
||||
vo.setOpenCourse((Integer) row[21]);
|
||||
vo.setIsTop((Boolean) row[22]);
|
||||
vo.setTeacherName((String) row[23]);
|
||||
vo.setSortWeight((Integer) row[24]);
|
||||
// 增加对Timestamp和LocalDateTime的兼容性,防止Timestamp为null的情况
|
||||
Timestamp sysUpdateTimestamp = (Timestamp) row[25];
|
||||
if (sysUpdateTimestamp != null) {
|
||||
vo.setSysUpdateTime(sysUpdateTimestamp.toLocalDateTime());
|
||||
}
|
||||
coursePageVos.add(vo);
|
||||
}
|
||||
return coursePageVos;
|
||||
}
|
||||
|
||||
public long countCourse(CoursePageQueryDTO queryDTO,
|
||||
boolean isSystemAdmin, List<String> orgIds, String currentAccountId, String userCenterSchema) {
|
||||
// select count
|
||||
StringBuilder builder = new StringBuilder("select count(*)");
|
||||
// 拼接FROM及查询条件语句
|
||||
appendFrom(builder, queryDTO, isSystemAdmin, orgIds, currentAccountId, userCenterSchema);
|
||||
// 排序语句
|
||||
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, String userCenterSchema) {
|
||||
// 开头判断:课程培训时间的两个参数是否不为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");
|
||||
// 组织机构聚合
|
||||
builder.append(System.lineSeparator());
|
||||
builder.append("LEFT JOIN ").append(userCenterSchema)
|
||||
.append(".organization org ON c.org_id = org.organization_id AND org.deleted = 0");
|
||||
// 教师联查
|
||||
builder.append(System.lineSeparator());
|
||||
builder.append("LEFT JOIN boe_course_teacher ct ON c.id = ct.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.getTeacherId())) {
|
||||
builder.append(System.lineSeparator());
|
||||
// 判断teacherId是一个还是多个
|
||||
if (queryDTO.getTeacherId().contains(",")) {
|
||||
builder.append("AND ct.teacher_id IN (:teacherIdList)");
|
||||
} else {
|
||||
builder.append("AND ct.teacher_id = :teacherId");
|
||||
}
|
||||
}
|
||||
// 展开后条件
|
||||
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.getOrgId())) {
|
||||
builder.append(System.lineSeparator());
|
||||
builder.append("AND c.org_id = :orgId");
|
||||
}
|
||||
if (StringUtils.isNotBlank(queryDTO.getCreateUserId())) {
|
||||
builder.append(System.lineSeparator());
|
||||
if (queryDTO.getCreateUserId().contains(",")) {
|
||||
builder.append("AND c.sys_create_aid IN (:createUserIdList)");
|
||||
} else {
|
||||
builder.append("AND c.sys_create_aid = :createUserId");
|
||||
}
|
||||
}
|
||||
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")) {
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
builder.append("c.").append(orderFieldSql).append(i).append(" ").append(orderAscStr).append(", ");
|
||||
}
|
||||
} else if (StringUtils.equals(orderField, "resOwner")) {
|
||||
builder.append("org.org_name ").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.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.getTeacherId())) {
|
||||
String teacherIdStr = queryDTO.getTeacherId();
|
||||
if (teacherIdStr.contains(",")) {
|
||||
List<String> teacherIdList = Arrays.asList(teacherIdStr.split(","));
|
||||
query.setParameter("teacherIdList", teacherIdList);
|
||||
} else {
|
||||
query.setParameter("teacherId", queryDTO.getTeacherId());
|
||||
}
|
||||
}
|
||||
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("orgId", queryDTO.getResOwner1());
|
||||
}
|
||||
if (StringUtils.isNotBlank(queryDTO.getCreateUserId())) {
|
||||
String createUserIdStr = queryDTO.getCreateUserId();
|
||||
if (createUserIdStr.contains(",")) {
|
||||
List<String> createUserIdList = Arrays.asList(createUserIdStr.split(","));
|
||||
query.setParameter("createUserIdList", createUserIdList);
|
||||
} else {
|
||||
query.setParameter("createUserId", queryDTO.getCreateUserId());
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xboe.module.course.dao;
|
||||
|
||||
import com.xboe.core.orm.BaseDao;
|
||||
import com.xboe.module.course.entity.ThreadLog;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class ThreadLogDao extends BaseDao<ThreadLog> {
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 资源归属:机构id
|
||||
*/
|
||||
private String orgId;
|
||||
|
||||
/**是否发布,无就是全部*/
|
||||
private Boolean publish;
|
||||
|
||||
/**创建人*/
|
||||
private String createUser;
|
||||
|
||||
/**
|
||||
* 创建人id
|
||||
*/
|
||||
private String createUserId;
|
||||
|
||||
/**课程分类的一级*/
|
||||
private String sysType1;
|
||||
|
||||
/**课程分类的二级*/
|
||||
private String sysType2;
|
||||
|
||||
/**课程分类的三级*/
|
||||
private String sysType3;
|
||||
|
||||
/**授课教师*/
|
||||
private String teacherName;
|
||||
|
||||
/**
|
||||
* 授课教师id
|
||||
*/
|
||||
private String teacherId;
|
||||
|
||||
/**培训时间筛选类型*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 是否是新建在线课程页面
|
||||
* false时,代表是在管理端的“在线管理”、“我开发的课程”
|
||||
* true时,代表是在学习路径图/项目管理 中,创建在线课程的选择页面
|
||||
*/
|
||||
private Boolean isCreateCourse;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -406,26 +406,6 @@ 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;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.xboe.module.course.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.xboe.core.SysConstant;
|
||||
import com.xboe.core.orm.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 线程日志表实体
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Table(name = SysConstant.TABLE_PRE + "thread_log")
|
||||
public class ThreadLog {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", columnDefinition = "BIGINT UNSIGNED COMMENT '主键'")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 系统/子系统标识
|
||||
*/
|
||||
@Column(name = "system_name", nullable = false, length = 64)
|
||||
private String systemName;
|
||||
|
||||
/**
|
||||
* 功能模块
|
||||
*/
|
||||
@Column(name = "module_name", nullable = false, length = 64)
|
||||
private String moduleName;
|
||||
|
||||
/**
|
||||
* 具体动作/事件
|
||||
*/
|
||||
@Column(name = "action_name", nullable = false, length = 64)
|
||||
private String actionName;
|
||||
|
||||
/**
|
||||
* 日志级别(INFO/WARN/ERROR/DEBUG等)
|
||||
*/
|
||||
@Column(name = "level", nullable = false, length = 16)
|
||||
private String level;
|
||||
|
||||
/**
|
||||
* 日志正文/描述
|
||||
*/
|
||||
@Column(name = "content", columnDefinition = "TEXT COMMENT '日志正文/描述'")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 线程名称
|
||||
*/
|
||||
@Column(name = "thread_name", length = 64)
|
||||
private String threadName;
|
||||
|
||||
/**
|
||||
* 结构化扩展信息(JSON)
|
||||
* 注:用String接收JSON字符串,如需反序列化可自行处理(如使用ObjectMapper转换为Map/自定义DTO)
|
||||
*/
|
||||
@Column(name = "extra_data", columnDefinition = "JSON COMMENT '结构化扩展信息(JSON)'")
|
||||
private String extraData;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@Column(name = "remark", length = 255)
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Column(name = "create_time", nullable = false)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
@Column(name = "create_id", columnDefinition = "BIGINT COMMENT '创建人ID'")
|
||||
private Long createId;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@Column(name = "create_name", length = 128)
|
||||
private String createName;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Column(name = "update_time", nullable = false)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 更新人ID
|
||||
*/
|
||||
@Column(name = "update_id", columnDefinition = "BIGINT COMMENT '更新人ID'")
|
||||
private Long updateId;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
@Column(name = "update_name", length = 128)
|
||||
private String updateName;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.xboe.module.course.service;
|
||||
|
||||
import com.xboe.common.PageList;
|
||||
import java.util.List;
|
||||
|
||||
import com.xboe.module.course.dto.CourseContentDto;
|
||||
import com.xboe.module.course.dto.SortItem;
|
||||
import com.xboe.module.course.entity.CourseAssess;
|
||||
@@ -8,8 +9,6 @@ import com.xboe.module.course.entity.CourseContent;
|
||||
import com.xboe.module.course.entity.CourseExam;
|
||||
import com.xboe.module.course.entity.CourseHomeWork;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 课程内容,当前是分着处理,之后看是否与课程服务合并在一起
|
||||
* */
|
||||
@@ -55,19 +54,7 @@ public interface ICourseContentService{
|
||||
* @return
|
||||
*/
|
||||
List<CourseContent> getByCourseId(String courseId);
|
||||
|
||||
/**
|
||||
* 根据课程id、章节id得到课程所有目录(即章节,分页),顺序按orderIndex 从小到大的顺序
|
||||
* 25.11.26新增
|
||||
*
|
||||
* @param pageIndex 页码
|
||||
* @param pageSize 每页数量
|
||||
* @param courseId 课程id
|
||||
* @param sectionIdList 章节id集合
|
||||
* @return 课程章节信息集合分页
|
||||
*/
|
||||
PageList<CourseContent> getByCourseIdPage(int pageIndex, int pageSize, String courseId, List<String> sectionIdList);
|
||||
|
||||
|
||||
/**
|
||||
* 更新内容顺序
|
||||
* @param courseId
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -5,12 +5,11 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.xboe.common.OrderCondition;
|
||||
import com.xboe.common.PageList;
|
||||
import com.xboe.core.cache.IXaskCache;
|
||||
import com.xboe.core.cache.XaskCacheProvider;
|
||||
import com.xboe.core.orm.FieldFilters;
|
||||
import com.xboe.core.orm.IFieldFilter;
|
||||
import com.xboe.core.orm.UpdateBuilder;
|
||||
import com.xboe.module.course.dao.*;
|
||||
import com.xboe.module.course.dto.CourseContentDto;
|
||||
@@ -142,27 +141,6 @@ public class CourseContentServiceImpl implements ICourseContentService {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据课程id、章节id得到课程所有目录(即章节,分页),顺序按orderIndex 从小到大的顺序
|
||||
* 25.11.26新增
|
||||
*
|
||||
* @param pageIndex 页码
|
||||
* @param pageSize 每页数量
|
||||
* @param courseId 课程id
|
||||
* @param sectionIdList 章节id集合
|
||||
* @return 课程章节信息集合分页
|
||||
*/
|
||||
@Override
|
||||
public PageList<CourseContent> getByCourseIdPage(int pageIndex, int pageSize, String courseId, List<String> sectionIdList) {
|
||||
List<IFieldFilter> filters = new ArrayList<>();
|
||||
filters.add(FieldFilters.eq("courseId", courseId));
|
||||
filters.add(FieldFilters.eq("deleted", false));
|
||||
if (sectionIdList != null && !sectionIdList.isEmpty()) {
|
||||
filters.add(FieldFilters.in("csectionId", sectionIdList));
|
||||
}
|
||||
return ccDao.findPage(pageIndex, pageSize, OrderCondition.asc("sortIndex"), filters.toArray(new IFieldFilter[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CourseHomeWork getHomework(String ccid) {
|
||||
CourseHomeWork hw=homeworkDao.findOne(FieldFilters.eq("contentId", ccid));
|
||||
|
||||
@@ -1,419 +0,0 @@
|
||||
package com.xboe.module.course.service.impl;
|
||||
|
||||
import com.boe.feign.api.usercenter.entity.ApiOrgListVo;
|
||||
import com.xboe.common.OrderCondition;
|
||||
import com.xboe.common.PageList;
|
||||
import com.xboe.common.exception.AppException;
|
||||
import com.xboe.config.MySqlSchemaProperties;
|
||||
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 com.xboe.module.type.entity.Type;
|
||||
import com.xboe.module.type.service.ITypeService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.OutputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@EnableConfigurationProperties({MySqlSchemaProperties.class})
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CoursePageServiceImpl implements ICoursePageService {
|
||||
|
||||
@Autowired
|
||||
private MySqlSchemaProperties mySqlSchemaProperties;
|
||||
|
||||
@Resource
|
||||
private CourseDao courseDao;
|
||||
|
||||
@Resource
|
||||
private CourseTeacherDao courseTeacherDao;
|
||||
|
||||
@Autowired
|
||||
private IOutSideDataService outSideDataService;
|
||||
|
||||
@Resource
|
||||
private ITypeService typeService;
|
||||
|
||||
@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<String> readIds = userOrgIds.getReadIds();
|
||||
orgIds.addAll(readIds);
|
||||
|
||||
long total = courseDao.countCourse(coursePageQueryDTO, isSystemAdmin, orgIds, currentAccountId, mySqlSchemaProperties.getUserCenterSchema());
|
||||
List<CoursePageVo> voList = courseDao.queryCourse(coursePageQueryDTO, isSystemAdmin, orgIds, currentAccountId, true, mySqlSchemaProperties.getUserCenterSchema());
|
||||
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, mySqlSchemaProperties.getUserCenterSchema());
|
||||
|
||||
// 导出
|
||||
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("资源归属", "orgName");
|
||||
exportMap.put("创建人", "sysCreateBy");
|
||||
exportMap.put("创建来源", "createFrom");
|
||||
exportMap.put("创建时间", "sysCreateTime");
|
||||
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||
if (courseList != null && !courseList.isEmpty()) {
|
||||
// 查询sysType
|
||||
Type sysTypeParam = new Type();
|
||||
sysTypeParam.setSysResType(1);
|
||||
sysTypeParam.setStatus(1);
|
||||
List<Type> typeList = typeService.findList(sysTypeParam);
|
||||
|
||||
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());
|
||||
// 课程分类
|
||||
StringBuilder sysTypeBuilder = new StringBuilder();
|
||||
typeList.stream()
|
||||
.filter(t -> StringUtils.equals(coursePageVo.getSysType1(), t.getId()))
|
||||
.findFirst().ifPresent(t -> sysTypeBuilder.append(t.getName()));
|
||||
if (StringUtils.isNotBlank(coursePageVo.getSysType2())) {
|
||||
typeList.stream()
|
||||
.filter(t -> StringUtils.equals(coursePageVo.getSysType2(), t.getId()))
|
||||
.findFirst().ifPresent(t -> sysTypeBuilder.append("/").append(t.getName()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(coursePageVo.getSysType3())) {
|
||||
typeList.stream()
|
||||
.filter(t -> StringUtils.equals(coursePageVo.getSysType3(), t.getId()))
|
||||
.findFirst().ifPresent(t -> sysTypeBuilder.append("/").append(t.getName()));
|
||||
}
|
||||
map.put("sysType", sysTypeBuilder.toString());
|
||||
// map.put("sysType", coursePageVo.getSysType1());
|
||||
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("orgName", coursePageVo.getOrgFullName()); // 修改为组织全名
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
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 Integer type;
|
||||
|
||||
/**
|
||||
* 课程封面图片地址
|
||||
*/
|
||||
private String coverImg;
|
||||
|
||||
/**
|
||||
* 课程分类一级
|
||||
*/
|
||||
private String sysType1;
|
||||
|
||||
/**
|
||||
* 课程分类二级
|
||||
*/
|
||||
private String sysType2;
|
||||
|
||||
/**
|
||||
* 课程分类三级
|
||||
*/
|
||||
private String sysType3;
|
||||
|
||||
/**
|
||||
* 资源归属一级
|
||||
*/
|
||||
private String resOwner1;
|
||||
|
||||
/**
|
||||
* 资源归属二级
|
||||
*/
|
||||
private String resOwner2;
|
||||
|
||||
/**
|
||||
* 资源归属三级
|
||||
*/
|
||||
private String resOwner3;
|
||||
|
||||
/**
|
||||
* 机构ID
|
||||
*/
|
||||
private String orgId;
|
||||
|
||||
/**
|
||||
* 机构名称
|
||||
*/
|
||||
private String orgName;
|
||||
|
||||
/**
|
||||
* 机构全名
|
||||
*/
|
||||
private String orgFullName;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 最后修改时间
|
||||
*/
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime sysUpdateTime;
|
||||
|
||||
/**
|
||||
* 目标人群
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -8,32 +8,13 @@ import lombok.Data;
|
||||
@Data
|
||||
public class CourseStudyVo {
|
||||
|
||||
/**
|
||||
* 内容id
|
||||
*/
|
||||
private String contentId;
|
||||
|
||||
/**
|
||||
* 内容名称
|
||||
*/
|
||||
private String contentName;
|
||||
|
||||
/**
|
||||
* 学习状态( 1-未学习,2-学习中,9-学习完成)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
|
||||
/**
|
||||
* 学习时长
|
||||
* 25.11.26新增
|
||||
* studyDuration字段被占用了,换一个
|
||||
*/
|
||||
private Integer learningDuration;
|
||||
|
||||
/**
|
||||
* 学习进度(理论上来说这个字段应该是学习时长,但是原接口如此,暂时保留此字段)
|
||||
*/
|
||||
private Integer studyDuration;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
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.*;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 导出excel工具类
|
||||
@@ -24,6 +34,148 @@ import java.util.*;
|
||||
@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
|
||||
@@ -171,106 +323,10 @@ public class ExportsExcelSenderUtil {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出动态列Excel
|
||||
*
|
||||
* @param kvMap 自定义标题属性,key:标题 value:属性
|
||||
* @param dataList 数据列表,每个元素是一个Map,key为属性名,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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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;
|
||||
@@ -23,6 +25,8 @@ import com.xboe.module.interaction.service.IFavoritesService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.persistence.Cacheable;
|
||||
|
||||
/**
|
||||
* 收藏处理
|
||||
*
|
||||
@@ -40,7 +44,7 @@ public class FavoritesApi extends ApiBaseController {
|
||||
ICourseTeacherService ctService;
|
||||
|
||||
@Autowired
|
||||
IStudyCourseService studyCourseService;
|
||||
IStudyCourseService studyCourseService;
|
||||
|
||||
@Autowired
|
||||
IQuestionService questionService;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,20 @@
|
||||
package com.xboe.school.study.api;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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;
|
||||
@@ -11,16 +26,8 @@ import com.xboe.core.api.ApiBaseController;
|
||||
import com.xboe.school.study.dto.BatchSignup;
|
||||
import com.xboe.school.study.entity.StudySignup;
|
||||
import com.xboe.school.study.service.IStudySignupService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 课程报名, 本控制器只有一个报名处理,没有其它的处理
|
||||
@@ -56,24 +63,17 @@ public class StudySignupApi extends ApiBaseController{
|
||||
return error("报名失败,请与管理员联系",e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 分页查询报名人员
|
||||
* @param pager 分页
|
||||
* @param courseId 课程id
|
||||
* @param name 姓名
|
||||
* @param signType 报名方式
|
||||
* @return 返回课程报名表的集合
|
||||
*/
|
||||
|
||||
@PostMapping("/pagelist")
|
||||
public JsonResponse<PageList<StudySignup>> findPage(Pagination pager, String courseId, String name, Integer signType) {
|
||||
public JsonResponse<PageList<StudySignup>> findPage(Pagination pager,String courseId,String name,Integer status){
|
||||
// if(StringUtils.isBlank(courseId)){
|
||||
// return error("无课程信息");
|
||||
// }
|
||||
StudySignup ss=new StudySignup();
|
||||
ss.setCourseId(courseId);
|
||||
ss.setName(name);
|
||||
//2025.11.28 新增signType筛选
|
||||
ss.setSignType(signType);
|
||||
ss.setStatus(status);
|
||||
|
||||
//CurrentUser cuser=getCurrent();
|
||||
try {
|
||||
PageList<StudySignup> list =signupService.findPage(pager.getPageIndex(),pager.getPageSize(), ss, OrderCondition.desc("id"));
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
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> {
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
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.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public class CourseStatDao extends BaseDao<StudyCourse> {
|
||||
|
||||
/**
|
||||
* 查询课程完成人数
|
||||
*
|
||||
* @param startIndex 分页开始索引
|
||||
* @param pageSize 分页大小
|
||||
* @param courseId 课程ID
|
||||
* @param contentName 资源名称
|
||||
* @return 课程完成人数统计DTO集合
|
||||
*/
|
||||
public List<CourseFinishCountDto> findFinishCountPage(int startIndex, int pageSize, String courseId, String contentName) {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT ")
|
||||
// 资源名
|
||||
.append("cc.content_name AS contentName, ")
|
||||
// 资源ID
|
||||
.append("cc.id AS contentId, ")
|
||||
// 完成人数(去重统计)
|
||||
.append("COUNT(DISTINCT c.aid) AS finishCount, ")
|
||||
//2025.11.27新增:资源类型
|
||||
.append("cc.content_type AS contentType ")
|
||||
// 你的课程表名
|
||||
.append("FROM boe_course_content cc ")
|
||||
// 2025.11.27新增:连表 boe_course_content
|
||||
.append("LEFT JOIN boe_study_course_item c ON cc.id = c.content_id AND c.status = 9 ")
|
||||
.append("WHERE 1=1 ");
|
||||
List<Object> params = new ArrayList<>();
|
||||
// courseId非空则过滤(参数化防注入)
|
||||
if (StringUtils.isNotBlank(courseId)) {
|
||||
sql.append("AND cc.course_id = ? ");
|
||||
params.add(courseId);
|
||||
}
|
||||
// 25.12.1修改 新增:contentName模糊查询(粗略匹配)
|
||||
if (StringUtils.isNotBlank(contentName)) {
|
||||
// 实现“包含contentName”的模糊查询
|
||||
sql.append("AND cc.content_name LIKE ? ");
|
||||
// 通配符%拼接在参数上防注入,表示“前后任意字符包含contentName”
|
||||
params.add("%" + contentName + "%");
|
||||
}
|
||||
// 分组+排序+分页(聚合函数必须分组,排序参考第一个代码的desc id)
|
||||
sql.append("GROUP BY cc.id, cc.content_name, cc.content_type ")
|
||||
.append("ORDER BY cc.id DESC ")
|
||||
// MySQL分页:偏移量,每页条数
|
||||
.append("LIMIT ?, ?");
|
||||
// 补充分页参数(顺序: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.setContentId(((BigInteger) objs[1]).toString());
|
||||
dto.setFinishCount(objs[2] != null ? ((Number) objs[2]).intValue() : 0);
|
||||
dto.setContentType((Integer) objs[3]);
|
||||
dtoList.add(dto);
|
||||
}
|
||||
return dtoList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询课程完成人数
|
||||
*
|
||||
* @param courseId 课程ID
|
||||
* @param contentName 资源名称
|
||||
* @return 课程完成人数
|
||||
*/
|
||||
public int findFinishCountTotal(String courseId, String contentName) {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT ")
|
||||
// 总条数=去重后的人数
|
||||
.append("COUNT(DISTINCT c.id) ")
|
||||
.append("FROM boe_course_content c ")
|
||||
.append("WHERE 1=1 ");
|
||||
List<Object> params = new ArrayList<>();
|
||||
// courseId非空则过滤(参数化防注入)
|
||||
if (StringUtils.isNotBlank(courseId)) {
|
||||
sql.append("AND c.course_id = ? ");
|
||||
params.add(courseId);
|
||||
}
|
||||
// 25.12.1修改 新增:contentName模糊查询(粗略匹配)
|
||||
if (StringUtils.isNotBlank(contentName)) {
|
||||
// 实现“包含contentName”的模糊查询
|
||||
sql.append("AND c.content_name LIKE ? ");
|
||||
// 通配符%拼接在参数上防注入,表示“前后任意字符包含contentName”
|
||||
params.add("%" + contentName + "%");
|
||||
}
|
||||
// 用sqlCount替代sqlFindObject,直接返回int类型结果
|
||||
// sqlCount会执行SQL并返回COUNT的结果,无需手动转换Object
|
||||
return this.sqlCount(sql.toString(), params.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.xboe.api.ThirdApi;
|
||||
import com.xboe.constants.CacheName;
|
||||
import com.xboe.module.course.dao.ThreadLogDao;
|
||||
import com.xboe.school.study.dto.StudyContentDto;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -32,7 +35,8 @@ public class StudyCourseDao extends BaseDao<StudyCourse> {
|
||||
StudyCourseItemDao scItemDao;
|
||||
@Autowired
|
||||
StringRedisTemplate redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private ThreadLogDao threadLogDao;
|
||||
|
||||
@Resource
|
||||
private ThirdApi thirdApi;
|
||||
@@ -45,6 +49,8 @@ public class StudyCourseDao extends BaseDao<StudyCourse> {
|
||||
public void finishCheck(String studyId,String courseId,Integer total,String token){
|
||||
|
||||
if(StringUtils.isNotEmpty(redisTemplate.opsForValue().get(studyId + "_" + courseId + "_" + total))){
|
||||
log.info("进入埋点finishCheck");
|
||||
saveThreadLog(studyId, courseId, total, token);
|
||||
return ;
|
||||
}
|
||||
|
||||
@@ -73,6 +79,8 @@ public class StudyCourseDao extends BaseDao<StudyCourse> {
|
||||
UpdateBuilder.create("finishTime",now),
|
||||
UpdateBuilder.create("status",StudyCourse.STATUS_FINISH));
|
||||
redisTemplate.opsForValue().set(studyId + "_" + courseId + "_" + total, "100", 24, TimeUnit.HOURS);
|
||||
log.info("进入埋点finishCheck");
|
||||
saveThreadLog(studyId, courseId, total, token);
|
||||
}else {
|
||||
super.updateMultiFieldById(studyId,
|
||||
UpdateBuilder.create("progress",percent),
|
||||
@@ -85,6 +93,41 @@ public class StudyCourseDao extends BaseDao<StudyCourse> {
|
||||
log.info("在线课学习记录"+allUserList);
|
||||
}
|
||||
|
||||
private void saveThreadLog(String studyId,String courseId,Integer total,String token) {
|
||||
try {
|
||||
JSONObject extraData = new JSONObject();
|
||||
extraData.put("studyId", studyId);
|
||||
extraData.put("courseId", courseId);
|
||||
extraData.put("total", total);
|
||||
extraData.put("token", token);
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String threadName = Thread.currentThread().getName();
|
||||
|
||||
String sql = "INSERT INTO boe_thread_log (system_name,module_name,action_name,level,content,thread_name,extra_data,remark,create_time,create_id,create_name,update_time,update_id,update_name) "
|
||||
+ "VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14)";
|
||||
|
||||
threadLogDao.sqlUpdate(sql,
|
||||
"学习",
|
||||
"学习进度更新",
|
||||
"更新StudyCourse进度完成",
|
||||
"info",
|
||||
null,
|
||||
threadName,
|
||||
extraData.toJSONString(),
|
||||
null,
|
||||
now,
|
||||
null,
|
||||
null,
|
||||
now,
|
||||
null,
|
||||
null);
|
||||
log.info("saveThreadLog插入成功");
|
||||
} catch (Exception ex) {
|
||||
log.error("保存线程日志失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void finishCheck1(String studyId,String courseId,Integer total){
|
||||
LocalDateTime now=LocalDateTime.now();
|
||||
//已完成的内容
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.xboe.school.study.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 课程完成人数统计DTO
|
||||
*/
|
||||
@Data
|
||||
public class CourseFinishCountDto {
|
||||
|
||||
/**
|
||||
* 资源名称
|
||||
*/
|
||||
private String contentName;
|
||||
|
||||
/**
|
||||
* 内容ID
|
||||
*/
|
||||
private String contentId;
|
||||
|
||||
/**
|
||||
* 完成人数(数据库 count 统计得出)
|
||||
*/
|
||||
private Integer finishCount;
|
||||
|
||||
/**
|
||||
* 内容类型
|
||||
* 枚举值参考BoedxContentType
|
||||
*/
|
||||
private Integer contentType;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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 Integer signType;
|
||||
|
||||
/**
|
||||
* 报名状态显示(1.自主报名 2.手动加入)
|
||||
*/
|
||||
private String signTypeName;
|
||||
|
||||
/**
|
||||
* 课程名称
|
||||
*/
|
||||
private String courseName;
|
||||
|
||||
/**
|
||||
* 工号
|
||||
*/
|
||||
private String userNo;
|
||||
|
||||
/**
|
||||
* 部门
|
||||
*/
|
||||
private String departName;
|
||||
|
||||
/**
|
||||
* 报名时间
|
||||
*/
|
||||
private LocalDateTime signTime;
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
package com.xboe.school.study.entity;
|
||||
|
||||
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;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
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;
|
||||
|
||||
/*
|
||||
* 课程学习表,课程图片通过对应图片的api获取,这里不做存储。
|
||||
@@ -81,9 +83,8 @@ public class StudyCourse extends IdEntity{
|
||||
|
||||
/*
|
||||
* 开始学习时间,报名和学习是一体的,此字段为后续报名学习不致的情况,当前这种情况没有
|
||||
* 25.11.21修改:经生产数据查证本字段实际未废弃,因此继续使用
|
||||
*/
|
||||
// @Deprecated
|
||||
* */
|
||||
@Deprecated
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Column(name = "start_time")
|
||||
private LocalDateTime startTime;
|
||||
@@ -104,9 +105,8 @@ public class StudyCourse extends IdEntity{
|
||||
/*
|
||||
* 学习总时间,秒,没有小数的情况,所以直接使用整数类型
|
||||
* 这一项计算时间,在二期中已经不在使用,学习时长已经移到统计服务中,单独的课程不再记录
|
||||
* 25.11.21修改:经生产数据查证本字段实际未废弃,因此继续使用
|
||||
*/
|
||||
// @Deprecated
|
||||
@Deprecated
|
||||
@Column(name = "total_duration")
|
||||
private Integer totalDuration;
|
||||
|
||||
@@ -142,51 +142,9 @@ 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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package com.xboe.school.study.entity;
|
||||
|
||||
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.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.xboe.core.SysConstant;
|
||||
import com.xboe.core.orm.IdEntity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/*
|
||||
* 课程学习记录表,相当于课程学习表的子表
|
||||
@@ -137,19 +138,4 @@ public class StudyCourseItem extends IdEntity {
|
||||
@Transient
|
||||
private BigDecimal progressVideo;
|
||||
|
||||
/**
|
||||
* 考试记录集合
|
||||
* 仅在资源学习情况分页查询-考试信息接口使用
|
||||
* 25.11.25新增
|
||||
*/
|
||||
@Transient
|
||||
private List<StudyExam> studyExams;
|
||||
|
||||
/**
|
||||
* 评估记录集合
|
||||
* 仅在资源学习情况分页查询-评估信息接口使用
|
||||
* 25.11.28新增
|
||||
*/
|
||||
@Transient
|
||||
private List<StudyAssess> studyAssesses;
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -28,13 +28,4 @@ public interface IStudyAssessService {
|
||||
* @return
|
||||
*/
|
||||
List<StudyAssess> getByStudyIdAndContentId(String studyId,String contentId);
|
||||
|
||||
/**
|
||||
* 根据课程id和内容id得到评估的内容
|
||||
* @param courseId 课程id
|
||||
* @param contentId 内容
|
||||
* @return
|
||||
*/
|
||||
List<StudyAssess> getByCourseIdAndContentId(String courseId,String contentId);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
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.*;
|
||||
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.entity.StudyCourse;
|
||||
import com.xboe.school.study.entity.StudyCourseItem;
|
||||
import com.xboe.school.study.entity.StudySignup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IStudyCourseService {
|
||||
|
||||
/**
|
||||
@@ -43,36 +46,15 @@ public interface IStudyCourseService {
|
||||
PageList<StudyCourse> findByES(int pageIndex,int pageSize) throws Exception;
|
||||
|
||||
/**
|
||||
* 列表查询课程学习记录,用户的课程学习记录
|
||||
* 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 课程学习记录分页集合
|
||||
* 分页查询课程学习记录,用户的课程学习记录
|
||||
* @param pageIndex
|
||||
* @param pageSize
|
||||
* @param sc
|
||||
* @param oc
|
||||
* @return
|
||||
*/
|
||||
PageList<StudyCourse> findPage(int pageIndex,int pageSize,StudyCourse sc,OrderCondition oc,Boolean isFinish);
|
||||
|
||||
/**
|
||||
* 分页查询课程的资源名称以及资源学习完成人数
|
||||
* @param pageIndex 页码
|
||||
* @param pageSize 每页数据条数
|
||||
* @param courseId 课程id
|
||||
* @param contentName 资源名称
|
||||
*/
|
||||
PageList<CourseFinishCountDto> findPageResource(int pageIndex, int pageSize, String courseId, String contentName);
|
||||
|
||||
/**
|
||||
* 热度榜
|
||||
* */
|
||||
@@ -123,15 +105,4 @@ 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);
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.xboe.school.study.service;
|
||||
|
||||
import com.xboe.school.study.entity.StudyExam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.xboe.school.study.entity.StudyExam;
|
||||
|
||||
public interface IStudyExamService {
|
||||
|
||||
/**
|
||||
@@ -30,16 +30,5 @@ public interface IStudyExamService {
|
||||
*/
|
||||
List<StudyExam> getByStudyIdAndContentId(String studyId,String contentId);
|
||||
|
||||
/**
|
||||
* 根据课程id或者内容id得到对应的考试记录
|
||||
* 课程id必填,内容id选填
|
||||
* 25.11.20新增
|
||||
*
|
||||
* @param courseId 课程id
|
||||
* @param contentId 内容id
|
||||
* @return 考试记录集合
|
||||
*/
|
||||
List<StudyExam> getByCourseIdAndContentId(String courseId, String contentId);
|
||||
|
||||
void correctStstus(String courseId);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.xboe.school.study.service;
|
||||
|
||||
import com.xboe.school.study.entity.StudyHomeWork;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.xboe.school.study.entity.StudyHomeWork;
|
||||
|
||||
public interface IStudyHomeWorkService {
|
||||
|
||||
/**
|
||||
@@ -11,17 +12,17 @@ public interface IStudyHomeWorkService {
|
||||
* @param exam
|
||||
*/
|
||||
void save(StudyHomeWork homework,String token);
|
||||
|
||||
|
||||
|
||||
|
||||
StudyHomeWork get(String id);
|
||||
|
||||
|
||||
/**
|
||||
* 根据学习内容id得到答卷信息
|
||||
* @param studyItemId
|
||||
* @return
|
||||
*/
|
||||
List<StudyHomeWork> getByStudyItem(String studyItemId);
|
||||
|
||||
|
||||
/**
|
||||
* 根据学习id和内容id查出作业列表
|
||||
* @param studyId
|
||||
@@ -31,15 +32,4 @@ public interface IStudyHomeWorkService {
|
||||
List<StudyHomeWork> getByStudyIdAndContentId(String studyId,String contentId);
|
||||
|
||||
List<StudyHomeWork>getByStudnetNameAndContentId(List<String> studentName, String contentId);
|
||||
|
||||
/**
|
||||
* 根据课程id得到对应的作业记录
|
||||
* 25.11.20新增
|
||||
*
|
||||
* @param courseId 课程id
|
||||
* @param contentId 内容id
|
||||
* @return 作业记录集合
|
||||
*/
|
||||
List<StudyHomeWork> getByCourseIdAndContentId(String courseId, String contentId);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.xboe.school.study.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.xboe.common.PageList;
|
||||
import com.xboe.school.study.dto.StudyContentDto;
|
||||
import com.xboe.school.study.entity.StudyCourseItem;
|
||||
import com.xboe.school.study.entity.StudyTime;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 学习情况处理,比较综合一个处理类
|
||||
* @author seastar
|
||||
@@ -85,21 +85,18 @@ public interface IStudyService {
|
||||
* @return
|
||||
*/
|
||||
List<StudyCourseItem> findByStudyId(String studyId);
|
||||
|
||||
|
||||
/**
|
||||
* 资源学习情况分页查询,此接口用于管理端处理
|
||||
* 25.11.25修改,修改资源学习情况的查询字段,修改为管理端和教师端均可以查询,并添加部门和工号查询逻辑
|
||||
*
|
||||
* @param ids 资源学习id集合
|
||||
* @param pageIndex 页码
|
||||
* @param pageSize 每页数量
|
||||
* @param courseId 课程id
|
||||
* @param contentId 内容id
|
||||
* @param name 用户名称
|
||||
* @param status 用户学习状态(1-未开始,2-已完成,3-进行中)
|
||||
* @return 资源学习情况分页集合
|
||||
* 查询学习内容记录
|
||||
* @param pageIndex
|
||||
* @param pageSize
|
||||
* @param contentId
|
||||
* @param courseId
|
||||
* @param name
|
||||
* @param status
|
||||
* @return
|
||||
*/
|
||||
PageList<StudyCourseItem> findItemPage(int pageIndex, int pageSize, List<String> ids, String contentId, String courseId, String name, Integer status);
|
||||
PageList<StudyCourseItem> findItemPage(int pageIndex, int pageSize, String contentId, String courseId, String name, Integer status);
|
||||
|
||||
List<StudyCourseItem> getList(String courseId, String contentId, String name, Integer status);
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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("signType", queryDto.getSignType()));
|
||||
}
|
||||
}
|
||||
return coursesignDao.findList(query.builder());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,15 +86,4 @@ public class StudyAssessServiceImpl implements IStudyAssessService{
|
||||
return dao.findList(FieldFilters.eq("studyId", studyId),FieldFilters.eq("contentId", contentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据课程id和内容id得到评估的内容
|
||||
* @param courseId 课程id
|
||||
* @param contentId 内容
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<StudyAssess> getByCourseIdAndContentId(String courseId,String contentId){
|
||||
return dao.findList(FieldFilters.eq("courseId", courseId),FieldFilters.eq("contentId", contentId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
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;
|
||||
@@ -7,39 +20,29 @@ import com.xboe.core.orm.FieldUpdateType;
|
||||
import com.xboe.core.orm.QueryBuilder;
|
||||
import com.xboe.core.orm.UpdateBuilder;
|
||||
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.*;
|
||||
import com.xboe.school.study.dto.*;
|
||||
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.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.entity.Organization;
|
||||
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.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -52,19 +55,6 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
|
||||
@Autowired
|
||||
IStudySignupService signupService;
|
||||
|
||||
@Autowired
|
||||
IOutSideDataService outsideService;
|
||||
|
||||
@Autowired
|
||||
IUserService userService;
|
||||
|
||||
@Autowired
|
||||
IOrganizationService organizationService;
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
IStudyCourseService studyCourseService;
|
||||
|
||||
@Autowired
|
||||
StudyCourseDao studyCourseDao;
|
||||
|
||||
@@ -92,22 +82,12 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
|
||||
@Autowired
|
||||
StudyTimeDao studyTimeDao;
|
||||
|
||||
@Autowired
|
||||
CourseStatDao courseStatDao;
|
||||
|
||||
@Autowired
|
||||
CourseDao courseDao;
|
||||
|
||||
@Autowired(required = false)
|
||||
ICourseStudySearch esSearch;//对ES的处理
|
||||
|
||||
/**
|
||||
* 用于避免JPA查询后修改entity实体字段,自动更新到数据库
|
||||
*/
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
|
||||
@Override
|
||||
public StudyCourse findByCourseIdAndAid(String courseId, String aid) {
|
||||
//加上排序,如果是多条学习记录,只会取最新的一条
|
||||
@@ -121,16 +101,6 @@ 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);
|
||||
@@ -163,134 +133,16 @@ public class StudyCourseServiceImpl implements IStudyCourseService{
|
||||
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));
|
||||
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());
|
||||
// 获取部门信息(参考现有机构表,获取namePath字段)
|
||||
Organization organization = organizationService.get(userInfo.getDepartId());
|
||||
String departName = organization != null ? organization.getNamePath() : "";
|
||||
studyCourse1.setOrgInfo(departName);
|
||||
// 取userNo为工号
|
||||
studyCourse1.setUserNo(userInfo.getUserNo());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.error("【分页查询课程学习记录】用户信息查询boe人员表失败,用户id:{}", userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 25.12.3新增,添加基于时间的查询条件,规则如下:
|
||||
if (sc != null && StringUtils.isNotBlank(sc.getCourseId()) && StringUtils.isNotBlank(sc.getQueryStartTime()) && StringUtils.isNotBlank(sc.getQueryFinishTime())) {
|
||||
List<StudyCourseItem> studyCourseItemList = studyCourseService.getStudyCourseItemByCourseId(sc.getCourseId());
|
||||
if (studyCourseItemList != null && !studyCourseItemList.isEmpty()) {
|
||||
// 将学习内容按照studyId进行分组,便于后续计算每个studyId下的总学习时长
|
||||
Map<String, List<StudyCourseItem>> itemsByStudyId = studyCourseItemList.stream().filter(item -> item.getFinishTime() != null).collect(Collectors.groupingBy(StudyCourseItem::getStudyId));
|
||||
// 获取查询的起止时间
|
||||
String queryStartTime = sc.getQueryStartTime();
|
||||
String queryFinishTime = sc.getQueryFinishTime();
|
||||
for (StudyCourse studyCourse : studyCourses) {
|
||||
// 实体类解耦
|
||||
entityManager.detach(studyCourse);
|
||||
// 解析查询时间范围
|
||||
LocalDate startDate = LocalDate.parse(queryStartTime);
|
||||
LocalDate endDate = LocalDate.parse(queryFinishTime);
|
||||
// 如果有查询时间范围,则计算筛选后的学习时长
|
||||
if (itemsByStudyId.containsKey(studyCourse.getId())) {
|
||||
List<StudyCourseItem> items = itemsByStudyId.get(studyCourse.getId());
|
||||
int filteredDuration = 0;
|
||||
for (StudyCourseItem item : items) {
|
||||
LocalDateTime finishTime = item.getFinishTime();
|
||||
LocalDate finishDate = finishTime.toLocalDate();
|
||||
// 判断结束时间是否在筛选范围内
|
||||
boolean inRange = true;
|
||||
if (finishDate.isBefore(startDate)) {
|
||||
inRange = false;
|
||||
}
|
||||
if (finishDate.isAfter(endDate)) {
|
||||
inRange = false;
|
||||
}
|
||||
// 如果在范围内,则累加学习时长
|
||||
if (inRange) {
|
||||
Integer duration = item.getStudyDuration();
|
||||
if (duration != null) {
|
||||
filteredDuration += duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 更新学习时长
|
||||
studyCourse.setTotalDuration(filteredDuration);
|
||||
} else {
|
||||
// 查询不到item的情况,默认置空
|
||||
studyCourse.setTotalDuration(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return studyCoursePageList;
|
||||
return studyCourseDao.findPage(query.builder());
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询课程的资源名称以及资源学习完成人数
|
||||
*
|
||||
* @param pageIndex 页码
|
||||
* @param pageSize 每页数据条数
|
||||
* @param courseId 课程id
|
||||
* @param contentName 资源名称
|
||||
*/
|
||||
@Override
|
||||
public PageList<CourseFinishCountDto> findPageResource(int pageIndex, int pageSize, String courseId, String contentName) {
|
||||
// 1. 手动计算分页偏移量(数据库分页必需)
|
||||
// pageIndex<1时设为0,避免数据库LIMIT偏移量为负数
|
||||
int startIndex = (pageIndex < 1) ? 0 : (pageIndex - 1) * pageSize;
|
||||
// 2. 调用Dao层:查当前页数据(传入偏移量、每页条数、courseId条件)
|
||||
List<CourseFinishCountDto> dtoList = courseStatDao.findFinishCountPage(startIndex, pageSize, courseId,contentName);
|
||||
// 3. 调用Dao层:查总条数(对应PageList的count字段,用int类型,和PageList一致)
|
||||
int totalCount = courseStatDao.findFinishCountTotal(courseId,contentName);
|
||||
// 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);
|
||||
@@ -822,94 +674,6 @@ 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()));
|
||||
}
|
||||
}
|
||||
if (isFinish != null) {
|
||||
if (isFinish) {
|
||||
query.addFilter(FieldFilters.eq("status", 9));
|
||||
} else {
|
||||
query.addFilter(FieldFilters.lt("status", 9));
|
||||
}
|
||||
}
|
||||
// 25.12.3新增,添加基于时间的查询条件,规则如下:
|
||||
List<StudyCourse> studyCourses = studyCourseDao.findList(query.builder());
|
||||
if (sc != null && StringUtils.isNotBlank(sc.getCourseId()) && StringUtils.isNotBlank(sc.getQueryStartTime()) && StringUtils.isNotBlank(sc.getQueryFinishTime())) {
|
||||
List<StudyCourseItem> studyCourseItemList = studyCourseService.getStudyCourseItemByCourseId(sc.getCourseId());
|
||||
if (studyCourseItemList != null && !studyCourseItemList.isEmpty()) {
|
||||
// 将学习内容按照studyId进行分组,便于后续计算每个studyId下的总学习时长
|
||||
Map<String, List<StudyCourseItem>> itemsByStudyId = studyCourseItemList.stream().filter(item -> item.getFinishTime() != null).collect(Collectors.groupingBy(StudyCourseItem::getStudyId));
|
||||
// 获取查询的起止时间
|
||||
String queryStartTime = sc.getQueryStartTime();
|
||||
String queryFinishTime = sc.getQueryFinishTime();
|
||||
for (StudyCourse studyCourse : studyCourses) {
|
||||
// 实体类解耦
|
||||
entityManager.detach(studyCourse);
|
||||
// 解析查询时间范围
|
||||
LocalDate startDate = LocalDate.parse(queryStartTime);
|
||||
LocalDate endDate = LocalDate.parse(queryFinishTime);
|
||||
// 如果有查询时间范围,则计算筛选后的学习时长
|
||||
if (itemsByStudyId.containsKey(studyCourse.getId())) {
|
||||
List<StudyCourseItem> items = itemsByStudyId.get(studyCourse.getId());
|
||||
int filteredDuration = 0;
|
||||
for (StudyCourseItem item : items) {
|
||||
LocalDateTime finishTime = item.getFinishTime();
|
||||
LocalDate finishDate = finishTime.toLocalDate();
|
||||
// 判断结束时间是否在筛选范围内
|
||||
boolean inRange = true;
|
||||
if (finishDate.isBefore(startDate)) {
|
||||
inRange = false;
|
||||
}
|
||||
if (finishDate.isAfter(endDate)) {
|
||||
inRange = false;
|
||||
}
|
||||
// 如果在范围内,则累加学习时长
|
||||
if (inRange) {
|
||||
Integer duration = item.getStudyDuration();
|
||||
if (duration != null) {
|
||||
filteredDuration += duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 更新学习时长
|
||||
studyCourse.setTotalDuration(filteredDuration);
|
||||
} else {
|
||||
// 查询不到item的情况,默认置空
|
||||
studyCourse.setTotalDuration(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return studyCourses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CourseStudyItem> findByCourseAndUsers(String courseId, List<String> aids) throws Exception {
|
||||
|
||||
@@ -955,17 +719,4 @@ 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.xboe.school.study.service.impl;
|
||||
|
||||
import com.xboe.common.OrderCondition;
|
||||
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.module.course.dao.CourseContentDao;
|
||||
@@ -23,6 +22,7 @@ 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,27 +159,6 @@ public class StudyExamServiceImpl implements IStudyExamService{
|
||||
return dao.findList(OrderCondition.desc("id"),FieldFilters.eq("studyId", studyId),FieldFilters.eq("contentId", contentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据课程id或者内容id得到对应的考试记录
|
||||
* 课程id必填,内容id选填
|
||||
* 25.11.20新增
|
||||
*
|
||||
* @param courseId 课程id
|
||||
* @param contentId 内容id
|
||||
* @return 考试记录集合
|
||||
*/
|
||||
@Override
|
||||
public List<StudyExam> getByCourseIdAndContentId(String courseId, String contentId) {
|
||||
if (StringUtils.isEmpty(courseId)) {
|
||||
throw new IllegalArgumentException("课程id不能为空");
|
||||
}
|
||||
IFieldFilter filter = FieldFilters.eq("courseId", courseId);
|
||||
if (StringUtils.isNotEmpty(contentId)) {
|
||||
filter = FieldFilters.and(filter, FieldFilters.eq("contentId", contentId));
|
||||
}
|
||||
return dao.findList(OrderCondition.desc("id"), filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void correctStstus(String courseId) {
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package com.xboe.school.study.service.impl;
|
||||
|
||||
import com.xboe.common.OrderCondition;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.xboe.core.orm.FieldFilters;
|
||||
import com.xboe.core.orm.IFieldFilter;
|
||||
import com.xboe.module.course.dao.CourseContentDao;
|
||||
import com.xboe.school.study.dao.StudyCourseDao;
|
||||
import com.xboe.school.study.dao.StudyCourseItemDao;
|
||||
@@ -10,14 +17,6 @@ import com.xboe.school.study.dao.StudyHomeWorkDao;
|
||||
import com.xboe.school.study.entity.StudyCourseItem;
|
||||
import com.xboe.school.study.entity.StudyHomeWork;
|
||||
import com.xboe.school.study.service.IStudyHomeWorkService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class StudyHomeWorkServiceImpl implements IStudyHomeWorkService{
|
||||
@@ -96,24 +95,4 @@ public class StudyHomeWorkServiceImpl implements IStudyHomeWorkService{
|
||||
return dao.findList(FieldFilters.in("student_name", studentName),FieldFilters.eq("contentId", contentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据课程id得到对应的作业记录
|
||||
* 25.11.20新增
|
||||
*
|
||||
* @param courseId 课程id
|
||||
* @param contentId 内容id
|
||||
* @return 作业记录集合
|
||||
*/
|
||||
@Override
|
||||
public List<StudyHomeWork> getByCourseIdAndContentId(String courseId, String contentId) {
|
||||
if (StringUtils.isEmpty(courseId)) {
|
||||
throw new IllegalArgumentException("课程id不能为空");
|
||||
}
|
||||
IFieldFilter filter = FieldFilters.eq("courseId", courseId);
|
||||
if (StringUtils.isNotEmpty(contentId)) {
|
||||
filter = FieldFilters.and(filter, FieldFilters.eq("contentId", contentId));
|
||||
}
|
||||
return dao.findList(OrderCondition.desc("id"), filter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
package com.xboe.school.study.service.impl;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
import com.xboe.module.course.dao.ThreadLogDao;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.xboe.common.OrderCondition;
|
||||
import com.xboe.common.PageList;
|
||||
import com.xboe.core.orm.FieldFilters;
|
||||
@@ -17,20 +34,8 @@ import com.xboe.school.study.entity.StudyTime;
|
||||
import com.xboe.school.study.service.IStudyService;
|
||||
import com.xboe.standard.enums.BoedxContentType;
|
||||
import com.xboe.system.user.dao.UserDao;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class StudyServiceImpl implements IStudyService{
|
||||
@@ -49,6 +54,8 @@ public class StudyServiceImpl implements IStudyService{
|
||||
@Autowired
|
||||
StringRedisTemplate redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private ThreadLogDao threadLogDao;
|
||||
|
||||
@Override
|
||||
public StudyCourseItem checkHas(String studyId,String contentId) {
|
||||
@@ -79,6 +86,8 @@ public class StudyServiceImpl implements IStudyService{
|
||||
sci.setStudyDuration(0);
|
||||
sci.setCourseId(dto.getCourseId());
|
||||
sci.setCsectionId(dto.getCsectionId());
|
||||
log.info("saveStudyInfo进入埋点");
|
||||
saveThreadLog(dto);
|
||||
}
|
||||
//进度状态
|
||||
if(dto.getProgress()==null) {
|
||||
@@ -220,30 +229,14 @@ public class StudyServiceImpl implements IStudyService{
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源学习情况分页查询,此接口用于管理端处理
|
||||
* 25.11.25修改,修改资源学习情况的查询字段,修改为管理端和教师端均可以查询,并添加部门和工号查询逻辑
|
||||
*
|
||||
* @param ids 资源学习id集合
|
||||
* @param pageIndex 页码
|
||||
* @param pageSize 每页数量
|
||||
* @param courseId 课程id
|
||||
* @param contentId 内容id
|
||||
* @param name 用户名称
|
||||
* @param status 用户学习状态(1-未开始,2-已完成,3-进行中)
|
||||
* @return 资源学习情况分页集合
|
||||
*/
|
||||
@Override
|
||||
public PageList<StudyCourseItem> findItemPage(int pageIndex, int pageSize, List<String> ids, String contentId, String courseId,String name,Integer status) {
|
||||
public PageList<StudyCourseItem> findItemPage(int pageIndex, int pageSize, String contentId, String courseId,String name,Integer status) {
|
||||
QueryBuilder query = QueryBuilder.from(StudyCourseItem.class);
|
||||
query.setPageIndex(pageIndex);
|
||||
query.setPageSize(pageSize);
|
||||
|
||||
OrderCondition oc=OrderCondition.desc("id");
|
||||
query.addOrder(oc);
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
query.addFilter(FieldFilters.in("id",ids));
|
||||
}
|
||||
if(StringUtils.isNotBlank(contentId)) {
|
||||
query.addFilter(FieldFilters.eq("contentId",contentId));
|
||||
}
|
||||
@@ -251,49 +244,76 @@ public class StudyServiceImpl implements IStudyService{
|
||||
query.addFilter(FieldFilters.eq("courseId",courseId));
|
||||
}
|
||||
if(StringUtils.isNotBlank(name)) {
|
||||
query.addFilter(FieldFilters.like("aname", name));
|
||||
query.addFilter(FieldFilters.eq("aname", name));
|
||||
}
|
||||
int pageIndex2 = (pageIndex-1)*10;
|
||||
if(status!=null) {
|
||||
if(status==3) {
|
||||
// 进行中
|
||||
query.addFilter(FieldFilters.eq("status", 2));
|
||||
return scItemDao.findPage(query.builder());
|
||||
}else if(status==2){
|
||||
// 已完成
|
||||
query.addFilter(FieldFilters.eq("status",9));
|
||||
return scItemDao.findPage(query.builder());
|
||||
}else if (status == 1) {
|
||||
// 25.11.27标注:这里是查询没有item联表数据的主表数据,等同于没有学习情况的资源
|
||||
// 25.12.1修改:找技术帮忙查看了前端代码,没有status == 1的调用,此处应查询状态为未开始的数据,重写此处逻辑
|
||||
// 未开始(结合现有数据,这里筛选状态为1及为null的数据)
|
||||
query.addFilter(FieldFilters.or(FieldFilters.eq("status", 1), FieldFilters.isNull("status")));
|
||||
return scItemDao.findPage(query.builder());
|
||||
String sql = "select bsc.id,bsc.course_id,bsc.course_name,bsc.aname,item.content_id,0 as progress,1 as status from boe_study_course bsc " +
|
||||
" left join boe_study_course_item item on bsc.course_id = item.course_id and bsc.id = item.study_id" +
|
||||
" where bsc.course_id = '"+courseId+"' and bsc.aname like '%"+name+"%' and bsc.id not in(" +
|
||||
" select item.study_id from boe_study_course_item item " +
|
||||
" where item.course_id = '" + courseId + "' and item.content_id = '"+ contentId+"' and item.aname like '%"+name+"%' group by item.study_id" +
|
||||
" ) group by bsc.id limit "+ pageIndex2+","+ pageSize+";";
|
||||
|
||||
String sql2 = "select count(*) as total from (select bsc.id,bsc.course_id,bsc.course_name,bsc.aname,item.content_id,0 as progress,1 as status from boe_study_course bsc " +
|
||||
" left join boe_study_course_item item on bsc.course_id = item.course_id and bsc.id = item.study_id" +
|
||||
" where bsc.course_id = '"+courseId+"' and bsc.aname like '%"+name+"%' and bsc.id not in(" +
|
||||
" select item.study_id from boe_study_course_item item " +
|
||||
" where item.course_id = '" + courseId + "' and item.content_id = '"+ contentId+"' and item.aname like '%"+name+"%' group by item.study_id" +
|
||||
" ) group by bsc.id) as total";
|
||||
log.info("资源完成情况未开始sql"+sql);
|
||||
List<Object[]> list = scDao.sqlFindList(sql);
|
||||
log.info("资源完成情况人数"+list);
|
||||
int totalCount = scDao.sqlCount(sql2);
|
||||
List<StudyCourseItem> item = new ArrayList<>();
|
||||
for (Object[] objs : list) {
|
||||
StudyCourseItem sc = new StudyCourseItem();
|
||||
sc.setProgress(Integer.valueOf(objs[5].toString()));
|
||||
sc.setStatus(Integer.valueOf(objs[6].toString()));
|
||||
sc.setAname(objs[3].toString());
|
||||
item.add(sc);
|
||||
}
|
||||
log.info("资源完成情况人员"+item);
|
||||
PageList<StudyCourseItem> pageList = new PageList<>(item);
|
||||
pageList.setCount(totalCount);
|
||||
pageList.setPageSize(pageSize);
|
||||
pageList.setList(item);
|
||||
return pageList;
|
||||
}
|
||||
}
|
||||
// 未传输status的情况,查询所有资源学习情况数据
|
||||
String sql = "select a.id, a.course_id, a.course_name, a.aname, " + "IFNULL(b.finish_time, '0') as finish_time, IFNULL(b.progress, 0) as progress, IFNULL(b.status, 1) as status " + ",b.score,b.item_id,b.aid " + "from (select id, course_id, course_name, aname, 0, 1 from boe_study_course where course_id = '" + courseId + "'" + (StringUtils.isBlank(name) ? "" : "and aname like '%" + name + "%'") + ") a " + "inner join " + "(select bsc.id, bsc.course_id, bsc.course_name, bsc.aname, item.id as item_id,item.finish_time, item.progress, item.status,MAX(item.score) score,item.aid " + "from boe_study_course bsc left join boe_study_course_item item on item.course_id = bsc.course_id and item.study_id = bsc.id " + "where bsc.course_id = '" + courseId + "'" +
|
||||
(StringUtils.isBlank(contentId) ? "" : "and item.content_id = '" + contentId + "'") +
|
||||
(StringUtils.isBlank(name) ? "" : "and item.aname like '%" + name +"%'") + " group by bsc.id) b " +
|
||||
|
||||
String sql = "select a.id, a.course_id, a.course_name, a.aname, " +
|
||||
"IFNULL(b.finish_time, '0') as finish_time, IFNULL(b.progress, 0) as progress, IFNULL(b.status, 1) as status,b.score " +
|
||||
"from (select id, course_id, course_name, aname, 0, 1 from boe_study_course where course_id = '" + courseId + "' and aname like '%"+name+"%') a " +
|
||||
"left join " +
|
||||
"(select bsc.id, bsc.course_id, bsc.course_name, bsc.aname, item.finish_time, item.progress, item.status,MAX(item.score) score " +
|
||||
"from boe_study_course bsc left join boe_study_course_item item on item.course_id = bsc.course_id and item.study_id = bsc.id " +
|
||||
"where bsc.course_id = '" + courseId + "' and item.content_id = '" + contentId + "' and item.aname like '%"+name+"%' group by bsc.id) b " +
|
||||
"on a.course_id = b.course_id and a.id = b.id " +
|
||||
"group by a.id limit "+ pageIndex2+","+ pageSize+";";
|
||||
String sql2 = "select count(*) as total from (select a.id, a.course_id, a.course_name, a.aname, " + "IFNULL(b.finish_time, 0) as finish_time, IFNULL(b.progress, 0) as progress, IFNULL(b.status, 1) as status " + "from (select id, course_id, course_name, aname, 0, 1 from boe_study_course where course_id = '" + courseId + "'" + (StringUtils.isBlank(name) ? "" : "and aname like '%" + name + "%'") + ") a " + "inner join " + "(select bsc.id, bsc.course_id, bsc.course_name, bsc.aname, item.finish_time, item.progress, item.status " + "from boe_study_course bsc left join boe_study_course_item item on item.course_id = bsc.course_id and item.study_id = bsc.id " + "where bsc.course_id = '" + courseId + "'" +
|
||||
(StringUtils.isBlank(contentId) ? "" : "and item.content_id = '" + contentId + "'") +
|
||||
(StringUtils.isBlank(name) ? "" : "and item.aname like '%" + name +"%'") +
|
||||
" group by bsc.id) b " +
|
||||
String sql2 = "select count(*) as total from (select a.id, a.course_id, a.course_name, a.aname, " +
|
||||
"IFNULL(b.finish_time, 0) as finish_time, IFNULL(b.progress, 0) as progress, IFNULL(b.status, 1) as status " +
|
||||
"from (select id, course_id, course_name, aname, 0, 1 from boe_study_course where course_id = '" + courseId + "' and aname like '%"+name+"%') a " +
|
||||
"left join " +
|
||||
"(select bsc.id, bsc.course_id, bsc.course_name, bsc.aname, item.finish_time, item.progress, item.status " +
|
||||
"from boe_study_course bsc left join boe_study_course_item item on item.course_id = bsc.course_id and item.study_id = bsc.id " +
|
||||
"where bsc.course_id = '" + courseId + "' and item.content_id = '" + contentId + "' and item.aname like '%"+name+"%' group by bsc.id) b " +
|
||||
"on a.course_id = b.course_id and a.id = b.id " +
|
||||
"group by a.id) as total";
|
||||
log.info("资源完成情况sql:{}", sql);
|
||||
log.info("数量查询sql:{}", sql2);
|
||||
log.info("资源完成情况全部sql"+sql);
|
||||
List<Object[]> list = scDao.sqlFindList(sql);
|
||||
log.info("资源完成情况人数"+list);
|
||||
int totalCount = scDao.sqlCount(sql2);
|
||||
List<StudyCourseItem> item = new ArrayList<>();
|
||||
for (Object[] objs : list) {
|
||||
StudyCourseItem sc = new StudyCourseItem();
|
||||
// 25.11.27新增,如果有id,添加item表id供后续联表使用
|
||||
if (objs[8] != null) {
|
||||
sc.setId(String.valueOf(objs[8]));
|
||||
}
|
||||
if (!"0".equals(objs[4].toString())) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
LocalDateTime finishTime = LocalDateTime.parse(objs[4].toString(), formatter);
|
||||
@@ -305,12 +325,9 @@ public class StudyServiceImpl implements IStudyService{
|
||||
if(objs[7] != null){
|
||||
sc.setScore(Float.valueOf(objs[7].toString()));
|
||||
}
|
||||
// 25.12.5新增,补全aid查询
|
||||
if (objs[8] != null) {
|
||||
sc.setAid(String.valueOf(objs[8].toString()));
|
||||
}
|
||||
item.add(sc);
|
||||
}
|
||||
log.info("资源完成情况人员"+item);
|
||||
PageList<StudyCourseItem> pageList = new PageList<>(item);
|
||||
pageList.setCount(totalCount);
|
||||
pageList.setPageSize(pageSize);
|
||||
@@ -483,4 +500,52 @@ public class StudyServiceImpl implements IStudyService{
|
||||
scDao.finishCheck(studyId,courseId,cnum,token);
|
||||
}
|
||||
|
||||
private void saveThreadLog(StudyContentDto dto) {
|
||||
try {
|
||||
JSONObject extraData = new JSONObject();
|
||||
extraData.put("studyId", dto.getStudyId());
|
||||
extraData.put("contentId", dto.getContentId());
|
||||
extraData.put("aid", dto.getAid());
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Long creatorId = parseLong(dto.getAid());
|
||||
String creatorName = dto.getAname();
|
||||
String threadName = Thread.currentThread().getName();
|
||||
|
||||
String sql = "INSERT INTO boe_thread_log (system_name,module_name,action_name,level,content,thread_name,extra_data,remark,create_time,create_id,create_name,update_time,update_id,update_name) "
|
||||
+ "VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14)";
|
||||
|
||||
threadLogDao.sqlUpdate(sql,
|
||||
"学习",
|
||||
"学习进度更新",
|
||||
"新增StudyCourseItem",
|
||||
"info",
|
||||
null,
|
||||
threadName,
|
||||
extraData.toJSONString(),
|
||||
null,
|
||||
now,
|
||||
creatorId,
|
||||
creatorName,
|
||||
now,
|
||||
creatorId,
|
||||
creatorName);
|
||||
log.info("saveStudyInfo埋点插入成功");
|
||||
} catch (Exception ex) {
|
||||
log.error("保存线程日志失败 studyId={}, contentId={}, aid={}", dto.getStudyId(), dto.getContentId(), dto.getAid(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Long parseLong(String value) {
|
||||
if(StringUtils.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(value);
|
||||
}catch (NumberFormatException ex){
|
||||
log.warn("无法解析为数字的aid: {}", value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
package com.xboe.school.study.service.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
import com.xboe.module.course.dao.CourseContentDao;
|
||||
import com.xboe.module.course.dto.CourseContentDto;
|
||||
import com.xboe.module.course.entity.CourseContent;
|
||||
import com.xboe.school.study.dao.StudyCourseItemDao;
|
||||
import com.xboe.school.study.entity.StudyCourseItem;
|
||||
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.common.beans.KeyValue;
|
||||
@@ -7,27 +24,16 @@ 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.module.course.dao.CourseContentDao;
|
||||
import com.xboe.module.course.dao.CourseDao;
|
||||
import com.xboe.module.course.dto.CourseStudyDto;
|
||||
import com.xboe.module.course.service.ICourseStudySearch;
|
||||
import com.xboe.school.study.dao.StudyCourseDao;
|
||||
import com.xboe.school.study.dao.StudyCourseItemDao;
|
||||
import com.xboe.school.study.dao.StudySignupDao;
|
||||
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.IStudySignupService;
|
||||
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.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -226,10 +232,6 @@ public class StudySignupServiceImpl implements IStudySignupService{
|
||||
if(StringUtils.isNotBlank(ss.getAid())) {
|
||||
query.addFilter(FieldFilters.eq("aid", ss.getAid()));
|
||||
}
|
||||
// 25.11.26新增:添加根据报名方式查询(1自主报名,2管理代报)
|
||||
if (ss.getSignType() != null) {
|
||||
query.addFilter(FieldFilters.eq("signType", ss.getSignType()));
|
||||
}
|
||||
}
|
||||
return signupDao.findPage(query.builder());
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
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:
|
||||
@@ -117,8 +112,4 @@ aop-log-record:
|
||||
password: admin
|
||||
elasticsearch:
|
||||
host: 192.168.0.253
|
||||
port: 9200
|
||||
|
||||
mysql:
|
||||
schema:
|
||||
user-center-schema: userbasic
|
||||
port: 9200
|
||||
@@ -128,11 +128,7 @@ 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
|
||||
@@ -194,8 +190,4 @@ aop-log-record:
|
||||
password: admin
|
||||
elasticsearch:
|
||||
host: 10.251.129.21
|
||||
port: 9200
|
||||
|
||||
mysql:
|
||||
schema:
|
||||
user-center-schema: user_basic
|
||||
port: 9200
|
||||
@@ -111,6 +111,203 @@ xboe:
|
||||
- "10827857"
|
||||
- "11339772"
|
||||
- "pctest06"
|
||||
# 20251202 新增天使用户
|
||||
- "30103141"
|
||||
- "60001391"
|
||||
- "61001278"
|
||||
- "30101301"
|
||||
- "10444837"
|
||||
- "50102190"
|
||||
- "10745030"
|
||||
- "11417101"
|
||||
- "11305432"
|
||||
- "10103037"
|
||||
- "10035168"
|
||||
- "30118060"
|
||||
- "11490910"
|
||||
- "11402931"
|
||||
- "50102196"
|
||||
- "00004896"
|
||||
- "98050025"
|
||||
- "15014359"
|
||||
- "98000758"
|
||||
- "10111538"
|
||||
- "62000137"
|
||||
- "10621476"
|
||||
- "11698996"
|
||||
- "10626304"
|
||||
- "1215826"
|
||||
- "30101887"
|
||||
- "10111915"
|
||||
- "11456852"
|
||||
- "126458"
|
||||
- "30141438"
|
||||
- "10209179"
|
||||
- "22BT15420"
|
||||
- "21BB2053"
|
||||
- "10449861"
|
||||
- "130325"
|
||||
- "11331818"
|
||||
- "10117022"
|
||||
- "10105891"
|
||||
- "121649"
|
||||
- "110338"
|
||||
- "1217784"
|
||||
- "30105038"
|
||||
- "98000792"
|
||||
- "60001146"
|
||||
- "11698607"
|
||||
- "11493629"
|
||||
- "10164819"
|
||||
- "11463452"
|
||||
- "10412122"
|
||||
- "11677116"
|
||||
- "98000780"
|
||||
- "61004269"
|
||||
- "1218902"
|
||||
- "111038"
|
||||
- "10056775"
|
||||
- "50125311"
|
||||
- "50100445"
|
||||
- "00003320"
|
||||
- "11672602"
|
||||
- "30129421"
|
||||
- "11433296"
|
||||
- "11759796"
|
||||
- "10063656"
|
||||
- "10829939"
|
||||
- "98050190"
|
||||
- "10061076"
|
||||
- "60001460"
|
||||
- "10415155"
|
||||
- "60000626"
|
||||
- "110791"
|
||||
- "60000984"
|
||||
- "62000025"
|
||||
- "11794394"
|
||||
- "11681568"
|
||||
- "00002915"
|
||||
- "1210874"
|
||||
- "132046"
|
||||
- "10157955"
|
||||
- "00004409"
|
||||
- "10773520"
|
||||
- "102403"
|
||||
- "10119108"
|
||||
- "10062300"
|
||||
- "10334899"
|
||||
- "10111689"
|
||||
- "10258267"
|
||||
- "60000327"
|
||||
- "50100096"
|
||||
- "10075741"
|
||||
- "1000477"
|
||||
- "1218405"
|
||||
- "132666"
|
||||
- "10183064"
|
||||
- "50101990"
|
||||
- "120869"
|
||||
- "11291711"
|
||||
- "11670020"
|
||||
- "11321710"
|
||||
- "10855714"
|
||||
- "11331449"
|
||||
- "50108923"
|
||||
- "66001553"
|
||||
- "81011081"
|
||||
- "11098405"
|
||||
- "10158509"
|
||||
- "11327800"
|
||||
- "10065717"
|
||||
- "10897206"
|
||||
- "30135784"
|
||||
- "1200373"
|
||||
- "10048566"
|
||||
- "10059710"
|
||||
- "11834720"
|
||||
- "1200384"
|
||||
- "60000973"
|
||||
- "11282207"
|
||||
- "40865"
|
||||
- "10811920"
|
||||
- "00003324"
|
||||
- "00003937"
|
||||
- "10031853"
|
||||
- "1201730"
|
||||
- "00004615"
|
||||
- "10613607"
|
||||
- "10166435"
|
||||
- "11407507"
|
||||
- "21BB0031"
|
||||
- "00002198"
|
||||
- "30104243"
|
||||
- "10840493"
|
||||
- "10046158"
|
||||
- "132164"
|
||||
- "11257354"
|
||||
- "11753398"
|
||||
- "10230265"
|
||||
- "11293165"
|
||||
- "10114925"
|
||||
- "S638"
|
||||
- "10833174"
|
||||
- "10926203"
|
||||
- "124046"
|
||||
- "201181"
|
||||
- "11319329"
|
||||
- "10884794"
|
||||
- "10331955"
|
||||
- "60000847"
|
||||
- "1411"
|
||||
- "126581"
|
||||
- "00003375"
|
||||
- "132539"
|
||||
- "98050455"
|
||||
- "10053666"
|
||||
- "11697194"
|
||||
- "61002398"
|
||||
- "00002971"
|
||||
- "14157"
|
||||
- "132989"
|
||||
- "50103467"
|
||||
- "37315"
|
||||
- "10088583"
|
||||
- "11048954"
|
||||
- "110202"
|
||||
- "30141433"
|
||||
- "1000079"
|
||||
- "11783149"
|
||||
- "10025448"
|
||||
- "98000579"
|
||||
- "10614158"
|
||||
- "30104381"
|
||||
- "60000122"
|
||||
- "11074875"
|
||||
- "10009047"
|
||||
- "10228087"
|
||||
- "10875722"
|
||||
- "10041401"
|
||||
- "110679"
|
||||
- "11167945"
|
||||
- "11288196"
|
||||
- "00003111"
|
||||
- "11780879"
|
||||
- "10836255"
|
||||
- "10753364"
|
||||
- "50102132"
|
||||
- "10711537"
|
||||
- "15001329"
|
||||
- "11733703"
|
||||
- "10450632"
|
||||
- "98050011"
|
||||
- "10224644"
|
||||
- "120931"
|
||||
- "10743223"
|
||||
- "107873"
|
||||
- "11141942"
|
||||
- "120434"
|
||||
- "126466"
|
||||
- "98050020"
|
||||
alert-email-recipients:
|
||||
- chengmeng@boe.com.cn
|
||||
- liyubing@boe.com.cn
|
||||
@@ -139,16 +336,4 @@ aop-log-record:
|
||||
password: admin
|
||||
elasticsearch:
|
||||
host: 10.251.88.218
|
||||
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
|
||||
|
||||
mysql:
|
||||
schema:
|
||||
user-center-schema: user_basic
|
||||
port: 9200
|
||||
@@ -153,11 +153,7 @@ 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
|
||||
@@ -221,8 +217,4 @@ aop-log-record:
|
||||
host: 10.251.129.25
|
||||
port: 9200
|
||||
user: elastic
|
||||
password: Boe@es123
|
||||
|
||||
mysql:
|
||||
schema:
|
||||
user-center-schema: userbasic
|
||||
password: Boe@es123
|
||||
@@ -31,26 +31,42 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Log file error output -->
|
||||
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<appender name="caseAiChat"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<encoder>
|
||||
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>ERROR</level>
|
||||
</filter>
|
||||
<file>${log.path}/caseAiChat.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/caseAiChat.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<!-- Log file error output -->
|
||||
<!-- <appender name="caseAiChat" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
|
||||
<!-- <file>${log.path}/caseAiChat.log</file>-->
|
||||
<!-- <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">-->
|
||||
<!-- <fileNamePattern>${log.path}/%d{yyyy-MM}/caseAiChat.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>-->
|
||||
<!-- <maxFileSize>50MB</maxFileSize>-->
|
||||
<!-- <maxHistory>30</maxHistory>-->
|
||||
<!-- </rollingPolicy>-->
|
||||
<!-- <encoder>-->
|
||||
<!-- <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>-->
|
||||
<!-- </encoder>-->
|
||||
<!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">-->
|
||||
<!-- <level>ERROR</level>-->
|
||||
<!-- </filter>-->
|
||||
<!-- </appender>-->
|
||||
|
||||
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="info"/>
|
||||
<!-- <appender-ref ref="console"/>-->
|
||||
<!-- <appender-ref ref="error"/> -->
|
||||
</root>
|
||||
|
||||
<logger name="caseAiChatLogger" additivity="false" level="INFO">
|
||||
<appender-ref ref="caseAiChat"/>
|
||||
</logger>
|
||||
</configuration>
|
||||
|
||||
@@ -47,10 +47,26 @@
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="caseAiChat"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<encoder>
|
||||
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<File>${log.path}/caseAiChat.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<FileNamePattern>${log.path}/caseAiChat.%d{yyyy-MM-dd}.log</FileNamePattern>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="debug"/>
|
||||
<appender-ref ref="error"/>
|
||||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
|
||||
<logger name="caseAiChatLogger" additivity="false" level="INFO">
|
||||
<appender-ref ref="caseAiChat"/>
|
||||
</logger>
|
||||
</configuration>
|
||||
|
||||
Reference in New Issue
Block a user