Compare commits

..

6 Commits

Author SHA1 Message Date
DESKTOP-9ERGOBP\任维炳
02bc650c1b 【修改】 1、新增项目启动清除所有的任务ID;2、修改ws send方法参数 2025-10-20 12:58:25 +08:00
DESKTOP-9ERGOBP\任维炳
ad2b0b510e 【修改】 修改学生端倒计时闪烁补充项 2025-10-20 12:25:30 +08:00
DESKTOP-9ERGOBP\任维炳
c708a30443 【修改】 修改考试剩余时间异常闪动问题 2025-10-20 11:02:57 +08:00
hyc
4095ef88f9 Merge pull request '【修改】试卷任务白名单' (#9) from hyc into master
Reviewed-on: #9
2025-10-20 06:22:43 +08:00
huababa1
3889ce1414 【修改】试卷任务白名单 2025-10-20 06:22:20 +08:00
DESKTOP-9ERGOBP\任维炳
981fb9b466 【新增】 1、学生账号导入可以新增角色权限字段;2、教师账号导入新增 2025-10-18 21:47:51 +08:00
27 changed files with 554 additions and 39 deletions

View File

@@ -64,7 +64,7 @@ public class AppCheckController {
// 根据taskId查询下面有多少题型然后查找对应的软件环境在新增
@GetMapping("/getAppCheckListByTaskId/{taskId}")
public CommonResult<Boolean> getAppCheckListByTaskId(@PathVariable("taskId") String taskId){
List<AppCheckDO> appCheckDOList = new ArrayList<>();
List<AppCheckDO> appCheckDOList = appCheckService.getAppList(taskId);
// 根据试卷方案ID查询下面试卷的试题的组成部分
List<EducationPaperScheme> list = educationPaperSchemeService.getInfoDataByTaskId(taskId);
for (EducationPaperScheme educationPaperScheme : list) {
@@ -80,16 +80,19 @@ public class AppCheckController {
// 判断是否在数组中存在
boolean exists = appCheckDOList.stream()
.anyMatch(a -> exams.getRoles().equals(a.getAppName())); // 根据 appName 判断
if (!exists) {
appCheckDOList.add(appCheckDO);
// appCheckDOList.add(appCheckDO);
// 添加
appCheckService.insertAppCheck(appCheckDO);
}
}
}
}
// 添加
for (AppCheckDO appCheckDO : appCheckDOList) {
appCheckService.insertAppCheck(appCheckDO);
}
// for (AppCheckDO appCheckDO : appCheckDOList) {
// appCheckService.insertAppCheck(appCheckDO);
// }
return success(true);
}

View File

@@ -3,13 +3,20 @@ package pc.exam.pp.module.exam.controller.admin.paper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import pc.exam.pp.framework.common.pojo.CommonResult;
import pc.exam.pp.framework.tenant.core.aop.TenantIgnore;
import pc.exam.pp.module.exam.dal.dataobject.EducationPaperParam;
import pc.exam.pp.module.exam.dal.dataobject.ExamWhiteListDO;
import pc.exam.pp.module.exam.dal.dataobject.app.AppCheckDO;
import pc.exam.pp.module.exam.service.paper.IEducationPaperParamService;
import java.util.List;
import static pc.exam.pp.framework.common.pojo.CommonResult.success;
import static pc.exam.pp.module.infra.enums.ErrorCodeConstants.DEMO03_PAPER_SESSION_EXISTS;
/**
@@ -102,5 +109,16 @@ public class EducationPaperParamController {
}
return CommonResult.success("200");
}
@GetMapping("/getAppWhiteList/{taskId}")
@PermitAll
@TenantIgnore
@Operation(summary = "查看白名单列表", description = "查看白名单列表")
public CommonResult<List<String>> getAppWhiteList(@PathVariable("taskId") String taskId){
// 使用传入的IP进行ping查看是否存在连接并返回信号的强度
System.out.println(educationPaperParamService.getAppWhiteList(taskId));
return success(educationPaperParamService.getAppWhiteList(taskId));
}
}

View File

@@ -22,6 +22,7 @@ import pc.exam.pp.module.exam.service.paper.IEducationPaperTaskService;
import static pc.exam.pp.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -72,6 +73,11 @@ public class EducationPaperTaskController {
@GetMapping("/stulist")
public CommonResult<PageResult<Map<String, Object>>> stulist(PaperTaskPageVo educationPaperTask) {
PageResult<EducationPaperTask> pageResult = educationPaperTaskService.selectEducationPaperTaskListByStu(educationPaperTask);
// ✅ 防止 pageResult 或其 list 为空
if (pageResult == null || pageResult.getList() == null) {
PageResult<Map<String, Object>> emptyPage = new PageResult<>(Collections.emptyList(), 0L);
return CommonResult.success(emptyPage);
}
List<Map<String, Object>> newList = pageResult.getList().stream()
.map(task -> {
Map<String, Object> map = BeanUtil.beanToMap(task, false, true);

View File

@@ -0,0 +1,16 @@
package pc.exam.pp.module.exam.controller.admin.paper.dto;
import lombok.Data;
import java.util.List;
@Data
public class ExamWhiteListSaveReqDTO {
private Long examId;
private List<WhiteApp> whiteList;
@Data
public static class WhiteApp {
private String name;
}
}

View File

@@ -1,14 +1,14 @@
package pc.exam.pp.module.exam.dal.dataobject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.sql.Time;
import java.util.List;
/**
* 通用参数对象 education_paper_param
@@ -93,5 +93,7 @@ public class EducationPaperParam
private String isScoreDetail;
// 是否删除考生文件
private String isDelete;
// 白名单
@TableField(exist = false)
private List<ExamWhiteListDO> whiteList;
}

View File

@@ -0,0 +1,19 @@
package pc.exam.pp.module.exam.dal.dataobject;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@TableName("exam_white_list")
@Data
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExamWhiteListDO {
@TableId
private Long id;
private String taskId;
private String name;
}

View File

@@ -0,0 +1,19 @@
package pc.exam.pp.module.exam.dal.mysql.paper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import pc.exam.pp.framework.mybatis.core.mapper.BaseMapperX;
import pc.exam.pp.module.exam.dal.dataobject.EducationPaperTask;
import pc.exam.pp.module.exam.dal.dataobject.ExamWhiteListDO;
import java.util.List;
@Mapper
public interface ExamWhiteListMapper extends BaseMapperX<ExamWhiteListDO> {
List<ExamWhiteListDO> selectByTaskId(String taskId);
void deleteByTaskId(String taskId);
List<String> selectNameByTaskId(String taskId);
}

View File

@@ -480,7 +480,13 @@ public class MonitorServiceImpl implements MonitorService {
// redis_key
String key = "userCache:" + taskId + ":" + stuId;
// 获取缓存数据
MonitorDO info = JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(key), MonitorDO.class);
// MonitorDO info = JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(key), MonitorDO.class);
MonitorDO info =monitorMapper.selectOne(
new QueryWrapper<MonitorDO>()
.eq("task_id", taskId)
.eq("stu_id", stuId)
);
// 如果考试状态存在,更新考试状态
if (status != null) {
if (info != null) {

View File

@@ -3,10 +3,13 @@ package pc.exam.pp.module.exam.service.paper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import pc.exam.pp.module.exam.dal.dataobject.ExamWhiteListDO;
import pc.exam.pp.module.exam.dal.dataobject.EducationPaperParam;
import pc.exam.pp.module.exam.dal.mysql.paper.EducationPaperParamMapper;
import pc.exam.pp.module.exam.dal.mysql.paper.EducationPaperTaskMapper;
import pc.exam.pp.module.exam.dal.mysql.paper.ExamWhiteListMapper;
import java.util.ArrayList;
import java.util.List;
/**
@@ -22,6 +25,9 @@ public class EducationPaperParamServiceImpl implements IEducationPaperParamServi
private EducationPaperParamMapper educationPaperParamMapper;
@Autowired
private EducationPaperTaskMapper educationPaperTaskMapper;
@Autowired
private ExamWhiteListMapper examWhiteListMapper;
/**
* 查询通用参数
*
@@ -55,6 +61,16 @@ public class EducationPaperParamServiceImpl implements IEducationPaperParamServi
@Override
public int insertEducationPaperParam(EducationPaperParam educationPaperParam)
{
List<ExamWhiteListDO> whiteList = educationPaperParam.getWhiteList();
String taskId = educationPaperParam.getTaskId();
// ✅ 统一赋值
if (whiteList != null && !whiteList.isEmpty()) {
for (ExamWhiteListDO examWhiteListDO : whiteList) {
examWhiteListDO.setTaskId(taskId);
examWhiteListDO.setName(examWhiteListDO.getName());
}
examWhiteListMapper.insertBatch(whiteList);
}
return educationPaperParamMapper.insertEducationPaperParam(educationPaperParam);
}
@@ -67,6 +83,20 @@ public class EducationPaperParamServiceImpl implements IEducationPaperParamServi
@Override
public int updateEducationPaperParam(EducationPaperParam educationPaperParam)
{
List<ExamWhiteListDO> whiteList = educationPaperParam.getWhiteList();
String taskId = educationPaperParam.getTaskId();
if (taskId!=null){
//删除旧白名单
examWhiteListMapper.deleteByTaskId(taskId);
//插入新白名单
if (whiteList != null && !whiteList.isEmpty()) {
for (ExamWhiteListDO examWhiteListDO : whiteList) {
examWhiteListDO.setTaskId(taskId);
examWhiteListDO.setName(examWhiteListDO.getName());
}
examWhiteListMapper.insertBatch(whiteList);
}
}
return educationPaperParamMapper.updateEducationPaperParam(educationPaperParam);
}
@@ -96,7 +126,19 @@ public class EducationPaperParamServiceImpl implements IEducationPaperParamServi
@Override
public EducationPaperParam selectEducationPaperParamByTaskId(String taskId) {
return educationPaperParamMapper.selectEducationPaperParamByTaskId(taskId);
List<ExamWhiteListDO> whiteList = examWhiteListMapper.selectByTaskId(taskId);
EducationPaperParam educationPaperParam = educationPaperParamMapper.selectEducationPaperParamByTaskId(taskId);
if (whiteList != null && !whiteList.isEmpty()) {
educationPaperParam.setWhiteList(whiteList);
}
return educationPaperParam;
}
@Override
public List<String> getAppWhiteList(String taskId) {
return examWhiteListMapper.selectNameByTaskId(taskId);
}
}

View File

@@ -81,6 +81,8 @@ public class EducationPaperTaskServiceImpl implements IEducationPaperTaskService
private ExamQuestionMapper examQuestionMapper;
@Resource
AppCheckMapper appCheckMapper;
@Autowired
private ExamWhiteListMapper examWhiteListMapper;
/**
* 查询试卷任务
@@ -190,6 +192,15 @@ public class EducationPaperTaskServiceImpl implements IEducationPaperTaskService
} else {
educationPaperTask.setIsOne("1");
}
List<ExamWhiteListDO> whiteList = new ArrayList<>();
ExamWhiteListDO examWhiteListDO=new ExamWhiteListDO();
examWhiteListDO.setTaskId(uuid);
examWhiteListDO.setName("ExamStudent");
whiteList.add(examWhiteListDO);
examWhiteListMapper.insertBatch(whiteList);
// 新增任务参数
educationPaperParamMapper.insertEducationPaperParam(educationPaperParam);
// 新增任务
@@ -539,6 +550,14 @@ public class EducationPaperTaskServiceImpl implements IEducationPaperTaskService
educationPaperTask.setTaskName(educationPaperTask.getTaskName() + timeString);
educationPaperTask.setCreateTime(now);
educationPaperTask.setIsTemplate(1);
//白名单
List<ExamWhiteListDO> whiteList = examWhiteListMapper.selectByTaskId(taskId);
if (whiteList != null && !whiteList.isEmpty()) {
for (ExamWhiteListDO examWhiteListDO : whiteList) {
examWhiteListDO.setTaskId(newtaskId);
}
examWhiteListMapper.insertBatch(whiteList);
}
educationPaperTaskMapper.insertEducationPaperTask(educationPaperTask);
if (options.contains("1")) {

View File

@@ -0,0 +1,7 @@
package pc.exam.pp.module.exam.service.paper;
public interface ExamWhiteListService {
}

View File

@@ -0,0 +1,12 @@
package pc.exam.pp.module.exam.service.paper;
import jakarta.annotation.Resource;
import pc.exam.pp.module.exam.dal.mysql.paper.ExamWhiteListMapper;
public class ExamWhiteListServiceImpl implements ExamWhiteListService {
@Resource
ExamWhiteListMapper examWhiteListMapper;
}

View File

@@ -2,6 +2,7 @@ package pc.exam.pp.module.exam.service.paper;
import pc.exam.pp.module.exam.dal.dataobject.EducationPaperParam;
import pc.exam.pp.module.exam.dal.dataobject.ExamWhiteListDO;
import java.util.List;
@@ -63,5 +64,6 @@ public interface IEducationPaperParamService
EducationPaperParam selectEducationPaperParamByTaskId(String taskId);
List<String> getAppWhiteList(String taskId);
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="pc.exam.pp.module.exam.dal.mysql.paper.ExamWhiteListMapper">
<resultMap type="ExamWhiteListDO" id="ExamWhiteListDOResult">
<result property="id" column="id"/>
<result property="taskId" column="task_id"/>
<result property="name" column="name"/>
</resultMap>
<insert id="insertBatch">
INSERT INTO exam_white_list (name,task_id)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.taskId})
</foreach>
</insert>
<delete id="deleteByTaskId">
DELETE FROM exam_white_list WHERE task_id = #{taskId}
</delete>
<select id="selectByTaskId" resultMap="ExamWhiteListDOResult">
select name from exam_white_list where task_id =#{taskId}
</select>
<select id="selectNameByTaskId" resultType="java.lang.String">
select name from exam_white_list where task_id =#{taskId}
</select>
</mapper>

View File

@@ -157,11 +157,9 @@ public class AutoToolsController {
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
// 获取登录用户
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
// userId 是否存在
String wsKey = taskManager.getTaskById(String.valueOf(loginUser.getId()));
if (wsKey != null) {
// 停止任务
taskManager.stopTask(wsKey);
// 通过ID 直接停止
if (loginUser != null) {
taskManager.stopTaskByUserId(String.valueOf(loginUser.getId()));
}
int startTime = 0;
// 查找对应的task
@@ -213,7 +211,9 @@ public class AutoToolsController {
// 返回数据-网络状态
stuTheExamInfo.setNetwork("");
// 创建对应的线程池
taskManager.startTask(stuInTheExam, stuTheExamInfo, token, countdown, new AtomicInteger(0), loginUser.getId() + "_" + stuInTheExam.getTaskId() + "_" + stuInTheExam.getPaperId());
if (loginUser != null) {
taskManager.startTask(loginUser.getId(), stuInTheExam, stuTheExamInfo, token, countdown, new AtomicInteger(0), loginUser.getId() + "_" + stuInTheExam.getTaskId() + "_" + stuInTheExam.getPaperId());
}
return CommonResult.success(token);
}
return CommonResult.success("未登录");
@@ -261,8 +261,10 @@ public class AutoToolsController {
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
ExamRestartPaperVo examRestartPaperVo = new ExamRestartPaperVo();
// 如果继续考试需要关闭之前的重新开始ws
String taskManagerId = loginUser.getId() + "_" + stuInTheExam.getTaskId() + "_" + stuInTheExam.getPaperId();
taskManager.stopTask(taskManagerId);
// 通过ID 直接停止
if (loginUser != null) {
taskManager.stopTaskByUserId(String.valueOf(loginUser.getId()));
}
// 获取试卷详情
ExamPaperVo examPaperVo = educationPaperQuService.selectPaperQuListByPaperId(stuInTheExam.getPaperId());
// 获取剩余的时间
@@ -319,7 +321,7 @@ public class AutoToolsController {
stuTheExamInfo.setEndStatus(1);
// 返回数据-网络状态
stuTheExamInfo.setNetwork("");
taskManager.startTask(stuInTheExam, stuTheExamInfo, token, countdown, new AtomicInteger(0), loginUser.getId() + "_" + stuInTheExam.getTaskId() + "_" + stuInTheExam.getPaperId());
taskManager.startTask(loginUser.getId(), stuInTheExam, stuTheExamInfo, token, countdown, new AtomicInteger(0), loginUser.getId() + "_" + stuInTheExam.getTaskId() + "_" + stuInTheExam.getPaperId());
// 将数组的值进行复制
BeanUtils.copyProperties(examPaperVo, examRestartPaperVo);
examRestartPaperVo.setWsToken(token);
@@ -353,6 +355,12 @@ public class AutoToolsController {
userId += "_" + taskId + "_" + paperId;
// 删除对应的线程池
taskManager.stopTask(userId);
// 为了确保停止成功,笨办法在请求另一个停止接口
try {
taskManager.stopTaskByUserId(String.valueOf(userId));
} catch (Exception e) {
e.printStackTrace();
}
return CommonResult.success(true);
}
@@ -394,6 +402,8 @@ public class AutoToolsController {
stringRedisTemplate.opsForValue().set(key, JsonUtils.toJsonString(info));
monitorMapper.updateById(info);
}
// 删除对应任务
taskManager.stopTaskByUserId(userId);
return CommonResult.success(true);
}
@@ -404,13 +414,9 @@ public class AutoToolsController {
* @return true
*/
@GetMapping("/stopExamForAdmin")
public CommonResult<Boolean> stopExamForAdmin(@RequestParam("userId") String userId, @RequestParam("taskId") String taskId, @RequestParam("paperNum") String paperNum) {
String paperId = educationPaperService.selectPaperByPaperNum(paperNum);
// 将userId 拼接 taskId 作为key
// 防止不同试卷ID冲突
userId += "_" + taskId + "_" + paperId;
public CommonResult<Boolean> stopExamForAdmin(@RequestParam("userId") String userId) {
// 删除对应的线程池
taskManager.stopTask(userId);
taskManager.stopTaskByUserId(userId);
return CommonResult.success(true);
}
@@ -495,7 +501,7 @@ public class AutoToolsController {
stuTheExamInfo.setEndStatus(1);
// 返回数据-网络状态
stuTheExamInfo.setNetwork("");
taskManager.startTask(stuInTheExam, stuTheExamInfo, token, countdown, new AtomicInteger(0), stuId + "_" + stuInTheExam.getTaskId() + "_" + paperId);
taskManager.startTask(Long.valueOf(stuId), stuInTheExam, stuTheExamInfo, token, countdown, new AtomicInteger(0), stuId + "_" + stuInTheExam.getTaskId() + "_" + paperId);
// 将数组的值进行复制
BeanUtils.copyProperties(examPaperVo, examRestartPaperVo);
examRestartPaperVo.setWsToken(token);

View File

@@ -29,7 +29,7 @@ public class TaskManager {
private final Map<String, ScheduledFuture<?>> tasks = new ConcurrentHashMap<>();
/** 开始任务(每秒执行一次) */
public void startTask(StuInTheExam stuInTheExam, StuTheExamInfo stuTheExamInfo, String token, AtomicInteger countdown, AtomicInteger counter, String userId) {
public void startTask(Long userIds, StuInTheExam stuInTheExam, StuTheExamInfo stuTheExamInfo, String token, AtomicInteger countdown, AtomicInteger counter, String userId) {
// 判断 token 的线程是否存在,存在则不进行任何动作
if (tasks.containsKey(userId)) {
log.info("任务 {} 已存在,未重复启动", userId);
@@ -59,7 +59,8 @@ public class TaskManager {
}
}
stuTheExamInfo.setTime(formatLongDuration(remaining));
webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), "InTheExam", stuTheExamInfo);
webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userIds, "InTheExam", stuTheExamInfo);
// webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), "InTheExam", stuTheExamInfo);
});
return scheduler.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
});
@@ -93,6 +94,18 @@ public class TaskManager {
.orElse(null);
}
/**通过ID结束任务*/
public void stopTaskByUserId(String userId) {
String taskId = tasks.keySet().stream()
.filter(key -> key.startsWith(userId + "_"))
.findFirst()
.orElse(null);
if (taskId != null) {
stopTask(taskId);
}
}
/** 全部停止(可选) */
public void stopAll() {
tasks.forEach((k, f) -> f.cancel(false));

View File

@@ -118,7 +118,7 @@ public class CellIng {
String formula = cell.getCellFormula();
// 转为小写再比较
if (formula.toLowerCase().contains(keyWords.toLowerCase())) {
return formula; // 包含关键字时返回公式内容
return keyWords; // 包含关键字时返回公式内容
} else {
return ""; // 不包含关键字
}

View File

@@ -231,13 +231,31 @@ public class UserController {
public void importTemplateStu(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<UserStuImportExcelVO> list = Arrays.asList(
UserStuImportExcelVO.builder().userType("学生").username("xueshen").password("xxxxxxx").classId("三年二班").schoolName("xx学校").mobile("xxxxxxxx")
.nickname("xxxx").sfz("xxxxxxxxxxxxx").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build()
UserStuImportExcelVO.builder().userType("学生").username("xueshen").password("不填写默认学生账号为登录用户名,直接为空即可").classId("三年二班").schoolName("xx学校").mobile("xxxxxxxx")
.nickname("xxxx").sfz("xxxxxxxxxxxxx").permission("系统中角色管理中名称").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build()
);
// 输出
ExcelUtils.write(response, "学生导入模板.xls", "学生列表", UserStuImportExcelVO.class, list);
}
@GetMapping("/get-import-template-teacher")
@Operation(summary = "获得导入教师模板")
public void importTemplateTeacher(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<UserTeacherImportExcelVO> list = Arrays.asList(
UserTeacherImportExcelVO.builder().userType("教师").username("jiaoshi").password("不填写默认教师账号为登录用户名,直接为空即可").classId("三年二班,三年三班,三年四班").mobile("xxxxxxxx").permission("系统中角色管理中名称")
.nickname("xxxx").professionalAuthority("[\n" +
" \"计算机专业\",\n" +
" [\n" +
" [\"Mysql\", [\"程序设计\"]],\n" +
" [\"C语言\", [\"编程题\"]]\n" +
" ]\n" +
"]").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build()
);
// 输出
ExcelUtils.write(response, "教师导入模板.xls", "教师列表", UserTeacherImportExcelVO.class, list);
}
@PostMapping("/import")
@Operation(summary = "导入用户")
@Parameters({
@@ -263,6 +281,18 @@ public class UserController {
return success(userService.importUserStuList(list, updateSupport));
}
@PostMapping("/importTeacher")
@Operation(summary = "导入教师")
@Parameters({
@Parameter(name = "file", description = "Excel 文件", required = true),
@Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
})
public CommonResult<UserImportRespVO> importTeacherExcel(@RequestParam("file") MultipartFile file,
@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
List<UserTeacherImportExcelVO> list = ExcelUtils.read(file, UserTeacherImportExcelVO.class);
return success(userService.importUserTeacherList(list, updateSupport));
}
@GetMapping("/getSpecialtListByUser")
@Operation(summary = "获取专业信息,判断用户类型")

View File

@@ -19,19 +19,19 @@ import pc.exam.pp.module.system.enums.DictTypeConstants;
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false避免用户导入有问题
public class UserStuImportExcelVO {
@ExcelProperty("属性学生*")
@ExcelProperty("*属性(学生)")
private String userType;
@ExcelProperty("学生账号*")
@ExcelProperty("*学生账号")
private String username;
@ExcelProperty("密码")
private String password;
@ExcelProperty("姓名*")
@ExcelProperty("*姓名")
private String nickname;
@ExcelProperty("班级*")
@ExcelProperty("*班级")
private String classId;
@ExcelProperty("学校名称")
@@ -43,11 +43,14 @@ public class UserStuImportExcelVO {
@ExcelProperty("身份证")
private String sfz;
@ExcelProperty(value = "用户性别", converter = DictConvert.class)
@ExcelProperty("*学生权限名称")
private String permission;
@ExcelProperty(value = "性别", converter = DictConvert.class)
@DictFormat(DictTypeConstants.USER_SEX)
private Integer sex;
@ExcelProperty(value = "账号状态*", converter = DictConvert.class)
@ExcelProperty(value = "*账号状态(开启/关闭)", converter = DictConvert.class)
@DictFormat(DictTypeConstants.COMMON_STATUS)
private Integer status;

View File

@@ -0,0 +1,54 @@
package pc.exam.pp.module.system.controller.admin.user.vo.user;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import pc.exam.pp.framework.excel.core.annotations.DictFormat;
import pc.exam.pp.framework.excel.core.convert.DictConvert;
import pc.exam.pp.module.system.enums.DictTypeConstants;
/**
* 用户 Excel 导入 VO
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false避免用户导入有问题
public class UserTeacherImportExcelVO {
@ExcelProperty("*属性(教师)")
private String userType;
@ExcelProperty("*教师账号")
private String username;
@ExcelProperty("密码")
private String password;
@ExcelProperty("*姓名")
private String nickname;
@ExcelProperty("*班级列表")
private String classId;
@ExcelProperty("手机号码")
private String mobile;
@ExcelProperty("*教师权限名称")
private String permission;
@ExcelProperty("*教师专业权限列表")
private String professionalAuthority;
@ExcelProperty(value = "性别", converter = DictConvert.class)
@DictFormat(DictTypeConstants.USER_SEX)
private Integer sex;
@ExcelProperty(value = "*账号状态(开启/关闭)", converter = DictConvert.class)
@DictFormat(DictTypeConstants.COMMON_STATUS)
private Integer status;
}

View File

@@ -1,6 +1,7 @@
package pc.exam.pp.module.system.dal.mysql.permission;
import pc.exam.pp.framework.mybatis.core.mapper.BaseMapperX;
import pc.exam.pp.module.system.dal.dataobject.permission.RoleDO;
import pc.exam.pp.module.system.dal.dataobject.permission.UserRoleDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;

View File

@@ -1,6 +1,7 @@
package pc.exam.pp.module.system.service.permission;
import pc.exam.pp.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import pc.exam.pp.module.system.dal.dataobject.permission.RoleDO;
import java.util.Collection;
import java.util.Set;
@@ -161,4 +162,6 @@ public interface PermissionService {
*/
DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
RoleDO getRoleByName(String name);
}

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import pc.exam.pp.framework.common.enums.CommonStatusEnum;
import pc.exam.pp.framework.common.util.collection.CollectionUtils;
import pc.exam.pp.framework.datapermission.core.annotation.DataPermission;
@@ -13,6 +14,7 @@ import pc.exam.pp.module.system.dal.dataobject.permission.RoleDO;
import pc.exam.pp.module.system.dal.dataobject.permission.RoleMenuDO;
import pc.exam.pp.module.system.dal.dataobject.permission.UserRoleDO;
import pc.exam.pp.module.system.dal.dataobject.user.TeacherSpecialtyDO;
import pc.exam.pp.module.system.dal.mysql.permission.RoleMapper;
import pc.exam.pp.module.system.dal.mysql.permission.RoleMenuMapper;
import pc.exam.pp.module.system.dal.mysql.permission.UserRoleMapper;
import pc.exam.pp.module.system.dal.mysql.user.TeacherSpecialtyMapper;
@@ -62,6 +64,8 @@ public class PermissionServiceImpl implements PermissionService {
private DeptService deptService;
@Resource
private AdminUserService userService;
@Resource
private RoleMapper roleMapper;
@Override
public boolean hasAnyPermissions(Long userId, String... permissions) {
@@ -348,6 +352,11 @@ public class PermissionServiceImpl implements PermissionService {
return result;
}
@Override
public RoleDO getRoleByName(String name) {
return roleMapper.selectByName(name);
}
/**
* 获得自身的代理对象,解决 AOP 生效问题
*

View File

@@ -251,6 +251,15 @@ public interface AdminUserService {
*/
UserImportRespVO importUserStuList(List<UserStuImportExcelVO> importUsers, boolean isUpdateSupport);
/**
* 批量导入教师
*
* @param importUsers 导入教师列表
* @param isUpdateSupport 是否支持更新
* @return 导入结果
*/
UserImportRespVO importUserTeacherList(List<UserTeacherImportExcelVO> importUsers, boolean isUpdateSupport);
/**
* 获得指定状态的用户们
*

View File

@@ -4,6 +4,8 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import pc.exam.pp.framework.common.enums.CommonStatusEnum;
import pc.exam.pp.framework.common.exception.ServiceException;
import pc.exam.pp.framework.common.pojo.PageResult;
@@ -29,11 +31,14 @@ import pc.exam.pp.module.system.controller.admin.user.vo.profile.UserProfileUpda
import pc.exam.pp.module.system.controller.admin.user.vo.user.*;
import pc.exam.pp.module.system.dal.dataobject.dept.DeptDO;
import pc.exam.pp.module.system.dal.dataobject.dept.UserPostDO;
import pc.exam.pp.module.system.dal.dataobject.permission.RoleDO;
import pc.exam.pp.module.system.dal.dataobject.permission.UserRoleDO;
import pc.exam.pp.module.system.dal.dataobject.tenant.TenantSpcialtyDO;
import pc.exam.pp.module.system.dal.dataobject.user.AdminUserDO;
import pc.exam.pp.module.system.dal.dataobject.user.TeacherClassDO;
import pc.exam.pp.module.system.dal.dataobject.user.TeacherSpecialtyDO;
import pc.exam.pp.module.system.dal.mysql.dept.UserPostMapper;
import pc.exam.pp.module.system.dal.mysql.permission.UserRoleMapper;
import pc.exam.pp.module.system.dal.mysql.tenant.TenantSpecialtyMapper;
import pc.exam.pp.module.system.dal.mysql.user.AdminUserMapper;
import pc.exam.pp.module.system.dal.mysql.user.TeacherClassMapper;
@@ -57,6 +62,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static pc.exam.pp.framework.common.exception.util.ServiceExceptionUtil.exception;
import static pc.exam.pp.framework.common.util.collection.CollectionUtils.*;
@@ -105,6 +111,8 @@ public class AdminUserServiceImpl implements AdminUserService {
private ExamSpecialtyMapper examSpecialtyMapper;
@Resource
TenantSpecialtyMapper tenantSpecialtyMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -753,6 +761,8 @@ public class AdminUserServiceImpl implements AdminUserService {
UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
.updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
importUsers.forEach(importUser -> {
// 权限名称
String permissionName = importUser.getPermission();
String password = "";
// 判断上传文件是否存在密码,如果存在密码,则使用密码,不存在密码,则使用用户名
if (importUser.getPassword() == null) {
@@ -767,6 +777,7 @@ public class AdminUserServiceImpl implements AdminUserService {
respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
return;
}
// 判断角色权限
// 2.1.2 校验,判断是否有不符合的原因
// try {
// validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
@@ -791,8 +802,17 @@ public class AdminUserServiceImpl implements AdminUserService {
// 2.2.1 判断如果不存在,在进行插入
AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
if (existUser == null) {
userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
.setPassword(encodePassword(password)).setPostIds(new HashSet<>())); // 设置默认密码(如果没有密码填写username)及空岗位编号数组
AdminUserDO newUser = BeanUtils.toBean(importUser, AdminUserDO.class)
.setPassword(encodePassword(password)).setPostIds(new HashSet<>());
userMapper.insert(newUser); // 设置默认密码(如果没有密码填写username)及空岗位编号数组
// 获取权限ID
RoleDO roleDO = permissionService.getRoleByName(permissionName);
// 更新用户权限关联表
UserRoleDO userRoleDO = new UserRoleDO();
userRoleDO.setUserId(newUser.getId());
userRoleDO.setRoleId(roleDO.getId());
userRoleMapper.insert(userRoleDO);
// TODO
respVO.getCreateUsernames().add(importUser.getUsername());
return;
}
@@ -804,11 +824,150 @@ public class AdminUserServiceImpl implements AdminUserService {
AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
updateUser.setId(existUser.getId());
userMapper.updateById(updateUser);
userRoleMapper.selectListByUserId(updateUser.getId()).forEach(role -> {
// 获取权限ID
RoleDO roleDO = permissionService.getRoleByName(permissionName);
// 更新用户权限关联表
role.setUserId(updateUser.getId());
role.setRoleId(roleDO.getId());
userRoleMapper.updateById(role);
});
respVO.getUpdateUsernames().add(importUser.getUsername());
});
return respVO;
}
@Override
public UserImportRespVO importUserTeacherList(List<UserTeacherImportExcelVO> importUsers, boolean isUpdateSupport) {
// 1.1 参数校验
if (CollUtil.isEmpty(importUsers)) {
throw exception(USER_IMPORT_LIST_IS_EMPTY);
}
// 1.2 初始化密码不能为空
String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY);
if (StrUtil.isEmpty(initPassword)) {
throw exception(USER_IMPORT_INIT_PASSWORD);
}
// 2. 遍历,逐个创建 or 更新
UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
.updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
importUsers.forEach(importUser -> {
String pro = importUser.getProfessionalAuthority();
ObjectMapper mapper = new ObjectMapper();
// 将字符串转换为 List 对象
// 权限名称
String permissionName = importUser.getPermission();
String password = "";
// 判断上传文件是否存在密码,如果存在密码,则使用密码,不存在密码,则使用用户名
if (importUser.getPassword() == null) {
password = importUser.getUsername();
} else {
password = importUser.getPassword();
}
// 2.1.1 校验字段是否符合要求
try {
ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(password));
} catch (ConstraintViolationException ex) {
respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
return;
}
// 判断角色权限
// 2.1.2 校验,判断是否有不符合的原因
// try {
// validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
// importUser.getDeptId(), null);
// } catch (ServiceException ex) {
// respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
// return;
// }
// 教师 to 1
importUser.setUserType("1");
// 1.3 导入的报警是必须存在的,如果不存在不允许导入
String[] classNames = importUser.getClassId().split(",");
Set<Long> classIds = new HashSet<>();
boolean flagClassIds = true;
for (String className : classNames) {
// 通过名称查询班级ID
ClassDO classDO = classMapper.getClassNameOne(className);
if (classDO != null) {
classIds.add(classDO.getId());
} else {
flagClassIds = false;
respVO.getFailureUsernames().put(importUser.getUsername(), className + ":班级不存在!");
}
}
// 2.2.1 判断如果不存在,在进行插入
AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
if (existUser == null) {
importUser.setClassId(null);
AdminUserDO newUser = BeanUtils.toBean(importUser, AdminUserDO.class)
.setPassword(encodePassword(password)).setPostIds(new HashSet<>());
if (flagClassIds) {
newUser.setClassIds(classIds);
}
userMapper.insert(newUser); // 设置默认密码(如果没有密码填写username)及空岗位编号数组
try {
List<Object> data = mapper.readValue(pro, List.class);
// 提取数据
// 使用流式处理获取所有字段
List<String> allFields = flattenData(data);
System.out.println("所有字段 (" + allFields.size() + " 个):");
for (String field : allFields) {
TeacherSpecialtyDO teacherSpecialtyDO = new TeacherSpecialtyDO();
teacherSpecialtyDO.setUserId(newUser.getId());
teacherSpecialtyDO.setSpecialtyId(examSpecialtyMapper.selectExamSpecialtyBySpNameOne(field).getSpId());
teacherSpecialtyMapper.insert(teacherSpecialtyDO);
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
// 获取权限ID
RoleDO roleDO = permissionService.getRoleByName(permissionName);
// 更新用户权限关联表
UserRoleDO userRoleDO = new UserRoleDO();
userRoleDO.setUserId(newUser.getId());
userRoleDO.setRoleId(roleDO.getId());
userRoleMapper.insert(userRoleDO);
respVO.getCreateUsernames().add(importUser.getUsername());
return;
}
// 2.2.2 如果存在,判断是否允许更新
if (!isUpdateSupport) {
respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());
return;
}
AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
updateUser.setId(existUser.getId());
updateUser.setClassIds(classIds);
userMapper.updateById(updateUser);
userRoleMapper.selectListByUserId(updateUser.getId()).forEach(role -> {
// 获取权限ID
RoleDO roleDO = permissionService.getRoleByName(permissionName);
// 更新用户权限关联表
role.setUserId(updateUser.getId());
role.setRoleId(roleDO.getId());
userRoleMapper.updateById(role);
});
respVO.getUpdateUsernames().add(importUser.getUsername());
});
return respVO;
}
/**
* 扁平化数据结构,获取所有字符串
*/
public static List<String> flattenData(Object obj) {
if (obj instanceof String) {
return Collections.singletonList((String) obj);
} else if (obj instanceof List) {
return ((List<?>) obj).stream()
.flatMap(item -> flattenData(item).stream())
.collect(Collectors.toList());
}
return Collections.emptyList();
}
@Override
public List<AdminUserDO> getUserListByStatus(Integer status) {

View File

@@ -0,0 +1,24 @@
package pc.exam.pp.server.config;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import pc.exam.pp.module.judgement.service.TaskManager;
@Slf4j
@Component
public class TaskToStop {
@Resource
TaskManager taskManager;
@PostConstruct
public void init() {
// 清除所有的任务
taskManager.stopAll();
log.info("✅ 已经清除所有任务ID");
}
}

View File

@@ -293,6 +293,7 @@ exam:
- /jmreport/* # 积木报表,无法携带租户编号
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号
- /admin-api/exam/app/getAppCheckList/* # 学生端环境监测
- /admin-api/exam/param/getAppWhiteList/* # 学生端环境监测
- /admin-api/system/auth/login_config # 学生端判断学生的登录方式
- /admin-api/system/auth/refreshLogout # 登出用户
ignore-tables:
@@ -303,6 +304,7 @@ exam:
- exam_knowledge_points
- exam_specialty
- exam_app_check
- exam_white_list
- system_tenant
- system_tenant_package
- system_dict_data