崔智尧分支提交

zhangli_branch
zl 9 months ago
parent c63419e015
commit 07f12faadc

@ -1,18 +1,15 @@
// 定义包路径,用于存放系统基础功能相关的类
package com.yf.exam.ability; package com.yf.exam.ability;
/** /**
* *
* 使访
* @author bool * @author bool
*/ */
public class Constant { public class Constant {
/** /**
* *
* URL
* : /upload/file/example.jpg
* URL便访
*/ */
public static final String FILE_PREFIX = "/upload/file/"; public static final String FILE_PREFIX = "/upload/file/";
} }

@ -1,19 +1,13 @@
// 定义包路径,用于存放任务分组相关的枚举类
package com.yf.exam.ability.job.enums; package com.yf.exam.ability.job.enums;
/** /**
* *
*
*
*
* @author van * @author van
*/ */
public interface JobGroup { public interface JobGroup {
/** /**
* *
*
* 便
*/ */
String SYSTEM = "system"; // 系统任务组的标识符 String SYSTEM = "system";
} }

@ -1,19 +1,14 @@
// 定义包路径,用于存放任务前缀相关的枚举类
package com.yf.exam.ability.job.enums; package com.yf.exam.ability.job.enums;
/** /**
* *
*
*
*
* @author bool * @author bool
*/ */
public interface JobPrefix { public interface JobPrefix {
/** /**
* *
* 便
* break_exam_12345 ID12345
*/ */
String BREAK_EXAM = "break_exam_"; // 强制交卷任务的前缀标识符 String BREAK_EXAM = "break_exam_";
} }

@ -1,66 +1,53 @@
// 定义包路径,用于存放任务服务接口
package com.yf.exam.ability.job.service; package com.yf.exam.ability.job.service;
/** /**
* *
*
*
* @author bool * @author bool
* @date 2020/11/29 2:17 * @date 2020/11/29 2:17
*/ */
public interface JobService { public interface JobService {
/** /**
* *
* JobDataMap
*/ */
String TASK_DATA = "taskData"; // 用于存储任务相关数据的键名 String TASK_DATA = "taskData";
/** /**
* *
* cron * @param jobClass
* * @param jobName
* @param jobClass * @param cron
* @param jobName * @param data
* @param cron cron
* @param data
*/ */
void addCronJob(Class jobClass, String jobName, String cron, String data); void addCronJob(Class jobClass, String jobName, String cron, String data);
/** /**
* *
* * @param jobClass
* * @param jobName
* @param jobClass * @param data
* @param jobName
* @param data
*/ */
void addCronJob(Class jobClass, String jobName, String data); void addCronJob(Class jobClass, String jobName, String data);
/** /**
* *
* * @param jobName
* * @param jobGroup
* @param jobName
* @param jobGroup
*/ */
void pauseJob(String jobName, String jobGroup); void pauseJob(String jobName, String jobGroup);
/** /**
* *
* * @param triggerName
* * @param triggerGroup
* @param triggerName
* @param triggerGroup
*/ */
void resumeJob(String triggerName, String triggerGroup); void resumeJob(String triggerName, String triggerGroup);
/** /**
* * job
* * @param jobName
* * @param jobGroup
* @param jobName
* @param jobGroup
*/ */
void deleteJob(String jobName, String jobGroup); void deleteJob(String jobName, String jobGroup);
} }

@ -1,185 +1,122 @@
// 定义包路径,用于存放任务服务实现类
package com.yf.exam.ability.job.service.impl; package com.yf.exam.ability.job.service.impl;
// 导入所需的外部依赖包 import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSON; // 用于JSON数据处理 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; // 用于生成唯一ID import com.yf.exam.ability.job.enums.JobGroup;
import com.yf.exam.ability.job.enums.JobGroup; // 任务分组枚举 import com.yf.exam.ability.job.service.JobService;
import com.yf.exam.ability.job.service.JobService; // 任务服务接口 import lombok.extern.log4j.Log4j2;
import lombok.extern.log4j.Log4j2; // 日志注解 import org.quartz.*;
import org.quartz.*; // Quartz定时任务框架相关类 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Autowired; // Spring自动注入注解 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean; // Quartz调度器工厂Bean import org.springframework.stereotype.Service;
import org.springframework.stereotype.Service; // Spring服务注解 import org.springframework.util.StringUtils;
import org.springframework.util.StringUtils; // Spring字符串工具类
/** /**
*
*
* @author bool * @author bool
*/ */
@Log4j2 // 启用Log4j2日志 @Log4j2
@Service // 标记为Spring服务组件 @Service
public class JobServiceImpl implements JobService { public class JobServiceImpl implements JobService {
/** /**
* Quartz * Quartz
*
*/ */
private Scheduler scheduler; // 定时任务调度器实例 private Scheduler scheduler;
/** /**
* SchedulerFactoryBean *
* SpringQuartzBean * @param schedulerFactoryBean
* @param schedulerFactoryBean QuartzBean
*/ */
public JobServiceImpl(@Autowired SchedulerFactoryBean schedulerFactoryBean) { public JobServiceImpl(@Autowired SchedulerFactoryBean schedulerFactoryBean) {
// 从工厂Bean中获取调度器实例
scheduler = schedulerFactoryBean.getScheduler(); scheduler = schedulerFactoryBean.getScheduler();
} }
/**
*
* cron
*
* @param jobClass
* @param jobName
* @param cron cron
* @param data
*/
@Override @Override
public void addCronJob(Class jobClass, String jobName, String cron, String data) { public void addCronJob(Class jobClass, String jobName, String cron, String data) {
// 设置任务组为系统任务组
String jobGroup = JobGroup.SYSTEM; String jobGroup = JobGroup.SYSTEM;
// 如果任务名为空,则自动生成任务 // 自动命名
if(StringUtils.isEmpty(jobName)){ if(StringUtils.isEmpty(jobName)){
// 使用类名大写+下划线+唯一ID作为任务名
jobName = jobClass.getSimpleName().toUpperCase() + "_"+IdWorker.getIdStr(); jobName = jobClass.getSimpleName().toUpperCase() + "_"+IdWorker.getIdStr();
} }
try { try {
// 创建任务键,用于唯一标识任务
JobKey jobKey = JobKey.jobKey(jobName, jobGroup); JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
// 获取任务详情
JobDetail jobDetail = scheduler.getJobDetail(jobKey); JobDetail jobDetail = scheduler.getJobDetail(jobKey);
// 如果任务已存在,则删除旧任务
if (jobDetail != null) { if (jobDetail != null) {
log.info("++++++++++任务:{} 已存在", jobName); log.info("++++++++++任务:{} 已存在", jobName);
this.deleteJob(jobName, jobGroup); this.deleteJob(jobName, jobGroup);
} }
// 记录任务构建信息
log.info("++++++++++构建任务:{},{},{},{},{} ", jobClass.toString(), jobName, jobGroup, cron, data); log.info("++++++++++构建任务:{},{},{},{},{} ", jobClass.toString(), jobName, jobGroup, cron, data);
// 构建新的任务详情 //构建job信息
jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build(); jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build();
// 设置任务数据 //用JopDataMap来传递数据
jobDetail.getJobDataMap().put(TASK_DATA, data); jobDetail.getJobDataMap().put(TASK_DATA, data);
// 声明触发器 //按新的cronExpression表达式构建一个新的trigger
Trigger trigger = null; Trigger trigger = null;
// 如果有cron表达式则创建cron触发器 // 有表达式的按表达式
if(!StringUtils.isEmpty(cron)){ if(!StringUtils.isEmpty(cron)){
log.info("+++++表达式执行:"+ JSON.toJSONString(jobDetail)); log.info("+++++表达式执行:"+ JSON.toJSONString(jobDetail));
// 创建cron调度构建器 //表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
// 构建触发器 trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
trigger = TriggerBuilder.newTrigger()
.withIdentity(jobName, jobGroup)
.withSchedule(scheduleBuilder)
.build();
}else{ }else{
// 无cron表达式则立即执行一次 // 无表达式则立即执行
log.info("+++++立即执行:"+ JSON.toJSONString(jobDetail)); log.info("+++++立即执行:"+ JSON.toJSONString(jobDetail));
// 构建立即执行的触发器 trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).startNow().build();
trigger = TriggerBuilder.newTrigger()
.withIdentity(jobName, jobGroup)
.startNow()
.build();
} }
// 调度任务
scheduler.scheduleJob(jobDetail, trigger); scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) { } catch (Exception e) {
// 打印异常堆栈信息
e.printStackTrace(); e.printStackTrace();
} }
} }
/**
*
* cron
*
* @param jobClass
* @param jobName
* @param data
*/
@Override @Override
public void addCronJob(Class jobClass, String jobName, String data) { public void addCronJob(Class jobClass, String jobName, String data) {
// 立即执行任务不需要cron表达式传入null // 立即执行任务
this.addCronJob(jobClass, jobName, null, data); this.addCronJob(jobClass, jobName, null, data);
} }
/**
*
*
*
* @param jobName
* @param jobGroup
*/
@Override @Override
public void pauseJob(String jobName, String jobGroup) { public void pauseJob(String jobName, String jobGroup) {
try { try {
// 创建触发器键并暂停触发器
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
scheduler.pauseTrigger(triggerKey); scheduler.pauseTrigger(triggerKey);
log.info("++++++++++暂停任务:{}", jobName); log.info("++++++++++暂停任务:{}", jobName);
} catch (SchedulerException e) { } catch (SchedulerException e) {
// 打印异常堆栈信息
e.printStackTrace(); e.printStackTrace();
} }
} }
/**
*
*
*
* @param jobName
* @param jobGroup
*/
@Override @Override
public void resumeJob(String jobName, String jobGroup) { public void resumeJob(String jobName, String jobGroup) {
try { try {
// 创建触发器键并恢复触发器
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
scheduler.resumeTrigger(triggerKey); scheduler.resumeTrigger(triggerKey);
log.info("++++++++++重启任务:{}", jobName); log.info("++++++++++重启任务:{}", jobName);
} catch (SchedulerException e) { } catch (SchedulerException e) {
// 打印异常堆栈信息
e.printStackTrace(); e.printStackTrace();
} }
} }
/**
*
*
*
* @param jobName
* @param jobGroup
*/
@Override @Override
public void deleteJob(String jobName, String jobGroup) { public void deleteJob(String jobName, String jobGroup) {
try { try {
// 创建任务键并删除任务
JobKey jobKey = JobKey.jobKey(jobName,jobGroup); JobKey jobKey = JobKey.jobKey(jobName,jobGroup);
scheduler.deleteJob(jobKey); scheduler.deleteJob(jobKey);
log.info("++++++++++删除任务:{}", jobKey); log.info("++++++++++删除任务:{}", jobKey);
} catch (SchedulerException e) { } catch (SchedulerException e) {
// 打印异常堆栈信息
e.printStackTrace(); e.printStackTrace();
} }
} }

@ -1,39 +1,29 @@
// 定义包路径,用于存放自定义过滤器相关的类
package com.yf.exam.ability.shiro; package com.yf.exam.ability.shiro;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; // Shiro过滤器工厂类用于配置Shiro过滤器 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.InvalidRequestFilter; // Shiro无效请求过滤器用于处理无效请求 import org.apache.shiro.web.filter.InvalidRequestFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilter; // Shiro默认过滤器提供默认的过滤逻辑 import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.filter.mgt.FilterChainManager; // Shiro过滤器链管理器管理过滤器链的配置 import org.apache.shiro.web.filter.mgt.FilterChainManager;
import javax.servlet.Filter; // Servlet过滤器接口定义了过滤器的基本操作 import javax.servlet.Filter;
import java.util.Map; // Map集合类用于存储键值对 import java.util.Map;
/** /**
* Shiro * URL
* URL400 * 400https://youdomain.com/upload/file/云帆考试系统用户手册.pdf
* https://youdomain.com/upload/file/云帆考试系统用户手册.pdf 这样的URL可能会因为中文字符而导致问题。
* @author van * @author van
*/ */
public class CNFilterFactoryBean extends ShiroFilterFactoryBean { public class CNFilterFactoryBean extends ShiroFilterFactoryBean {
/**
*
*
* @return FilterChainManager
*/
@Override @Override
protected FilterChainManager createFilterChainManager() { protected FilterChainManager createFilterChainManager() {
FilterChainManager manager = super.createFilterChainManager(); // 调用父类方法创建过滤器链管理器 FilterChainManager manager = super.createFilterChainManager();
// URL携带中文400servletPath中文校验bug
// 获取过滤器映射,以便修改特定过滤器的配置
Map<String, Filter> filterMap = manager.getFilters(); Map<String, Filter> filterMap = manager.getFilters();
// 获取无效请求过滤器实例
Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
if (invalidRequestFilter instanceof InvalidRequestFilter) { if (invalidRequestFilter instanceof InvalidRequestFilter) {
// 设置无效请求过滤器不阻止非ASCII字符以允许中文URL
((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false); ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
} }
return manager; // 返回配置好的过滤器链管理器 return manager;
} }
} }

@ -1,135 +1,131 @@
// 定义包路径用于存放Shiro领域相关的类
package com.yf.exam.ability.shiro; package com.yf.exam.ability.shiro;
import com.yf.exam.ability.shiro.jwt.JwtToken; // JWT令牌类
import com.yf.exam.ability.shiro.jwt.JwtUtils; // JWT工具类 import com.yf.exam.ability.shiro.jwt.JwtToken;
import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; // 用户登录DTO import com.yf.exam.ability.shiro.jwt.JwtUtils;
import com.yf.exam.modules.sys.user.service.SysUserRoleService; // 用户角色服务 import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
import com.yf.exam.modules.sys.user.service.SysUserService; // 用户服务 import com.yf.exam.modules.sys.user.service.SysUserRoleService;
import lombok.extern.slf4j.Slf4j; // 日志注解 import com.yf.exam.modules.sys.user.service.SysUserService;
import org.apache.shiro.authc.AuthenticationException; // 认证异常 import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationInfo; // 认证信息 import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken; // 认证令牌 import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.SimpleAuthenticationInfo; // 简单认证信息 import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo; // 授权信息 import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo; // 简单授权信息 import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm; // 授权领域 import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection; // 主体集合 import org.apache.shiro.realm.AuthorizingRealm;
import org.springframework.beans.factory.annotation.Autowired; // Spring自动注入注解 import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.context.annotation.Lazy; // 延迟注入注解 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; // Spring组件注解 import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.util.HashSet; // 哈希集合
import java.util.List; // 列表 import java.util.HashSet;
import java.util.List;
/** /**
* Shiro *
* Shiro
* @author bool * @author bool
*/ */
@Component // 标记为Spring组件 @Component
@Slf4j // 启用Slf4j日志 @Slf4j
public class ShiroRealm extends AuthorizingRealm { public class ShiroRealm extends AuthorizingRealm {
@Autowired @Autowired
@Lazy // 延迟注入,避免循环依赖 @Lazy
private SysUserService sysUserService; // 用户服务 private SysUserService sysUserService;
@Autowired @Autowired
@Lazy // 延迟注入,避免循环依赖 @Lazy
private SysUserRoleService sysUserRoleService; // 用户角色服务 private SysUserRoleService sysUserRoleService;
/**
* JWT
* JWT
* @param token
* @return JWT
*/
@Override @Override
public boolean supports(AuthenticationToken token) { public boolean supports(AuthenticationToken token) {
// 判断是否支持JWT令牌 return token instanceof JwtToken;
return token instanceof JwtToken; // 返回是否为JwtToken
} }
/** /**
* *
* * @param principals
* @param principals * @return
* @return
*/ */
@Override @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userId = null; // 用户ID
String userId = null;
if (principals != null) { if (principals != null) {
SysUserLoginDTO user = (SysUserLoginDTO) principals.getPrimaryPrincipal(); // 获取用户信息 SysUserLoginDTO user = (SysUserLoginDTO) principals.getPrimaryPrincipal();
userId = user.getId(); // 获取用户ID userId = user.getId();
} }
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 创建授权信息 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 查找用户角色 // 查找用户角色
List<String> roles = sysUserRoleService.listRoles(userId); // 获取用户角色列表 List<String> roles = sysUserRoleService.listRoles(userId);
info.setRoles(new HashSet<>(roles)); // 设置角色 info.setRoles(new HashSet<>(roles));
log.info("++++++++++校验详细权限完成"); // 日志记录 log.info("++++++++++校验详细权限完成");
return info; // 返回授权信息 return info;
} }
/** /**
* *
* * @param auth
* @param auth * @return
* @return * @throws AuthenticationException
* @throws AuthenticationException
*/ */
@Override @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String token = (String) auth.getCredentials(); // 获取token String token = (String) auth.getCredentials();
if (token == null) { if (token == null) {
throw new AuthenticationException("token为空!"); // 抛出异常 throw new AuthenticationException("token为空!");
} }
// 校验token有效性 // 校验token有效性
SysUserLoginDTO user = this.checkToken(token); // 验证token并获取用户信息 SysUserLoginDTO user = this.checkToken(token);
return new SimpleAuthenticationInfo(user, token, getName()); // 返回认证信息 return new SimpleAuthenticationInfo(user, token, getName());
} }
/** /**
* Token * Token
* JWT * @param token
* @param token JWT * @return
* @return DTO * @throws AuthenticationException
* @throws AuthenticationException
*/ */
public SysUserLoginDTO checkToken(String token) throws AuthenticationException { public SysUserLoginDTO checkToken(String token) throws AuthenticationException {
// 查询用户信息 // 查询用户信息
log.debug("++++++++++校验用户token "+ token); // 日志记录 log.debug("++++++++++校验用户token "+ token);
// 从token中获取用户名 // 从token中获取用户名
String username = JwtUtils.getUsername(token); // 获取用户名 String username = JwtUtils.getUsername(token);
log.debug("++++++++++用户名: "+ username); // 日志记录 log.debug("++++++++++用户名: "+ username);
if (username == null) { if (username == null) {
throw new AuthenticationException("无效的token"); // 抛出异常 throw new AuthenticationException("无效的token");
} }
// 查找登录用户对象 // 查找登录用户对象
SysUserLoginDTO user = sysUserService.token(token); // 获取用户信息 SysUserLoginDTO user = sysUserService.token(token);
// 校验token是否失效 // 校验token是否失效
if (!JwtUtils.verify(token, username)) { if (!JwtUtils.verify(token, username)) {
throw new AuthenticationException("登陆失效,请重试登陆!"); // 抛出异常 throw new AuthenticationException("登陆失效,请重试登陆!");
} }
return user; // 返回用户信息 return user;
} }
/** /**
* *
* * @param principals
* @param principals
*/ */
@Override @Override
public void clearCache(PrincipalCollection principals) { public void clearCache(PrincipalCollection principals) {
super.clearCache(principals); // 清除缓存 super.clearCache(principals);
} }
} }

@ -1,84 +1,53 @@
// 定义包路径用于存放Shiro JWT认证过滤器相关的类
package com.yf.exam.ability.shiro.aop; package com.yf.exam.ability.shiro.aop;
// 导入所需的外部依赖包
import com.yf.exam.ability.shiro.jwt.JwtToken; // JWT令牌类
import com.yf.exam.aspect.utils.InjectUtils; // 工具类,用于注入错误信息
import com.yf.exam.modules.Constant; // 常量类
import lombok.extern.slf4j.Slf4j; // 日志注解
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; // Shiro基础认证过滤器
import javax.servlet.ServletRequest; // Servlet请求接口 import com.yf.exam.ability.shiro.jwt.JwtToken;
import javax.servlet.ServletResponse; // Servlet响应接口 import com.yf.exam.aspect.utils.InjectUtils;
import javax.servlet.http.HttpServletRequest; // HTTP请求类 import com.yf.exam.modules.Constant;
import javax.servlet.http.HttpServletResponse; // HTTP响应类 import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** /**
* JWT *
* JWTJWT访
* @author bool * @author bool
*/ */
@Slf4j // 启用Slf4j日志 @Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter { public class JwtFilter extends BasicHttpAuthenticationFilter {
/** /**
* 访 *
* * @param request
* 访true访 * @param response
* 访 * @param mappedValue
* * @return
* @param request
* @param response
* @param mappedValue
* @return 访
*/ */
@Override @Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try { try {
// 尝试执行登录认证
executeLogin(request, response); executeLogin(request, response);
// 认证成功返回true
return true; return true;
} catch (Exception e) { } catch (Exception e) {
// 认证失败时写入错误信息 // 写出统一错误信息
InjectUtils.restError((HttpServletResponse) response); InjectUtils.restError((HttpServletResponse) response);
// 返回false表示不允许访问
return false; return false;
} }
} }
/**
*
* JWT token
* tokenisAccessAllowed
*
* @param request
* @param response
* @return
*/
@Override @Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
// 将ServletRequest转换为HttpServletRequest
HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 从请求头中获取token
String token = httpServletRequest.getHeader(Constant.TOKEN); String token = httpServletRequest.getHeader(Constant.TOKEN);
// 如果token为空则抛出异常
if (token == null || "".equals(token)) {
throw new Exception("token不能为空");
}
// 创建JWT token对象
JwtToken jwtToken = new JwtToken(token); JwtToken jwtToken = new JwtToken(token);
// 提交给realm进行登录认证 // 提交给realm进行登入如果错误他会抛出异常并被捕获
try {
getSubject(request, response).login(jwtToken); getSubject(request, response).login(jwtToken);
// 如果没有抛出异常则表示登录成功 // 如果没有抛出异常则代表登入成功返回true
return true; return true;
} catch (Exception e) {
// 登录失败,记录日志
log.error("JWT认证失败", e);
throw e;
}
} }
} }

@ -1,50 +1,33 @@
// 定义包路径用于存放JWT令牌相关的类
package com.yf.exam.ability.shiro.jwt; package com.yf.exam.ability.shiro.jwt;
import lombok.Data; // Lombok注解用于生成getter和setter import lombok.Data;
import org.apache.shiro.authc.AuthenticationToken; // Shiro认证令牌接口 import org.apache.shiro.authc.AuthenticationToken;
/** /**
* JWT
* ShiroAuthenticationTokenJWT
* @author bool * @author bool
*/ */
@Data // 自动生成getter和setter方法 @Data
public class JwtToken implements AuthenticationToken { public class JwtToken implements AuthenticationToken {
private static final long serialVersionUID = 1L; // 序列化ID private static final long serialVersionUID = 1L;
/** /**
* JWTtoken * JWTtoken
* JWT
*/ */
private String token; // JWT令牌字符串 private String token;
/**
*
* @param token JWT
*/
public JwtToken(String token) { public JwtToken(String token) {
this.token = token; // 设置token this.token = token;
} }
/**
*
* AuthenticationToken
* @return token
*/
@Override @Override
public Object getPrincipal() { public Object getPrincipal() {
return token; // 返回token作为身份信息 return token;
} }
/**
*
* AuthenticationToken
* @return token
*/
@Override @Override
public Object getCredentials() { public Object getCredentials() {
return token; // 返回token作为凭证信息 return token;
} }
} }

@ -1,15 +1,14 @@
// 定义包路径用于存放JWT工具类
package com.yf.exam.ability.shiro.jwt; package com.yf.exam.ability.shiro.jwt;
import com.auth0.jwt.JWT; // JWT工具类 import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier; // JWT验证器 import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm; // JWT算法 import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException; // JWT解码异常 import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT; // 解码后的JWT import com.auth0.jwt.interfaces.DecodedJWT;
import com.yf.exam.core.utils.file.Md5Util; // MD5工具类 import com.yf.exam.core.utils.file.Md5Util;
import java.util.Calendar; // 日历类 import java.util.Calendar;
import java.util.Date; // 日期类 import java.util.Date;
/** /**
* JWT * JWT
@ -20,74 +19,81 @@ public class JwtUtils {
/** /**
* 24 * 24
*/ */
private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; // JWT有效期 private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;
/** /**
* *
* @param token JWT * @param token
* @param username * @param username
* @return * @return
*/ */
public static boolean verify(String token, String username) { public static boolean verify(String token, String username) {
try { try {
// 根据密码生成JWT效验器 // 根据密码生成JWT效验器
Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); // 创建算法 Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username));
JWTVerifier verifier = JWT.require(algorithm) // 创建验证器 JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username) // 添加用户名声明 .withClaim("username", username)
.build(); // 构建验证器 .build();
// 效验TOKEN // 效验TOKEN
verifier.verify(token); // 验证token verifier.verify(token);
return true; // 返回验证成功 return true;
} catch (Exception exception) { } catch (Exception exception) {
return false; // 返回验证失败 return false;
} }
} }
/** /**
* Token * Token
* @param token JWT * @param token
* @return * @return
*/ */
public static String getUsername(String token) { public static String getUsername(String token) {
try { try {
DecodedJWT jwt = JWT.decode(token); // 解码JWT DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString(); // 获取用户名 return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) { } catch (JWTDecodeException e) {
return null; // 返回null表示解码失败 return null;
} }
} }
/** /**
* JWT Token * JWT Token
* @param username * @param username
* @return JWT * @return
*/ */
public static String sign(String username) { public static String sign(String username) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); // 设置过期时间 Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); // 创建算法 Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username));
// 附带username信息 // 附带username信息
return JWT.create() // 创建JWT return JWT.create()
.withClaim("username", username) // 添加用户名声明 .withClaim("username", username)
.withExpiresAt(date).sign(algorithm); // 设置过期时间并签名 .withExpiresAt(date).sign(algorithm);
} }
/** /**
* JWT * JWT
* @param userName * @param userName
* @return * @return
*/ */
private static String encryptSecret(String userName){ private static String encryptSecret(String userName){
// 一个简单的登录规则,用户名+当前月份为加密串,意思每个月会变,要重新登录 // 一个简单的登录规则,用户名+当前月份为加密串,意思每个月会变,要重新登录
// 可自行修改此规则 // 可自行修改此规则
Calendar cl = Calendar.getInstance(); // 获取当前日历 Calendar cl = Calendar.getInstance();
cl.setTimeInMillis(System.currentTimeMillis()); // 设置当前时间 cl.setTimeInMillis(System.currentTimeMillis());
StringBuffer sb = new StringBuffer(userName) // 创建字符串缓冲区 StringBuffer sb = new StringBuffer(userName)
.append("&") // 添加分隔符 .append("&")
.append(cl.get(Calendar.MONTH)); // 添加当前月份 .append(cl.get(Calendar.MONTH));
// 获取MD5 // 获取MD5
String secret = Md5Util.md5(sb.toString()); // 生成MD5秘钥 String secret = Md5Util.md5(sb.toString());
return Md5Util.md5(userName + "&" + secret); // 返回加密后的秘钥 return Md5Util.md5(userName + "&" + secret);
} }
} }

@ -1,36 +1,32 @@
// 定义包路径,用于存放文件上传配置相关的类
package com.yf.exam.ability.upload.config; package com.yf.exam.ability.upload.config;
import lombok.Data; // Lombok注解用于简化数据类的编写自动生成getter和setter import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; // Spring Boot配置属性注解用于将配置文件中的属性绑定到Java对象 import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; // Spring配置注解标记为配置类 import org.springframework.context.annotation.Configuration;
/** /**
* *
* 访
* application.ymlapplication.propertiesSpring Boot
* @author van * @author van
*/ */
@Data // 使用Lombok注解自动生成getter和setter方法 @Data
@Configuration // 标记为Spring配置类表示这是一个配置类 @Configuration
@ConfigurationProperties(prefix = "conf.upload") // 指定配置文件中属性的前缀,这里是"conf.upload" @ConfigurationProperties(prefix = "conf.upload")
public class UploadConfig { public class UploadConfig {
/** /**
* 访 * 访
* 访URL
*/ */
private String url; // 文件访问的URL private String url;
/** /**
* *
*
*/ */
private String dir; // 文件存储的物理目录 private String dir;
/** /**
* *
*
*/ */
private String[] allowExtensions; // 允许上传的文件后缀 private String [] allowExtensions;
} }

@ -1,62 +1,57 @@
// 定义包路径,用于存放文件上传下载请求类
package com.yf.exam.ability.upload.controller; package com.yf.exam.ability.upload.controller;
import com.yf.exam.ability.Constant; // 常量类,包含系统配置的常量值
import com.yf.exam.ability.upload.dto.UploadReqDTO; // 文件上传请求DTO封装上传文件所需的数据
import com.yf.exam.ability.upload.dto.UploadRespDTO; // 文件上传响应DTO封装上传文件后的响应数据
import com.yf.exam.ability.upload.service.UploadService; // 文件上传服务,提供文件上传和下载的业务逻辑
import com.yf.exam.core.api.ApiRest; // API响应类封装统一的API响应格式
import com.yf.exam.core.api.controller.BaseController; // 基础控制器,提供基础的控制器功能
import io.swagger.annotations.Api; // Swagger API注解用于描述API信息
import io.swagger.annotations.ApiOperation; // Swagger API操作注解用于描述单个API操作
import lombok.extern.log4j.Log4j2; // 日志注解,提供日志功能
import org.springframework.beans.factory.annotation.Autowired; // Spring自动注入注解用于注入Spring管理的Bean
import org.springframework.web.bind.annotation.GetMapping; // GET请求映射注解用于映射GET请求到方法
import org.springframework.web.bind.annotation.ModelAttribute; // 模型属性注解,用于将请求参数绑定到模型对象
import org.springframework.web.bind.annotation.PostMapping; // POST请求映射注解用于映射POST请求到方法
import org.springframework.web.bind.annotation.RestController; // REST控制器注解标记为REST风格的控制器
import javax.servlet.http.HttpServletRequest; // HTTP请求类表示HTTP请求 import com.yf.exam.ability.Constant;
import javax.servlet.http.HttpServletResponse; // HTTP响应类表示HTTP响应 import com.yf.exam.ability.upload.dto.UploadReqDTO;
import com.yf.exam.ability.upload.dto.UploadRespDTO;
import com.yf.exam.ability.upload.service.UploadService;
import com.yf.exam.core.api.ApiRest;
import com.yf.exam.core.api.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** /**
* *
* RESTful API
* @author bool * @author bool
*/ */
@Log4j2 // 启用Log4j2日志 @Log4j2
@Api(tags = {"文件上传"}) // Swagger API标签用于分类API @Api(tags = {"文件上传"})
@RestController // 标记为REST控制器表示该类是一个REST风格的控制器 @RestController
public class UploadController extends BaseController { public class UploadController extends BaseController {
@Autowired @Autowired
private UploadService uploadService; // 文件上传服务,自动注入 private UploadService uploadService;
/** /**
* *
* * @param reqDTO
* * @return
* @param reqDTO DTO
* @return
*/ */
@PostMapping("/common/api/file/upload") // POST请求映射指定请求路径 @PostMapping("/common/api/file/upload")
@ApiOperation(value = "文件上传", notes = "此接口较为特殊参数都通过表单方式提交而非JSON") // Swagger API操作描述 @ApiOperation(value = "文件上传", notes = "此接口较为特殊参数都通过表单方式提交而非JSON")
public ApiRest<UploadRespDTO> upload(@ModelAttribute UploadReqDTO reqDTO) { public ApiRest<UploadRespDTO> upload(@ModelAttribute UploadReqDTO reqDTO) {
// 上传并返回URL // 上传并返回URL
UploadRespDTO respDTO = uploadService.upload(reqDTO); // 调用上传服务 UploadRespDTO respDTO = uploadService.upload(reqDTO);
return super.success(respDTO); // 返回成功响应 return super.success(respDTO);
} }
/** /**
* *
* * @param request
* * @param response
* @param request HTTP
* @param response HTTP
*/ */
@GetMapping(Constant.FILE_PREFIX+"**") // GET请求映射指定请求路径前缀 @GetMapping(Constant.FILE_PREFIX+"**")
@ApiOperation(value = "文件下载", notes = "文件下载") // Swagger API操作描述 @ApiOperation(value = "文件下载", notes = "文件下载")
public void download(HttpServletRequest request, HttpServletResponse response) { public void download(HttpServletRequest request, HttpServletResponse response) {
uploadService.download(request, response); // 调用下载服务 uploadService.download(request, response);
} }
} }

@ -1,26 +1,22 @@
// 定义包路径用于存放文件上传请求DTO
package com.yf.exam.ability.upload.dto; package com.yf.exam.ability.upload.dto;
import com.yf.exam.core.api.dto.BaseDTO; // 导入基础DTO类提供通用的数据传输对象功能
import io.swagger.annotations.ApiModel; // 导入Swagger API模型注解用于描述API模型 import com.yf.exam.core.api.dto.BaseDTO;
import io.swagger.annotations.ApiModelProperty; // 导入Swagger API模型属性注解用于描述API模型的属性 import io.swagger.annotations.ApiModel;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写自动生成getter和setter import io.swagger.annotations.ApiModelProperty;
import org.springframework.web.multipart.MultipartFile; // 导入Spring文件上传类用于处理上传的文件 import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
/** /**
* *
* * @author
* @author van
* @date 2019-12-26 17:54 * @date 2019-12-26 17:54
*/ */
@Data // 使用Lombok注解自动生成getter和setter方法 @Data
@ApiModel(value="文件上传参数", description="文件上传参数") // 使用Swagger注解描述API模型 @ApiModel(value="文件上传参数", description="文件上传参数")
public class UploadReqDTO extends BaseDTO { public class UploadReqDTO extends BaseDTO {
/** @ApiModelProperty(value = "上传文件内容", required=true)
* private MultipartFile file;
*
*/
@ApiModelProperty(value = "上传文件内容", required=true) // 使用Swagger注解描述API模型属性
private MultipartFile file; // 上传的文件内容
} }

@ -1,28 +1,23 @@
// 定义包路径用于存放文件上传响应DTO
package com.yf.exam.ability.upload.dto; package com.yf.exam.ability.upload.dto;
import com.yf.exam.core.api.dto.BaseDTO; // 导入基础DTO类提供通用的数据传输对象功能 import com.yf.exam.core.api.dto.BaseDTO;
import io.swagger.annotations.ApiModel; // 导入Swagger API模型注解用于描述API模型 import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; // 导入Swagger API模型属性注解用于描述API模型的属性 import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor; // 导入Lombok注解用于生成全参构造函数 import lombok.AllArgsConstructor;
import lombok.Data; // 导入Lombok注解用于生成getter和setter方法 import lombok.Data;
import lombok.NoArgsConstructor; // 导入Lombok注解用于生成无参构造函数 import lombok.NoArgsConstructor;
/** /**
* DTO *
* URL
* @author bool * @author bool
*/ */
@Data // 使用Lombok注解自动生成getter和setter方法 @Data
@AllArgsConstructor // 使用Lombok注解生成全参构造函数 @AllArgsConstructor
@NoArgsConstructor // 使用Lombok注解生成无参构造函数 @NoArgsConstructor
@ApiModel(value="文件上传响应", description="文件上传响应") // 使用Swagger注解描述API模型 @ApiModel(value="文件上传响应", description="文件上传响应")
public class UploadRespDTO extends BaseDTO { public class UploadRespDTO extends BaseDTO {
/** @ApiModelProperty(value = "上传后的完整的URL地址", required=true)
* URL private String url;
* 访URL
*/
@ApiModelProperty(value = "上传后的完整的URL地址", required=true) // 使用Swagger注解描述API模型属性
private String url; // 上传后的完整URL地址
} }

@ -1,62 +1,30 @@
package com.yf.exam.ability.upload.service; package com.yf.exam.ability.upload.service;
// 这一行声明了该Java类所属的包名为com.yf.exam.ability.upload.service。
// 包用于组织和管理相关的Java类避免类名冲突方便代码的模块化和复用。
import com.yf.exam.ability.upload.dto.UploadReqDTO; // 导入文件上传请求DTO import com.yf.exam.ability.upload.dto.UploadReqDTO;
// 导入了名为UploadReqDTO的类它位于com.yf.exam.ability.upload.dto包下。 import com.yf.exam.ability.upload.dto.UploadRespDTO;
// 这个类通常用于封装文件上传请求相关的数据,比如要上传的文件信息、上传的相关参数等。
import com.yf.exam.ability.upload.dto.UploadRespDTO; // 导入文件上传响应DTO import javax.servlet.http.HttpServletRequest;
// 导入了名为UploadRespDTO的类同样位于com.yf.exam.ability.upload.dto包下。 import javax.servlet.http.HttpServletResponse;
// 它主要用于封装文件上传操作完成后返回的响应数据,例如上传是否成功的标识、上传后的文件存储路径等信息。
import javax.servlet.http.HttpServletRequest; // 导入HTTP请求类
// 引入了Java EE中用于处理HTTP请求的标准类HttpServletRequest。
// 在文件下载等操作中会通过这个类获取客户端发送过来的关于下载请求的各种信息如请求的URL、请求头信息等。
import javax.servlet.http.HttpServletResponse; // 导入HTTP响应类
// 引入了Java EE中用于处理HTTP响应的标准类HttpServletResponse。
// 在文件下载操作中,会使用这个类来设置响应的状态码、响应头信息以及将文件内容返回给客户端。
/** /**
* * OSS
*
* @author bool * @author bool
* @date 2019-07-12 16:45 * @date 2019-07-12 16:45
*/ */
// 这是一个Java接口的文档注释用于描述该接口的整体功能和用途。
// 说明这个接口主要是用来定义与文件上传和下载相关的业务操作方法,
// 并且其他类可以通过实现这个接口来提供具体的实现,以达到统一调用的目的。
// 同时标注了接口的作者是bool创建日期是2019年7月12日16:45。
public interface UploadService { public interface UploadService {
// 这里定义了一个名为UploadService的公共接口。
// 接口中只包含方法的声明,不包含方法的具体实现,具体实现由实现该接口的类来完成。
/** /**
* *
* * @param reqDTO
* * @return
* @param reqDTO DTO
* @return DTO
*/ */
// 这是接口中定义的一个方法声明名为upload。
// 它的功能是处理文件上传请求通过接收传入的UploadReqDTO对象其中包含了上传文件所需的各种数据
// 然后在具体实现类中执行实际的上传操作最后返回一个UploadRespDTO对象该对象封装了上传文件后的响应数据。
UploadRespDTO upload(UploadReqDTO reqDTO); UploadRespDTO upload(UploadReqDTO reqDTO);
/** /**
* *
* * @param request
* * @param response
* @param request HTTP
* @param response HTTP
*/ */
// 这是接口中定义的另一个方法声明名为download。
// 它用于处理文件下载请求会接收一个HttpServletRequest对象其中包含了客户端发送的关于下载请求的所有信息
// 和一个HttpServletResponse对象用于设置响应相关的信息并将文件内容返回给客户端
// 在具体实现类中根据请求参数找到对应的要下载的文件并通过HttpServletResponse将文件内容返回给客户端。
void download(HttpServletRequest request, HttpServletResponse response); void download(HttpServletRequest request, HttpServletResponse response);
} }

@ -1,143 +1,135 @@
// 定义包路径,用于存放文件上传服务实现类
package com.yf.exam.ability.upload.service.impl; package com.yf.exam.ability.upload.service.impl;
import com.yf.exam.ability.Constant; // 导入常量类,包含系统配置的常量值 import com.yf.exam.ability.Constant;
import com.yf.exam.ability.upload.config.UploadConfig; // 导入文件上传配置类,包含文件上传的相关配置 import com.yf.exam.ability.upload.config.UploadConfig;
import com.yf.exam.ability.upload.dto.UploadReqDTO; // 导入文件上传请求DTO封装上传文件所需的数据 import com.yf.exam.ability.upload.dto.UploadReqDTO;
import com.yf.exam.ability.upload.dto.UploadRespDTO; // 导入文件上传响应DTO封装上传文件后的响应数据 import com.yf.exam.ability.upload.dto.UploadRespDTO;
import com.yf.exam.ability.upload.service.UploadService; // 导入文件上传服务接口,定义文件上传的相关业务操作 import com.yf.exam.ability.upload.service.UploadService;
import com.yf.exam.ability.upload.utils.FileUtils; // 导入文件工具类,提供文件操作的辅助功能 import com.yf.exam.ability.upload.utils.FileUtils;
import com.yf.exam.core.exception.ServiceException; // 导入服务异常类,处理业务逻辑中的异常情况 import com.yf.exam.core.exception.ServiceException;
import lombok.extern.log4j.Log4j2; // 导入日志注解,提供日志功能 import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FilenameUtils; // 导入文件名工具类,提供文件名和扩展名操作的辅助功能 import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired; // 导入Spring自动注入注解用于注入Spring管理的Bean import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; // 导入Spring服务注解标记为服务组件 import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils; // 导入文件复制工具类,提供文件复制的功能 import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile; // 导入Spring文件上传类处理上传的文件 import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest; // 导入HTTP请求类表示HTTP请求 import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; // 导入HTTP响应类表示HTTP响应 import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream; // 导入文件输出流,用于将文件内容写入文件 import java.io.FileOutputStream;
import java.io.IOException; // 导入IO异常类处理IO操作中的异常情况 import java.io.IOException;
import java.io.UnsupportedEncodingException; // 导入不支持的编码异常类,处理编码问题 import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; // 导入URL解码类用于解码URL import java.net.URLDecoder;
import java.util.regex.Matcher; // 导入正则表达式匹配器,用于匹配正则表达式 import java.util.regex.Matcher;
import java.util.regex.Pattern; // 导入正则表达式类,用于编译正则表达式 import java.util.regex.Pattern;
/** /**
* *
*
* @author bool * @author bool
* @date 2019-07-30 21:02 * @date 2019-07-30 21:02
*/ */
@Log4j2 // 使用Log4j2日志注解启用日志功能 @Log4j2
@Service // 使用Spring服务注解标记为服务组件 @Service
public class UploadServiceImpl implements UploadService { public class UploadServiceImpl implements UploadService {
@Autowired @Autowired
private UploadConfig conf; // 自动注入文件上传配置 private UploadConfig conf;
/**
*
*
*
* @param reqDTO DTO
* @return DTO
*/
@Override @Override
public UploadRespDTO upload(UploadReqDTO reqDTO) { public UploadRespDTO upload(UploadReqDTO reqDTO) {
// 文件内容 // 文件内容
MultipartFile file = reqDTO.getFile(); // 获取上传的文件 MultipartFile file = reqDTO.getFile();
// 验证文件后缀 // 验证文件后缀
boolean allow = FilenameUtils.isExtension(file.getOriginalFilename(), conf.getAllowExtensions()); // 验证文件后缀 boolean allow = FilenameUtils.isExtension(file.getOriginalFilename(), conf.getAllowExtensions());
if(!allow){ if(!allow){
throw new ServiceException("文件类型不允许上传!"); // 抛出异常 throw new ServiceException("文件类型不允许上传!");
} }
// 上传文件夹 // 上传文件夹
String fileDir = conf.getDir(); // 获取文件存储目录 String fileDir = conf.getDir();
// 真实物理地址 // 真实物理地址
String fullPath; String fullPath;
try { try {
// 新文件 // 新文件
String filePath = FileUtils.processPath(file); // 处理文件路径 String filePath = FileUtils.processPath(file);
// 文件保存地址 // 文件保存地址
fullPath = fileDir + filePath; // 拼接完整路径 fullPath = fileDir + filePath;
// 创建文件夹 // 创建文件夹
FileUtils.checkDir(fullPath); // 检查并创建文件夹 FileUtils.checkDir(fullPath);
// 上传文件 // 上传文件
FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(fullPath)); // 复制文件内容到指定路径 FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(fullPath));
return this.generateResult(filePath); // 返回上传结果 return this.generateResult(filePath);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); // 打印异常堆栈 e.printStackTrace();
throw new ServiceException("文件上传失败:"+e.getMessage()); // 抛出异常 throw new ServiceException("文件上传失败:"+e.getMessage());
} }
} }
/**
*
*
*
* @param request HTTP
* @param response HTTP
*/
@Override @Override
public void download(HttpServletRequest request, HttpServletResponse response) { public void download(HttpServletRequest request, HttpServletResponse response) {
// 获取真实的文件路径 // 获取真实的文件路径
String filePath = this.getRealPath(request.getRequestURI()); // 获取文件的真实路径 String filePath = this.getRealPath(request.getRequestURI());
// 处理中文问题 // 处理中文问题
try { try {
filePath = URLDecoder.decode(filePath, "utf-8"); // 解码文件路径 filePath = URLDecoder.decode(filePath, "utf-8");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // 抛出运行时异常 throw new RuntimeException(e);
} }
System.out.println("++++完整路径为:"+filePath); // 打印完整路径 System.out.println("++++完整路径为:"+filePath);
try { try {
FileUtils.writeRange(request, response, filePath); // 调用文件工具类进行文件写入 FileUtils.writeRange(request, response, filePath);
} catch (IOException e) { } catch (IOException e) {
response.setStatus(404); // 设置响应状态为404 response.setStatus(404);
log.error("预览文件失败" + e.getMessage()); // 打印错误日志 log.error("预览文件失败" + e.getMessage());
} }
} }
/** /**
* *
* DTOURL * @param fileName
* * @return
* @param fileName
* @return DTO
*/ */
private UploadRespDTO generateResult(String fileName) { private UploadRespDTO generateResult(String fileName) {
//获取加速域名 //获取加速域名
String domain = conf.getUrl(); // 获取文件访问的URL String domain = conf.getUrl();
// 返回结果 // 返回结果
return new UploadRespDTO(domain + fileName); // 返回上传响应DTO return new UploadRespDTO(domain + fileName);
} }
/** /**
* *
* URI * @param uri
* * @return
* @param uri URI
* @return
*/ */
public String getRealPath(String uri){ public String getRealPath(String uri){
String regx = Constant.FILE_PREFIX+"(.*)"; // 正则表达式匹配文件路径
String regx = Constant.FILE_PREFIX+"(.*)";
// 查找全部变量 // 查找全部变量
Pattern pattern = Pattern.compile(regx); // 编译正则表达式 Pattern pattern = Pattern.compile(regx);
Matcher m = pattern.matcher(uri); // 创建匹配器 Matcher m = pattern.matcher(uri);
if (m.find()) { if (m.find()) {
String str = m.group(1); // 获取匹配的文件路径 String str = m.group(1);
return conf.getDir() + str; // 返回真实文件路径 return conf.getDir() + str;
} }
return null; // 返回null表示未找到 return null;
} }
} }

@ -1,169 +1,172 @@
// 定义包路径,用于存放文件工具类
package com.yf.exam.ability.upload.utils; package com.yf.exam.ability.upload.utils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; // 导入ID生成工具用于生成唯一的文件名 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.yf.exam.core.utils.DateUtils; // 导入日期工具类,用于处理日期相关的操作 import com.yf.exam.core.utils.DateUtils;
import org.apache.commons.io.FilenameUtils; // 导入文件名工具类,用于处理文件名和扩展名 import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile; // 导入Spring文件上传类用于处理上传的文件 import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream; // 导入Servlet输出流用于写入HTTP响应 import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; // 导入HTTP请求类表示客户端的请求 import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; // 导入HTTP响应类表示服务器的响应 import javax.servlet.http.HttpServletResponse;
import java.io.File; // 导入文件类,用于操作文件和目录 import java.io.File;
import java.io.IOException; // 导入IO异常类处理IO操作中的异常 import java.io.IOException;
import java.io.RandomAccessFile; // 导入随机访问文件类,用于高效地读写文件 import java.io.RandomAccessFile;
import java.util.Date; // 导入日期类,用于处理日期和时间 import java.util.Date;
/** /**
* *
*
* @author bool * @author bool
*/ */
public class FileUtils { public class FileUtils {
/** /**
* *
*
*/ */
private static final String SUFFIX_SPLIT = "."; // 文件后缀分隔符 private static final String SUFFIX_SPLIT = ".";
/** /**
* 线线 * 线线
* HTTP线线 * @param request
* * @param response
* @param request HTTP * @param filePath
* @param response HTTP * @throws IOException
* @param filePath
* @throws IOException IO
*/ */
public static void writeRange(HttpServletRequest request, HttpServletResponse response, String filePath) throws IOException { public static void writeRange(HttpServletRequest request,
HttpServletResponse response, String filePath) throws IOException {
// 读取文件 // 读取文件
File file = new File(filePath); // 创建文件对象 File file = new File(filePath);
//只读模式 //只读模式
RandomAccessFile randomFile = new RandomAccessFile(file, "r"); // 创建随机访问文件对象 RandomAccessFile randomFile = new RandomAccessFile(file, "r");
long contentLength = randomFile.length(); // 获取文件长度 long contentLength = randomFile.length();
String range = request.getHeader("Range"); // 获取请求头中的Range String range = request.getHeader("Range");
int start = 0, end = 0; // 初始化起始和结束位置 int start = 0, end = 0;
if (range != null && range.startsWith("bytes=")) { if (range != null && range.startsWith("bytes=")) {
String[] values = range.split("=")[1].split("-"); // 解析Range String[] values = range.split("=")[1].split("-");
start = Integer.parseInt(values[0]); // 获取起始位置 start = Integer.parseInt(values[0]);
if (values.length > 1) { if (values.length > 1) {
end = Integer.parseInt(values[1]); // 获取结束位置 end = Integer.parseInt(values[1]);
} }
} }
int requestSize; // 请求大小 int requestSize;
if (end != 0 && end > start) { if (end != 0 && end > start) {
requestSize = end - start + 1; // 计算请求大小 requestSize = end - start + 1;
} else { } else {
requestSize = Integer.MAX_VALUE; // 设置为最大值 requestSize = Integer.MAX_VALUE;
} }
byte[] buffer = new byte[128]; // 创建缓冲区 byte[] buffer = new byte[128];
response.setContentType(MediaUtils.getContentType(filePath)); // 设置响应内容类型 response.setContentType(MediaUtils.getContentType(filePath));
response.setHeader("Accept-Ranges", "bytes"); // 设置支持的范围 response.setHeader("Accept-Ranges", "bytes");
response.setHeader("ETag", file.getName()); // 设置文件名 response.setHeader("ETag", file.getName());
response.setHeader("Last-Modified", new Date().toString()); // 设置最后修改时间 response.setHeader("Last-Modified", new Date().toString());
//第一次请求只返回content length来让客户端请求多次实际数据 //第一次请求只返回content length来让客户端请求多次实际数据
if (range == null) { if (range == null) {
response.setHeader("Content-length", contentLength + ""); // 设置内容长度 response.setHeader("Content-length", contentLength + "");
} else { } else {
//以后的多次以断点续传的方式来返回视频数据 //以后的多次以断点续传的方式来返回视频数据
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 设置部分内容状态 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
long requestStart = 0, requestEnd = 0; // 初始化请求起始和结束位置 long requestStart = 0, requestEnd = 0;
String[] ranges = range.split("="); // 解析Range String[] ranges = range.split("=");
if (ranges.length > 1) { if (ranges.length > 1) {
String[] rangeData = ranges[1].split("-"); // 获取范围数据 String[] rangeData = ranges[1].split("-");
requestStart = Integer.parseInt(rangeData[0]); // 获取请求起始位置 requestStart = Integer.parseInt(rangeData[0]);
if (rangeData.length > 1) { if (rangeData.length > 1) {
requestEnd = Integer.parseInt(rangeData[1]); // 获取请求结束位置 requestEnd = Integer.parseInt(rangeData[1]);
} }
} }
long length; // 请求长度 long length;
if (requestEnd > 0) { if (requestEnd > 0) {
length = requestEnd - requestStart + 1; // 计算请求长度 length = requestEnd - requestStart + 1;
response.setHeader("Content-length", "" + length); // 设置内容长度 response.setHeader("Content-length", "" + length);
response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength); // 设置内容范围 response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength);
} else { } else {
length = contentLength - requestStart; // 计算请求长度 length = contentLength - requestStart;
response.setHeader("Content-length", "" + length); // 设置内容长度 response.setHeader("Content-length", "" + length);
response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1) + "/" + contentLength); // 设置内容范围 response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1) + "/" + contentLength);
} }
} }
ServletOutputStream out = response.getOutputStream(); // 获取输出流 ServletOutputStream out = response.getOutputStream();
int needSize = requestSize; // 需要的大小 int needSize = requestSize;
randomFile.seek(start); // 移动到起始位置 randomFile.seek(start);
while (needSize > 0) { while (needSize > 0) {
int len = randomFile.read(buffer); // 读取文件内容 int len = randomFile.read(buffer);
if (needSize < buffer.length) { if (needSize < buffer.length) {
out.write(buffer, 0, needSize); // 写入需要的大小 out.write(buffer, 0, needSize);
} else { } else {
out.write(buffer, 0, len); // 写入缓冲区内容 out.write(buffer, 0, len);
if (len < buffer.length) { if (len < buffer.length) {
break; // 如果读取的长度小于缓冲区长度,退出循环 break;
} }
} }
needSize -= buffer.length; // 减少需要的大小 needSize -= buffer.length;
} }
randomFile.close(); // 关闭随机访问文件 randomFile.close();
out.close(); // 关闭输出流 out.close();
} }
/** /**
* *
* * @param fileName
* * @return
* @param fileName
* @return
*/ */
public static String renameFile(String fileName) { public static String renameFile(String fileName) {
//没有后缀名不处理 //没有后缀名不处理
if (!fileName.contains(SUFFIX_SPLIT)) { if (!fileName.contains(SUFFIX_SPLIT)) {
return fileName; // 返回原文件名 return fileName;
} }
//文件后缀 //文件后缀
String extension = FilenameUtils.getExtension(fileName); // 获取文件后缀 String extension = FilenameUtils.getExtension(fileName);
//以系统时间命名 //以系统时间命名
return IdWorker.getIdStr() + "." + extension; // 返回新文件名 return IdWorker.getIdStr() + "."+ extension;
} }
/** /**
* 2021/01/01/xxx.jpg * 2021/01/01/xxx.jpg
*
*
* @param file * @param file
* @return * @return
*/ */
public static String processPath(MultipartFile file){ public static String processPath(MultipartFile file){
// 创建OSSClient实例。 // 创建OSSClient实例。
String fileName = file.getOriginalFilename(); // 获取原始文件名 String fileName = file.getOriginalFilename();
// 需要重命名 // 需要重命名
fileName = renameFile(fileName); // 重命名文件 fileName = renameFile(fileName);
//获得上传的文件夹 //获得上传的文件夹
String dir = DateUtils.formatDate(new Date(), "yyyy/MM/dd/"); // 获取当前日期格式化后的目录 String dir = DateUtils.formatDate(new Date(), "yyyy/MM/dd/");
return new StringBuffer(dir).append(fileName).toString();
return new StringBuffer(dir).append(fileName).toString(); // 返回处理后的文件路径
} }
/** /**
* *
* * @param fileName
* * @return
* @param fileName
*/ */
public static void checkDir(String fileName){ public static void checkDir(String fileName){
int index = fileName.lastIndexOf("/"); // 获取最后一个斜杠的位置 int index = fileName.lastIndexOf("/");
if(index == -1){ if(index == -1){
return; // 如果没有斜杠,返回 return;
} }
File file = new File(fileName.substring(0, index)); // 创建文件对象 File file = new File(fileName.substring(0,index));
if(!file.exists()){ if(!file.exists()){
file.mkdirs(); // 如果文件夹不存在,创建文件夹 file.mkdirs();
} }
} }
} }

@ -1,75 +1,47 @@
package com.yf.exam.ability.upload.utils; package com.yf.exam.ability.upload.utils;
// 这一行声明了该Java类所属的包名为com.yf.exam.ability.upload.utils。
// 包用于对相关的Java类进行组织和管理方便代码的分类、复用以及避免类名冲突。
import org.apache.commons.lang3.StringUtils; // 导入Apache Commons Lang的字符串工具类用于字符串操作 import org.apache.commons.lang3.StringUtils;
// 引入了Apache Commons Lang库中的StringUtils类。
// 这个类提供了许多方便的字符串操作方法,比如判断字符串是否为空、是否空白(包含空格等空白字符)、字符串的拼接、截取等操作,在这里主要用于对文件路径字符串进行相关判断。
import java.util.HashMap; // 导入Java的HashMap类用于创建映射 import java.util.HashMap;
// 导入了Java标准库中的HashMap类。 import java.util.Map;
// HashMap是实现了Map接口的一个具体类它用于存储键值对形式的数据通过键可以快速获取对应的值在这里用于创建文件后缀名到MIME类型的映射关系。
import java.util.Map; // 导入Java的Map接口用于键值对映射
// 引入了Java标准库中的Map接口。
// Map接口定义了键值对数据结构的通用操作规范如添加键值对、根据键获取值、删除键值对等操作。
// 虽然这里同时导入了HashMap类但导入Map接口使得代码在使用映射数据结构时更具通用性方便后续可能的替换为其他实现Map接口的类。
/** /**
* MIME *
* MIME便
* @author bool * @author bool
* @date 2019-11-14 16:21 * @date 2019-11-14 16:21
*/ */
// 这是一个Java类的文档注释用于描述该类的整体功能和用途。
// 说明这个类主要是作为媒体工具类其核心功能是判断和获取媒体文件的MIME类型。
// 通过维护一个静态的映射关系文件后缀名到MIME类型的映射在文件上传和下载的业务场景中能够依据文件的后缀名准确地确定其对应的MIME类型从而正确处理文件的传输和展示等操作。
// 同时标注了类的作者是bool创建日期是2019年11月14日16:21。
public class MediaUtils { public class MediaUtils {
/** public static final Map<String, String> MEDIA_MAP = new HashMap(){
* {
* MIME
*/
// 这是对下面定义的MEDIA_MAP成员变量的文档注释说明它是一个静态的映射用于存储文件后缀名和对应的MIME类型之间的映射关系。
public static final Map<String, String> MEDIA_MAP = new HashMap<String, String>() {{ //本来是pdf的
// 初始化映射
// PDF文件的MIME类型
put(".pdf", "application/pdf"); put(".pdf", "application/pdf");
// 视频文件的MIME类型
put(".mp4", "video/mp4"); //视频
}}; put(".mp4", "video,video/mp4");
// 这里定义了一个名为MEDIA_MAP的公共静态最终变量它是一个HashMap类型的映射。
// 通过匿名内部类的初始化方式在创建HashMap实例的同时向其中添加了一些常见的文件后缀名到MIME类型的映射关系比如将".pdf"后缀名映射到"application/pdf"这个MIME类型将".mp4"后缀名映射到"video/mp4"这个MIME类型。 }
// 由于被声明为静态最终变量它在类加载时就会被初始化并且其值不能再被修改方便在整个类的其他地方直接使用这个映射关系来获取文件的MIME类型。 };
/** /**
* MIME *
* MEDIA_MAPMIME * @param filePath
* * @return
* @param filePath
* @return MIME
*/ */
// 这是对下面定义的getContentType方法的文档注释说明该方法的功能是根据传入的文件路径提取出文件的后缀名然后从MEDIA_MAP这个静态映射中获取对应的MIME类型并返回。
public static String getContentType(String filePath){ public static String getContentType(String filePath){
if (!StringUtils.isBlank(filePath) && filePath.indexOf(".")!= -1) {
// 提取文件后缀名,并转换为小写 if(!StringUtils.isBlank(filePath)
&& filePath.indexOf(".")!=-1) {
// 后缀转换成小写
String suffix = filePath.substring(filePath.lastIndexOf(".")).toLowerCase(); String suffix = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
// 从映射中获取MIME类型
if (MEDIA_MAP.containsKey(suffix)) { if (MEDIA_MAP.containsKey(suffix)) {
return MEDIA_MAP.get(suffix); return MEDIA_MAP.get(suffix);
} }
} }
// 如果没有找到对应的MIME类型返回默认值
return "application/octet-stream"; return "application/octet-stream";
} }
// 这是定义的一个公共静态方法getContentType它接受一个字符串类型的参数filePath表示文件的路径。
// 首先通过StringUtils.isBlank方法判断文件路径是否不为空且包含小数点即有文件后缀名
// 如果满足条件就使用substring方法从文件路径中提取出文件的后缀名并通过toLowerCase方法将其转换为小写形式。
// 然后检查提取出的后缀名是否存在于MEDIA_MAP这个静态映射中如果存在就返回对应的MIME类型如果不存在就返回默认的MIME类型"application/octet-stream",这个默认值通常用于表示未知类型的二进制数据文件。
} }

@ -1,189 +1,156 @@
package com.yf.exam.aspect; package com.yf.exam.aspect;
// 导入FastJSON库用于将Java对象转换为JSON字符串以及从JSON字符串解析为Java对象等操作
// 在本类的多个方法中用于对象与JSON字符串之间的转换以便于处理数据字典相关的值。
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
// 导入MyBatis Plus的接口用于处理分页相关的数据结构在本类中用于判断和处理分页数据中的数据字典值。
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
// 导入Jackson库的注解用于指定日期格式的序列化和反序列化方式
// 在本类的parseObject方法中用于根据注解设置日期字段的格式化输出。
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
// 导入自定义的注解,可能用于标记与数据字典相关的字段,以便在本类中识别并处理这些字段的数据字典值。
import com.yf.exam.core.annon.Dict; import com.yf.exam.core.annon.Dict;
// 导入自定义的API响应类用于封装API调用的返回结果包括数据、状态码等信息
// 在本类的多个方法中用于获取和设置返回结果中的数据部分,以便处理其中的数据字典值。
import com.yf.exam.core.api.ApiRest; import com.yf.exam.core.api.ApiRest;
// 导入自定义的反射工具类,可能用于获取对象的所有字段等反射相关操作,
// 在本类的parseObject方法中用于获取对象的所有字段以便遍历处理数据字典值。
import com.yf.exam.core.utils.Reflections; import com.yf.exam.core.utils.Reflections;
// 导入系统数据字典服务类,用于查询数据字典表以获取数据字典值的翻译文本,
// 在本类的translateDictValue方法中用于根据字典代码、文本、表名和键值查询对应的字典文本。
import com.yf.exam.modules.sys.system.service.SysDictService; import com.yf.exam.modules.sys.system.service.SysDictService;
import lombok.extern.slf4j.Slf4j;
// 导入Lombok的Log4j2注解用于简化日志记录的配置通过该注解可以方便地在类中使用Log4j2进行日志输出。
import lombok.extern.log4j.Log4j2;
// 导入AspectJ的相关类用于定义切面、切点和环绕通知等AOP相关的操作
// 在本类中用于实现对特定方法的拦截和处理,以实现数据字典值的翻译等功能。
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
// 导入Spring框架的注解用于自动注入依赖对象和标记类为Spring组件
// 在本类中通过@Autowired注入SysDictService对象并通过@Component标记本类为Spring组件使其可被Spring容器管理。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
// 导入Spring框架的工具类用于判断字符串是否为空等操作
// 在本类的多个方法中用于判断字符串是否为空,以便进行相应的处理逻辑。
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
// 导入Java标准库中的反射相关类用于通过反射操作对象的字段、获取类型信息等
// 在本类的多个方法中广泛用于获取对象的字段、判断字段类型、获取字段注解等操作。
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
// 导入Java标准库中的日期格式化类和日期类用于处理日期格式的转换和操作
// 在本类的parseObject方法中用于根据注解或默认格式对日期字段进行格式化输出。
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
/** /**
* AOP * AOP
*
* *
* @author bool * @author bool
*/ */
@Aspect // 标记该类为一个AspectJ切面类用于定义切面相关的逻辑。 @Aspect
@Component // 标记该类为Spring组件使其能够被Spring容器管理和实例化以便在应用中使用。 @Component
@Log4j2 // 使用Log4j2注解启用日志记录功能方便在类中记录相关操作的日志信息。 @Slf4j
public class DictAspect { public class DictAspect {
@Autowired @Autowired
private SysDictService sysDictService; // 通过自动注入获取系统数据字典服务对象,用于查询数据字典值。 private SysDictService sysDictService;
/** /**
* Controller * Controller
* com.yf.examController * @param pjp
* * @return
* @param pjp * @throws Throwable
* @return
* @throws Throwable
*/ */
@Around("execution(public * com.yf.exam..*.*Controller.*(..))") @Around("execution(public * com.yf.exam..*.*Controller.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable { public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
return this.translate(pjp); // 调用translate方法对被拦截方法的执行结果进行处理主要是处理数据字典值。 return this.translate(pjp);
} }
/** /**
* * BaseDictService
* BaseDictService
* *
* @param pjp * @param pjp
* @return * @return
* @throws Throwable * @throws Throwable
*/ */
public Object translate(ProceedingJoinPoint pjp) throws Throwable { public Object translate(ProceedingJoinPoint pjp) throws Throwable {
// 调用被拦截方法获取原始结果然后调用parseAllDictText方法对结果进行数据字典值的处理。 // 处理字典
return this.parseAllDictText(pjp.proceed()); return this.parseAllDictText(pjp.proceed());
} }
/** /**
* *
* ApiRestparseFullDictText
* *
* @param result * @param result
*/ */
private Object parseAllDictText(Object result) { private Object parseAllDictText(Object result) {
// 判断结果对象是否是ApiRest类型如果是则进行数据字典值的处理。
// 非ApiRest类型不处理
if (result instanceof ApiRest) { if (result instanceof ApiRest) {
parseFullDictText(result); parseFullDictText(result);
} }
return result; return result;
} }
/** /**
* ApiRest *
* *
* @param result ApiRest * @param result
*/ */
private void parseFullDictText(Object result) { private void parseFullDictText(Object result) {
try { try {
Object rest = ((ApiRest) result).getData(); // 获取ApiRest对象中的数据部分这部分数据可能包含数据字典相关字段。
// 如果数据部分为空或者是基本数据类型,则不需要进行数据字典值的处理,直接返回。 Object rest = ((ApiRest) result).getData();
// 不处理普通数据类型
if (rest == null || this.isBaseType(rest.getClass())) { if (rest == null || this.isBaseType(rest.getClass())) {
return; return;
} }
// 如果数据部分是分页数据类型IPage则对分页数据中每条记录进行数据字典值处理。 // 分页的
if (rest instanceof IPage) { if (rest instanceof IPage) {
List<Object> items = new ArrayList<>(16); List<Object> items = new ArrayList<>(16);
for (Object record : ((IPage) rest).getRecords()) { for (Object record : ((IPage) rest).getRecords()) {
Object item = this.parseObject(record); // 调用parseObject方法对每条记录进行数据字典值处理。 Object item = this.parseObject(record);
items.add(item); items.add(item);
} }
((IPage) rest).setRecords(items); // 将处理后的记录列表重新设置回分页对象中。 ((IPage) rest).setRecords(items);
return; return;
} }
// 如果数据部分是列表数据类型List则对列表中每条记录进行数据字典值处理。 // 数据列表的
if (rest instanceof List) { if (rest instanceof List) {
List<Object> items = new ArrayList<>(); List<Object> items = new ArrayList<>();
for (Object record : ((List) rest)) { for (Object record : ((List) rest)) {
Object item = this.parseObject(record); // 调用parseObject方法对每条记录进行数据字典值处理。 Object item = this.parseObject(record);
items.add(item); items.add(item);
} }
// 将处理后的记录列表重新设置回ApiRest对象的数据部分。 // 重新回写值
((ApiRest) result).setData(items); ((ApiRest) result).setData(items);
return; return;
} }
// 如果数据部分是单对象数据,则对该单对象进行数据字典值处理。 // 处理单对象
Object item = this.parseObject(((ApiRest) result).getData()); Object item = this.parseObject(((ApiRest) result).getData());
((ApiRest) result).setData(item); ((ApiRest) result).setData(item);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); // 如果在处理过程中出现异常,则打印异常堆栈信息。 e.printStackTrace();
} }
} }
/** /**
* *
* *
* @param record * @param record
* @return * @return
*/ */
public Object parseObject(Object record) { public Object parseObject(Object record) {
if (record == null) { if (record == null) {
return null; return null;
} }
// 如果记录对象是基本数据类型,则不需要进行数据字典值处理,直接返回原对象。 // 不处理普通数据类型
if (this.isBaseType(record.getClass())) { if (this.isBaseType(record.getClass())) {
return record; return record;
} }
// 将记录对象转换JSON字符再解析为JSONObject对象以便于通过字段名获取和设置值。 // 转换JSON字符
String json = JSON.toJSONString(record); String json = JSON.toJSONString(record);
JSONObject item = JSONObject.parseObject(json); JSONObject item = JSONObject.parseObject(json);
for (Field field : Reflections.getAllFields(record)) { // 遍历记录对象的所有字段。 for (Field field : Reflections.getAllFields(record)) {
// 如果字段类型是List类型,则对列表字段进行特殊处理。 // 如果是List类型
if (List.class.isAssignableFrom(field.getType())) { if (List.class.isAssignableFrom(field.getType())) {
try { try {
List list = this.processList(field, item.getObject(field.getName(), List.class)); // 调用processList方法处理列表字段。 List list = this.processList(field, item.getObject(field.getName(), List.class));
item.put(field.getName(), list); item.put(field.getName(), list);
continue; continue;
} catch (Exception e) { } catch (Exception e) {
@ -192,14 +159,14 @@ public class DictAspect {
continue; continue;
} }
// 如果字段带有数据字典注解Dict则对该字段进行数据字典值的翻译处理。 // 处理普通字段
if (field.getAnnotation(Dict.class) != null) { if (field.getAnnotation(Dict.class) != null) {
String code = field.getAnnotation(Dict.class).dicCode(); String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText(); String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable(); String table = field.getAnnotation(Dict.class).dictTable();
String key = String.valueOf(item.get(field.getName())); String key = String.valueOf(item.get(field.getName()));
// 调用translateDictValue方法翻译字典值对应的文本根据字典代码、文本、表名和键值查询对应的字典文本。 //翻译字典值对应的txt
String textValue = this.translateDictValue(code, text, table, key); String textValue = this.translateDictValue(code, text, table, key);
if (StringUtils.isEmpty(textValue)) { if (StringUtils.isEmpty(textValue)) {
textValue = ""; textValue = "";
@ -208,22 +175,24 @@ public class DictAspect {
continue; continue;
} }
// 如果字段类型是日期类型java.util.Date且字段值不为空则对日期字段进行格式转换处理。 //日期格式转换
if ("java.util.Date".equals(field.getType().getName()) && item.get(field.getName()) != null) { if ("java.util.Date".equals(field.getType().getName()) && item.get(field.getName()) != null) {
// 获取字段上的JsonFormat注解。
// 获取注解
JsonFormat ann = field.getAnnotation(JsonFormat.class); JsonFormat ann = field.getAnnotation(JsonFormat.class);
// 定义日期格式化对象。 // 格式化方式
SimpleDateFormat fmt; SimpleDateFormat fmt;
// 如果注解不为空且指定了日期格式模式,则使用注解指定的格式创建日期格式化对象。 // 使用注解指定的
if (ann != null && !StringUtils.isEmpty(ann.pattern())) { if (ann != null && !StringUtils.isEmpty(ann.pattern())) {
fmt = new SimpleDateFormat(ann.pattern()); fmt = new SimpleDateFormat(ann.pattern());
} else { } else {
// 如果注解为空或未指定格式,则使用默认的日期格式创建日期格式化对象。 // 默认时间样式
fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
} }
item.put(field.getName(), fmt.format(new Date((Long) item.get(field.getName())))); item.put(field.getName(), fmt.format(new Date((Long) item.get(field.getName()))));
continue; continue;
} }
} }
@ -231,23 +200,23 @@ public class DictAspect {
} }
/** /**
* List * List
* *
* @param field List * @param field
* @return
* @param list
* @return
*/ */
private List<Object> processList(Field field, List list) { private List<Object> processList(Field field, List list) {
// 如果列表为空则返回一个空的ArrayList对象。
// 空判断
if (list == null || list.size() == 0) { if (list == null || list.size() == 0) {
return new ArrayList<>(); return new ArrayList<>();
} }
// 获取List属性的真实类型通过反射获取字段的泛型类型再尝试获取其实际的类型参数。 // 获得List属性的真实类
Type genericType = field.getGenericType(); Type genericType = field.getGenericType();
Class<?> actualType = null; Class<?> actualType = null;
if (genericType instanceof ParameterizedType) { if (genericType instanceof ParameterizedType) {
// 尝试获取数据类型
ParameterizedType pt = (ParameterizedType) genericType; ParameterizedType pt = (ParameterizedType) genericType;
try { try {
actualType = (Class) pt.getActualTypeArguments()[0]; actualType = (Class) pt.getActualTypeArguments()[0];
@ -256,28 +225,24 @@ public class DictAspect {
} }
} }
// 如果列表元素的类型是基本数据类型,则不需要进行数据字典值处理,直接返回原列表。 // 常规列表无需处理
if (isBaseType(actualType)) { if (isBaseType(actualType)) {
return list; return list;
} }
// 创建一个新的ArrayList对象用于存储处理后的列表元素。 // 返回列表
List<Object> result = new ArrayList<>(16); List<Object> result = new ArrayList<>(16);
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
// 获取列表中的每个元素。 // 创建实例-->赋值-->字典处理
Object data = list.get(i); Object data = list.get(i);
try { try {
// 将列表元素转换为JSON字符串再解析为其真实类型的对象以便进行数据字典值处理。
data = JSON.parseObject(JSON.toJSONString(data), actualType); data = JSON.parseObject(JSON.toJSONString(data), actualType);
}catch (Exception e){ }catch (Exception e){
// 如果转换过程中出现错误,则不进行处理,直接使用原元素。 // 转换出错不处理
// 这里可以根据实际需求进一步处理错误情况,比如记录日志等。
// 目前只是简单地忽略错误,继续处理下一个元素。
;
} }
// 处理后的元素进行数据字典值处理调用parseObject方法。 // 处理后的数据
Object pds = this.parseObject(data); Object pds = this.parseObject(data);
result.add(pds); result.add(pds);
} }
@ -286,24 +251,22 @@ public class DictAspect {
} }
/** /**
*
*
* *
* @param code * @param code
* @param text * @param text
* @param table * @param table
* @param key * @param key
* @return * @return
*/ */
private String translateDictValue(String code, String text, String table, String) { private String translateDictValue(String code, String text, String table, String key) {
if (StringUtils.isEmpty(key)) { if (StringUtils.isEmpty(key)) {
return null; return null;
} }
try { try {
// 定义变量用于存储翻译后的字典文本 // 翻译值
String dictText = null; String dictText = null;
if (!StringUtils.isEmpty(table)) { if (!StringUtils.isEmpty(table)) {
// 如果字典表名不为空则调用sysDictService的findDict方法查询数据字典表获取对应的字典文本值。
dictText = sysDictService.findDict(table, text, code, key.trim()); dictText = sysDictService.findDict(table, text, code, key.trim());
} }
@ -317,13 +280,15 @@ public class DictAspect {
} }
/** /**
* *
* *
* @param clazz * @param clazz
* @return truefalse * @return
*/ */
private boolean isBaseType(Class clazz) { private boolean isBaseType(Class clazz) {
// 判断是否是常见的基本数据类型,如整数、字节、长整数等。
// 基础数据类型
if (clazz.equals(java.lang.Integer.class) || if (clazz.equals(java.lang.Integer.class) ||
clazz.equals(java.lang.Byte.class) || clazz.equals(java.lang.Byte.class) ||
clazz.equals(java.lang.Long.class) || clazz.equals(java.lang.Long.class) ||
@ -335,16 +300,18 @@ public class DictAspect {
return true; return true;
} }
// 判断是否是字符串类型。 // String类型
if (clazz.equals(java.lang.String.class)) { if (clazz.equals(java.lang.String.class)) {
return true; return true;
} }
// 判断是否是数字类型这里的数字类型可能是指抽象的数字类型比如Number的子类等 // 数字
if (clazz.equals(java.lang.Number.class)) { if (clazz.equals(java.lang.Number.class)) {
return true; return true;
} }
return false; return false;
} }
} }

@ -1,190 +1,90 @@
package com.yf.exam.aspect.mybatis; package com.yf.exam.aspect.mybatis;
// 导入MyBatis Plus的分页拦截器类用于实现分页功能
// 本类继承自该类以在拦截查询操作时能正确处理分页相关逻辑。
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
// 导入系统用户登录信息的数据传输对象DTO用于获取当前登录用户的相关信息
// 比如在处理查询拦截时可能需要根据登录用户的信息来过滤或修改查询语句。
import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
// 导入Lombok的Log4j2注解用于简化日志记录的配置通过该注解可以方便地在类中使用Log4j2进行日志输出。
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
// 导入JSqlParser库中的解析器管理器类用于解析SQL语句
// 在本类中用于解析查询语句以便进行后续的处理如替换用户ID等操作。
import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.parser.CCJSqlParserManager;
// 导入JSqlParser库中表示简单查询语句的类它是查询语句的一种具体表示形式
// 在解析查询语句后可以获取到该对象来进一步处理查询语句的内容。
import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.PlainSelect;
// 导入JSqlParser库中表示查询语句的通用类用于接收解析后的查询语句对象
// 以便进行后续的类型转换和处理操作。
import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.Select;
// 导入Apache Commons Lang3库中的StringUtils类用于处理字符串相关的操作
// 如判断字符串是否为空、非空等情况在本类中用于判断用户ID等字符串是否有效。
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
// 导入MyBatis中的语句处理器接口类它是MyBatis在执行SQL语句时涉及的一个重要组件
// 在本类中作为拦截的目标对象类型,以便在其执行查询准备阶段进行拦截操作。
import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.executor.statement.StatementHandler;
// 导入MyBatis中的映射语句类它包含了关于SQL语句的映射信息
// 如SQL语句的ID、参数映射、结果映射等在本类中用于获取SQL语句的相关属性如语句类型等。
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement;
// 导入MyBatis中的SQL命令类型枚举类用于表示不同类型的SQL命令
// 如SELECT、INSERT、UPDATE、DELETE等在本类中用于判断当前拦截的SQL语句是否为查询语句。
import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlCommandType;
// 导入MyBatis的拦截器接口类定义了拦截器的基本行为和方法
// 本类实现了该接口以作为一个MyBatis的拦截器来拦截查询语句的执行过程。
import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Interceptor;
// 导入MyBatis的拦截器注解类用于标注一个类是MyBatis的拦截器并指定拦截的目标和方法
// 本类通过该注解指定了要拦截StatementHandler的prepare方法。
import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Intercepts;
// 导入MyBatis的拦截器调用类用于在拦截器方法中传递被拦截方法的调用信息
// 包括被拦截的目标对象、方法参数等在本类的intercept方法中会接收到该对象来处理拦截逻辑。
import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Invocation;
// 导入MyBatis的插件包装类用于将拦截器包装成MyBatis可识别的插件形式
// 在本类的plugin方法中会使用该类来包装目标对象以便实现拦截功能。
import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Plugin;
// 导入MyBatis的拦截器签名类用于定义拦截器拦截的具体目标、方法和参数类型
// 在本类的@Intercepts注解中会使用该类来指定具体的拦截信息。
import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.plugin.Signature;
// 导入MyBatis的默认反射工厂类用于创建反射对象来访问和修改MyBatis相关对象的属性
// 在本类中用于创建MetaObject对象来操作StatementHandler等对象的属性。
import org.apache.ibatis.reflection.DefaultReflectorFactory; import org.apache.ibatis.reflection.DefaultReflectorFactory;
// 导入MyBatis的元对象类它提供了一种通过反射来访问和操作MyBatis相关对象属性的机制
// 在本类中用于获取和设置StatementHandler等对象的内部属性如SQL语句等。
import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.MetaObject;
// 导入MyBatis的系统元对象类它是MetaObject的一种特殊实现提供了一些默认的对象工厂和包装器工厂
// 在本类中用于创建MetaObject对象来操作StatementHandler等对象的属性。
import org.apache.ibatis.reflection.SystemMetaObject; import org.apache.ibatis.reflection.SystemMetaObject;
// 导入Apache Shiro的安全工具类用于获取当前安全上下文的相关信息
// 在本类中用于获取当前登录用户的信息,以便根据用户信息来处理查询语句。
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
// 导入Java标准库中的字符串读取器类用于将字符串转换为可读取的流形式
// 在本类中用于将SQL语句字符串提供给CCJSqlParserManager进行解析。
import java.io.StringReader; import java.io.StringReader;
// 导入Java标准库中的数据库连接接口类它是Java数据库操作中与数据库建立连接的关键接口
// 在本类中作为StatementHandler的prepare方法的参数类型之一出现虽然在本类代码中未直接对其进行复杂操作
// 但它是MyBatis执行SQL语句过程中涉及到的重要组件之一。
import java.sql.Connection; import java.sql.Connection;
// 导入Java标准库中的属性类用于存储和管理键值对形式的属性信息
// 在本类中作为Interceptor接口的setProperties方法的参数类型虽然在本类代码中该方法暂未实现具体功能
// 但它是符合Interceptor接口规范的一部分可用于接收外部配置的属性信息来动态调整拦截器的行为。
import java.util.Properties; import java.util.Properties;
/** /**
* ID * ID
* PaginationInterceptor * PaginationInterceptor
*
* @author bool * @author bool
*/ */
@Log4j2 @Log4j2
// 使用@Intercepts注解指定该类作为MyBatis的拦截器要拦截的目标和方法。 @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),})
// 这里拦截的是StatementHandler类的prepare方法并且该方法的参数类型为Connection和Integer。
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class QueryInterceptor extends PaginationInterceptor implements Interceptor { public class QueryInterceptor extends PaginationInterceptor implements Interceptor {
/** /**
* IDSQLID * ID
*/ */
private static final String USER_FILTER = "{{userId}}"; private static final String USER_FILTER = "{{userId}}";
/**
* StatementHandlerprepare
*
* @param invocation
* @return
* @throws Throwable
*/
@Override @Override
public Object intercept(Invocation invocation) throws Throwable { public Object intercept(Invocation invocation) throws Throwable {
// 从invocation对象中获取被拦截的目标对象即StatementHandler对象。
StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 使用MetaObject.forObject方法创建一个元对象用于通过反射访问和修改StatementHandler对象的属性。
// 这里传入了默认的对象工厂、包装器工厂和反射工厂以便能够正确地操作StatementHandler对象的内部属性。
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
// 从元对象中获取MappedStatement对象该对象包含了关于SQL语句的映射信息如SQL语句的ID、参数映射、结果映射等。
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 获取当前SQL语句的类型通过MappedStatement对象的getSqlCommandType方法获取。 //sql语句类型
// 该类型是一个枚举值如SELECT、INSERT、UPDATE、DELETE等用于判断当前拦截的SQL语句是何种类型的操作。
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
// 只对查询类型的SQL语句进行处理如果当前SQL语句类型是SELECT则进入下面的处理逻辑。 // 只过滤查询的
if (SqlCommandType.SELECT == sqlCommandType) { if (SqlCommandType.SELECT == sqlCommandType) {
// 获得原始SQL
// 获取原始的SQL语句通过StatementHandler的getBoundSql方法获取绑定的SQL语句对象再获取其SQL字符串。
String sql = statementHandler.getBoundSql().getSql(); String sql = statementHandler.getBoundSql().getSql();
// 如果原始SQL语句中不包含用户ID占位符USER_FILTER则直接调用父类PaginationInterceptor的intercept方法 // 不处理
// 即按照原有的分页逻辑进行处理不进行用户ID相关的替换等操作。
if(!sql.contains(USER_FILTER)){ if(!sql.contains(USER_FILTER)){
return super.intercept(invocation); return super.intercept(invocation);
} }
// 处理SQL语句
// 如果原始SQL语句中包含用户ID占位符则需要对SQL语句进行处理。
// 首先调用parseSql方法对SQL语句进行解析和处理包括替换用户ID等操作。
String outSql = this.parseSql(sql); String outSql = this.parseSql(sql);
// 设置SQL
// 将处理后的SQL语句设置回StatementHandler对象的内部属性中通过元对象的setValue方法设置boundSql.sql属性的值。
metaObject.setValue("delegate.boundSql.sql", outSql); metaObject.setValue("delegate.boundSql.sql", outSql);
// 再分页
// 再次调用父类PaginationInterceptor的intercept方法此时是在处理完用户ID等信息后
// 按照正确的分页逻辑进行处理,以确保分页功能在拦截数据并处理相关信息后正确执行。
return super.intercept(invocation); return super.intercept(invocation);
} }
// 如果当前SQL语句不是查询类型SELECT则直接执行被拦截方法的原有逻辑不进行额外的处理。
return invocation.proceed(); return invocation.proceed();
} }
/**
* MyBatis
*
* @param target StatementHandler
* @return 便MyBatis
*/
@Override @Override
public Object plugin(Object target) { public Object plugin(Object target) {
return Plugin.wrap(target, this); return Plugin.wrap(target, this);
} }
/**
*
* Interceptor
*
* @param properties Properties
*/
@Override @Override
public void setProperties(Properties properties) { public void setProperties(Properties properties) {
} }
/** /**
* Apache ShiroSecurityUtils *
* SysUserLoginDTOnull * @return
*
* @return SysUserLoginDTOnull
*/ */
private SysUserLoginDTO getLoginUser() { private SysUserLoginDTO getLoginUser() {
@ -196,60 +96,49 @@ public class QueryInterceptor extends PaginationInterceptor implements Intercept
} }
/** /**
* IDIDSQLIDUSER_FILTERID * ID
* * @param sql
* @param sql SQL * @return
* @return IDSQLIDnull
*/ */
private String processUserId(String sql) { private String processUserId(String sql) {
// 获取当前登录用户的信息通过调用getLoginUser方法获取。 // 当前用户
SysUserLoginDTO user = this.getLoginUser(); SysUserLoginDTO user = this.getLoginUser();
String userId = user.getId(); String userId = user.getId();
// 如果获取到的用户ID不为空则将原始SQL语句中的用户ID占位符替换为实际的用户ID并返回替换后的SQL语句。
if(StringUtils.isNotBlank(userId)){ if(StringUtils.isNotBlank(userId)){
return sql.replace(USER_FILTER, userId); return sql.replace(USER_FILTER, userId);
} }
// 如果用户ID为空则返回null。
return null; return null;
} }
/** /**
* SQLID *
* * @param src
* @param src SQL * @return
* @return SQLSQL
*/ */
private String parseSql(String src) { private String parseSql(String src) {
// 创建一个CCJSqlParserManager对象用于解析SQL语句。
CCJSqlParserManager parserManager = new CCJSqlParserManager(); CCJSqlParserManager parserManager = new CCJSqlParserManager();
try { try {
// 使用CCJSqlParserManager对象的parse方法解析原始SQL语句将其转换为Select类型的对象。
// 这里需要将原始SQL语句通过StringReader转换为可读取的流形式提供给parse方法进行解析。
Select select = (Select) parserManager.parse(new StringReader(src)); Select select = (Select) parserManager.parse(new StringReader(src));
// 从Select对象中获取其查询主体部分即PlainSelect对象它包含了查询语句的具体内容如查询条件、字段等。
PlainSelect selectBody = (PlainSelect) select.getSelectBody(); PlainSelect selectBody = (PlainSelect) select.getSelectBody();
// 将PlainSelect对象转换为字符串形式得到初步处理后的SQL语句。 // 过滤客户
// 这里主要是为了后续方便进行用户ID等信息的替换操作。
String sql = selectBody.toString(); String sql = selectBody.toString();
// 调用processUserId方法对初步处理后的SQL语句进行用户ID的替换操作将用户ID占位符替换为实际的用户ID。 // 过滤用户ID
sql = this.processUserId(sql); sql = this.processUserId(sql);
// 返回处理后的SQL语句。 // 获得SQL
return sql; return sql;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
// 如果在处理过程中出现异常则返回原始的SQL语句。
return src; return src;
} }
} }

@ -1,188 +1,80 @@
// 定义包路径用于存放更新拦截器相关的类。这个包名表明该类属于特定的项目模块com.yf.exam下的aspect.mybatis部分
// 通常用于组织和管理与MyBatis相关的切面逻辑代码。
package com.yf.exam.aspect.mybatis; package com.yf.exam.aspect.mybatis;
// 导入MyBatis-Plus的抽象SQL解析处理器类它提供了一些基础的SQL解析处理功能
// 本类继承自该类以便在处理更新操作时利用其相关功能或遵循其处理规范。
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
// 导入Apache Commons Lang3库中的数组工具类用于对数组进行各种操作
// 如合并数组等,在本类中用于合并对象的字段数组(当存在父类字段时)。
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
// 导入MyBatis中的执行器接口类它是MyBatis执行SQL语句的核心组件负责实际执行数据库操作
// 在本类中作为拦截的目标对象类型通过拦截其update方法来实现自动设置创建时间和更新时间的功能。
import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.Executor;
// 导入MyBatis中的映射语句类它包含了关于SQL语句的映射信息
// 如SQL语句的ID、参数映射、结果映射等在本类中用于获取SQL语句的相关属性如语句类型等
// 以便根据不同的操作类型(插入、更新等)来处理创建时间和更新时间的赋值。
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement;
// 导入MyBatis中的SQL命令类型枚举类用于表示不同类型的SQL命令
// 如SELECT、INSERT、UPDATE、DELETE等在本类中用于判断当前拦截的SQL语句是何种类型的操作
// 从而确定是否需要对创建时间和更新时间进行赋值。
import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlCommandType;
// 导入MyBatis的拦截器接口类定义了拦截器的基本行为和方法
// 本类实现了该接口以作为一个MyBatis的拦截器来拦截更新语句的执行过程实现自动设置时间的功能。
import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Interceptor;
// 导入MyBatis的拦截器注解类用于标注一个类是MyBatis的拦截器并指定拦截的目标和方法
// 本类通过该注解指定了要拦截Executor类的update方法。
import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Intercepts;
// 导入MyBatis的拦截器调用类用于在拦截器方法中传递被拦截方法的调用信息
// 包括被拦截的目标对象、方法参数等在本类的intercept方法中会接收到该对象来处理拦截逻辑。
import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Invocation;
// 导入MyBatis的插件包装类用于将拦截器包装成MyBatis可识别的插件形式
// 在本类的plugin方法中会使用该类来包装目标对象以便实现拦截功能。
import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Plugin;
// 导入MyBatis的拦截器签名类用于定义拦截器拦截的具体目标、方法和参数类型
// 在本类的@Intercepts注解中会使用该类来指定具体的拦截信息。
import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.plugin.Signature;
// 导入Java标准库中的反射字段类用于通过反射操作对象的私有字段
// 在本类中用于获取和设置对象的创建时间和更新时间字段的值。
import java.lang.reflect.Field; import java.lang.reflect.Field;
// 导入Java标准库中的时间戳类用于表示某个特定时刻的时间点
// 在本类中用于设置创建时间和更新时间为当前的时间戳,以记录操作发生的准确时间。
import java.sql.Timestamp; import java.sql.Timestamp;
// 导入Java标准库中的对象工具类提供了一些用于比较和操作对象的方法
// 在本类中用于比较字段名与预定义的创建时间、更新时间字段名是否相等。
import java.util.Objects; import java.util.Objects;
// 导入Java标准库中的属性类用于存储和管理键值对形式的属性信息
// 在本类中作为Interceptor接口的setProperties方法的参数类型虽然在本类代码中该方法暂未实现具体功能
// 但它是符合Interceptor接口规范的一部分可用于接收外部配置的属性信息来动态调整拦截器的行为。
import java.util.Properties; import java.util.Properties;
/** /**
* *
* MyBatisExecutorupdate
*
* @author bool * @author bool
*/ */
@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) @Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
// 使用@Intercepts注解并通过@Signature指定该类作为MyBatis的拦截器要拦截的目标和方法。
// 这里拦截的是Executor类的update方法并且该方法的参数类型为MappedStatement和Object。
public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor { public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor {
/** /**
* 便 *
*/ */
private static final String CREATE_TIME = "createTime"; private static final String CREATE_TIME = "createTime";
/** /**
* 便 *
*/ */
private static final String UPDATE_TIME = "updateTime"; private static final String UPDATE_TIME = "updateTime";
/**
* Executorupdate
*
* @param invocation
* @return
* @throws Throwable
*/
@Override @Override
public Object intercept(Invocation invocation) throws Throwable { public Object intercept(Invocation invocation) throws Throwable {
// 从invocation对象的参数数组中获取第一个参数即MappedStatement对象
// 它包含了关于SQL语句的映射信息用于后续获取SQL命令类型等操作。
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// SQL操作命令
// 获取当前SQL语句的类型通过MappedStatement对象的getSqlCommandType方法获取。
// 该类型是一个枚举值如SELECT、INSERT、UPDATE、DELETE等用于判断当前拦截的SQL语句是何种类型的操作。
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
// 获取新增或修改的对象参数
// 从invocation对象的参数数组中获取第二个参数即要进行新增或修改操作的对象参数
// 后续会通过反射操作该对象来设置创建时间和更新时间字段的值。
Object parameter = invocation.getArgs()[1]; Object parameter = invocation.getArgs()[1];
// 获取对象中所有的私有成员变量(对应表字段)
// 获取对象中所有的私有成员变量对应表字段通过反射获取parameter对象的声明字段数组。
Field[] declaredFields = parameter.getClass().getDeclaredFields(); Field[] declaredFields = parameter.getClass().getDeclaredFields();
// 判断parameter对象的父类是否存在如果存在则获取父类的声明字段数组
// 并将其与之前获取的子类声明字段数组进行合并,以获取完整的包含父类和子类字段的数组。
if (parameter.getClass().getSuperclass() != null) { if (parameter.getClass().getSuperclass() != null) {
Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields(); Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields();
declaredFields = ArrayUtils.addAll(declaredFields, superField); declaredFields = ArrayUtils.addAll(declaredFields, superField);
} }
// 用于临时存储当前遍历到的字段名,以便后续与预定义的创建时间、更新时间字段名进行比较。
String fieldName = null; String fieldName = null;
// 遍历获取到的所有字段数组(包含父类和子类字段),对每个字段进行处理。
for (Field field : declaredFields) { for (Field field : declaredFields) {
fieldName = field.getName(); fieldName = field.getName();
// 如果当前遍历到的字段名与预定义的创建时间字段名相等,说明找到了创建时间字段。
if (Objects.equals(CREATE_TIME, fieldName)) { if (Objects.equals(CREATE_TIME, fieldName)) {
// 如果当前SQL语句的操作类型是插入操作INSERT则需要为创建时间字段设置当前时间。
if (SqlCommandType.INSERT.equals(sqlCommandType)) { if (SqlCommandType.INSERT.equals(sqlCommandType)) {
// 通过反射设置该字段可访问,因为私有字段默认是不可直接访问的。
field.setAccessible(true); field.setAccessible(true);
// 使用反射设置该字段的值为当前的时间戳通过System.currentTimeMillis()获取当前时间的毫秒数,
// 再创建一个Timestamp对象来表示该时间点从而为创建时间字段赋值。
field.set(parameter, new Timestamp(System.currentTimeMillis())); field.set(parameter, new Timestamp(System.currentTimeMillis()));
} }
} }
// 如果当前遍历到的字段名与预定义的更新时间字段名相等,说明找到了更新时间字段。
if (Objects.equals(UPDATE_TIME, fieldName)) { if (Objects.equals(UPDATE_TIME, fieldName)) {
// 如果当前SQL语句的操作类型是插入操作INSERT或者更新操作UPDATE
// 则需要为更新时间字段设置当前时间。
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// 通过反射设置该字段可访问,因为私有字段默认是不可直接访问的。
field.setAccessible(true); field.setAccessible(true);
// 使用反射设置该字段的值为当前的时间戳通过System.currentTimeMillis()获取当前时间的毫秒数,
// 再创建一个Timestamp对象来表示该时间点从而为更新时间字段赋值。
field.set(parameter, new Timestamp(System.currentTimeMillis())); field.set(parameter, new Timestamp(System.currentTimeMillis()));
} }
} }
} }
// 继续执行被拦截的Executor的update方法的原有逻辑经过上述处理后
// 已经为创建时间和更新时间字段设置了合适的值(如果符合条件的话),现在继续执行原始的更新操作。
return invocation.proceed(); return invocation.proceed();
} }
/**
* MyBatis
*
* @param target Executor
* @return 便MyBatis
*/
@Override @Override
public Object plugin(Object target) { public Object plugin(Object target) {
// 如果目标对象是Executor类型说明是我们要拦截的对象
// 则使用Plugin.wrap方法将拦截器this包装到目标对象上以便实现拦截功能。
if (target instanceof Executor) { if (target instanceof Executor) {
return Plugin.wrap(target, this); return Plugin.wrap(target, this);
} }
// 如果目标对象不是Executor类型说明不是我们要拦截的对象直接返回该目标对象即可。
return target; return target;
} }
/**
*
* Interceptor
*
* @param properties Properties
*/
@Override @Override
public void setProperties(Properties properties) { public void setProperties(Properties properties) {
// 这里暂时没有具体的设置属性操作,可根据后续需求添加相关逻辑来处理接收到的属性信息。
} }
} }

@ -1,128 +1,99 @@
// 定义包路径用于存放注入工具类相关的代码。这个包名表明该类属于特定的项目模块com.yf.exam下的aspect.utils部分
// 通常用于组织和管理与注入操作相关的工具类代码。
package com.yf.exam.aspect.utils; package com.yf.exam.aspect.utils;
// 导入FastJSON库它是一个用于将Java对象与JSON字符串进行相互转换的高性能库
// 在本类的restError方法中用于将API响应对象转换为JSON字符串以便写入HTTP响应中。
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
// 导入API错误类它可能定义了一系列表示不同类型API错误的常量或属性
// 在本类的restError方法中用于创建包含特定错误信息的API响应对象。
import com.yf.exam.core.api.ApiError; import com.yf.exam.core.api.ApiError;
// 导入API响应类它用于封装API调用的返回结果包括成功的结果以及可能出现的错误信息等
// 在本类的restError方法中用于创建要返回给客户端的错误响应对象。
import com.yf.exam.core.api.ApiRest; import com.yf.exam.core.api.ApiRest;
// 导入Lombok的Log4j2注解用于简化日志记录的配置通过该注解可以方便地在类中使用Log4j2进行日志输出。
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
// 导入Spring框架的组件注解用于标记该类是一个Spring组件这样Spring容器可以对其进行管理和实例化
// 使其能够在Spring应用程序中被其他组件所使用。
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
// 导入Java标准库中的HTTP响应类用于处理HTTP协议相关的响应操作
// 在本类的restError方法中用于设置响应的编码、内容类型以及写入响应内容等操作。
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
// 导入Java标准库中的IO异常类用于处理输入输出操作过程中可能出现的异常情况
// 在本类的restError方法以及其他可能涉及到IO操作的地方用于捕获和处理相关异常。
import java.io.IOException; import java.io.IOException;
// 导入Java标准库中的反射字段类用于通过反射操作对象的私有字段
// 在本类的setValue和getFiled方法中用于获取和设置对象的指定字段的值。
import java.lang.reflect.Field; import java.lang.reflect.Field;
/** /**
* *
* @author bool * @author bool
* @date 2019-07-17 09:32 * @date 2019-07-17 09:32
*/ */
@Log4j2 // 使用Log4j2注解启用日志记录功能方便在类中记录相关操作的日志信息。 @Log4j2
@Component // 将该类标记为Spring组件使其能够被Spring容器管理和使用。 @Component
public class InjectUtils { public class InjectUtils {
/** /**
* *
* *
* @param object * @param object
* @param value * @param value
* @param fields * @param fields
* @throws Exception * @throws Exception
*/ */
public void setValue(Object object, Object value, String... fields) throws Exception { public void setValue(Object object, Object value, String... fields) throws Exception {
// 遍历要赋值的字段名数组
//设置同类的属性
for (String fieldName : fields) { for (String fieldName : fields) {
// 根据对象的类和字段名获取对应的字段对象
Field field = this.getFiled(object.getClass(), fieldName);
// 如果获取到的字段对象为空,说明未找到对应的字段,继续下一个字段的处理。 //获取当前
Field field = this.getFiled(object.getClass(), fieldName);
if(field == null){ if(field == null){
continue; continue;
} }
// 通过反射设置该字段可访问,因为私有字段默认是不可直接访问的。
field.setAccessible(true); field.setAccessible(true);
// 使用反射设置该字段的值为传入的值,即将指定的值赋给对象的指定字段。
field.set(object, value); field.set(object, value);
} }
} }
/** /**
* *
* *
* @param clazz * @param clazz
* @param fieldName * @param fieldName
* @return null
*/ */
private Field getFiled(Class clazz, String fieldName) { private Field getFiled(Class clazz, String fieldName) {
System.out.println("注入的类:" + clazz.toString()); // 打印当前正在查找字段的目标类的信息,方便调试查看。
// 尝试获取当前类中指定字段名的字段对象 System.out.println("注入的类:"+clazz.toString());
//是否具有包含关系
try { try {
//获取当前类的属性
return clazz.getDeclaredField(fieldName); return clazz.getDeclaredField(fieldName);
}catch (Exception e){ }catch (Exception e){
log.error(clazz.toString() + ": not exist field, try superclass " + fieldName); // 如果获取字段失败,记录错误日志,
// 提示在当前类中不存在指定字段,
// 并准备尝试在父类中查找。
// 如果当前类未找到指定字段且存在父类,则递归调用本方法在父类中继续查找指定字段。 log.error(clazz.toString() + ": not exist field, try superclass " + fieldName);
//如果为空且存在父类,则往上找
if(clazz.getSuperclass()!=null){ if(clazz.getSuperclass()!=null){
return this.getFiled(clazz.getSuperclass(), fieldName); return this.getFiled(clazz.getSuperclass(), fieldName);
} }
// 如果在当前类及其所有父类中都未找到指定字段则返回null。
return null; return null;
} }
} }
/** /**
* HTTP *
* * @param response
* @param response HTTP * @throws IOException
* @throws IOException IO
*/ */
public static void restError(HttpServletResponse response) { public static void restError(HttpServletResponse response) {
try { try {
// 创建一个包含特定API错误信息的API响应对象这里使用了ApiError.ERROR_10010002作为错误码
// 具体含义可能在ApiError类中定义通常表示某种常见的错误情况。
ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002);
// 设置HTTP响应的字符编码为UTF-8确保能够正确处理和传输各种字符特别是中文等非ASCII字符。 //固定错误
ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002);
response.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");
// 设置HTTP响应的内容类型为application/json表明返回给客户端的内容是JSON格式的数据。
response.setContentType("application/json"); response.setContentType("application/json");
// 将创建的API响应对象转换为JSON字符串并写入到HTTP响应的输出流中以便客户端能够接收到错误信息。
response.getWriter().write(JSON.toJSONString(apiRest)); response.getWriter().write(JSON.toJSONString(apiRest));
// 关闭HTTP响应的写入流释放相关资源。
response.getWriter().close(); response.getWriter().close();
}catch (IOException e){ }catch (IOException e){
// 如果在设置响应属性或写入响应内容过程中出现IO异常这里只是简单地捕获异常
// 可以根据实际需求进一步处理异常,比如记录更详细的日志信息等。
} }
} }
} }

@ -1,81 +1,35 @@
package com.yf.exam.config; package com.yf.exam.config;
// 导入Spring Boot中用于注册Servlet过滤器的类通过它可以将自定义的过滤器注册到Servlet容器中
// 并配置相关属性如过滤器的执行顺序等。在本类中用于注册跨域过滤器CorsFilter
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
// 导入Spring框架中用于标记一个类是配置类的注解被该注解标记的类可以在其中定义各种Bean配置方法
// 这些方法会被Spring容器在启动时自动识别并执行用于创建和配置应用程序所需的各种组件。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
// 导入Spring框架中用于指定顺序的接口实现该接口可以定义对象的顺序
// 在本类中用于指定跨域过滤器CorsFilter在过滤器链中的执行顺序使其具有最高优先级。
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
// 导入Spring框架中用于配置跨域资源共享Cors的类通过它可以设置允许的源、请求头、请求方法等跨域相关的配置项。
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
// 导入Spring框架中基于URL的跨域配置源类它允许根据不同的URL模式来配置不同的跨域设置
// 在本类中用于创建一个基于URL的跨域配置源并将统一的跨域配置应用到所有的URL路径"**")上。
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
// 导入Spring框架中实现跨域过滤功能的类它会根据配置的跨域规则对请求进行过滤和处理
// 以实现允许跨域访问的功能。在本类中创建该过滤器并将其注册到Servlet容器中。
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
/** /**
* 访 *
* CorsFilter
* 使访
*
* @author bool * @author bool
* @date 2019-08-13 17:28 * @date 2019-08-13 17:28
*/ */
@Configuration // 标记该类为Spring框架的配置类表明其中可以定义各种Bean的创建和配置方法。
@Configuration
public class CorsConfig { public class CorsConfig {
/**
* BeanFilterRegistrationBeanCorsFilter
*
* @return FilterRegistrationBean
*/
@Bean @Bean
public FilterRegistrationBean corsFilter() { public FilterRegistrationBean corsFilter() {
// 创建一个基于URL的跨域配置源对象它将作为跨域配置的基础用于后续设置跨域规则并应用到特定的URL路径上。
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 创建一个CorsConfiguration对象用于设置具体的跨域配置项如允许的源、请求头、请求方法等。
CorsConfiguration config = new CorsConfiguration(); CorsConfiguration config = new CorsConfiguration();
// 设置是否允许携带凭证如Cookie等进行跨域请求这里设置为true表示允许携带凭证。
config.setAllowCredentials(true); config.setAllowCredentials(true);
// 添加允许的源这里使用CorsConfiguration.ALL表示允许所有的源进行跨域访问。
// 可以根据实际需求设置具体的源地址,如"http://example.com"等多个源地址可以多次调用addAllowedOrigin方法添加。
config.addAllowedOrigin(CorsConfiguration.ALL); config.addAllowedOrigin(CorsConfiguration.ALL);
// 添加允许的请求头同样使用CorsConfiguration.ALL表示允许所有的请求头进行跨域请求。
// 如果需要限制特定的请求头,可以通过其他方式设置具体的请求头列表。
config.addAllowedHeader(CorsConfiguration.ALL); config.addAllowedHeader(CorsConfiguration.ALL);
// 添加允许的请求方法使用CorsConfiguration.ALL表示允许所有的请求方法进行跨域请求
// 包括常见的GET、POST、PUT、DELETE等方法。也可以根据实际需求设置具体的请求方法列表。
config.addAllowedMethod(CorsConfiguration.ALL); config.addAllowedMethod(CorsConfiguration.ALL);
// 将上述设置好的跨域配置应用到所有的URL路径上"**"表示匹配任意的URL路径。
// 这样,对于应用程序接收到的任何请求,都会根据这个统一的跨域配置进行处理。
source.registerCorsConfiguration("/**", config); source.registerCorsConfiguration("/**", config);
// 创建一个FilterRegistrationBean对象用于注册跨域过滤器CorsFilter
// 将创建好的基于上述跨域配置源的跨域过滤器new CorsFilter(source)作为参数传入FilterRegistrationBean的构造函数。
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
// 设置跨域过滤器在过滤器链中的执行顺序这里设置为Ordered.HIGHEST_PRECEDENCE表示具有最高优先级。
// 即该过滤器会在其他过滤器之前首先对请求进行处理,以确保跨域配置能够最先生效。
bean.setOrder(Ordered.HIGHEST_PRECEDENCE); bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
// 返回配置好的FilterRegistrationBean对象完成跨域过滤器的注册和相关设置。
return bean; return bean;
} }
} }

@ -1,67 +1,28 @@
// 定义包路径用于存放文件上传配置类相关的代码。这个包名表明该类属于特定的项目模块com.yf.exam下的config部分
// 通常用于组织和管理与文件上传配置相关的代码。
package com.yf.exam.config; package com.yf.exam.config;
// 导入Spring Boot中用于创建和配置多部分Multipart上传相关设置的工厂类。
// 通过这个工厂类可以方便地设置如单个文件大小限制、总上传数据大小限制等参数,
// 在本类中用于创建和配置文件上传的相关限制。
import org.springframework.boot.web.servlet.MultipartConfigFactory; import org.springframework.boot.web.servlet.MultipartConfigFactory;
// 导入Spring框架中用于标记一个方法返回值为Spring Bean的注解。
// 被该注解标记的方法其返回值会被Spring容器管理作为一个可被注入到其他组件中的Bean实例。
// 在本类中用于将multipartConfigElement方法返回的MultipartConfigElement对象注册为Spring Bean。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
// 导入Spring框架中用于标记一个类是配置类的注解。
// 被该注解标记的类可以在其中定义各种Bean配置方法这些方法会被Spring容器在启动时自动识别并执行
// 用于创建和配置应用程序所需的各种组件。在本类中用于标记该类为Spring配置类以便进行文件上传相关的配置。
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
// 导入Spring框架中用于处理数据大小单位转换和表示的工具类。
// 它可以方便地将不同单位的数据大小表示转换为统一的格式,
// 在本类中用于设置文件上传的单个文件大小和总上传数据大小的限制以兆字节MB为单位进行设置。
import org.springframework.util.unit.DataSize; import org.springframework.util.unit.DataSize;
// 导入Java标准库中的用于表示多部分Multipart上传配置元素的接口。
// 这个接口定义了多部分上传相关的配置参数,如文件大小限制、请求大小限制等,
// 在本类中通过MultipartConfigFactory创建并返回实现了该接口的对象以完成文件上传的配置设置。
import javax.servlet.MultipartConfigElement; import javax.servlet.MultipartConfigElement;
/** /**
* *
// 包括单个文件的最大允许大小以及整个上传请求的最大允许数据大小等。
*
* @author bool * @author bool
* @date 2019-07-29 16:23 * @date 2019-07-29 16:23
*/ */
@Configuration // 使用该注解标记此为Spring配置类表明这个类是用来进行Spring应用程序的配置工作的。 @Configuration
public class MultipartConfig { public class MultipartConfig {
/**
* 使@BeanSpring Bean
* MultipartConfigElement
*
* @return MultipartConfigElement
*/
@Bean @Bean
public MultipartConfigElement multipartConfigElement() { public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory(); // 创建一个MultipartConfigFactory对象 MultipartConfigFactory factory = new MultipartConfigFactory();
// 它是用于创建MultipartConfigElement对象的工厂类 // 单个数据大小
// 通过它可以方便地设置各种文件上传的配置参数。
// 设置单个文件的最大允许大小。
// 这里使用DataSize.ofMegabytes(5000L)将大小设置为5000兆字节MB
// 即限制单个上传的文件大小不能超过5000MB。
factory.setMaxFileSize(DataSize.ofMegabytes(5000L)); factory.setMaxFileSize(DataSize.ofMegabytes(5000L));
/// 总上传数据大小
// 设置整个上传请求的最大允许数据大小。
// 同样使用DataSize.ofMegabytes(5000L)将大小设置为5000兆字节MB
// 也就是说整个上传请求包括可能上传的多个文件以及其他相关数据的总数据量不能超过5000MB。
factory.setMaxRequestSize(DataSize.ofMegabytes(5000L)); factory.setMaxRequestSize(DataSize.ofMegabytes(5000L));
// 通过MultipartConfigFactory对象的createMultipartConfig方法创建并返回一个MultipartConfigElement对象
// 这个对象包含了我们刚刚设置的单个文件大小和总上传数据大小等配置信息,
// 它会被Spring容器管理并应用到文件上传的相关处理中。
return factory.createMultipartConfig(); return factory.createMultipartConfig();
} }
} }

@ -1,70 +1,37 @@
// 定义包路径用于存放MyBatis配置类相关的代码。这个包名表明该类属于特定的项目模块com.yf.exam下的config部分
// 通常用于组织和管理与MyBatis配置相关的代码。
package com.yf.exam.config; package com.yf.exam.config;
// 导入自定义的查询拦截器类,该拦截器用于在查询操作时进行一些额外的处理,
// 比如可能会根据特定条件对查询语句进行修改、添加过滤条件等操作。
import com.yf.exam.aspect.mybatis.QueryInterceptor; import com.yf.exam.aspect.mybatis.QueryInterceptor;
// 导入自定义的更新拦截器类,该拦截器用于在插入或更新操作时进行相关处理,
// 例如自动设置创建时间、更新时间等字段的值。
import com.yf.exam.aspect.mybatis.UpdateInterceptor; import com.yf.exam.aspect.mybatis.UpdateInterceptor;
// 导入MyBatis与Spring集成时用于扫描MyBatis映射接口的注解。
// 通过该注解可以指定要扫描的包路径以便Spring能够自动发现并注册MyBatis的映射接口
// 使得这些接口可以被正确地用于数据库操作。
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
// 导入Spring框架中用于标记一个方法返回值为Spring Bean的注解。
// 被该注解标记的方法其返回值会被Spring容器管理作为一个可被注入到其他组件中的Bean实例。
// 在本类中用于将queryInterceptor和updateInterceptor方法返回的拦截器对象注册为Spring Bean。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
// 导入Spring框架中用于标记一个类是配置类的注解。
// 被该注解标记的类可以在其中定义各种Bean配置方法这些方法会被Spring容器在启动时自动识别并执行
// 用于创建和配置应用程序所需的各种组件。在本类中用于标记该类为Spring配置类以便进行MyBatis相关的配置。
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/** /**
* MybatisMyBatis * Mybatis
* MyBatis *
*
* @author bool * @author bool
*/ */
@Configuration // 使用该注解标记此为Spring配置类表明这个类是用来进行Spring应用程序的配置工作的。 @Configuration
@MapperScan("com.yf.exam.modules.**.mapper") // 使用MapperScan注解指定要扫描的MyBatis映射接口所在的包路径 @MapperScan("com.yf.exam.modules.**.mapper")
// "com.yf.exam.modules.**.mapper"表示会扫描com.yf.exam.modules包及其子包下的所有mapper接口。
public class MybatisConfig { public class MybatisConfig {
/** /**
* 使@BeanSpring Bean *
* QueryInterceptor
*
* @return QueryInterceptor
*/ */
@Bean // 将该方法返回的查询拦截器对象声明为Spring Bean以便Spring容器能够管理和注入到其他需要使用的地方。 @Bean
public QueryInterceptor queryInterceptor() { public QueryInterceptor queryInterceptor() {
QueryInterceptor query = new QueryInterceptor(); // 创建一个新的查询拦截器对象。 QueryInterceptor query = new QueryInterceptor();
// 设置查询限制,这里将查询限制设置为 -1L具体含义可能根据QueryInterceptor类的内部逻辑而定
// 可能表示不限制查询结果的数量或者有其他特殊的处理方式与该值相关。
query.setLimit(-1L); query.setLimit(-1L);
// 返回设置好的查询拦截器对象该对象将被Spring容器管理并在合适的查询操作场景中被调用执行拦截处理。
return query; return query;
} }
/** /**
* 使@BeanSpring Bean *
* UpdateInterceptor
*
* @return UpdateInterceptor
*/ */
@Bean // 将该方法返回的更新拦截器对象声明为Spring Bean以便Spring容器能够管理和注入到其他需要使用的地方。 @Bean
public UpdateInterceptor updateInterceptor() { public UpdateInterceptor updateInterceptor() {
// 创建一个新的更新拦截器对象这里没有进行额外的设置操作可能在UpdateInterceptor类内部有默认的处理逻辑
// 直接返回该对象它将被Spring容器管理并在合适的插入或更新操作场景中被调用执行拦截处理。
return new UpdateInterceptor(); return new UpdateInterceptor();
} }
} }

@ -1,182 +1,77 @@
// 定义包路径用于存放任务调度配置类相关的代码。这个包名表明该类属于特定的项目模块com.yf.exam下的config部分
// 通常用于组织和管理与任务调度配置相关的代码。
package com.yf.exam.config; package com.yf.exam.config;
// 导入Lombok的Log4j2注解用于简化日志记录的配置通过该注解可以方便地在类中使用Log4j2进行日志输出。
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
// 导入Spring框架中用于处理异步方法执行过程中未捕获异常的处理器接口。
// 当异步方法抛出异常且未在方法内部被捕获时,会由该处理器来处理异常情况,
// 在本类中实现该接口来定义异步未捕获异常的处理逻辑。
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
// 导入Spring框架中用于标记一个方法返回值为Spring Bean的注解。
// 被该注解标记的方法其返回值会被Spring容器管理作为一个可被注入到其他组件中的Bean实例。
// 在本类中用于将taskScheduler和asyncExecutor等方法返回的对象注册为Spring Bean。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
// 导入Spring框架中用于标记一个类是配置类的注解。
// 被该注解标记的类可以在其中定义各种Bean配置方法这些方法会被Spring容器在启动时自动识别并执行
// 用于创建和配置应用程序所需的各种组件。在本类中用于标记该类为Spring配置类以便进行任务调度相关的配置。
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
// 导入Spring框架中用于配置异步任务执行相关设置的接口。
// 实现该接口可以定义异步任务执行的线程池等配置信息,在本类中实现该接口来配置异步任务执行的相关参数。
import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.AsyncConfigurer;
// 导入Spring框架中用于启用异步任务执行功能的注解。
// 当在类上添加该注解后Spring会自动识别并处理类中的异步方法使其能够在独立的线程中执行。
// 在本类上添加该注解以启用异步任务执行功能。
import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableAsync;
// 导入Spring框架中用于启用任务调度功能的注解。
// 当在类上添加该注解后Spring会自动识别并处理类中的定时任务等调度相关的设置使其能够按照预定的时间执行任务。
// 在本类上添加该注解以启用任务调度功能。
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
// 导入Spring框架中用于配置任务调度相关设置的接口。
// 实现该接口可以定义任务调度的线程池、任务注册等配置信息,在本类中实现该接口来配置任务调度的相关参数。
import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.annotation.SchedulingConfigurer;
// 导入Spring框架中用于执行线程池任务的执行器类。
// 它可以创建一个线程池并通过该线程池来执行任务在本类的asyncExecutor方法中用于创建异步任务执行的线程池。
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
// 导入Spring框架中用于调度线程池任务的调度器类。
// 它可以创建一个线程池并通过该线程池来调度任务的执行时间在本类的taskScheduler方法中用于创建定时任务执行的线程池。
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
// 导入Spring框架中用于注册调度任务的类。
// 通过它可以将需要调度执行的任务注册到相应的调度器中在本类的configureTasks方法中用于设置任务调度器。
import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.ScheduledTaskRegistrar;
// 导入Java标准库中的执行器接口它定义了执行任务的通用规范
// 在本类的getAsyncExecutor方法中用于返回异步任务执行的执行器对象。
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
// 导入Java标准库中的线程池执行器类它是实现了线程池功能的具体类
// 在本类的asyncExecutor方法中用于设置异步任务执行线程池的拒绝策略。
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
/** /**
* *
* 线线线
*
* @author bool * @author bool
*/ */
@Log4j2 // 使用Log4j2注解启用日志记录功能方便在类中记录相关操作的日志信息。 @Log4j2
@Configuration // 使用该注解标记此为Spring配置类表明这个类是用来进行Spring应用程序的配置工作的。 @Configuration
@EnableScheduling // 使用该注解启用任务调度功能使得Spring能够识别并处理类中的定时任务等调度相关设置。 @EnableScheduling
@EnableAsync // 使用该注解启用异步任务执行功能使得Spring能够识别并处理类中的异步方法使其能够在独立的线程中执行。 @EnableAsync
public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer { public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer {
/** /**
* 使@BeanSpring Bean * 使线
* 线ThreadPoolTaskScheduler * @return
*
* @return 线ThreadPoolTaskScheduler线
*/ */
@Bean(destroyMethod = "shutdown", name = "taskScheduler") // 将该方法返回的线程池任务调度器对象声明为Spring Bean @Bean(destroyMethod = "shutdown", name = "taskScheduler")
// 并指定销毁方法为"shutdown",以便在容器关闭时正确关闭线程池。
public ThreadPoolTaskScheduler taskScheduler(){ public ThreadPoolTaskScheduler taskScheduler(){
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); // 创建一个新的线程池任务调度器对象。 ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
// 设置线程池的大小即同时可执行的定时任务数量这里设置为10表示线程池最多可同时执行10个定时任务。
scheduler.setPoolSize(10); scheduler.setPoolSize(10);
// 设置线程的名称前缀,用于在日志等场景中方便识别线程所属的任务调度器,这里设置为"task-"
// 生成的线程名称可能类似"task-1"、"task-2"等。
scheduler.setThreadNamePrefix("task-"); scheduler.setThreadNamePrefix("task-");
// 设置线程池在关闭时等待任务完成的时间单位为秒这里设置为600秒10分钟
// 表示在容器关闭时线程池会等待正在执行的任务完成最长等待时间为10分钟。
scheduler.setAwaitTerminationSeconds(600); scheduler.setAwaitTerminationSeconds(600);
// 设置在关闭时是否等待任务完成这里设置为true表示在容器关闭时线程池会等待所有任务完成后再关闭。
scheduler.setWaitForTasksToCompleteOnShutdown(true); scheduler.setWaitForTasksToCompleteOnShutdown(true);
// 返回设置好的线程池任务调度器对象该对象将被Spring容器管理并在定时任务执行场景中被调用进行任务调度。
return scheduler; return scheduler;
} }
/** /**
* 使@BeanSpring Bean * 线
* 线ThreadPoolTaskExecutor * @return
*
* @return 线ThreadPoolTaskExecutor线
*/ */
@Bean(name = "asyncExecutor") // 将该方法返回的线程池任务执行器对象声明为Spring Bean以便Spring容器能够管理和注入到其他需要使用的地方。 @Bean(name = "asyncExecutor")
public ThreadPoolTaskExecutor asyncExecutor() { public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 创建一个新的线程池任务执行器对象。 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置线程池的核心线程数即线程池始终保持的活跃线程数量这里设置为10表示线程池至少会保持10个线程处于活跃状态。
executor.setCorePoolSize(10); executor.setCorePoolSize(10);
// 设置线程池的队列容量即用于存储等待执行任务的队列大小这里设置为1000表示队列最多可容纳1000个等待执行的任务。
executor.setQueueCapacity(1000); executor.setQueueCapacity(1000);
// 设置线程池中非核心线程的保持活动时间单位为秒这里设置为600秒10分钟
// 表示当非核心线程空闲时间超过10分钟时可能会被回收。
executor.setKeepAliveSeconds(600); executor.setKeepAliveSeconds(600);
// 设置线程池的最大线程数即线程池最多可同时拥有的线程数量这里设置为20表示线程池最多可扩展到20个线程同时执行任务。
executor.setMaxPoolSize(20); executor.setMaxPoolSize(20);
// 设置线程的名称前缀,用于在日志等场景中方便识别线程所属的任务执行器,这里设置为"taskExecutor-"
// 生成的线程名称可能类似"taskExecutor-1"、"taskExecutor-2"等。
executor.setThreadNamePrefix("taskExecutor-"); executor.setThreadNamePrefix("taskExecutor-");
// 设置线程池的拒绝策略这里采用ThreadPoolExecutor.CallerRunsPolicy()
// 表示当线程池和队列都已满,无法再接受新任务时,会由调用线程来执行该任务,而不是直接抛出异常。
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化线程池,使其处于可使用状态,完成各项参数的设置和内部资源的初始化。
executor.initialize(); executor.initialize();
// 返回设置好的线程池任务执行器对象该对象将被Spring容器管理并在异步任务执行场景中被调用进行任务执行。
return executor; return executor;
} }
/**
* SchedulingConfigurer
* 线
* 便使
*
* @param scheduledTaskRegistrar
*/
@Override @Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = taskScheduler(); // 获取之前创建的线程池任务调度器对象。 ThreadPoolTaskScheduler taskScheduler = taskScheduler();
// 将获取到的线程池任务调度器对象设置到调度任务注册类中,使得后续注册的定时任务能够使用该调度器进行任务调度。
scheduledTaskRegistrar.setTaskScheduler(taskScheduler); scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
} }
/**
* AsyncConfigurer
* 线
*
* @return 线
*/
@Override @Override
public Executor getAsyncExecutor() { public Executor getAsyncExecutor() {
return asyncExecutor(); return asyncExecutor();
} }
/**
* AsyncConfigurer
*
*
*
* @param throwable
* @param method
* @param objects
* @return AsyncUncaughtExceptionHandler
*/
@Override @Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> { return (throwable, method, objects) -> {
log.error("异步任务执行出现异常, message {}, method {}, params {}", throwable, method, objects); log.error("异步任务执行出现异常, message {}, emthod {}, params {}", throwable, method, objects);
}; };
} }
} }

@ -1,133 +1,127 @@
// 定义包路径用于存放Shiro配置类
package com.yf.exam.config; package com.yf.exam.config;
import com.yf.exam.ability.shiro.CNFilterFactoryBean; // 自定义过滤器工厂类 import com.yf.exam.ability.shiro.CNFilterFactoryBean;
import com.yf.exam.ability.shiro.ShiroRealm; // 自定义Shiro领域类 import com.yf.exam.ability.shiro.ShiroRealm;
import com.yf.exam.ability.shiro.aop.JwtFilter; // JWT认证过滤器 import com.yf.exam.ability.shiro.aop.JwtFilter;
import lombok.extern.slf4j.Slf4j; // Lombok日志注解 import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; // Shiro默认会话存储评估器 import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO; // Shiro默认主体DAO import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager; // Shiro安全管理器 import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor; // Spring生命周期Bean后处理器 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; // Shiro授权属性源顾问 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; // Shiro过滤器工厂类 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager; // Shiro默认Web安全管理器 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; // Spring默认顾问自动代理创建器 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean; // Spring Bean注解 import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; // Spring配置注解 import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn; // Spring依赖注解 import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter; // Servlet过滤器接口 import javax.servlet.Filter;
import java.util.HashMap; // Java哈希映射类 import java.util.HashMap;
import java.util.LinkedHashMap; // Java链接哈希映射类 import java.util.LinkedHashMap;
import java.util.Map; // Java映射类 import java.util.Map;
/** /**
* Shiro * Shiro
* ShiroRealm
* @author bool * @author bool
*/ */
@Slf4j // 使用Lombok注解启用Log4j2日志 @Slf4j
@Configuration // 使用Spring注解标记为配置类 @Configuration
public class ShiroConfig { public class ShiroConfig {
/** /**
* Shiro * Filter Chain
* ShiroURL
* *
* @param securityManager Shiro * 1URLFilter使
* @return ShiroFilterFactoryBean ShiroBean * 2
* 3permsroles
*/ */
@Bean("shiroFilterFactoryBean") // 使用Spring注解声明为Bean并指定Bean名称 @Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
CNFilterFactoryBean shiroFilterFactoryBean = new CNFilterFactoryBean(); // 创建自定义过滤器工厂Bean ShiroFilterFactoryBean shiroFilterFactoryBean = new CNFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager); // 设置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
// 定义过滤器链 Map<String, String> map = new LinkedHashMap<>();
Map<String, String> map = new LinkedHashMap<>(); // 创建拦截器映射
// 需要排除的一些接口
// 配置不需要认证的路径 map.put("/exam/api/sys/user/login", "anon");
map.put("/exam/api/sys/user/login", "anon"); // 登录接口不需要认证 map.put("/exam/api/sys/user/reg", "anon");
map.put("/exam/api/sys/user/reg", "anon"); // 注册接口不需要认证 map.put("/exam/api/sys/user/quick-reg", "anon");
map.put("/exam/api/sys/user/quick-reg", "anon"); // 快速注册接口不需要认证
// 获取网站基本信息
// 配置其他不需要认证的静态资源路径 map.put("/exam/api/sys/config/detail", "anon");
map.put("/upload/file/**", "anon"); // 文件上传路径不需要认证
map.put("/", "anon"); // 根路径不需要认证 // 文件读取
map.put("/v2/**", "anon"); // Swagger路径不需要认证 map.put("/upload/file/**", "anon");
map.put("/doc.html", "anon"); // Swagger文档不需要认证
// ... 省略其他静态资源配置 map.put("/", "anon");
map.put("/v2/**", "anon");
// 添加自定义JWT过滤器 map.put("/doc.html", "anon");
Map<String, Filter> filterMap = new HashMap<>(); // 创建过滤器映射 map.put("/**/*.js", "anon");
filterMap.put("jwt", new JwtFilter()); // 添加JWT过滤器 map.put("/**/*.css", "anon");
shiroFilterFactoryBean.setFilters(filterMap); // 设置过滤器 map.put("/**/*.html", "anon");
map.put("/**/*.svg", "anon");
// 设置过滤器链定义 map.put("/**/*.pdf", "anon");
map.put("/**", "jwt"); // 所有请求都需要通过JWT过滤器 map.put("/**/*.jpg", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map); // 设置过滤器链定义 map.put("/**/*.png", "anon");
map.put("/**/*.ico", "anon");
return shiroFilterFactoryBean; // 返回Shiro过滤器工厂Bean
// 字体
map.put("/**/*.ttf", "anon");
map.put("/**/*.woff", "anon");
map.put("/**/*.woff2", "anon");
map.put("/druid/**", "anon");
map.put("/swagger-ui.html", "anon");
map.put("/swagger**/**", "anon");
map.put("/webjars/**", "anon");
// 添加自己的过滤器并且取名为jwt
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
map.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
} }
/** @Bean("securityManager")
* Shiro
* ShiroRealmShiro
*
* @param myRealm Shiro
* @return DefaultWebSecurityManager Shiro
*/
@Bean("securityManager") // 使用Spring注解声明为Bean并指定Bean名称
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 创建默认Web安全管理器 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm); // 设置自定义Realm securityManager.setRealm(myRealm);
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
// 禁用Shiro的会话存储 DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); // 创建默认主体DAO defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); // 创建默认会话存储评估器 subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
defaultSessionStorageEvaluator.setSessionStorageEnabled(false); // 禁用会话存储 securityManager.setSubjectDAO(subjectDAO);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); // 设置会话存储评估器 return securityManager;
securityManager.setSubjectDAO(subjectDAO); // 设置主体DAO
return securityManager; // 返回安全管理器
} }
/** /**
* *
* 使Shiro * @return
*
* @return DefaultAdvisorAutoProxyCreator
*/ */
@Bean // 使用Spring注解声明为Bean @Bean
@DependsOn("lifecycleBeanPostProcessor") // 依赖于生命周期Bean后处理器 @DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 创建顾问自动代理创建器 DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); // 设置代理目标类 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator; // 返回顾问自动代理创建器 defaultAdvisorAutoProxyCreator.setUsePrefix(true);
defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor");
return defaultAdvisorAutoProxyCreator;
} }
/** @Bean
* Bean
* ShiroBeanShiro
*
* @return LifecycleBeanPostProcessor Bean
*/
@Bean // 使用Spring注解声明为Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor(); // 返回生命周期Bean后处理器 return new LifecycleBeanPostProcessor();
} }
/** @Bean
*
* ShiroShiro
*
* @param securityManager Shiro
* @return AuthorizationAttributeSourceAdvisor
*/
@Bean // 使用Spring注解声明为Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); // 创建授权属性源顾问 AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager); // 设置安全管理器 advisor.setSecurityManager(securityManager);
return advisor; // 返回授权属性源顾问 return advisor;
} }
} }

@ -1,164 +1,65 @@
// 定义包路径用于存放Swagger配置类相关的代码。这个包名表明该类属于特定的项目模块com.yf.exam下的config部分
// 通常用于组织和管理与Swagger配置相关的代码。
package com.yf.exam.config; package com.yf.exam.config;
// 导入用于启用Swagger Bootstrap UI的注解Swagger Bootstrap UI是对Swagger UI的一种增强
// 提供了更友好的界面展示和交互功能,通过该注解可以在项目中启用这一特性。
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
// 导入Swagger用于标注API操作的注解通过在方法上添加该注解可以为该方法在Swagger文档中生成详细的操作描述信息
// 包括方法的功能、参数、返回值等内容方便开发者和使用者了解API的具体使用方式。
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
// 导入Spring框架中用于将类的属性与配置文件中的属性进行绑定的注解。
// 通过指定prefix属性可以将配置文件中以该前缀开头的属性值绑定到类的对应属性上
// 在本类中用于绑定以"swagger"为前缀的配置属性。
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
// 导入Spring框架中用于标记一个方法返回值为Spring Bean的注解。
// 被该注解标记的方法其返回值会被Spring容器管理作为一个可被注入到其他组件中的Bean实例。
// 在本类中用于将examApi、securityScheme等方法返回的对象注册为Spring Bean。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
// 导入Spring框架中用于标记一个类是配置类的注解。
// 被该注解标记的类可以在其中定义各种Bean配置方法这些方法会被Spring容器在启动时自动识别并执行
// 用于创建和配置应用程序所需的各种组件。在本类中用于标记该类为Spring配置类以便进行Swagger相关的配置。
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
// 导入Swagger用于构建API信息的构建器类通过该构建器可以方便地设置API的标题、描述、联系人、版本等信息
// 在本类的apiInfo方法中用于创建并返回包含详细API信息的ApiInfo对象。
import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ApiInfoBuilder;
// 导入Swagger用于选择路径的选择器类通过该选择器可以指定哪些路径下的API需要被Swagger生成文档并展示
// 在本类的examApi方法中用于选择符合特定路径模式的API。
import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.PathSelectors;
// 导入Swagger用于选择请求处理器即包含API方法的类或接口的选择器类
// 通过该选择器可以指定哪些请求处理器中的方法需要被Swagger生成文档并展示
// 在本类的examApi方法中用于选择带有ApiOperation注解的方法所在的请求处理器。
import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.builders.RequestHandlerSelectors;
// 导入Swagger用于表示API信息的类该类包含了API的标题、描述、联系人、版本等详细信息
// 在本类的examApi方法中通过调用apiInfo方法获取该对象并设置到Docket中以便在Swagger文档中展示这些信息。
import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiInfo;
// 导入Swagger用于表示API密钥的类用于设置API的授权相关信息如授权的键名、值的位置如在请求头中
// 在本类的securityScheme方法中用于创建并返回一个ApiKey对象用于设置API的授权方案。
import springfox.documentation.service.ApiKey; import springfox.documentation.service.ApiKey;
// 导入Swagger用于表示联系人信息的类通过该类可以设置API的联系人姓名、联系方式、网址等信息
// 在本类的apiInfo方法中用于创建并返回一个包含联系人信息的Contact对象并设置到ApiInfo中。
import springfox.documentation.service.Contact; import springfox.documentation.service.Contact;
// 导入Swagger用于表示安全方案的类它是一个通用的接口用于定义不同类型的安全方案
// 在本类的examApi方法中通过调用securityScheme方法获取具体的安全方案实现如ApiKey并设置到Docket中。
import springfox.documentation.service.SecurityScheme; import springfox.documentation.service.SecurityScheme;
// 导入Swagger用于表示文档类型的枚举类目前主要有SWAGGER_2等类型
// 在本类的examApi方法中用于指定创建Docket对象时所采用的文档类型为SWAGagger_2。
import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.DocumentationType;
// 导入Swagger用于生成Swagger文档的核心类通过该类可以配置各种Swagger相关的设置如API信息、路径选择、
// 授权方案等在本类的examApi方法中用于创建并配置Docket对象以生成符合项目需求的Swagger文档。
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.Docket;
// 导入用于启用Swagger 2的注解通过在类上添加该注解可以在项目中启用Swagger 2的功能
// 使得Swagger能够为项目中的API生成详细的文档并提供交互界面方便开发者和使用者查看和测试API。
import springfox.documentation.swagger2.annotations.EnableSwagger2; import springfox.documentation.swagger2.annotations.EnableSwagger2;
// 导入Java标准库中的集合类用于处理集合相关的操作在本类的examApi方法中用于创建一个包含单个安全方案的列表
// 以便将安全方案设置到Docket对象中。
import java.util.Collections; import java.util.Collections;
/** /**
* SwaggerSwagger * Swagger
* Swagger 2Swagger Bootstrap UI"swagger"
* DocketAPIAPI
* API便API使
* @author bool * @author bool
* @date 2020/8/19 20:53 * @date 2020/8/19 20:53
*/ */
@Configuration // 使用该注解标记此为Spring配置类表明这个类是用来进行Spring应用程序的配置工作的。 @Configuration
@EnableSwagger2 // 使用该注解启用Swagger 2功能使得Swagger能够为项目中的API生成详细的文档并提供交互界面。 @EnableSwagger2
@EnableSwaggerBootstrapUI // 使用该注解启用Swagger Bootstrap UI功能提供更友好的界面展示和交互功能。 @EnableSwaggerBootstrapUI
@ConfigurationProperties(prefix = "swagger") // 使用该注解将类的属性与以"swagger"为前缀的配置文件中的属性进行绑定, @ConfigurationProperties(prefix = "swagger")
// 以便在类中可以方便地使用这些配置属性来定制Swagger的设置。
public class SwaggerConfig { public class SwaggerConfig {
/**
* 使@BeanSpring Bean
* DocketSwagger
* API
*
* @return DocketAPIAPI
*/
@Bean // 将该方法返回的Docket对象声明为Spring Bean以便Spring容器能够管理和注入到其他需要使用的地方。
public Docket examApi() {
return new Docket(DocumentationType.SWAGGER_2) // 创建一个新的Docket对象并指定文档类型为SWAGGER_2
// 这是目前较为常用的Swagger文档类型用于生成详细的API文档。
.apiInfo(apiInfo()) // 调用apiInfo方法获取包含详细API信息的ApiInfo对象并设置到Docket对象中
// 以便在生成的Swagger文档中展示API的标题、描述、联系人、版本等信息。
.groupName("考试模块接口") // 设置Docket对象的分组名称为"考试模块接口"
// 这样可以将项目中的API按照不同的模块或功能进行分组展示方便查看和管理。
.select() // 开始选择需要生成文档的API通过一系列选择器来指定具体的选择条件。
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 使用RequestHandlerSelectors的withMethodAnnotation方法选择带有ApiOperation注解的方法
// 即只选择那些在方法上标注了ApiOperation注解的API方法进行文档生成
// 这样可以确保只生成我们希望展示的重要API的文档。
.paths(PathSelectors.ant("/exam/api/**")) // 使用PathSelectors的ant方法选择符合特定路径模式的API @Bean
// 这里选择的路径模式是"/exam/api/**",表示只选择以"/exam/api/"开头的任意路径下的API进行文档生成 public Docket examApi() {
// 进一步限定了生成文档的API范围使其更加聚焦于项目中的特定部分如考试模块相关的API return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.build() // 完成上述选择条件的设置后调用build方法构建Docket对象使其生效并包含我们所设置的所有选择条件和信息。 .groupName("考试模块接口")
.select()
.securitySchemes(Collections.singletonList(securityScheme())) // 创建一个包含单个安全方案的列表, .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 通过调用securityScheme方法获取具体的安全方案对象如ApiKey .paths(PathSelectors.ant("/exam/api/**"))
// 并将其添加到列表中然后设置到Docket对象中 .build()
// 以便在Swagger文档中展示API的授权相关信息如需要在请求头中传递的授权密钥等。 .securitySchemes(Collections.singletonList(securityScheme()));
;
} }
/**
* APIApiInfo
* ApiInfoBuilder便API
*
* @return ApiInfoAPI
* Docket便Swagger
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("考试系统接口") // 使用ApiInfoBuilder的title方法设置API的标题为"考试系统接口"
// 这将在Swagger文档的顶部显著位置展示让使用者快速了解该API所属的系统。
.description("考试系统接口") // 使用ApiInfoBuilder的description方法设置API的描述为"考试系统接口"
// 可以在这里详细描述API的功能、用途、特点等信息方便使用者进一步了解API的具体情况。
.contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com")) // 使用ApiInfoBuilder的contact方法创建一个包含联系人信息的Contact对象
// 并设置联系人姓名为"Van",网址为"https://exam.yfhl.net",邮箱为"18365918@qq.com"
// 这些信息将在Swagger文档中展示方便使用者在有问题时能够联系到相关人员。
.version("1.0.0") // 使用ApiInfoBuilder的version方法设置API的版本号为"1.0.0" private ApiInfo apiInfo() {
// 让使用者了解该API的版本情况以便在不同版本之间进行对比和选择。 return new ApiInfoBuilder().title("考试系统接口")
.description("考试系统接口")
.build(); // 完成上述各项信息的设置后调用build方法构建ApiInfo对象使其生效并包含我们所设置的所有信息。 .contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com"))
.version("1.0.0")
.build();
} }
/** /**
* APIApiKeyAPI *
* * @return
* @return ApiKeyAPI
* Docket便SwaggerAPI
*/ */
@Bean // 将该方法返回的SecurityScheme对象实际为ApiKey类型声明为Spring Bean以便Spring容器能够管理和注入到其他需要使用的地方。 @Bean
SecurityScheme securityScheme() { SecurityScheme securityScheme() {
return new ApiKey("token", "token", "header"); // 创建一个新的ApiKey对象 return new ApiKey("token", "token", "header");
// 第一个参数"token"表示授权的键名,即客户端在请求时需要在指定位置(这里是请求头)传递的键名;
// 第二个参数"token"表示授权的值,这里简单设置为与键名相同,实际应用中可能是根据用户登录等情况生成的授权令牌;
// 第三个参数"header"表示授权的值应该放置的位置,这里指定为在请求头中传递。
} }
} }

@ -1,144 +1,151 @@
package com.yf.exam.modules.exam.controller; // 定义包名,控制器所在的包路径 package com.yf.exam.modules.exam.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 导入MyBatis Plus的查询包装类 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; // 导入MyBatis Plus的分页接口 import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yf.exam.core.api.ApiRest; // 导入自定义的API响应类 import com.yf.exam.core.api.ApiRest;
import com.yf.exam.core.api.controller.BaseController; // 导入基控制器类 import com.yf.exam.core.api.controller.BaseController;
import com.yf.exam.core.api.dto.BaseIdReqDTO; // 导入基础ID请求数据传输对象 import com.yf.exam.core.api.dto.BaseIdReqDTO;
import com.yf.exam.core.api.dto.BaseIdsReqDTO; // 导入基础IDs请求数据传输对象 import com.yf.exam.core.api.dto.BaseIdsReqDTO;
import com.yf.exam.core.api.dto.BaseStateReqDTO; // 导入基础状态请求数据传输对象 import com.yf.exam.core.api.dto.BaseStateReqDTO;
import com.yf.exam.core.api.dto.PagingReqDTO; // 导入分页请求数据传输对象 import com.yf.exam.core.api.dto.PagingReqDTO;
import com.yf.exam.modules.exam.dto.ExamDTO; // 导入考试DTO import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; // 导入考试保存请求DTO import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; // 导入在线考试响应DTO import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; // 导入考试审核响应DTO import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
import com.yf.exam.modules.exam.entity.Exam; // 导入考试实体类 import com.yf.exam.modules.exam.entity.Exam;
import com.yf.exam.modules.exam.service.ExamService; // 导入考试服务接口 import com.yf.exam.modules.exam.service.ExamService;
import io.swagger.annotations.Api; // 导入Swagger注解 import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; // 导入Swagger操作注解 import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresRoles; // 导入Shiro权限注解 import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired; // 导入Spring的自动注入注解 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody; // 导入Spring MVC的请求体注解 import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; // 导入Spring MVC的请求映射注解 import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; // 导入Spring MVC的请求方法注解 import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; // 导入Spring MVC的控制器注解 import org.springframework.web.bind.annotation.RestController;
import java.util.Date; // 导入Java的日期类 import java.util.Date;
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
@Api(tags={"考试"}) // Swagger注解定义API的标签 @Api(tags={"考试"})
@RestController // Spring MVC注解声明这是一个REST控制器 @RestController
@RequestMapping("/exam/api/exam/exam") // Spring MVC注解定义请求的基础路径 @RequestMapping("/exam/api/exam/exam")
public class ExamController extends BaseController { // 声明控制器类,继承自基控制器 public class ExamController extends BaseController {
@Autowired @Autowired
private ExamService baseService; // 自动注入考试服务 private ExamService baseService;
/** /**
* *
* @param reqDTO * @param reqDTO
* @return ApiRest * @return
*/ */
@RequiresRoles("sa") // Shiro权限注解要求角色为"sa"(超级管理员) @RequiresRoles("sa")
@ApiOperation(value = "添加或修改") // Swagger注解定义操作的描述 @ApiOperation(value = "添加或修改")
@RequestMapping(value = "/save", method = { RequestMethod.POST}) // Spring MVC注解定义请求的映射和方法 @RequestMapping(value = "/save", method = { RequestMethod.POST})
public ApiRest save(@RequestBody ExamSaveReqDTO reqDTO) { // 定义添加或修改考试的方法 public ApiRest save(@RequestBody ExamSaveReqDTO reqDTO) {
// 复制参数并保存 //复制参数
baseService.save(reqDTO); // 调用服务层保存考试信息 baseService.save(reqDTO);
return super.success(); // 返回成功响应 return super.success();
} }
/** /**
* *
* @param reqDTO ID * @param reqDTO
* @return ApiRest * @return
*/ */
@RequiresRoles("sa") // Shiro权限注解要求角色为"sa"(超级管理员) @RequiresRoles("sa")
@ApiOperation(value = "批量删除") // Swagger注解定义操作的描述 @ApiOperation(value = "批量删除")
@RequestMapping(value = "/delete", method = { RequestMethod.POST}) // Spring MVC注解定义请求的映射和方法 @RequestMapping(value = "/delete", method = { RequestMethod.POST})
public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { // 定义批量删除考试的方法 public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) {
// 根据ID删除考试 //根据ID删除
baseService.removeByIds(reqDTO.getIds()); // 调用服务层根据ID列表删除考试 baseService.removeByIds(reqDTO.getIds());
return super.success(); // 返回成功响应 return super.success();
} }
/** /**
* *
* @param reqDTO ID * @param reqDTO
* @return ApiRest<ExamSaveReqDTO> * @return
*/ */
@ApiOperation(value = "查找详情") // Swagger注解定义操作的描述 @ApiOperation(value = "查找详情")
@RequestMapping(value = "/detail", method = { RequestMethod.POST}) // Spring MVC注解定义请求的映射和方法 @RequestMapping(value = "/detail", method = { RequestMethod.POST})
public ApiRest<ExamSaveReqDTO> find(@RequestBody BaseIdReqDTO reqDTO) { // 定义查找考试详情的方法 public ApiRest<ExamSaveReqDTO> find(@RequestBody BaseIdReqDTO reqDTO) {
ExamSaveReqDTO dto = baseService.findDetail(reqDTO.getId()); // 调用服务层查找考试详情 ExamSaveReqDTO dto = baseService.findDetail(reqDTO.getId());
return super.success(dto); // 返回成功响应和考试详情 return super.success(dto);
} }
/** /**
* *
* @param reqDTO ID * @param reqDTO
* @return ApiRest * @return
*/ */
@RequiresRoles("sa") // Shiro权限注解要求角色为"sa"(超级管理员) @RequiresRoles("sa")
@ApiOperation(value = "更新考试状态") // Swagger注解定义操作的描述 @ApiOperation(value = "查找详情")
@RequestMapping(value = "/state", method = { RequestMethod.POST}) // Spring MVC注解定义请求的映射和方法 @RequestMapping(value = "/state", method = { RequestMethod.POST})
public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) { // 定义更新考试状态的方法 public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) {
// 创建查询条件
QueryWrapper<Exam> wrapper = new QueryWrapper<>(); QueryWrapper<Exam> wrapper = new QueryWrapper<>();
wrapper.lambda().in(Exam::getId, reqDTO.getIds()); // 构造查询条件查询指定ID的考试 wrapper.lambda().in(Exam::getId, reqDTO.getIds());
Exam exam = new Exam(); // 创建考试实体 Exam exam = new Exam();
exam.setState(reqDTO.getState()); // 设置新状态 exam.setState(reqDTO.getState());
exam.setUpdateTime(new Date()); // 设置更新时间为当前时间 exam.setUpdateTime(new Date());
baseService.update(exam, wrapper); // 调用服务层更新考试状态 baseService.update(exam, wrapper);
return super.success(); // 返回成功响应 return super.success();
} }
/** /**
* *
* @param reqDTO * @param reqDTO
* @return ApiRest<IPage<ExamOnlineRespDTO>> * @return
*/ */
@ApiOperation(value = "考试视角") // Swagger注解定义操作的描述 @ApiOperation(value = "考试视角")
@RequestMapping(value = "/online-paging", method = { RequestMethod.POST}) // Spring MVC注解定义请求的映射和方法 @RequestMapping(value = "/online-paging", method = { RequestMethod.POST})
public ApiRest<IPage<ExamOnlineRespDTO>> myPaging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) { // 定义分页查找考试的方法 public ApiRest<IPage<ExamOnlineRespDTO>> myPaging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
//分页查询并转换 //分页查询并转换
IPage<ExamOnlineRespDTO> page = baseService.onlinePaging(reqDTO); // 调用服务层进行分页查询 IPage<ExamOnlineRespDTO> page = baseService.onlinePaging(reqDTO);
return super.success(page); // 返回成功响应和分页结果 return super.success(page);
} }
/** /**
* *
* @param reqDTO * @param reqDTO
* @return ApiRest<IPage<ExamDTO>> * @return
*/ */
@RequiresRoles("sa") // Shiro权限注解要求角色为"sa"(超级管理员) @RequiresRoles("sa")
@ApiOperation(value = "分页查找") // Swagger注解定义操作的描述 @ApiOperation(value = "分页查找")
@RequestMapping(value = "/paging", method = { RequestMethod.POST}) // Spring MVC注解定义请求的映射和方法 @RequestMapping(value = "/paging", method = { RequestMethod.POST})
public ApiRest<IPage<ExamDTO>> paging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) { // 定义分页查找考试的方法 public ApiRest<IPage<ExamDTO>> paging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
//分页查询并转换 //分页查询并转换
IPage<ExamDTO> page = baseService.paging(reqDTO); // 调用服务层进行分页查询 IPage<ExamDTO> page = baseService.paging(reqDTO);
return super.success(page); // 返回成功响应和分页结果
return super.success(page);
} }
/** /**
* *
* @param reqDTO * @param reqDTO
* @return ApiRest<IPage<ExamReviewRespDTO>> * @return
*/ */
@RequiresRoles("sa") // Shiro权限注解要求角色为"sa"(超级管理员) @RequiresRoles("sa")
@ApiOperation(value = "待阅试卷") // Swagger注解定义操作的描述 @ApiOperation(value = "待阅试卷")
@RequestMapping(value = "/review-paging", method = { RequestMethod.POST}) // Spring MVC注解定义请求的映射和方法 @RequestMapping(value = "/review-paging", method = { RequestMethod.POST})
public ApiRest<IPage<ExamReviewRespDTO>> reviewPaging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) { // 定义分页查找待阅试卷的方法 public ApiRest<IPage<ExamReviewRespDTO>> reviewPaging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
//分页查询并转换 //分页查询并转换
IPage<ExamReviewRespDTO> page = baseService.reviewPaging(reqDTO); // 调用服务层进行分页查询 IPage<ExamReviewRespDTO> page = baseService.reviewPaging(reqDTO);
return super.success(page); // 返回成功响应和分页结果 return super.success(page);
} }
} }

@ -1,93 +1,101 @@
package com.yf.exam.modules.exam.dto; // 定义包名DTO类所在的包路径 package com.yf.exam.modules.exam.dto;
import com.fasterxml.jackson.annotation.JsonFormat; // 导入Jackson库的注解用于格式化日期 import com.fasterxml.jackson.annotation.JsonFormat;
import com.yf.exam.modules.paper.enums.ExamState; // 导入考试状态枚举 import com.yf.exam.modules.paper.enums.ExamState;
import io.swagger.annotations.ApiModel; // 导入Swagger注解用于描述模型 import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解用于描述模型属性 import io.swagger.annotations.ApiModelProperty;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat; // 导入Spring的日期格式化注解 import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable; // 导入Java的序列化接口用于确保对象可以被序列化 import java.io.Serializable;
import java.util.Date; // 导入Java的日期类 import java.util.Date;
/** /**
* <p> * <p>
* *
* </p> * </p>
* 便
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@ApiModel(value="考试", description="考试") // Swagger注解描述这个类的用途 @ApiModel(value="考试", description="考试")
public class ExamDTO implements Serializable { // 声明类实现Serializable接口以确保可以序列化 public class ExamDTO implements Serializable {
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制确保类的版本唯一性
@ApiModelProperty(value = "ID", required=true) // Swagger注解描述字段的用途和是否必填 private static final long serialVersionUID = 1L;
private String id; // 考试ID
@ApiModelProperty(value = "考试名称", required=true) // Swagger注解描述字段的用途和是否必填
private String title; // 考试名称
@ApiModelProperty(value = "考试描述", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "ID", required=true)
private String content; // 考试描述 private String id;
@ApiModelProperty(value = "1公开2部门3定员", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "考试名称", required=true)
private Integer openType; // 开放类型1表示公开2表示部门3表示定员 private String title;
@ApiModelProperty(value = "考试状态", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "考试描述", required=true)
private Integer state; // 考试状态 private String content;
@ApiModelProperty(value = "是否限时", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "1公开2部门3定员", required=true)
private Boolean timeLimit; // 是否限时 private Integer openType;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") // Jackson注解格式化日期 @ApiModelProperty(value = "考试状态", required=true)
@DateTimeFormat(pattern = "yyyy-MM-dd") // Spring注解格式化日期 private Integer state;
@ApiModelProperty(value = "开始时间", required=true) // Swagger注解描述字段的用途和是否必填
private Date startTime; // 考试开始时间
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") // Jackson注解格式化日期 @ApiModelProperty(value = "是否限时", required=true)
@DateTimeFormat(pattern = "yyyy-MM-dd") // Spring注解格式化日期 private Boolean timeLimit;
@ApiModelProperty(value = "结束时间", required=true) // Swagger注解描述字段的用途和是否必填
private Date endTime; // 考试结束时间
@ApiModelProperty(value = "创建时间", required=true) // Swagger注解描述字段的用途和是否必填 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date createTime; // 创建时间 @DateTimeFormat(pattern = "yyyy-MM-dd")
@ApiModelProperty(value = "开始时间", required=true)
private Date startTime;
@ApiModelProperty(value = "更新时间", required=true) // Swagger注解描述字段的用途和是否必填 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date updateTime; // 更新时间 @DateTimeFormat(pattern = "yyyy-MM-dd")
@ApiModelProperty(value = "结束时间", required=true)
private Date endTime;
@ApiModelProperty(value = "创建时间", required=true)
private Date createTime;
@ApiModelProperty(value = "更新时间", required=true)
private Date updateTime;
@ApiModelProperty(value = "总分数", required=true)
private Integer totalScore;
@ApiModelProperty(value = "总时长(分钟)", required=true)
private Integer totalTime;
@ApiModelProperty(value = "及格分数", required=true)
private Integer qualifyScore;
@ApiModelProperty(value = "总分数", required=true) // Swagger注解描述字段的用途和是否必填
private Integer totalScore; // 总分数
@ApiModelProperty(value = "总时长(分钟)", required=true) // Swagger注解描述字段的用途和是否必填
private Integer totalTime; // 总时长(分钟)
@ApiModelProperty(value = "及格分数", required=true) // Swagger注解描述字段的用途和是否必填
private Integer qualifyScore; // 及格分数
/** /**
* *
* * @return
*
* @return
*/ */
public Integer getState(){ public Integer getState(){
if(this.timeLimit!=null && this.timeLimit){ if(this.timeLimit!=null && this.timeLimit){
if(System.currentTimeMillis() < startTime.getTime() ){ if(System.currentTimeMillis() < startTime.getTime() ){
return ExamState.READY_START; // 如果当前时间小于开始时间,状态为准备开始 return ExamState.READY_START;
} }
if(System.currentTimeMillis() > endTime.getTime()){ if(System.currentTimeMillis() > endTime.getTime()){
return ExamState.OVERDUE; // 如果当前时间大于结束时间,状态为已过期 return ExamState.OVERDUE;
} }
if(System.currentTimeMillis() > startTime.getTime() if(System.currentTimeMillis() > startTime.getTime()
&& System.currentTimeMillis() < endTime.getTime() && System.currentTimeMillis() < endTime.getTime()
&& !ExamState.DISABLED.equals(this.state)){ && !ExamState.DISABLED.equals(this.state)){
return ExamState.ENABLE; // 如果当前时间在开始时间和结束时间之间,并且状态不是禁用,状态为正在进行 return ExamState.ENABLE;
} }
} }
return this.state; // 如果不满足上述条件,返回当前状态
return this.state;
} }
} }

@ -1,32 +1,33 @@
package com.yf.exam.modules.exam.dto; // 定义包名DTO类所在的包路径 package com.yf.exam.modules.exam.dto;
import io.swagger.annotations.ApiModel; // 导入Swagger注解用于描述模型 import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解用于描述模型属性 import io.swagger.annotations.ApiModelProperty;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
import java.io.Serializable; // 导入Java的序列化接口用于确保对象可以被序列化 import java.io.Serializable;
/** /**
* <p> * <p>
* *
* </p> * </p>
* 便
* *
* @author * @author
* @since 2020-09-03 17:24 * @since 2020-09-03 17:24
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@ApiModel(value="考试部门", description="考试部门") // Swagger注解描述这个类的用途 @ApiModel(value="考试部门", description="考试部门")
public class ExamDepartDTO implements Serializable { // 声明类实现Serializable接口以确保可以序列化 public class ExamDepartDTO implements Serializable {
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制确保类的版本唯一性 private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "ID", required=true) // Swagger注解描述字段的用途和是否必填
private String id; // 唯一标识符标识考试部门记录的ID
@ApiModelProperty(value = "考试ID", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "ID", required=true)
private String examId; // 考试的唯一标识符,关联到具体的考试 private String id;
@ApiModelProperty(value = "考试ID", required=true)
private String examId;
@ApiModelProperty(value = "部门ID", required=true)
private String departId;
@ApiModelProperty(value = "部门ID", required=true) // Swagger注解描述字段的用途和是否必填
private String departId; // 部门的唯一标识符,关联到具体的部门
} }

@ -1,50 +1,51 @@
package com.yf.exam.modules.exam.dto; // 定义包名DTO类所在的包路径 package com.yf.exam.modules.exam.dto;
import io.swagger.annotations.ApiModel; // 导入Swagger注解用于描述模型 import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解用于描述模型属性 import io.swagger.annotations.ApiModelProperty;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
import java.io.Serializable; // 导入Java的序列化接口用于确保对象可以被序列化 import java.io.Serializable;
/** /**
* <p> * <p>
* *
* </p> * </p>
* 便
* *
* @author * @author
* @since 2020-09-05 11:14 * @since 2020-09-05 11:14
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@ApiModel(value="考试题库", description="考试题库") // Swagger注解描述这个类的用途 @ApiModel(value="考试题库", description="考试题库")
public class ExamRepoDTO implements Serializable { // 声明类实现Serializable接口以确保可以序列化 public class ExamRepoDTO implements Serializable {
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制确保类的版本唯一性 private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "ID", required=true) // Swagger注解描述字段的用途和是否必填
private String id; // 题库ID唯一标识符
@ApiModelProperty(value = "考试ID", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "ID", required=true)
private String examId; // 关联的考试ID标识与哪个考试相关联 private String id;
@ApiModelProperty(value = "题库ID", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "考试ID", required=true)
private String repoId; // 题库ID标识具体的题库 private String examId;
@ApiModelProperty(value = "单选题数量", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "题库ID", required=true)
private Integer radioCount; // 单选题数量,表示题库中单选题的总数 private String repoId;
@ApiModelProperty(value = "单选题数", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "单选题", required=true)
private Integer radioScore; // 单选题分数,表示单选题的总分 private Integer radioCount;
@ApiModelProperty(value = "多选题数量", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "单选题分数", required=true)
private Integer multiCount; // 多选题数量,表示题库中多选题的总数 private Integer radioScore;
@ApiModelProperty(value = "多选题数", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "多选题", required=true)
private Integer multiScore; // 多选题分数,表示多选题的总分 private Integer multiCount;
@ApiModelProperty(value = "判断题数量", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "多选题分数", required=true)
private Integer judgeCount; // 判断题数量,表示题库中判断题的总数 private Integer multiScore;
@ApiModelProperty(value = "判断题数量", required=true)
private Integer judgeCount;
@ApiModelProperty(value = "判断题分数", required=true)
private Integer judgeScore;
@ApiModelProperty(value = "判断题分数", required=true) // Swagger注解描述字段的用途和是否必填
private Integer judgeScore; // 判断题分数,表示判断题的总分
} }

@ -1,30 +1,32 @@
package com.yf.exam.modules.exam.dto.ext; // 定义包名DTO扩展类所在的包路径 package com.yf.exam.modules.exam.dto.ext;
import com.yf.exam.modules.exam.dto.ExamRepoDTO; // 导入考试题库数据传输类 import com.yf.exam.modules.exam.dto.ExamRepoDTO;
import io.swagger.annotations.ApiModel; // 导入Swagger注解用于描述模型 import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解用于描述模型属性 import io.swagger.annotations.ApiModelProperty;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
* @since 2020-09-05 11:14 * @since 2020-09-05 11:14
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类") // Swagger注解描述这个类的用途 @ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类")
public class ExamRepoExtDTO extends ExamRepoDTO { // 声明类继承自ExamRepoDTO public class ExamRepoExtDTO extends ExamRepoDTO {
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制 private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "单选题总量", required=true) // Swagger注解描述字段的用途和是否必填
private Integer totalRadio; // 单选题总量
@ApiModelProperty(value = "多选题总量", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "单选题总量", required=true)
private Integer totalMulti; // 多选题总量 private Integer totalRadio;
@ApiModelProperty(value = "多选题总量", required=true)
private Integer totalMulti;
@ApiModelProperty(value = "判断题总量", required=true)
private Integer totalJudge;
@ApiModelProperty(value = "判断题总量", required=true) // Swagger注解描述字段的用途和是否必填
private Integer totalJudge; // 判断题总量
} }

@ -1,30 +1,32 @@
package com.yf.exam.modules.exam.dto.request; // 定义包名请求DTO类所在的包路径 package com.yf.exam.modules.exam.dto.request;
import com.yf.exam.modules.exam.dto.ExamDTO; // 导入基础考试DTO类 import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO类 import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import io.swagger.annotations.ApiModel; // 导入Swagger注解用于描述模型 import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解用于描述模型属性 import io.swagger.annotations.ApiModelProperty;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
import java.util.List; // 导入Java的List接口用于定义列表类型的属性 import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@ApiModel(value="考试保存请求类", description="考试保存请求类") // Swagger注解描述这个类的用途 @ApiModel(value="考试保存请求类", description="考试保存请求类")
public class ExamSaveReqDTO extends ExamDTO { // 声明类继承自ExamDTO public class ExamSaveReqDTO extends ExamDTO {
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制 private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "题库列表", required=true) // Swagger注解描述字段的用途和是否必填
private List<ExamRepoExtDTO> repoList; // 题库列表,存储与考试相关的题库信息
@ApiModelProperty(value = "考试部门列表", required=true) // Swagger注解描述字段的用途和是否必填 @ApiModelProperty(value = "题库列表", required=true)
private List<String> departIds; // 考试部门ID列表存储与考试相关的部门ID private List<ExamRepoExtDTO> repoList;
@ApiModelProperty(value = "考试部门列表", required=true)
private List<String> departIds;
} }

@ -1,21 +1,22 @@
package com.yf.exam.modules.exam.dto.response; // 定义包名响应DTO类所在的包路径 package com.yf.exam.modules.exam.dto.response;
import com.yf.exam.modules.exam.dto.ExamDTO; // 导入基础考试DTO类 import com.yf.exam.modules.exam.dto.ExamDTO;
import io.swagger.annotations.ApiModel; // 导入Swagger注解用于描述模型 import io.swagger.annotations.ApiModel;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
/** /**
* <p> * <p>
* 线线 *
* </p> * </p>
* 线
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类") // Swagger注解描述这个类的用途 @ApiModel(value="在线考试分页响应类", description="在线考试分页响应类")
public class ExamOnlineRespDTO extends ExamDTO { // 声明类继承自ExamDTO public class ExamOnlineRespDTO extends ExamDTO {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制确保类的版本唯一性
} }

@ -1,28 +1,31 @@
package com.yf.exam.modules.exam.dto.response; // 定义包名响应DTO类所在的包路径 package com.yf.exam.modules.exam.dto.response;
import com.yf.exam.modules.exam.dto.ExamDTO; // 导入基础考试DTO类 import com.yf.exam.modules.exam.dto.ExamDTO;
import io.swagger.annotations.ApiModel; // 导入Swagger注解用于描述模型 import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解用于描述模型属性 import io.swagger.annotations.ApiModelProperty;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
/** /**
* <p> * <p>
* *
* </p> * </p>
*
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@ApiModel(value="阅卷分页响应类", description="阅卷分页响应类") // Swagger注解描述这个类的用途 @ApiModel(value="阅卷分页响应类", description="阅卷分页响应类")
public class ExamReviewRespDTO extends ExamDTO { // 声明类继承自ExamDTO public class ExamReviewRespDTO extends ExamDTO {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "考试人数", required=true)
private Integer examUser;
@ApiModelProperty(value = "待阅试卷", required=true)
private Integer unreadPaper;
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制确保类的版本唯一性
@ApiModelProperty(value = "考试人数", required=true) // Swagger注解描述字段的用途和是否必填
private Integer examUser; // 考试人数,表示参与考试的用户总数
@ApiModelProperty(value = "待阅试卷", required=true) // Swagger注解描述字段的用途和是否必填
private Integer unreadPaper; // 待阅试卷数量,表示尚未被阅卷的试卷数量
} }

@ -1,61 +1,100 @@
package com.yf.exam.modules.exam.entity; // 定义包名,实体类所在的包路径 package com.yf.exam.modules.exam.entity;
import com.baomidou.mybatisplus.annotation.IdType; // 导入MyBatis Plus的ID类型注解 import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; // 导入MyBatis Plus的表字段注解 import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; // 导入MyBatis Plus的表ID注解 import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; // 导入MyBatis Plus的表名注解 import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model; // 导入MyBatis Plus的模型类 import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
import java.util.Date; // 导入Java的日期类 import java.util.Date;
/** /**
* <p> * <p>
* *
* </p> * </p>
*
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@TableName("el_exam") // MyBatis Plus注解指定这个实体类对应的数据库表名 @TableName("el_exam")
public class Exam extends Model<Exam> { // 声明类继承自MyBatis Plus的Model类用于数据库操作 public class Exam extends Model<Exam> {
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制确保类的唯一性 private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定这个字段为表的主键类型为自增ID /**
private String id; // 考试ID唯一标识符 * ID
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
private String title; // 考试名称,描述考试的标题 /**
*
*/
private String title;
private String content; // 考试描述,详细描述考试的内容 /**
*
*/
private String content;
@TableField("open_type") // MyBatis Plus注解指定这个字段在数据库表中的列名 /**
private Integer openType; // 开放类型表示考试的开放范围1公开2部门3定员 * 123
*/
@TableField("open_type")
private Integer openType;
private Integer state; // 考试状态,表示考试的当前状态(如:未开始、进行中、已结束) /**
*
*/
private Integer state;
@TableField("time_limit") // MyBatis Plus注解指定这个字段在数据库表中的列名 /**
private Boolean timeLimit; // 是否限时,表示考试是否有时间限制 *
*/
@TableField("time_limit")
private Boolean timeLimit;
@TableField("start_time") // MyBatis Plus注解指定这个字段在数据库表中的列名 /**
private Date startTime; // 开始时间,表示考试的开始时间 *
*/
@TableField("start_time")
private Date startTime;
@TableField("end_time") // MyBatis Plus注解指定这个字段在数据库表中的列名 /**
private Date endTime; // 结束时间,表示考试的结束时间 *
*/
@TableField("end_time")
private Date endTime;
@TableField("create_time") // MyBatis Plus注解指定这个字段在数据库表中的列名 /**
private Date createTime; // 创建时间,表示记录的创建时间 *
*/
@TableField("create_time")
private Date createTime;
@TableField("update_time") // MyBatis Plus注解指定这个字段在数据库表中的列名 /**
private Date updateTime; // 更新时间,表示记录的最后更新时间 *
*/
@TableField("update_time")
private Date updateTime;
@TableField("total_score") // MyBatis Plus注解指定这个字段在数据库表中的列名 /**
private Integer totalScore; // 总分数,表示考试的总分 *
*/
@TableField("total_score")
private Integer totalScore;
/**
*
*/
@TableField("total_time")
private Integer totalTime;
@TableField("total_time") // MyBatis Plus注解指定这个字段在数据库表中的列名 /**
private Integer totalTime; // 总时长(分钟),表示考试的总时长 *
*/
@TableField("qualify_score")
private Integer qualifyScore;
@TableField("qualify_score") // MyBatis Plus注解指定这个字段在数据库表中的列名
private Integer qualifyScore; // 及格分数,表示考试的及格分数线
} }

@ -1,47 +1,42 @@
package com.yf.exam.modules.exam.entity; // 定义包名,实体类所在的包路径 package com.yf.exam.modules.exam.entity;
import com.baomidou.mybatisplus.annotation.IdType; // 导入MyBatis Plus的ID类型注解 import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; // 导入MyBatis Plus的表字段注解 import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; // 导入MyBatis Plus的表ID注解 import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; // 导入MyBatis Plus的表名注解 import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model; // 导入MyBatis Plus的模型类 import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
import java.io.Serializable; // 导入Java的序列化接口用于确保对象可以被序列化
/** /**
* <p> * <p>
* *
* </p> * </p>
*
* *
* @author * @author
* @since 2020-09-03 17:24 * @since 2020-09-03 17:24
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@TableName("el_exam_depart") // MyBatis Plus注解指定这个实体类对应的数据库表名 @TableName("el_exam_depart")
public class ExamDepart extends Model<ExamDepart> { // 声明类继承自MyBatis Plus的Model类用于数据库操作 public class ExamDepart extends Model<ExamDepart> {
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制确保类的唯一性 private static final long serialVersionUID = 1L;
/** /**
* ID * ID
*
*/ */
@TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定这个字段为表的主键类型为自增ID @TableId(value = "id", type = IdType.ASSIGN_ID)
private String id; private String id;
/** /**
* ID * ID
* ID
*/ */
@TableField("exam_id") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("exam_id")
private String examId; private String examId;
/** /**
* ID * ID
* ID
*/ */
@TableField("depart_id") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("depart_id")
private String departId; private String departId;
} }

@ -1,87 +1,78 @@
package com.yf.exam.modules.exam.entity; // 定义包名,实体类所在的包路径 package com.yf.exam.modules.exam.entity;
import com.baomidou.mybatisplus.annotation.IdType; // 导入MyBatis Plus的ID类型注解 import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; // 导入MyBatis Plus的表字段注解 import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; // 导入MyBatis Plus的表ID注解 import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; // 导入MyBatis Plus的表名注解 import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model; // 导入MyBatis Plus的模型类 import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data; // 导入Lombok注解用于简化数据类的编写 import lombok.Data;
/** /**
* <p> * <p>
* *
* </p> * </p>
*
* *
* @author * @author
* @since 2020-09-05 11:14 * @since 2020-09-05 11:14
*/ */
@Data // Lombok注解标记这个类为数据类自动生成getter和setter方法 @Data
@TableName("el_exam_repo") // MyBatis Plus注解指定这个实体类对应的数据库表名 @TableName("el_exam_repo")
public class ExamRepo extends Model<ExamRepo> { // 声明类继承自MyBatis Plus的Model类用于数据库操作 public class ExamRepo extends Model<ExamRepo> {
private static final long serialVersionUID = 1L; // 序列化ID用于版本控制确保类的唯一性 private static final long serialVersionUID = 1L;
/** /**
* ID * ID
*
*/ */
@TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定这个字段为表的主键类型为自增ID @TableId(value = "id", type = IdType.ASSIGN_ID)
private String id; private String id;
/** /**
* ID * ID
* ID
*/ */
@TableField("exam_id") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("exam_id")
private String examId; private String examId;
/** /**
* ID * ID
* ID
*/ */
@TableField("repo_id") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("repo_id")
private String repoId; private String repoId;
/** /**
* *
*
*/ */
@TableField("radio_count") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("radio_count")
private Integer radioCount; private Integer radioCount;
/** /**
* *
*
*/ */
@TableField("radio_score") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("radio_score")
private Integer radioScore; private Integer radioScore;
/** /**
* *
*
*/ */
@TableField("multi_count") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("multi_count")
private Integer multiCount; private Integer multiCount;
/** /**
* *
*
*/ */
@TableField("multi_score") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("multi_score")
private Integer multiScore; private Integer multiScore;
/** /**
* *
*
*/ */
@TableField("judge_count") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("judge_count")
private Integer judgeCount; private Integer judgeCount;
/** /**
* *
*
*/ */
@TableField("judge_score") // MyBatis Plus注解指定这个字段在数据库表中的列名 @TableField("judge_score")
private Integer judgeScore; private Integer judgeScore;
} }

@ -1,17 +1,15 @@
package com.yf.exam.modules.exam.mapper; // 定义包名Mapper接口所在的包路径 package com.yf.exam.modules.exam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis Plus的基础Mapper接口
import com.yf.exam.modules.exam.entity.ExamDepart; // 导入考试部门实体类
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yf.exam.modules.exam.entity.ExamDepart;
/** /**
* <p> * <p>
* Mapper * Mapper
* </p> * </p>
* MapperMyBatis PlusBaseMapper
* *
* @author * @author
* @since 2020-09-03 17:24 * @since 2020-09-03 17:24
*/ */
public interface ExamDepartMapper extends BaseMapper<ExamDepart> { // 声明接口继承自BaseMapper并指定操作的实体类为ExamDepart public interface ExamDepartMapper extends BaseMapper<ExamDepart> {
// 继承BaseMapper提供基本的CRUD操作创建、读取、更新、删除
} }

@ -1,52 +1,45 @@
package com.yf.exam.modules.exam.mapper; // 定义包名Mapper接口所在的包路径 package com.yf.exam.modules.exam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis Plus的基础Mapper接口 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage; // 导入MyBatis Plus的分页结果接口 import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入MyBatis Plus的分页对象 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yf.exam.modules.exam.dto.ExamDTO; // 导入考试DTO import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; // 导入阅卷分页响应DTO import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; // 导入在线考试分页响应DTO import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.entity.Exam; // 导入考试实体类 import com.yf.exam.modules.exam.entity.Exam;
import org.apache.ibatis.annotations.Param; // 导入MyBatis的参数注解 import org.apache.ibatis.annotations.Param;
/** /**
* <p> * <p>
* Mapper * Mapper
* </p> * </p>
* MapperMyBatis PlusBaseMapper
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
public interface ExamMapper extends BaseMapper<Exam> { // 声明接口继承自BaseMapper并指定操作的实体类为Exam public interface ExamMapper extends BaseMapper<Exam> {
/** /**
* *
* * @param page
* * @param query
* @param page * @return
* @param query
* @return IPage<ExamDTO> DTO
*/ */
IPage<ExamDTO> paging(Page page, @Param("query") ExamDTO query); IPage<ExamDTO> paging(Page page, @Param("query") ExamDTO query);
/** /**
* *
* * @param page
* * @param query
* @param page * @return
* @param query
* @return IPage<ExamReviewRespDTO> DTO
*/ */
IPage<ExamReviewRespDTO> reviewPaging(Page page, @Param("query") ExamDTO query); IPage<ExamReviewRespDTO> reviewPaging(Page page, @Param("query") ExamDTO query);
/** /**
* 线- * 线-
* 线 * @param page
* * @param query
* @param page * @return
* @param query
* @return IPage<ExamOnlineRespDTO> 线DTO
*/ */
IPage<ExamOnlineRespDTO> online(Page page, @Param("query") ExamDTO query); IPage<ExamOnlineRespDTO> online(Page page, @Param("query") ExamDTO query);
} }

@ -1,29 +1,26 @@
package com.yf.exam.modules.exam.mapper; // 定义包名Mapper接口所在的包路径 package com.yf.exam.modules.exam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis Plus的基础Mapper接口 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import com.yf.exam.modules.exam.entity.ExamRepo; // 导入考试题库实体类 import com.yf.exam.modules.exam.entity.ExamRepo;
import org.apache.ibatis.annotations.Param; // 导入MyBatis的参数注解 import org.apache.ibatis.annotations.Param;
import java.util.List; // 导入Java的List接口 import java.util.List;
/** /**
* <p> * <p>
* Mapper * Mapper
* </p> * </p>
* MapperMyBatis PlusBaseMapper
* *
* @author * @author
* @since 2020-09-05 11:14 * @since 2020-09-05 11:14
*/ */
public interface ExamRepoMapper extends BaseMapper<ExamRepo> { // 声明接口继承自BaseMapper并指定操作的实体类为ExamRepo public interface ExamRepoMapper extends BaseMapper<ExamRepo> {
/** /**
* *
* IDDTO * @param examId
* * @return
* @param examId ID
* @return List<ExamRepoExtDTO>
*/ */
List<ExamRepoExtDTO> listByExam(@Param("examId") String examId); // 使用MyBatis的@Param注解来指定方法参数的名称 List<ExamRepoExtDTO> listByExam(@Param("examId") String examId);
} }

@ -1,36 +1,32 @@
package com.yf.exam.modules.exam.service; // 定义包名,服务接口所在的包路径 package com.yf.exam.modules.exam.service;
import com.baomidou.mybatisplus.extension.service.IService; // 导入MyBatis Plus的服务接口 import com.baomidou.mybatisplus.extension.service.IService;
import com.yf.exam.modules.exam.entity.ExamDepart; // 导入考试部门实体类 import com.yf.exam.modules.exam.entity.ExamDepart;
import java.util.List; // 导入Java的List接口 import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* MyBatis PlusIService
* *
* @author * @author
* @since 2020-09-03 17:24 * @since 2020-09-03 17:24
*/ */
public interface ExamDepartService extends IService<ExamDepart> { // 声明接口继承自IService并指定操作的实体类为ExamDepart public interface ExamDepartService extends IService<ExamDepart> {
/** /**
* *
* IDID * @param examId
* * @param departs
* @param examId ID
* @param departs IDID
*/ */
void saveAll(String examId, List<String> departs); void saveAll(String examId, List<String> departs);
/** /**
* *
* IDID * @param examId
* * @return
* @param examId ID
* @return List<String> IDID
*/ */
List<String> listByExam(String examId); List<String> listByExam(String examId);
} }

@ -1,45 +1,40 @@
package com.yf.exam.modules.exam.service; // 定义包名,服务接口所在的包路径 package com.yf.exam.modules.exam.service;
import com.baomidou.mybatisplus.extension.service.IService; // 导入MyBatis Plus的服务接口 import com.baomidou.mybatisplus.extension.service.IService;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import com.yf.exam.modules.exam.entity.ExamRepo; // 导入考试题库实体类 import com.yf.exam.modules.exam.entity.ExamRepo;
import java.util.List; // 导入Java的List接口 import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* MyBatis PlusIService
* *
* @author * @author
* @since 2020-09-05 11:14 * @since 2020-09-05 11:14
*/ */
public interface ExamRepoService extends IService<ExamRepo> { // 声明接口继承自IService并指定操作的实体类为ExamRepo public interface ExamRepoService extends IService<ExamRepo> {
/** /**
* *
* ID * @param examId
* * @param list
* @param examId ID
* @param list
*/ */
void saveAll(String examId, List<ExamRepoExtDTO> list); void saveAll(String examId, List<ExamRepoExtDTO> list);
/** /**
* *
* ID * @param examId
* * @return
* @param examId ID
* @return List<ExamRepoExtDTO>
*/ */
List<ExamRepoExtDTO> listByExam(String examId); List<ExamRepoExtDTO> listByExam(String examId);
/** /**
* *
* ID * @param examId
*
* @param examId ID
*/ */
void clear(String examId); void clear(String examId);
} }

@ -1,75 +1,64 @@
package com.yf.exam.modules.exam.service; // 定义包名,服务接口所在的包路径 package com.yf.exam.modules.exam.service;
import com.baomidou.mybatisplus.core.metadata.IPage; // 导入MyBatis Plus的分页结果接口 import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; // 导入MyBatis Plus的服务接口 import com.baomidou.mybatisplus.extension.service.IService;
import com.yf.exam.core.api.dto.PagingReqDTO; // 导入分页请求DTO import com.yf.exam.core.api.dto.PagingReqDTO;
import com.yf.exam.modules.exam.dto.ExamDTO; // 导入考试DTO import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; // 导入考试保存请求DTO import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; // 导入在线考试响应DTO import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; // 导入阅卷响应DTO import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
import com.yf.exam.modules.exam.entity.Exam; // 导入考试实体类 import com.yf.exam.modules.exam.entity.Exam;
/** /**
* <p> * <p>
* *
* </p> * </p>
* MyBatis PlusIService
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
public interface ExamService extends IService<Exam> { // 声明接口继承自IService并指定操作的实体类为Exam public interface ExamService extends IService<Exam> {
/** /**
* *
* * @param reqDTO
*
* @param reqDTO
*/ */
void save(ExamSaveReqDTO reqDTO); void save(ExamSaveReqDTO reqDTO);
/** /**
* *
* ID * @param id
* * @return
* @param id ID
* @return ExamSaveReqDTO
*/ */
ExamSaveReqDTO findDetail(String id); ExamSaveReqDTO findDetail(String id);
/** /**
* -- * --
* ID * @param id
* * @return
* @param id ID
* @return ExamDTO
*/ */
ExamDTO findById(String id); ExamDTO findById(String id);
/** /**
* *
* * @param reqDTO
* * @return
* @param reqDTO
* @return IPage<ExamDTO>
*/ */
IPage<ExamDTO> paging(PagingReqDTO<ExamDTO> reqDTO); IPage<ExamDTO> paging(PagingReqDTO<ExamDTO> reqDTO);
/** /**
* 线- * 线-
* 线 * @param reqDTO
* * @return
* @param reqDTO
* @return IPage<ExamOnlineRespDTO> 线线
*/ */
IPage<ExamOnlineRespDTO> onlinePaging(PagingReqDTO<ExamDTO> reqDTO); IPage<ExamOnlineRespDTO> onlinePaging(PagingReqDTO<ExamDTO> reqDTO);
/** /**
* *
* * @param reqDTO
* * @return
* @param reqDTO
* @return IPage<ExamReviewRespDTO>
*/ */
IPage<ExamReviewRespDTO> reviewPaging(PagingReqDTO<ExamDTO> reqDTO); IPage<ExamReviewRespDTO> reviewPaging(PagingReqDTO<ExamDTO> reqDTO);
} }

@ -1,64 +1,66 @@
package com.yf.exam.modules.exam.service.impl; // 定义包名,服务实现类所在的包路径 package com.yf.exam.modules.exam.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 导入MyBatis Plus的查询包装类 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; // 导入MyBatis Plus的服务实现类 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yf.exam.core.exception.ServiceException; // 导入自定义的服务异常类 import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.modules.exam.entity.ExamDepart; // 导入考试部门实体类 import com.yf.exam.modules.exam.entity.ExamDepart;
import com.yf.exam.modules.exam.mapper.ExamDepartMapper; // 导入考试部门Mapper接口 import com.yf.exam.modules.exam.mapper.ExamDepartMapper;
import com.yf.exam.modules.exam.service.ExamDepartService; // 导入考试部门服务接口 import com.yf.exam.modules.exam.service.ExamDepartService;
import org.springframework.stereotype.Service; // 导入Spring的服务注解 import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; // 导入Spring的集合工具类 import org.springframework.util.CollectionUtils;
import java.util.ArrayList; // 导入Java的ArrayList类 import java.util.ArrayList;
import java.util.List; // 导入Java的List接口 import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* ExamDepartService
* *
* @author * @author
* @since 2020-09-03 17:24 * @since 2020-09-03 17:24
*/ */
@Service // Spring注解声明这是一个服务组件 @Service
public class ExamDepartServiceImpl extends ServiceImpl<ExamDepartMapper, ExamDepart> implements ExamDepartService { // 声明类继承自ServiceImpl并实现ExamDepartService接口 public class ExamDepartServiceImpl extends ServiceImpl<ExamDepartMapper, ExamDepart> implements ExamDepartService {
@Override @Override
public void saveAll(String examId, List<String> departs) { public void saveAll(String examId, List<String> departs) {
// 先删除已有的部门
// 先删除
QueryWrapper<ExamDepart> wrapper = new QueryWrapper<>(); QueryWrapper<ExamDepart> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(ExamDepart::getExamId, examId); // 构造查询条件查询指定考试ID的部门 wrapper.lambda().eq(ExamDepart::getExamId, examId);
this.remove(wrapper); // 根据条件删除部门 this.remove(wrapper);
// 再增加新的部门 // 再增加
if (CollectionUtils.isEmpty(departs)) { // 检查部门列表是否为空 if(CollectionUtils.isEmpty(departs)){
throw new ServiceException(1, "请至少选择选择一个部门!!"); // 如果为空,抛出异常 throw new ServiceException(1, "请至少选择选择一个部门!!");
} }
List<ExamDepart> list = new ArrayList<>(); // 创建考试部门列表 List<ExamDepart> list = new ArrayList<>();
for(String id: departs){ for(String id: departs){
ExamDepart depart = new ExamDepart(); // 创建考试部门对象 ExamDepart depart = new ExamDepart();
depart.setDepartId(id); // 设置部门ID depart.setDepartId(id);
depart.setExamId(examId); // 设置考试ID depart.setExamId(examId);
list.add(depart); // 添加到列表 list.add(depart);
} }
this.saveBatch(list); // 批量保存部门 this.saveBatch(list);
} }
@Override @Override
public List<String> listByExam(String examId) { public List<String> listByExam(String examId) {
// 查找考试对应的部门 // 先删除
QueryWrapper<ExamDepart> wrapper = new QueryWrapper<>(); QueryWrapper<ExamDepart> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(ExamDepart::getExamId, examId); // 构造查询条件查询指定考试ID的部门 wrapper.lambda().eq(ExamDepart::getExamId, examId);
List<ExamDepart> list = this.list(wrapper); // 根据条件查询部门列表 List<ExamDepart> list = this.list(wrapper);
List<String> ids = new ArrayList<>(); // 创建部门ID列表 List<String> ids = new ArrayList<>();
if (!CollectionUtils.isEmpty(list)) { // 检查部门列表是否为空 if(!CollectionUtils.isEmpty(list)){
for(ExamDepart item: list){ for(ExamDepart item: list){
ids.add(item.getDepartId()); // 添加部门ID到列表 ids.add(item.getDepartId());
} }
} }
return ids; // 返回部门ID列表
return ids;
} }
} }

@ -1,63 +1,67 @@
package com.yf.exam.modules.exam.service.impl; // 定义包名,服务实现类所在的包路径 package com.yf.exam.modules.exam.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 导入MyBatis Plus的查询包装类 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; // 导入MyBatis Plus的ID生成工具类 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; // 导入MyBatis Plus的服务实现类 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yf.exam.core.exception.ServiceException; // 导入自定义的服务异常类 import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.core.utils.BeanMapper; // 导入自定义的Bean映射工具类 import com.yf.exam.core.utils.BeanMapper;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import com.yf.exam.modules.exam.entity.ExamRepo; // 导入考试题库实体类 import com.yf.exam.modules.exam.entity.ExamRepo;
import com.yf.exam.modules.exam.mapper.ExamRepoMapper; // 导入考试题库Mapper接口 import com.yf.exam.modules.exam.mapper.ExamRepoMapper;
import com.yf.exam.modules.exam.service.ExamRepoService; // 导入考试题库服务接口 import com.yf.exam.modules.exam.service.ExamRepoService;
import org.springframework.stereotype.Service; // 导入Spring的服务注解 import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; // 导入Spring的事务注解 import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; // 导入Spring的集合工具类 import org.springframework.util.CollectionUtils;
import java.util.List; // 导入Java的List接口 import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* ExamRepoService
* *
* @author * @author
* @since 2020-09-05 11:14 * @since 2020-09-05 11:14
*/ */
@Service // Spring注解声明这是一个服务组件 @Service
public class ExamRepoServiceImpl extends ServiceImpl<ExamRepoMapper, ExamRepo> implements ExamRepoService { // 声明类继承自ServiceImpl并实现ExamRepoService接口 public class ExamRepoServiceImpl extends ServiceImpl<ExamRepoMapper, ExamRepo> implements ExamRepoService {
@Transactional(rollbackFor = Exception.class) // Spring事务注解声明事务边界和回滚条件
@Transactional(rollbackFor = Exception.class)
@Override @Override
public void saveAll(String examId, List<ExamRepoExtDTO> list) { public void saveAll(String examId, List<ExamRepoExtDTO> list) {
// 先删除已有的题库
// 先删除
QueryWrapper<ExamRepo> wrapper = new QueryWrapper<>(); QueryWrapper<ExamRepo> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(ExamRepo::getExamId, examId); // 构造查询条件查询指定考试ID的题库 wrapper.lambda().eq(ExamRepo::getExamId, examId);
this.remove(wrapper); // 根据条件删除题库 this.remove(wrapper);
// 再增加新的题库 // 再增加
if (CollectionUtils.isEmpty(list)) { // 检查题库列表是否为空 if(CollectionUtils.isEmpty(list)){
throw new ServiceException(1, "必须选择题库!"); // 如果为空,抛出异常 throw new ServiceException(1, "必须选择题库!");
} }
List<ExamRepo> repos = BeanMapper.mapList(list, ExamRepo.class); // 使用BeanMapper将DTO列表转换为实体类列表 List<ExamRepo> repos = BeanMapper.mapList(list, ExamRepo.class);
for(ExamRepo item: repos){ for(ExamRepo item: repos){
item.setExamId(examId); // 设置考试ID item.setExamId(examId);
item.setId(IdWorker.getIdStr()); // 使用IdWorker生成ID item.setId(IdWorker.getIdStr());
} }
this.saveBatch(repos); // 批量保存题库 this.saveBatch(repos);
} }
@Override @Override
public List<ExamRepoExtDTO> listByExam(String examId) { public List<ExamRepoExtDTO> listByExam(String examId) {
return baseMapper.listByExam(examId); // 调用Mapper接口的方法查找考试题库列表 return baseMapper.listByExam(examId);
} }
@Override @Override
public void clear(String examId) { public void clear(String examId) {
// 先删除已有的题库
// 先删除
QueryWrapper<ExamRepo> wrapper = new QueryWrapper<>(); QueryWrapper<ExamRepo> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(ExamRepo::getExamId, examId); // 构造查询条件查询指定考试ID的题库 wrapper.lambda().eq(ExamRepo::getExamId, examId);
this.remove(wrapper); // 根据条件删除题库 this.remove(wrapper);
} }
} }

@ -1,173 +1,194 @@
package com.yf.exam.modules.exam.service.impl; // 定义包名,服务实现类所在的包路径 package com.yf.exam.modules.exam.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage; // 导入MyBatis Plus的分页结果接口 import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; // 导入MyBatis Plus的ID生成工具类 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入MyBatis Plus的分页对象 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; // 导入MyBatis Plus的服务实现类 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yf.exam.core.api.dto.PagingReqDTO; // 导入分页请求DTO import com.yf.exam.core.api.dto.PagingReqDTO;
import com.yf.exam.core.enums.OpenType; // 导入开放类型枚举 import com.yf.exam.core.enums.OpenType;
import com.yf.exam.core.exception.ServiceException; // 导入自定义的服务异常类 import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.core.utils.BeanMapper; // 导入自定义的Bean映射工具类 import com.yf.exam.core.utils.BeanMapper;
import com.yf.exam.modules.exam.dto.ExamDTO; // 导入考试DTO import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.ExamRepoDTO; // 导入考试题库DTO import com.yf.exam.modules.exam.dto.ExamRepoDTO;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; // 导入考试保存请求DTO import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; // 导入在线考试响应DTO import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; // 导入阅卷响应DTO import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
import com.yf.exam.modules.exam.entity.Exam; // 导入考试实体类 import com.yf.exam.modules.exam.entity.Exam;
import com.yf.exam.modules.exam.mapper.ExamMapper; // 导入考试Mapper接口 import com.yf.exam.modules.exam.mapper.ExamMapper;
import com.yf.exam.modules.exam.service.ExamDepartService; // 导入考试部门服务接口 import com.yf.exam.modules.exam.service.ExamDepartService;
import com.yf.exam.modules.exam.service.ExamRepoService; // 导入考试题库服务接口 import com.yf.exam.modules.exam.service.ExamRepoService;
import com.yf.exam.modules.exam.service.ExamService; // 导入考试服务接口 import com.yf.exam.modules.exam.service.ExamService;
import org.apache.commons.lang3.StringUtils; // 导入Apache Commons Lang的字符串工具类 import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; // 导入Spring的自动注入注解 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException; // 导入Spring的重复键异常类 import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service; // 导入Spring的服务注解 import org.springframework.stereotype.Service;
import java.util.List; // 导入Java的List接口 import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* ExamService
* *
* @author * @author
* @since 2020-07-25 16:18 * @since 2020-07-25 16:18
*/ */
@Service // Spring注解声明这是一个服务组件 @Service
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements ExamService { // 声明类继承自ServiceImpl并实现ExamService接口 public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements ExamService {
@Autowired @Autowired
private ExamRepoService examRepoService; // 自动注入考试题库服务 private ExamRepoService examRepoService;
@Autowired @Autowired
private ExamDepartService examDepartService; // 自动注入考试部门服务 private ExamDepartService examDepartService;
@Override @Override
public void save(ExamSaveReqDTO reqDTO) { public void save(ExamSaveReqDTO reqDTO) {
// ID // ID
String id = reqDTO.getId(); String id = reqDTO.getId();
if(StringUtils.isBlank(id)){ // 如果ID为空则生成新的ID if(StringUtils.isBlank(id)){
id = IdWorker.getIdStr(); // 使用IdWorker生成ID id = IdWorker.getIdStr();
} }
//复制参数 //复制参数
Exam entity = new Exam(); // 创建考试实体 Exam entity = new Exam();
// 计算分值 // 计算分值
this.calcScore(reqDTO); // 调用方法计算分值 this.calcScore(reqDTO);
// 复制基本数据 // 复制基本数据
BeanMapper.copy(reqDTO, entity); // 使用BeanMapper复制属性 BeanMapper.copy(reqDTO, entity);
entity.setId(id); // 设置ID entity.setId(id);
// 修复状态 // 修复状态
if (reqDTO.getTimeLimit()!=null if (reqDTO.getTimeLimit()!=null
&& !reqDTO.getTimeLimit() && !reqDTO.getTimeLimit()
&& reqDTO.getState()!=null && reqDTO.getState()!=null
&& reqDTO.getState() == 2) { && reqDTO.getState() == 2) {
entity.setState(0); // 如果不限时且状态为2则状态设置为0 entity.setState(0);
} else { } else {
entity.setState(reqDTO.getState()); // 否则直接设置状态 entity.setState(reqDTO.getState());
} }
// 题库组卷 // 题库组卷
try { try {
examRepoService.saveAll(id, reqDTO.getRepoList()); // 调用考试题库服务保存题库 examRepoService.saveAll(id, reqDTO.getRepoList());
}catch (DuplicateKeyException e){ }catch (DuplicateKeyException e){
throw new ServiceException(1, "不能选择重复的题库!"); // 如果出现重复键异常,则抛出服务异常 throw new ServiceException(1, "不能选择重复的题库!");
} }
// 开放的部门 // 开放的部门
if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){ // 如果开放类型为部门开放 if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){
examDepartService.saveAll(id, reqDTO.getDepartIds()); // 调用考试部门服务保存部门 examDepartService.saveAll(id, reqDTO.getDepartIds());
} }
this.saveOrUpdate(entity); // 保存或更新考试实体 this.saveOrUpdate(entity);
} }
@Override @Override
public ExamSaveReqDTO findDetail(String id) { public ExamSaveReqDTO findDetail(String id) {
ExamSaveReqDTO respDTO = new ExamSaveReqDTO(); // 创建响应DTO ExamSaveReqDTO respDTO = new ExamSaveReqDTO();
Exam exam = this.getById(id); // 根据ID查询考试实体 Exam exam = this.getById(id);
BeanMapper.copy(exam, respDTO); // 使用BeanMapper复制属性 BeanMapper.copy(exam, respDTO);
// 考试部门 // 考试部门
List<String> departIds = examDepartService.listByExam(id); // 调用考试部门服务查询部门ID列表 List<String> departIds = examDepartService.listByExam(id);
respDTO.setDepartIds(departIds); // 设置部门ID列表 respDTO.setDepartIds(departIds);
// 题库 // 题库
List<ExamRepoExtDTO> repos = examRepoService.listByExam(id); // 调用考试题库服务查询题库列表 List<ExamRepoExtDTO> repos = examRepoService.listByExam(id);
respDTO.setRepoList(repos); // 设置题库列表 respDTO.setRepoList(repos);
return respDTO; // 返回响应DTO return respDTO;
} }
@Override @Override
public ExamDTO findById(String id) { public ExamDTO findById(String id) {
ExamDTO respDTO = new ExamDTO(); // 创建响应DTO ExamDTO respDTO = new ExamDTO();
Exam exam = this.getById(id); // 根据ID查询考试实体 Exam exam = this.getById(id);
BeanMapper.copy(exam, respDTO); // 使用BeanMapper复制属性 BeanMapper.copy(exam, respDTO);
return respDTO; // 返回响应DTO return respDTO;
} }
@Override @Override
public IPage<ExamDTO> paging(PagingReqDTO<ExamDTO> reqDTO) { public IPage<ExamDTO> paging(PagingReqDTO<ExamDTO> reqDTO) {
//创建分页对象 //创建分页对象
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); // 使用分页请求DTO创建分页对象 Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
//转换结果 //转换结果
IPage<ExamDTO> pageData = baseMapper.paging(page, reqDTO.getParams()); // 调用Mapper接口的分页方法 IPage<ExamDTO> pageData = baseMapper.paging(page, reqDTO.getParams());
return pageData; // 返回分页结果 return pageData;
} }
@Override @Override
public IPage<ExamOnlineRespDTO> onlinePaging(PagingReqDTO<ExamDTO> reqDTO) { public IPage<ExamOnlineRespDTO> onlinePaging(PagingReqDTO<ExamDTO> reqDTO) {
// 创建分页对象 // 创建分页对象
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); // 使用分页请求DTO创建分页对象 Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
// 查找分页 // 查找分页
IPage<ExamOnlineRespDTO> pageData = baseMapper.online(page, reqDTO.getParams()); // 调用Mapper接口的在线考试分页方法 IPage<ExamOnlineRespDTO> pageData = baseMapper.online(page, reqDTO.getParams());
return pageData; // 返回分页结果
return pageData;
} }
@Override @Override
public IPage<ExamReviewRespDTO> reviewPaging(PagingReqDTO<ExamDTO> reqDTO) { public IPage<ExamReviewRespDTO> reviewPaging(PagingReqDTO<ExamDTO> reqDTO) {
// 创建分页对象 // 创建分页对象
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); // 使用分页请求DTO创建分页对象 Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
// 查找分页 // 查找分页
IPage<ExamReviewRespDTO> pageData = baseMapper.reviewPaging(page, reqDTO.getParams()); // 调用Mapper接口的阅卷分页方法 IPage<ExamReviewRespDTO> pageData = baseMapper.reviewPaging(page, reqDTO.getParams());
return pageData; // 返回分页结果
return pageData;
} }
/** /**
* *
* * @param reqDTO
*
* @param reqDTO DTO
*/ */
private void calcScore(ExamSaveReqDTO reqDTO){ private void calcScore(ExamSaveReqDTO reqDTO){
// 主观题分数 // 主观题分数
int objScore = 0; int objScore = 0;
// 题库组卷 // 题库组卷
List<ExamRepoExtDTO> repoList = reqDTO.getRepoList(); // 获取题库列表 List<ExamRepoExtDTO> repoList = reqDTO.getRepoList();
for(ExamRepoDTO item : repoList){ // 遍历题库列表 for(ExamRepoDTO item: repoList){
if(item.getRadioCount() != null && item.getRadioCount() > 0 && item.getRadioScore() != null && item.getRadioScore() > 0){ if(item.getRadioCount()!=null
objScore += item.getRadioCount() * item.getRadioScore(); // 计算单选题分数 && item.getRadioCount()>0
&& item.getRadioScore()!=null
&& item.getRadioScore()>0){
objScore+=item.getRadioCount()*item.getRadioScore();
} }
if(item.getMultiCount() != null && item.getMultiCount() > 0 && item.getMultiScore() != null && item.getMultiScore() > 0){ if(item.getMultiCount()!=null
objScore += item.getMultiCount() * item.getMultiScore(); // 计算多选题分数 && item.getMultiCount()>0
&& item.getMultiScore()!=null
&& item.getMultiScore()>0){
objScore+=item.getMultiCount()*item.getMultiScore();
} }
if(item.getJudgeCount() != null && item.getJudgeCount() > 0 && item.getJudgeScore() != null && item.getJudgeScore() > 0){ if(item.getJudgeCount()!=null
objScore += item.getJudgeCount() * item.getJudgeScore(); // 计算判断题分数 && item.getJudgeCount()>0
&& item.getJudgeScore()!=null
&& item.getJudgeScore()>0){
objScore+=item.getJudgeCount()*item.getJudgeScore();
} }
} }
reqDTO.setTotalScore(objScore); // 设置总分值
reqDTO.setTotalScore(objScore);
} }
} }

@ -1,13 +1,19 @@
package com.yf.exam.modules.paper.controller; package com.yf.exam.modules.paper.controller; // 定义当前类所在的包路径
// 引入分页查询结果的接口
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
// 引入统一的API响应封装类
import com.yf.exam.core.api.ApiRest; import com.yf.exam.core.api.ApiRest;
// 引入基础控制器类提供通用的API响应方法
import com.yf.exam.core.api.controller.BaseController; import com.yf.exam.core.api.controller.BaseController;
// 引入各种DTO类用于请求和响应数据传输
import com.yf.exam.core.api.dto.BaseIdReqDTO; import com.yf.exam.core.api.dto.BaseIdReqDTO;
import com.yf.exam.core.api.dto.BaseIdRespDTO; import com.yf.exam.core.api.dto.BaseIdRespDTO;
import com.yf.exam.core.api.dto.BaseIdsReqDTO; import com.yf.exam.core.api.dto.BaseIdsReqDTO;
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO;
// 引入工具类,用于对象之间的属性拷贝
import com.yf.exam.core.utils.BeanMapper; import com.yf.exam.core.utils.BeanMapper;
// 引入试卷相关的DTO类
import com.yf.exam.modules.paper.dto.PaperDTO; import com.yf.exam.modules.paper.dto.PaperDTO;
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO;
@ -17,14 +23,22 @@ import com.yf.exam.modules.paper.dto.request.PaperQuQueryDTO;
import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO;
import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO;
import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; import com.yf.exam.modules.paper.dto.response.PaperListRespDTO;
// 引入试卷实体类
import com.yf.exam.modules.paper.entity.Paper; import com.yf.exam.modules.paper.entity.Paper;
// 引入试卷相关的业务处理服务类
import com.yf.exam.modules.paper.service.PaperService; import com.yf.exam.modules.paper.service.PaperService;
// 引入用户工具类,获取当前登录用户信息
import com.yf.exam.modules.user.UserUtils; import com.yf.exam.modules.user.UserUtils;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
// 引入Shiro注解用于角色权限管理
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
// 引入Spring的Bean工具类用于属性复制
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
// 引入Spring的自动注入注解用于自动注入服务
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
// 引入Spring的Web注解定义HTTP请求的映射方式
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@ -34,123 +48,126 @@ import org.springframework.web.bind.annotation.RestController;
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
*/ */
@Api(tags={"试卷"}) @Api(tags={"试卷"}) // Swagger API 文档注解,用于描述该接口属于试卷相关操作
@RestController @RestController // 标记为Spring的控制器处理HTTP请求
@RequestMapping("/exam/api/paper/paper") @RequestMapping("/exam/api/paper/paper") // 定义请求路径的基础路径
public class PaperController extends BaseController { public class PaperController extends BaseController { // 继承BaseController类提供基本的API返回
@Autowired @Autowired // 自动注入PaperService负责业务逻辑处理
private PaperService baseService; private PaperService baseService;
/** /**
* *
* @param reqDTO * @param reqDTO
* @return * @return
*/ */
@ApiOperation(value = "分页查找") @ApiOperation(value = "分页查找") // Swagger操作注解用于描述接口
@RequestMapping(value = "/paging", method = { RequestMethod.POST}) @RequestMapping(value = "/paging", method = { RequestMethod.POST}) // 定义POST请求路径
public ApiRest<IPage<PaperListRespDTO>> paging(@RequestBody PagingReqDTO<PaperListReqDTO> reqDTO) { public ApiRest<IPage<PaperListRespDTO>> paging(@RequestBody PagingReqDTO<PaperListReqDTO> reqDTO) {
//分页查询并转换 // 调用业务层进行分页查询
IPage<PaperListRespDTO> page = baseService.paging(reqDTO); IPage<PaperListRespDTO> page = baseService.paging(reqDTO);
// 返回成功响应,并附带查询结果
return super.success(page); return super.success(page);
} }
/** /**
* *
* @param reqDTO * @param reqDTO
* @return * @return ID
*/ */
@ApiOperation(value = "创建试卷") @ApiOperation(value = "创建试卷") // Swagger操作注解用于描述接口
@RequestMapping(value = "/create-paper", method = { RequestMethod.POST}) @RequestMapping(value = "/create-paper", method = { RequestMethod.POST}) // 定义POST请求路径
public ApiRest<BaseIdRespDTO> save(@RequestBody PaperCreateReqDTO reqDTO) { public ApiRest<BaseIdRespDTO> save(@RequestBody PaperCreateReqDTO reqDTO) {
//复制参数 // 调用业务层创建试卷传入当前用户ID和考试ID
String paperId = baseService.createPaper(UserUtils.getUserId(), reqDTO.getExamId()); String paperId = baseService.createPaper(UserUtils.getUserId(), reqDTO.getExamId());
// 返回创建结果包括试卷ID
return super.success(new BaseIdRespDTO(paperId)); return super.success(new BaseIdRespDTO(paperId));
} }
/** /**
* *
* @param reqDTO * @param reqDTO ID
* @return * @return
*/ */
@ApiOperation(value = "试卷详情") @ApiOperation(value = "试卷详情") // Swagger操作注解用于描述接口
@RequestMapping(value = "/paper-detail", method = { RequestMethod.POST}) @RequestMapping(value = "/paper-detail", method = { RequestMethod.POST}) // 定义POST请求路径
public ApiRest<ExamDetailRespDTO> paperDetail(@RequestBody BaseIdReqDTO reqDTO) { public ApiRest<ExamDetailRespDTO> paperDetail(@RequestBody BaseIdReqDTO reqDTO) {
//根据ID删除 // 调用业务层获取试卷详情
ExamDetailRespDTO respDTO = baseService.paperDetail(reqDTO.getId()); ExamDetailRespDTO respDTO = baseService.paperDetail(reqDTO.getId());
// 返回成功响应,并附带试卷详情
return super.success(respDTO); return super.success(respDTO);
} }
/** /**
* *
* @param reqDTO * @param reqDTO IDID
* @return * @return
*/ */
@ApiOperation(value = "试题详情") @ApiOperation(value = "试题详情") // Swagger操作注解用于描述接口
@RequestMapping(value = "/qu-detail", method = { RequestMethod.POST}) @RequestMapping(value = "/qu-detail", method = { RequestMethod.POST}) // 定义POST请求路径
public ApiRest<PaperQuDetailDTO> quDetail(@RequestBody PaperQuQueryDTO reqDTO) { public ApiRest<PaperQuDetailDTO> quDetail(@RequestBody PaperQuQueryDTO reqDTO) {
//根据ID删除 // 调用业务层获取试题详情
PaperQuDetailDTO respDTO = baseService.findQuDetail(reqDTO.getPaperId(), reqDTO.getQuId()); PaperQuDetailDTO respDTO = baseService.findQuDetail(reqDTO.getPaperId(), reqDTO.getQuId());
// 返回成功响应,并附带试题详情
return super.success(respDTO); return super.success(respDTO);
} }
/** /**
* *
* @param reqDTO * @param reqDTO
* @return * @return
*/ */
@ApiOperation(value = "填充答案") @ApiOperation(value = "填充答案") // Swagger操作注解用于描述接口
@RequestMapping(value = "/fill-answer", method = { RequestMethod.POST}) @RequestMapping(value = "/fill-answer", method = { RequestMethod.POST}) // 定义POST请求路径
public ApiRest<PaperQuDetailDTO> fillAnswer(@RequestBody PaperAnswerDTO reqDTO) { public ApiRest<PaperQuDetailDTO> fillAnswer(@RequestBody PaperAnswerDTO reqDTO) {
//根据ID删除 // 调用业务层填充答案操作
baseService.fillAnswer(reqDTO); baseService.fillAnswer(reqDTO);
// 返回成功响应
return super.success(); return super.success();
} }
/** /**
* *
* @param reqDTO * @param reqDTO ID
* @return * @return
*/ */
@ApiOperation(value = "交卷操作") @ApiOperation(value = "交卷操作") // Swagger操作注解用于描述接口
@RequestMapping(value = "/hand-exam", method = { RequestMethod.POST}) @RequestMapping(value = "/hand-exam", method = { RequestMethod.POST}) // 定义POST请求路径
public ApiRest<PaperQuDetailDTO> handleExam(@RequestBody BaseIdReqDTO reqDTO) { public ApiRest<PaperQuDetailDTO> handleExam(@RequestBody BaseIdReqDTO reqDTO) {
//根据ID删除 // 调用业务层进行交卷操作
baseService.handExam(reqDTO.getId()); baseService.handExam(reqDTO.getId());
// 返回成功响应
return super.success(); return super.success();
} }
/** /**
* *
* @param reqDTO * @param reqDTO ID
* @return * @return
*/ */
@ApiOperation(value = "试卷详情") @ApiOperation(value = "试卷结果") // Swagger操作注解用于描述接口
@RequestMapping(value = "/paper-result", method = { RequestMethod.POST}) @RequestMapping(value = "/paper-result", method = { RequestMethod.POST}) // 定义POST请求路径
public ApiRest<ExamResultRespDTO> paperResult(@RequestBody BaseIdReqDTO reqDTO) { public ApiRest<ExamResultRespDTO> paperResult(@RequestBody BaseIdReqDTO reqDTO) {
//根据ID删除 // 调用业务层获取试卷的考试结果
ExamResultRespDTO respDTO = baseService.paperResult(reqDTO.getId()); ExamResultRespDTO respDTO = baseService.paperResult(reqDTO.getId());
// 返回成功响应,并附带试卷结果
return super.success(respDTO); return super.success(respDTO);
} }
/** /**
* *
* @return * @return
*/ */
@ApiOperation(value = "检测进行中的考试") @ApiOperation(value = "检测进行中的考试") // Swagger操作注解用于描述接口
@RequestMapping(value = "/check-process", method = { RequestMethod.POST}) @RequestMapping(value = "/check-process", method = { RequestMethod.POST}) // 定义POST请求路径
public ApiRest<PaperDTO> checkProcess() { public ApiRest<PaperDTO> checkProcess() {
//复制参数 // 调用业务层检测用户是否有未完成的考试
PaperDTO dto = baseService.checkProcess(UserUtils.getUserId()); PaperDTO dto = baseService.checkProcess(UserUtils.getUserId());
// 返回成功响应,并附带考试进程数据
return super.success(dto); return super.success(dto);
} }
} }

@ -1,79 +1,83 @@
package com.yf.exam.modules.paper.dto; package com.yf.exam.modules.paper.dto; // 定义当前类所在的包路径
// 引入Dict注解用于字典表的映射
import com.yf.exam.core.annon.Dict; import com.yf.exam.core.annon.Dict;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入Serializable接口用于对象序列化
import java.io.Serializable; import java.io.Serializable;
// 引入Date类用于表示日期和时间
import java.util.Date; import java.util.Date;
/** /**
* <p> * <p>
* *
* </p> * </p>
* ID
* 使
* *
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="试卷", description="试卷") @ApiModel(value="试卷", description="试卷") // Swagger注解用于描述该类在API文档中的作用和说明
public class PaperDTO implements Serializable { public class PaperDTO implements Serializable { // 实现Serializable接口表示该类的对象可以被序列化
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "试卷ID", required=true) private static final long serialVersionUID = 1L; // 序列化版本ID用于序列化和反序列化操作
private String id;
@Dict(dictTable = "sys_user", dicText = "real_name", dicCode = "id") @ApiModelProperty(value = "试卷ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
@ApiModelProperty(value = "用户ID", required=true) private String id; // 试卷ID
private String userId;
@Dict(dictTable = "sys_depart", dicText = "dept_name", dicCode = "id") @Dict(dictTable = "sys_user", dicText = "real_name", dicCode = "id") // 字典表映射,映射用户表的姓名字段
@ApiModelProperty(value = "部门ID", required=true) @ApiModelProperty(value = "用户ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String departId; private String userId; // 用户ID
@ApiModelProperty(value = "规则ID", required=true) @Dict(dictTable = "sys_depart", dicText = "dept_name", dicCode = "id") // 字典表映射,映射部门表的部门名称字段
private String examId; @ApiModelProperty(value = "部门ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String departId; // 部门ID
@ApiModelProperty(value = "考试标题", required=true) @ApiModelProperty(value = "规则ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String title; private String examId; // 规则ID表示该试卷对应的考试规则
@ApiModelProperty(value = "考试时长", required=true) @ApiModelProperty(value = "考试标题", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer totalTime; private String title; // 考试标题,表示试卷的名称
@ApiModelProperty(value = "用户时长", required=true) @ApiModelProperty(value = "考试时长", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer userTime; private Integer totalTime; // 考试时长(单位:分钟)
@ApiModelProperty(value = "试卷总分", required=true) @ApiModelProperty(value = "用户时长", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer totalScore; private Integer userTime; // 用户已使用的时间(单位:分钟)
@ApiModelProperty(value = "及格分", required=true) @ApiModelProperty(value = "试卷总分", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer qualifyScore; private Integer totalScore; // 试卷总分
@ApiModelProperty(value = "客观分", required=true) @ApiModelProperty(value = "及格分", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer objScore; private Integer qualifyScore; // 及格分数
@ApiModelProperty(value = "观分", required=true) @ApiModelProperty(value = "观分", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer subjScore; private Integer objScore; // 客观题分数
@ApiModelProperty(value = "用户得分", required=true) @ApiModelProperty(value = "主观分", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer userScore; private Integer subjScore; // 主观题分数
@ApiModelProperty(value = "是否包含简答题", required=true) @ApiModelProperty(value = "用户得分", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Boolean hasSaq; private Integer userScore; // 用户得分
@ApiModelProperty(value = "试卷状态", required=true) @ApiModelProperty(value = "是否包含简答题", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer state; private Boolean hasSaq; // 是否包含简答题,布尔类型,表示该试卷是否有简答题
@ApiModelProperty(value = "创建时间", required=true) @ApiModelProperty(value = "试卷状态", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Date createTime; private Integer state; // 试卷状态,表示试卷的当前状态,如未开始、进行中、已结束等
@ApiModelProperty(value = "更新时间", required=true) @ApiModelProperty(value = "创建时间", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Date updateTime; private Date createTime; // 创建时间,表示试卷的创建时间
@ApiModelProperty(value = "截止时间") @ApiModelProperty(value = "更新时间", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Date limitTime; private Date updateTime; // 更新时间,表示试卷的最后更新时间
@ApiModelProperty(value = "截止时间") // Swagger注解描述该字段在API文档中的含义
private Date limitTime; // 截止时间,表示考试的最后提交时间
} }

@ -1,48 +1,50 @@
package com.yf.exam.modules.paper.dto; package com.yf.exam.modules.paper.dto; // 定义当前类所在的包路径
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入Serializable接口用于对象序列化
import java.io.Serializable; import java.io.Serializable;
/** /**
* <p> * <p>
* *
* </p> * </p>
* IDID
*
* *
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="试卷考题备选答案", description="试卷考题备选答案") @ApiModel(value="试卷考题备选答案", description="试卷考题备选答案") // Swagger注解用于描述该类在API文档中的作用和说明
public class PaperQuAnswerDTO implements Serializable { public class PaperQuAnswerDTO implements Serializable { // 实现Serializable接口表示该类的对象可以被序列化
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "自增ID", required=true) private static final long serialVersionUID = 1L; // 序列化版本ID用于序列化和反序列化操作
private String id;
@ApiModelProperty(value = "试卷ID", required=true) @ApiModelProperty(value = "自增ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String paperId; private String id; // 自增ID表示备选答案的唯一标识符
@ApiModelProperty(value = "回答项ID", required=true) @ApiModelProperty(value = "试卷ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String answerId; private String paperId; // 试卷ID表示该备选答案所属的试卷
@ApiModelProperty(value = "题目ID", required=true) @ApiModelProperty(value = "回答项ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String quId; private String answerId; // 回答项ID表示该备选答案的唯一标识符
@ApiModelProperty(value = "是否正确项", required=true) @ApiModelProperty(value = "题目ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Boolean isRight; private String quId; // 题目ID表示该备选答案所属的题目
@ApiModelProperty(value = "是否选中", required=true) @ApiModelProperty(value = "是否正确项", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Boolean checked; private Boolean isRight; // 是否正确项,布尔值,表示该备选答案是否是正确答案
@ApiModelProperty(value = "排序", required=true) @ApiModelProperty(value = "是否选中", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer sort; private Boolean checked; // 是否选中,布尔值,表示该备选答案是否已被选中
@ApiModelProperty(value = "选项标签", required=true) @ApiModelProperty(value = "排序", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String abc; private Integer sort; // 排序,表示该备选答案在题目中的排序位置
@ApiModelProperty(value = "选项标签", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String abc; // 选项标签,通常为 A、B、C、D 等,表示该备选答案的标识符
} }

@ -1,54 +1,55 @@
package com.yf.exam.modules.paper.dto; package com.yf.exam.modules.paper.dto; // 定义当前类所在的包路径
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入Serializable接口用于对象序列化
import java.io.Serializable; import java.io.Serializable;
/** /**
* <p> * <p>
* *
* </p> * </p>
* IDID
* *
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="试卷考题", description="试卷考题") @ApiModel(value="试卷考题", description="试卷考题") // Swagger注解用于描述该类在API文档中的作用和说明
public class PaperQuDTO implements Serializable { public class PaperQuDTO implements Serializable { // 实现Serializable接口表示该类的对象可以被序列化
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "ID", required=true) private static final long serialVersionUID = 1L; // 序列化版本ID用于序列化和反序列化操作
private String id;
@ApiModelProperty(value = "试卷ID", required=true) @ApiModelProperty(value = "ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String paperId; private String id; // 题目ID唯一标识符
@ApiModelProperty(value = "题目ID", required=true) @ApiModelProperty(value = "试卷ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String quId; private String paperId; // 试卷ID表示该题目所属的试卷
@ApiModelProperty(value = "题目类型", required=true) @ApiModelProperty(value = "题目ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer quType; private String quId; // 题目ID唯一标识该题目
@ApiModelProperty(value = "是否已答", required=true) @ApiModelProperty(value = "题目类型", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Boolean answered; private Integer quType; // 题目类型,表示题目的分类,如选择题、判断题、主观题等
@ApiModelProperty(value = "主观答案", required=true) @ApiModelProperty(value = "是否已答", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String answer; private Boolean answered; // 是否已答,布尔值,表示该题目是否已被回答
@ApiModelProperty(value = "问题排序", required=true) @ApiModelProperty(value = "主观答案", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer sort; private String answer; // 主观答案,表示对该题目的回答内容(适用于主观题)
@ApiModelProperty(value = "单题分分值", required=true) @ApiModelProperty(value = "问题排序", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer score; private Integer sort; // 问题排序,表示该题目在试卷中的顺序
@ApiModelProperty(value = "实际得分(主观题)", required=true) @ApiModelProperty(value = "单题分分值", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer actualScore; private Integer score; // 单题分值,表示该题目的满分
@ApiModelProperty(value = "是否答对", required=true) @ApiModelProperty(value = "实际得分(主观题)", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Boolean isRight; private Integer actualScore; // 实际得分,表示用户在该题目中实际得到的分数(适用于主观题)
@ApiModelProperty(value = "是否答对", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Boolean isRight; // 是否答对,布尔值,表示用户是否答对了该题目
} }

@ -1,29 +1,30 @@
package com.yf.exam.modules.paper.dto.ext; package com.yf.exam.modules.paper.dto.ext; // 定义该类所在的包路径
// 引入试题答案DTO类作为当前类的父类
import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
/** /**
* <p> * <p>
* *
* </p> * </p>
* * PaperQuAnswerDTO
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="试卷考题备选答案", description="试卷考题备选答案") @ApiModel(value="试卷考题备选答案", description="试卷考题备选答案") // Swagger注解描述模型信息生成API文档时使用
public class PaperQuAnswerExtDTO extends PaperQuAnswerDTO { public class PaperQuAnswerExtDTO extends PaperQuAnswerDTO { // 继承自PaperQuAnswerDTO类扩展了额外属性
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本号,用于序列化和反序列化时的版本控制
@ApiModelProperty(value = "试题图片", required=true)
private String image;
@ApiModelProperty(value = "答案内容", required=true)
private String content;
@ApiModelProperty(value = "试题图片", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String image; // 试题对应的图片内容
@ApiModelProperty(value = "答案内容", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String content; // 备选答案的具体内容
} }

@ -1,32 +1,35 @@
package com.yf.exam.modules.paper.dto.ext; package com.yf.exam.modules.paper.dto.ext; // 定义当前类所在的包路径
// 引入试题DTO类作为当前类的父类
import com.yf.exam.modules.paper.dto.PaperQuDTO; import com.yf.exam.modules.paper.dto.PaperQuDTO;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入List集合用于存储多个备选答案
import java.util.List; import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* * PaperQuDTO
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="试卷题目详情类", description="试卷题目详情类") @ApiModel(value="试卷题目详情类", description="试卷题目详情类") // Swagger注解用于描述该类在API文档中的作用和说明
public class PaperQuDetailDTO extends PaperQuDTO { public class PaperQuDetailDTO extends PaperQuDTO { // 继承自PaperQuDTO类扩展了额外属性
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本号,用于序列化和反序列化时的版本控制
@ApiModelProperty(value = "图片", required=true) @ApiModelProperty(value = "图片", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String image; private String image; // 题目的图片内容
@ApiModelProperty(value = "题目内容", required=true) @ApiModelProperty(value = "题目内容", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String content; private String content; // 试题的具体内容
@ApiModelProperty(value = "答案内容", required=true) @ApiModelProperty(value = "答案内容", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
List<PaperQuAnswerExtDTO> answerList; private List<PaperQuAnswerExtDTO> answerList; // 存储该题目的备选答案使用List集合存储多个答案
} }

@ -1,22 +1,25 @@
package com.yf.exam.modules.paper.dto.request; package com.yf.exam.modules.paper.dto.request; // 定义当前类所在的包路径
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入List集合用于存储多个答案
import java.util.List; import java.util.List;
/** /**
* @author bool * @author bool
*
* PaperQuQueryDTO
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类") @ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类") // Swagger注解用于描述该类在API文档中的作用和说明
public class PaperAnswerDTO extends PaperQuQueryDTO { public class PaperAnswerDTO extends PaperQuQueryDTO { // 继承自PaperQuQueryDTO类扩展了额外属性
@ApiModelProperty(value = "回答列表", required=true)
private List<String> answers;
@ApiModelProperty(value = "主观答案", required=true) @ApiModelProperty(value = "回答列表", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String answer; private List<String> answers; // 存储多个选择题答案的列表
@ApiModelProperty(value = "主观答案", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String answer; // 存储主观题的答案内容
} }

@ -1,22 +1,26 @@
package com.yf.exam.modules.paper.dto.request; package com.yf.exam.modules.paper.dto.request; // 定义当前类所在的包路径
// 引入父类BaseDTO用于继承基础字段和功能
import com.yf.exam.core.api.dto.BaseDTO; import com.yf.exam.core.api.dto.BaseDTO;
// 引入Jackson注解用于处理JSON序列化时忽略某些字段
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
/** /**
* @author bool * @author bool
* IDID
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="试卷创建请求类", description="试卷创建请求类") @ApiModel(value="试卷创建请求类", description="试卷创建请求类") // Swagger注解用于描述该类在API文档中的作用和说明
public class PaperCreateReqDTO extends BaseDTO { public class PaperCreateReqDTO extends BaseDTO { // 继承自BaseDTO类扩展了考试ID和用户ID字段
@JsonIgnore @JsonIgnore // Jackson注解表示在进行JSON序列化/反序列化时忽略该字段
private String userId; private String userId; // 存储用户ID通常用于标识发起请求的用户但在JSON序列化中不会被传递
@ApiModelProperty(value = "考试ID", required=true)
private String examId;
@ApiModelProperty(value = "考试ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String examId; // 存储考试ID用于创建试卷时指定关联的考试
} }

@ -1,39 +1,40 @@
package com.yf.exam.modules.paper.dto.request; package com.yf.exam.modules.paper.dto.request; // 定义当前类所在的包路径
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入Serializable接口用于对象序列化
import java.io.Serializable; import java.io.Serializable;
/** /**
* <p> * <p>
* *
* </p> * </p>
* * IDIDID
*
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="试卷", description="试卷") @ApiModel(value="试卷", description="试卷") // Swagger注解描述该类在API文档中的作用和说明
public class PaperListReqDTO implements Serializable { public class PaperListReqDTO implements Serializable { // 实现Serializable接口支持对象的序列化
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户ID", required=true)
private String userId;
@ApiModelProperty(value = "部门ID", required=true) private static final long serialVersionUID = 1L; // 序列化版本号,用于序列化和反序列化时的版本控制
private String departId;
@ApiModelProperty(value = "规则ID", required=true) @ApiModelProperty(value = "用户ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String examId; private String userId; // 存储请求发起者的用户ID通常用于标识用户
@ApiModelProperty(value = "用户昵称", required=true) @ApiModelProperty(value = "部门ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String realName; private String departId; // 存储请求者所在部门的ID用于查询特定部门的试卷
@ApiModelProperty(value = "试卷状态", required=true) @ApiModelProperty(value = "规则ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer state; private String examId; // 存储与试卷相关的考试规则ID用于标识试卷属于哪种考试
@ApiModelProperty(value = "用户昵称", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String realName; // 存储用户的真实姓名,用于标识请求者
@ApiModelProperty(value = "试卷状态", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private Integer state; // 存储试卷的状态,可能的值如:未开始、进行中、已完成等
} }

@ -1,21 +1,25 @@
package com.yf.exam.modules.paper.dto.request; package com.yf.exam.modules.paper.dto.request; // 定义当前类所在的包路径
// 引入父类BaseDTO用于继承基础字段和功能
import com.yf.exam.core.api.dto.BaseDTO; import com.yf.exam.core.api.dto.BaseDTO;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
/** /**
* @author bool * @author bool
*
* IDID
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类") @ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类") // Swagger注解用于描述该类在API文档中的作用和说明
public class PaperQuQueryDTO extends BaseDTO { public class PaperQuQueryDTO extends BaseDTO { // 继承自BaseDTO类扩展了试卷ID和题目ID字段
@ApiModelProperty(value = "试卷ID", required=true) @ApiModelProperty(value = "试卷ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String paperId; private String paperId; // 存储试卷ID用于查询特定试卷的题目详情
@ApiModelProperty(value = "题目ID", required=true)
private String quId;
@ApiModelProperty(value = "题目ID", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String quId; // 存储题目ID用于查询指定试卷中的某一道题目
} }

@ -1,38 +1,48 @@
package com.yf.exam.modules.paper.dto.response; package com.yf.exam.modules.paper.dto.response; // 定义当前类所在的包路径
// 引入父类PaperDTO用于继承基础字段和功能
import com.yf.exam.modules.paper.dto.PaperDTO; import com.yf.exam.modules.paper.dto.PaperDTO;
// 引入PaperQuDTO类用于表示试题的DTO
import com.yf.exam.modules.paper.dto.PaperQuDTO; import com.yf.exam.modules.paper.dto.PaperQuDTO;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入Calendar类用于时间计算
import java.util.Calendar; import java.util.Calendar;
// 引入List集合用于存储试题列表
import java.util.List; import java.util.List;
@Data /**
@ApiModel(value="考试详情", description="考试详情") * <p>
public class ExamDetailRespDTO extends PaperDTO { *
* </p>
* PaperDTO
*
*/
@Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="考试详情", description="考试详情") // Swagger注解用于描述该类在API文档中的作用和说明
public class ExamDetailRespDTO extends PaperDTO { // 继承自PaperDTO表示考试详情响应DTO
@ApiModelProperty(value = "单选题列表", required=true) @ApiModelProperty(value = "单选题列表", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private List<PaperQuDTO> radioList; private List<PaperQuDTO> radioList; // 存储单选题的列表使用PaperQuDTO表示单个试题
@ApiModelProperty(value = "多选题列表", required=true) @ApiModelProperty(value = "多选题列表", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private List<PaperQuDTO> multiList; private List<PaperQuDTO> multiList; // 存储多选题的列表使用PaperQuDTO表示单个试题
@ApiModelProperty(value = "判断题", required=true) @ApiModelProperty(value = "判断题", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private List<PaperQuDTO> judgeList; private List<PaperQuDTO> judgeList; // 存储判断题的列表使用PaperQuDTO表示单个试题
@ApiModelProperty(value = "剩余结束秒数", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
@ApiModelProperty(value = "剩余结束秒数", required=true) public Long getLeftSeconds(){ // 计算剩余时间的方法,返回剩余的秒数
public Long getLeftSeconds(){
// 结束时间 // 结束时间
Calendar cl = Calendar.getInstance(); Calendar cl = Calendar.getInstance(); // 获取当前时间的Calendar实例
cl.setTime(this.getCreateTime()); cl.setTime(this.getCreateTime()); // 设置Calendar的时间为试卷的创建时间
cl.add(Calendar.MINUTE, getTotalTime()); cl.add(Calendar.MINUTE, getTotalTime()); // 在创建时间的基础上加上试卷的总时间(分钟)
return (cl.getTimeInMillis() - System.currentTimeMillis()) / 1000; // 计算剩余时间(单位:秒)
return (cl.getTimeInMillis() - System.currentTimeMillis()) / 1000; // 返回剩余时间(当前时间到结束时间的差值,以秒为单位)
} }
} }

@ -1,18 +1,29 @@
package com.yf.exam.modules.paper.dto.response; package com.yf.exam.modules.paper.dto.response; // 定义当前类所在的包路径
// 引入父类PaperDTO用于继承基础字段和功能
import com.yf.exam.modules.paper.dto.PaperDTO; import com.yf.exam.modules.paper.dto.PaperDTO;
// 引入PaperQuDetailDTO类用于表示试题详细信息
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入List集合用于存储试题列表
import java.util.List; import java.util.List;
@Data /**
@ApiModel(value="考试结果展示响应类", description="考试结果展示响应类") * <p>
public class ExamResultRespDTO extends PaperDTO { *
* </p>
* PaperDTO
*
*/
@Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="考试结果展示响应类", description="考试结果展示响应类") // Swagger注解用于描述该类在API文档中的作用和说明
public class ExamResultRespDTO extends PaperDTO { // 继承自PaperDTO表示考试结果展示响应DTO
@ApiModelProperty(value = "问题列表", required=true) @ApiModelProperty(value = "问题列表", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private List<PaperQuDetailDTO> quList; private List<PaperQuDetailDTO> quList; // 存储试题详细信息的列表使用PaperQuDetailDTO表示每个试题的详细数据
} }

@ -1,26 +1,29 @@
package com.yf.exam.modules.paper.dto.response; package com.yf.exam.modules.paper.dto.response; // 定义当前类所在的包路径
// 引入父类PaperDTO用于继承基本的试卷信息
import com.yf.exam.modules.paper.dto.PaperDTO; import com.yf.exam.modules.paper.dto.PaperDTO;
// 引入Swagger注解用于API文档生成
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
/** /**
* <p> * <p>
* *
* </p> * </p>
* PaperDTOrealName
*
* *
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@ApiModel(value="试卷列表响应类", description="试卷列表响应类") @ApiModel(value="试卷列表响应类", description="试卷列表响应类") // Swagger注解用于描述该类在API文档中的作用和说明
public class PaperListRespDTO extends PaperDTO { public class PaperListRespDTO extends PaperDTO { // 继承自PaperDTO表示试卷列表响应DTO
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "人员", required=true)
private String realName;
private static final long serialVersionUID = 1L; // 序列化版本ID用于序列化和反序列化操作
@ApiModelProperty(value = "人员", required=true) // Swagger注解描述该字段在API文档中的含义并标注该字段为必填项
private String realName; // 存储人员姓名,用于表示与该试卷相关的人员信息
} }

@ -1,125 +1,104 @@
package com.yf.exam.modules.paper.entity; package com.yf.exam.modules.paper.entity; // 定义当前类所在的包路径
// 引入MyBatis Plus的相关注解
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model; import com.baomidou.mybatisplus.extension.activerecord.Model;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
// 引入Date类用于时间相关的字段
import java.util.Date; import java.util.Date;
/** /**
* <p> * <p>
* *
* </p> * </p>
* `el_paper`
* *
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@TableName("el_paper") @TableName("el_paper") // MyBatis Plus注解指定该实体类对应的数据库表名
public class Paper extends Model<Paper> { public class Paper extends Model<Paper> { // 继承MyBatis Plus的Model类提供了CRUD等基础操作
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本ID用于序列化和反序列化操作
/** /**
* ID * ID
*/ */
@TableId(value = "id", type = IdType.ASSIGN_ID) @TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定主键字段及其生成策略
private String id; private String id; // 试卷ID唯一标识符
/** /**
* ID * ID
*/ */
@TableField("user_id") @TableField("user_id") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private String userId; private String userId; // 用户ID表示创建该试卷的用户
/** /**
* ID * ID
*/ */
@TableField("depart_id") @TableField("depart_id") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private String departId; private String departId; // 部门ID表示该试卷所属的部门
/** /**
* ID * ID
*/ */
@TableField("exam_id") @TableField("exam_id") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private String examId; private String examId; // 规则ID表示该试卷所属的考试规则
/** /**
* *
*/ */
private String title; private String title; // 考试标题,表示试卷的名称或标题
/** /**
* *
*/ */
@TableField("total_time") @TableField("total_time") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer totalTime; private Integer totalTime; // 考试时长,单位为分钟
/** /**
* *
*/ */
@TableField("user_time") @TableField("user_time") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer userTime; private Integer userTime; // 用户实际用时,单位为分钟
/** /**
* *
*/ */
@TableField("total_score") @TableField("total_score") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer totalScore; private Integer totalScore; // 试卷的总分数
/** /**
* *
*/ */
@TableField("qualify_score") @TableField("qualify_score") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer qualifyScore; private Integer qualifyScore; // 及格分数,表示通过该试卷的最低分数
/** /**
* *
*/ */
@TableField("obj_score") @TableField("obj_score") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer objScore; private Integer objScore; // 客观题部分的得分
/** /**
* *
*/ */
@TableField("subj_score") @TableField("subj_score") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer subjScore; private Integer subjScore; // 主观题部分的得分
/** /**
* *
*/ */
@TableField("user_score") @TableField("user_score") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer userScore; private Integer userScore; // 用户在该试卷上的得分
/** /**
* *
*/ */
@TableField("has_saq") @TableField("has_saq") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Boolean hasSaq;
/**
*
*/
private Integer state;
/**
*
*/
@TableField("create_time")
private Date createTime;
/**
*
*/
@TableField("update_time")
private Date updateTime;
/**
*
*/
@TableField("limit_time")
private Date limitTime;
}

@ -1,80 +1,82 @@
package com.yf.exam.modules.paper.entity; package com.yf.exam.modules.paper.entity; // 定义当前类所在的包路径
// 引入MyBatis Plus的相关注解
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model; import com.baomidou.mybatisplus.extension.activerecord.Model;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
/** /**
* <p> * <p>
* *
* </p> * </p>
* `el_paper_qu`
* *
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@TableName("el_paper_qu") @TableName("el_paper_qu") // MyBatis Plus注解指定该实体类对应的数据库表名
public class PaperQu extends Model<PaperQu> { public class PaperQu extends Model<PaperQu> { // 继承MyBatis Plus的Model类提供了基础的CRUD等操作
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本ID用于序列化和反序列化操作
/** /**
* ID * ID
*/ */
@TableId(value = "id", type = IdType.ASSIGN_ID) @TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定主键字段及其生成策略
private String id; private String id; // 题目ID唯一标识符
/** /**
* ID * ID
*/ */
@TableField("paper_id") @TableField("paper_id") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private String paperId; private String paperId; // 试卷ID表示该题目所属的试卷ID
/** /**
* ID * ID
*/ */
@TableField("qu_id") @TableField("qu_id") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private String quId; private String quId; // 题目ID唯一标识符
/** /**
* *
*/ */
@TableField("qu_type") @TableField("qu_type") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer quType; private Integer quType; // 题目类型,表示该题目属于哪种类型(例如单选题、多选题、主观题等)
/** /**
* *
*/ */
private Boolean answered; private Boolean answered; // 是否已经回答,布尔值,表示该题目是否已经被回答
/** /**
* *
*/ */
private String answer; private String answer; // 主观题的答案,保存用户的回答内容
/** /**
* *
*/ */
private Integer sort; private Integer sort; // 问题在试卷中的排序,决定题目的显示顺序
/** /**
* *
*/ */
private Integer score; private Integer score; // 每道题的分值,表示该题目的得分值
/** /**
* () * ()
*/ */
@TableField("actual_score") @TableField("actual_score") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Integer actualScore; private Integer actualScore; // 主观题的实际得分,可能与用户给出的答案相关
/** /**
* *
*/ */
@TableField("is_right") @TableField("is_right") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Boolean isRight; private Boolean isRight; // 是否答对,布尔值,表示用户是否答对了该题目
} }

@ -1,68 +1,71 @@
package com.yf.exam.modules.paper.entity; package com.yf.exam.modules.paper.entity; // 定义当前类所在的包路径
// 引入MyBatis Plus的相关注解
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model; import com.baomidou.mybatisplus.extension.activerecord.Model;
// 引入Lombok注解用于自动生成getter、setter等方法
import lombok.Data; import lombok.Data;
/** /**
* <p> * <p>
* *
* </p> * </p>
* `el_paper_qu_answer`
* *
* @author * @author
* @since 2020-05-25 17:31 * @since 2020-05-25 17:31
*/ */
@Data @Data // Lombok注解自动生成getter、setter、toString、equals和hashCode方法
@TableName("el_paper_qu_answer") @TableName("el_paper_qu_answer") // MyBatis Plus注解指定该实体类对应的数据库表名
public class PaperQuAnswer extends Model<PaperQuAnswer> { public class PaperQuAnswer extends Model<PaperQuAnswer> { // 继承MyBatis Plus的Model类提供了基础的CRUD等操作
private static final long serialVersionUID = 1L; // 序列化版本ID用于序列化和反序列化操作
/** /**
* ID * ID
*/ */
@TableId(value = "id", type = IdType.ASSIGN_ID) @TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定主键字段及其生成策略
private String id; private String id; // 唯一标识符ID数据库中的主键
/** /**
* ID * ID
*/ */
@TableField("paper_id") @TableField("paper_id") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private String paperId; private String paperId; // 该备选答案对应的试卷ID
/** /**
* ID * ID
*/ */
@TableField("answer_id") @TableField("answer_id") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private String answerId; private String answerId; // 该备选答案的唯一标识符ID
/** /**
* ID * ID
*/ */
@TableField("qu_id") @TableField("qu_id") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private String quId; private String quId; // 该备选答案所属的题目ID
/** /**
* *
*/ */
@TableField("is_right") @TableField("is_right") // MyBatis Plus注解指定字段与数据库表字段的映射关系
private Boolean isRight; private Boolean isRight; // 是否是正确答案布尔值true 表示正确false 表示错误)
/** /**
* *
*/ */
private Boolean checked; private Boolean checked; // 该备选答案是否被选中布尔值true 表示已选中false 表示未选中)
/** /**
* *
*/ */
private Integer sort; private Integer sort; // 备选答案的排序,决定该答案在选项中的位置
/** /**
* *
*/ */
private String abc; private String abc; // 备选答案的标签通常为A、B、C、D等用于显示选项
} }

@ -1,33 +1,38 @@
package com.yf.exam.modules.paper.enums; package com.yf.exam.modules.paper.enums; // 定义当前类所在的包路径
/** /**
* * <p>
*
* </p>
* 使
*
* @author bool * @author bool
* @date 2019-10-30 13:11 * @date 2019-10-30 13:11
*/ */
public interface ExamState { public interface ExamState {
/** /**
* *
*
*/ */
Integer ENABLE = 0; Integer ENABLE = 0;
/** /**
* *
*
*/ */
Integer DISABLED = 1; Integer DISABLED = 1;
/** /**
* *
*
*/ */
Integer READY_START = 2; Integer READY_START = 2;
/** /**
* *
*
*/ */
Integer OVERDUE = 3; Integer OVERDUE = 3;
} }

@ -1,33 +1,38 @@
package com.yf.exam.modules.paper.enums; package com.yf.exam.modules.paper.enums; // 定义当前类所在的包路径
/** /**
* * <p>
*
* </p>
* 使
*
* @author bool * @author bool
* @date 2019-10-30 13:11 * @date 2019-10-30 13:11
*/ */
public interface PaperState { public interface PaperState {
/** /**
* *
*
*/ */
Integer ING = 0; Integer ING = 0;
/** /**
* *
*
*/ */
Integer WAIT_OPT = 1; Integer WAIT_OPT = 1;
/** /**
* *
*
*/ */
Integer FINISHED = 2; Integer FINISHED = 2;
/** /**
* *
* 退
*/ */
Integer BREAK = 3; Integer BREAK = 3;
} }

@ -1,45 +1,61 @@
package com.yf.exam.modules.paper.job; package com.yf.exam.modules.paper.job; // 定义类所在的包路径
import com.yf.exam.ability.job.service.JobService; import com.yf.exam.ability.job.service.JobService; // 导入 JobService 类,用于获取任务数据
import com.yf.exam.modules.paper.service.PaperService; import com.yf.exam.modules.paper.service.PaperService; // 导入 PaperService 类,用于处理试卷相关的业务逻辑
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2; // 引入 log4j2 日志工具
import org.quartz.Job; import org.quartz.Job; // 导入 Quartz 作业接口
import org.quartz.JobDetail; import org.quartz.JobDetail; // 导入 Quartz JobDetail 类
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext; // 导入 Quartz JobExecutionContext 类
import org.quartz.JobExecutionException; import org.quartz.JobExecutionException; // 导入 Quartz JobExecutionException 类
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired; // 引入 Spring 注解,用于自动注入依赖
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component; // 引入 Spring 组件注解,标识为 Spring Bean
/** /**
* *
* <p>
* Quartz
* PaperService
* </p>
*
* @author bool * @author bool
*/ */
@Log4j2 @Log4j2 // 启用 log4j2 日志记录
@Component @Component // 标识该类为一个 Spring 组件Spring 会自动将其注册为 Bean
public class BreakExamJob implements Job { public class BreakExamJob implements Job {
// 自动注入 PaperService用于处理与试卷相关的业务逻辑
@Autowired @Autowired
private PaperService paperService; private PaperService paperService;
/**
*
*
* <p>
* Quartz
* PaperService
* </p>
*
* @param jobExecutionContext Quartz
* @throws JobExecutionException
*/
@Override @Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDetail detail = jobExecutionContext.getJobDetail(); // 从 jobExecutionContext 中获取任务的详细信息
String name = detail.getKey().getName(); JobDetail detail = jobExecutionContext.getJobDetail(); // 获取任务详情
String group = detail.getKey().getGroup(); String name = detail.getKey().getName(); // 获取任务名称
String group = detail.getKey().getGroup(); // 获取任务分组
// 获取任务的附加数据,通常是任务触发时的相关参数
String data = String.valueOf(detail.getJobDataMap().get(JobService.TASK_DATA)); String data = String.valueOf(detail.getJobDataMap().get(JobService.TASK_DATA));
// 打印任务执行日志,便于调试和跟踪
log.info("++++++++++定时任务:处理到期的交卷"); log.info("++++++++++定时任务:处理到期的交卷");
log.info("++++++++++jobName:{}", name); log.info("++++++++++jobName:{}", name);
log.info("++++++++++jobGroup:{}", group); log.info("++++++++++jobGroup:{}", group);
log.info("++++++++++taskData:{}", data); log.info("++++++++++taskData:{}", data);
// 调用 PaperService 进行强制交卷操作
// 强制交卷 // data 参数通常是考试 ID 或者某种标识符,用于识别需要交卷的考试
paperService.handExam(data); paperService.handExam(data);
} }
} }

@ -1,39 +1,46 @@
package com.yf.exam.modules.paper.mapper; package com.yf.exam.modules.paper.mapper; // 定义类所在的包路径
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 引入 MyBatis-Plus 的 BaseMapper
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage; // 引入分页结果接口 IPage
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 引入分页插件 Page
import com.yf.exam.modules.paper.dto.PaperDTO; import com.yf.exam.modules.paper.dto.PaperDTO; // 引入 DTO 类,表示试卷数据传输对象
import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; // 引入请求 DTO 类,表示查询试卷时的请求参数
import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; // 引入响应 DTO 类,表示查询试卷时的响应结果
import com.yf.exam.modules.paper.entity.Paper; import com.yf.exam.modules.paper.entity.Paper; // 引入实体类,表示试卷数据表中的记录
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param; // 引入 MyBatis 注解,用于指定 SQL 查询中的参数
import java.util.List; import java.util.List;
/** /**
* <p> * <p>
* Mapper * Mapper
* </p> * </p>
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
*/ */
public interface PaperMapper extends BaseMapper<Paper> { public interface PaperMapper extends BaseMapper<Paper> { // 继承 MyBatis-Plus 提供的 BaseMapper自动实现 CRUD 操作
/** /**
* *
* @param page * <p>
* @param query * `Page` `PaperListReqDTO`
* @return * </p>
*
* @param page
* @param query ID ID
* @return `IPage<PaperListRespDTO>`
*/ */
IPage<PaperListRespDTO> paging(Page page, @Param("query") PaperListReqDTO query); IPage<PaperListRespDTO> paging(Page page, @Param("query") PaperListReqDTO query);
/** /**
* *
* @param query * <p>
* @return *
* </p>
*
* @param query ID ID
* @return `List<PaperListRespDTO>` DTO
*/ */
List<PaperListRespDTO> list(@Param("query") PaperDTO query); List<PaperListRespDTO> list(@Param("query") PaperDTO query);
} }

@ -1,27 +1,33 @@
package com.yf.exam.modules.paper.mapper; package com.yf.exam.modules.paper.mapper; // 指定该类所在的包路径
// 导入BaseMapper接口提供通用的CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入PaperQuAnswerExtDTO类表示试卷考题备选答案的扩展数据传输对象
import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO;
// 导入PaperQuAnswer实体类表示试卷考题备选答案的数据模型
import com.yf.exam.modules.paper.entity.PaperQuAnswer; import com.yf.exam.modules.paper.entity.PaperQuAnswer;
// 导入MyBatis的注解指定方法参数在SQL中的名称
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
// 导入List接口表示返回类型为List集合用于存储多个备选答案
import java.util.List; import java.util.List;
/** /**
* <p> * <p>
* Mapper * Mapper
* </p> * </p>
* MyBatis-PlusBaseMapperCRUD
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
*/ */
public interface PaperQuAnswerMapper extends BaseMapper<PaperQuAnswer> { public interface PaperQuAnswerMapper extends BaseMapper<PaperQuAnswer> { // 继承自BaseMapper接口提供了所有的基本CRUD操作
/** /**
* *
* @param paperId * @param paperId ID
* @param quId * @param quId ID
* @return * @return IDID
*/ */
List<PaperQuAnswerExtDTO> list(@Param("paperId") String paperId, @Param("quId") String quId); List<PaperQuAnswerExtDTO> list(@Param("paperId") String paperId, @Param("quId") String quId); // 根据试卷ID和题目ID查询对应的备选答案列表
} }

@ -1,42 +1,46 @@
package com.yf.exam.modules.paper.mapper; package com.yf.exam.modules.paper.mapper; // 定义该类所在的包路径
// 导入BaseMapper接口提供MyBatis-Plus的通用CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入PaperQuDetailDTO类表示试卷考题详细数据传输对象
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
// 导入PaperQu实体类表示试卷考题的数据模型
import com.yf.exam.modules.paper.entity.PaperQu; import com.yf.exam.modules.paper.entity.PaperQu;
// 导入MyBatis的@Param注解用于方法参数与SQL查询中的参数进行映射
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
// 导入List接口用于返回多个试题
import java.util.List; import java.util.List;
/** /**
* <p> * <p>
* Mapper * Mapper
* </p> * </p>
* MyBatis-PlusBaseMapperCRUD
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
*/ */
public interface PaperQuMapper extends BaseMapper<PaperQu> { public interface PaperQuMapper extends BaseMapper<PaperQu> { // 继承自BaseMapper接口提供通用的CRUD操作
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return
*/ */
int sumObjective(@Param("paperId") String paperId); int sumObjective(@Param("paperId") String paperId); // 根据试卷ID统计所有客观题的分数总和
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return
*/ */
int sumSubjective(@Param("paperId") String paperId); int sumSubjective(@Param("paperId") String paperId); // 根据试卷ID统计所有主观题的分数总和
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return PaperQuDetailDTO
*/ */
List<PaperQuDetailDTO> listByPaper(@Param("paperId") String paperId); List<PaperQuDetailDTO> listByPaper(@Param("paperId") String paperId); // 根据试卷ID查询所有试题的详细信息
} }

@ -1,18 +1,20 @@
// 导入所需的包
package com.yf.exam.modules.paper.service; package com.yf.exam.modules.paper.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage; // 用于分页查询
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService; // 用于继承通用服务接口
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO; // 分页请求DTO
import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; // 试卷问题答案DTO
import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; // 扩展的试卷问题答案DTO
import com.yf.exam.modules.paper.entity.PaperQuAnswer; import com.yf.exam.modules.paper.entity.PaperQuAnswer; // 试卷问题答案实体类
import java.util.List; import java.util.List; // 导入List类用于处理集合数据
/** /**
* <p> * <p>
* *
* </p> * </p>
*
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
@ -21,24 +23,24 @@ public interface PaperQuAnswerService extends IService<PaperQuAnswer> {
/** /**
* *
* @param reqDTO * @param reqDTO DTO
* @return * @return PaperQuAnswerDTO
*/ */
IPage<PaperQuAnswerDTO> paging(PagingReqDTO<PaperQuAnswerDTO> reqDTO); IPage<PaperQuAnswerDTO> paging(PagingReqDTO<PaperQuAnswerDTO> reqDTO);
/** /**
* *
* @param paperId * @param paperId ID
* @param quId * @param quId ID
* @return * @return PaperQuAnswerExtDTO
*/ */
List<PaperQuAnswerExtDTO> listForExam(String paperId, String quId); List<PaperQuAnswerExtDTO> listForExam(String paperId, String quId);
/** /**
* *
* @param paperId * @param paperId ID
* @param quId * @param quId ID
* @return * @return PaperQuAnswer
*/ */
List<PaperQuAnswer> listForFill(String paperId, String quId); List<PaperQuAnswer> listForFill(String paperId, String quId);
} }

@ -1,18 +1,20 @@
// 导入所需的包
package com.yf.exam.modules.paper.service; package com.yf.exam.modules.paper.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage; // 用于分页查询
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService; // 用于继承通用服务接口
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO; // 分页请求DTO
import com.yf.exam.modules.paper.dto.PaperQuDTO; import com.yf.exam.modules.paper.dto.PaperQuDTO; // 试卷问题DTO
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; // 试卷问题详情DTO
import com.yf.exam.modules.paper.entity.PaperQu; import com.yf.exam.modules.paper.entity.PaperQu; // 试卷问题实体类
import java.util.List; import java.util.List; // 导入List类用于处理集合数据
/** /**
* <p> * <p>
* *
* </p> * </p>
*
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
@ -21,50 +23,50 @@ public interface PaperQuService extends IService<PaperQu> {
/** /**
* *
* @param reqDTO * @param reqDTO DTO
* @return * @return PaperQuDTO
*/ */
IPage<PaperQuDTO> paging(PagingReqDTO<PaperQuDTO> reqDTO); IPage<PaperQuDTO> paging(PagingReqDTO<PaperQuDTO> reqDTO);
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return PaperQuDTO
*/ */
List<PaperQuDTO> listByPaper(String paperId); List<PaperQuDTO> listByPaper(String paperId);
/** /**
* *
* @param paperId * @param paperId ID
* @param quId * @param quId ID
* @return * @return PaperQu
*/ */
PaperQu findByKey(String paperId, String quId); PaperQu findByKey(String paperId, String quId);
/** /**
* *
* @param qu * @param qu
*/ */
void updateByKey(PaperQu qu); void updateByKey(PaperQu qu);
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return
*/ */
int sumObjective(String paperId); int sumObjective(String paperId);
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return
*/ */
int sumSubjective(String paperId); int sumSubjective(String paperId);
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return PaperQuDetailDTO
*/ */
List<PaperQuDetailDTO> listForPaperResult(String paperId); List<PaperQuDetailDTO> listForPaperResult(String paperId);
} }

@ -1,21 +1,23 @@
// 导入所需的包
package com.yf.exam.modules.paper.service; package com.yf.exam.modules.paper.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage; // 用于分页查询
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService; // 用于继承通用服务接口
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO; // 分页请求DTO
import com.yf.exam.modules.paper.dto.PaperDTO; import com.yf.exam.modules.paper.dto.PaperDTO; // 试卷DTO
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; // 试卷题目详情DTO
import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; // 提交答案请求DTO
import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; // 试卷列表请求DTO
import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; // 考试详情响应DTO
import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; // 考试结果响应DTO
import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; // 试卷列表响应DTO
import com.yf.exam.modules.paper.entity.Paper; import com.yf.exam.modules.paper.entity.Paper; // 试卷实体类
/** /**
* <p> * <p>
* *
* </p> * </p>
*
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
@ -24,60 +26,57 @@ public interface PaperService extends IService<Paper> {
/** /**
* *
* @param userId * @param userId ID
* @param examId * @param examId ID
* @return * @return ID
*/ */
String createPaper(String userId, String examId); String createPaper(String userId, String examId);
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return ExamDetailRespDTO
*/ */
ExamDetailRespDTO paperDetail(String paperId); ExamDetailRespDTO paperDetail(String paperId);
/** /**
* *
* @param paperId * @param paperId ID
* @return * @return ExamResultRespDTO
*/ */
ExamResultRespDTO paperResult(String paperId); ExamResultRespDTO paperResult(String paperId);
/** /**
* *
* @param paperId * @param paperId ID
* @param quId * @param quId ID
* @return * @return PaperQuDetailDTO
*/ */
PaperQuDetailDTO findQuDetail(String paperId, String quId); PaperQuDetailDTO findQuDetail(String paperId, String quId);
/** /**
* *
* @param reqDTO * @param reqDTO DTO
*/ */
void fillAnswer(PaperAnswerDTO reqDTO); void fillAnswer(PaperAnswerDTO reqDTO);
/** /**
* *
* @param paperId * @param paperId ID
* @return
*/ */
void handExam(String paperId); void handExam(String paperId);
/** /**
* *
* @param reqDTO * @param reqDTO DTO
* @return * @return IPage<PaperListRespDTO>
*/ */
IPage<PaperListRespDTO> paging(PagingReqDTO<PaperListReqDTO> reqDTO); IPage<PaperListRespDTO> paging(PagingReqDTO<PaperListReqDTO> reqDTO);
/** /**
* *
* @param userId * @param userId ID
* @return * @return PaperDTO
*/ */
PaperDTO checkProcess(String userId); PaperDTO checkProcess(String userId);
} }

@ -1,61 +1,95 @@
package com.yf.exam.modules.paper.service.impl; package com.yf.exam.modules.paper.service.impl; // 指定该类所在的包路径
// 导入FastJSON库用于处理JSON格式数据的转换
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
// 导入MyBatis-Plus的条件查询构造器
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
// 导入分页接口
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
// 导入分页Page类
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
// 导入MyBatis-Plus的服务实现类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入分页请求DTO用于分页查询
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO;
// 导入试卷考题备选答案DTO类
import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO;
// 导入试卷考题备选答案扩展DTO类
import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO;
// 导入试卷考题备选答案实体类
import com.yf.exam.modules.paper.entity.PaperQuAnswer; import com.yf.exam.modules.paper.entity.PaperQuAnswer;
// 导入试卷考题备选答案Mapper接口
import com.yf.exam.modules.paper.mapper.PaperQuAnswerMapper; import com.yf.exam.modules.paper.mapper.PaperQuAnswerMapper;
// 导入试卷考题备选答案服务接口
import com.yf.exam.modules.paper.service.PaperQuAnswerService; import com.yf.exam.modules.paper.service.PaperQuAnswerService;
// 导入Spring的Service注解表示这是一个服务实现类
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List; // 导入List接口用于返回多个结果
/** /**
* <p> * <p>
* *
* </p> * </p>
* PaperQuAnswerService
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
*/ */
@Service @Service // 标注为Spring的服务类
public class PaperQuAnswerServiceImpl extends ServiceImpl<PaperQuAnswerMapper, PaperQuAnswer> implements PaperQuAnswerService { public class PaperQuAnswerServiceImpl extends ServiceImpl<PaperQuAnswerMapper, PaperQuAnswer> implements PaperQuAnswerService { // 继承ServiceImpl类提供基本的数据库操作功能
/**
*
* @param reqDTO
* @return
*/
@Override @Override
public IPage<PaperQuAnswerDTO> paging(PagingReqDTO<PaperQuAnswerDTO> reqDTO) { public IPage<PaperQuAnswerDTO> paging(PagingReqDTO<PaperQuAnswerDTO> reqDTO) {
// 创建分页对象 // 创建分页对象
IPage<PaperQuAnswer> query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); IPage<PaperQuAnswer> query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
//查询条件 // 查询条件,可以根据需要添加更多过滤条件
QueryWrapper<PaperQuAnswer> wrapper = new QueryWrapper<>(); QueryWrapper<PaperQuAnswer> wrapper = new QueryWrapper<>();
//获得数据 // 执行分页查询操作
IPage<PaperQuAnswer> page = this.page(query, wrapper); IPage<PaperQuAnswer> page = this.page(query, wrapper);
//转换结果
// 将查询结果转换为DTO对象返回分页后的结果
IPage<PaperQuAnswerDTO> pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference<Page<PaperQuAnswerDTO>>(){}); IPage<PaperQuAnswerDTO> pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference<Page<PaperQuAnswerDTO>>(){});
return pageData;
return pageData; // 返回分页结果
} }
/**
* IDID
* @param paperId ID
* @param quId ID
* @return DTO
*/
@Override @Override
public List<PaperQuAnswerExtDTO> listForExam(String paperId, String quId) { public List<PaperQuAnswerExtDTO> listForExam(String paperId, String quId) {
// 调用Mapper中的list方法查询并返回试题的备选答案列表
return baseMapper.list(paperId, quId); return baseMapper.list(paperId, quId);
} }
/**
*
* @param paperId ID
* @param quId ID
* @return
*/
@Override @Override
public List<PaperQuAnswer> listForFill(String paperId, String quId) { public List<PaperQuAnswer> listForFill(String paperId, String quId) {
//查询条件 // 创建查询条件
QueryWrapper<PaperQuAnswer> wrapper = new QueryWrapper<>(); QueryWrapper<PaperQuAnswer> wrapper = new QueryWrapper<>();
wrapper.lambda() wrapper.lambda() // 使用Lambda表达式进行条件构造
.eq(PaperQuAnswer::getPaperId, paperId) .eq(PaperQuAnswer::getPaperId, paperId) // 通过试卷ID过滤
.eq(PaperQuAnswer::getQuId, quId); .eq(PaperQuAnswer::getQuId, quId); // 通过题目ID过滤
// 查询并返回符合条件的结果列表
return this.list(wrapper); return this.list(wrapper);
} }
} }

@ -1,33 +1,52 @@
package com.yf.exam.modules.paper.service.impl; package com.yf.exam.modules.paper.service.impl; // 指定该类所在的包路径
// 导入FastJSON库用于处理JSON格式数据的转换
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
// 导入MyBatis-Plus的条件查询构造器
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
// 导入分页接口
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
// 导入分页Page类
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
// 导入MyBatis-Plus的服务实现类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入分页请求DTO用于分页查询
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO;
// 导入BeanMapper工具类用于对象间的转换
import com.yf.exam.core.utils.BeanMapper; import com.yf.exam.core.utils.BeanMapper;
// 导入试卷考题DTO类
import com.yf.exam.modules.paper.dto.PaperQuDTO; import com.yf.exam.modules.paper.dto.PaperQuDTO;
// 导入试卷考题详情DTO类
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
// 导入试卷考题实体类
import com.yf.exam.modules.paper.entity.PaperQu; import com.yf.exam.modules.paper.entity.PaperQu;
// 导入试卷考题Mapper接口
import com.yf.exam.modules.paper.mapper.PaperQuMapper; import com.yf.exam.modules.paper.mapper.PaperQuMapper;
// 导入试卷考题服务接口
import com.yf.exam.modules.paper.service.PaperQuService; import com.yf.exam.modules.paper.service.PaperQuService;
// 导入Spring的Service注解表示这是一个服务实现类
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List; // 导入List接口用于返回多个结果
/** /**
* <p> * <p>
* *
* </p> * </p>
* PaperQuService
* *
* @author * @author
* @since 2020-05-25 16:33 * @since 2020-05-25 16:33
*/ */
@Service @Service // 标注为Spring的服务类
public class PaperQuServiceImpl extends ServiceImpl<PaperQuMapper, PaperQu> implements PaperQuService { public class PaperQuServiceImpl extends ServiceImpl<PaperQuMapper, PaperQu> implements PaperQuService { // 继承ServiceImpl类提供基本的数据库操作功能
/**
*
* @param reqDTO
* @return
*/
@Override @Override
public IPage<PaperQuDTO> paging(PagingReqDTO<PaperQuDTO> reqDTO) { public IPage<PaperQuDTO> paging(PagingReqDTO<PaperQuDTO> reqDTO) {
@ -37,58 +56,95 @@ public class PaperQuServiceImpl extends ServiceImpl<PaperQuMapper, PaperQu> impl
// 查询条件 // 查询条件
QueryWrapper<PaperQu> wrapper = new QueryWrapper<>(); QueryWrapper<PaperQu> wrapper = new QueryWrapper<>();
//获得数据 // 执行分页查询操作
IPage<PaperQu> page = this.page(query, wrapper); IPage<PaperQu> page = this.page(query, wrapper);
//转换结果
// 将查询结果转换为DTO对象返回分页后的结果
IPage<PaperQuDTO> pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference<Page<PaperQuDTO>>(){}); IPage<PaperQuDTO> pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference<Page<PaperQuDTO>>(){});
return pageData;
return pageData; // 返回分页结果
} }
/**
* ID
* @param paperId ID
* @return DTO
*/
@Override @Override
public List<PaperQuDTO> listByPaper(String paperId) { public List<PaperQuDTO> listByPaper(String paperId) {
// 查询条件 // 查询条件
QueryWrapper<PaperQu> wrapper = new QueryWrapper<>(); QueryWrapper<PaperQu> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(PaperQu::getPaperId, paperId) wrapper.lambda().eq(PaperQu::getPaperId, paperId) // 通过试卷ID过滤
.orderByAsc(PaperQu::getSort); .orderByAsc(PaperQu::getSort); // 按照题目排序字段升序排列
// 执行查询,获取试卷考题列表
List<PaperQu> list = this.list(wrapper); List<PaperQu> list = this.list(wrapper);
// 使用BeanMapper工具类将实体对象列表转换为DTO对象列表
return BeanMapper.mapList(list, PaperQuDTO.class); return BeanMapper.mapList(list, PaperQuDTO.class);
} }
/**
* IDID
* @param paperId ID
* @param quId ID
* @return
*/
@Override @Override
public PaperQu findByKey(String paperId, String quId) { public PaperQu findByKey(String paperId, String quId) {
// 查询条件 // 查询条件
QueryWrapper<PaperQu> wrapper = new QueryWrapper<>(); QueryWrapper<PaperQu> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(PaperQu::getPaperId, paperId) wrapper.lambda().eq(PaperQu::getPaperId, paperId) // 通过试卷ID过滤
.eq(PaperQu::getQuId, quId); .eq(PaperQu::getQuId, quId); // 通过题目ID过滤
return this.getOne(wrapper, false); // 获取匹配的单个试卷考题对象
return this.getOne(wrapper, false); // 返回查询到的结果false表示未查询到时返回null
} }
/**
* IDID
* @param qu
*/
@Override @Override
public void updateByKey(PaperQu qu) { public void updateByKey(PaperQu qu) {
// 查询条件 // 查询条件
QueryWrapper<PaperQu> wrapper = new QueryWrapper<>(); QueryWrapper<PaperQu> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(PaperQu::getPaperId, qu.getPaperId()) wrapper.lambda().eq(PaperQu::getPaperId, qu.getPaperId()) // 通过试卷ID过滤
.eq(PaperQu::getQuId, qu.getQuId()); .eq(PaperQu::getQuId, qu.getQuId()); // 通过题目ID过滤
this.update(qu, wrapper); // 执行更新操作
this.update(qu, wrapper); // 更新满足条件的试卷考题
} }
/**
*
* @param paperId ID
* @return
*/
@Override @Override
public int sumObjective(String paperId) { public int sumObjective(String paperId) {
return baseMapper.sumObjective(paperId); return baseMapper.sumObjective(paperId); // 调用Mapper方法统计客观题总分
} }
/**
*
* @param paperId ID
* @return
*/
@Override @Override
public int sumSubjective(String paperId) { public int sumSubjective(String paperId) {
return baseMapper.sumSubjective(paperId); return baseMapper.sumSubjective(paperId); // 调用Mapper方法统计主观题总分
} }
/**
* ID
* @param paperId ID
* @return
*/
@Override @Override
public List<PaperQuDetailDTO> listForPaperResult(String paperId) { public List<PaperQuDetailDTO> listForPaperResult(String paperId) {
return baseMapper.listByPaper(paperId); return baseMapper.listByPaper(paperId); // 调用Mapper方法获取试卷考题详细信息
} }
} }

@ -1,58 +1,111 @@
// 定义包名表示该类属于com.yf.exam.modules.paper.service.impl包下
package com.yf.exam.modules.paper.service.impl; package com.yf.exam.modules.paper.service.impl;
// 导入MyBatis Plus框架的核心类用于构建查询条件
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
// 导入MyBatis Plus框架的分页功能相关类
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
// 导入MyBatis Plus框架的工具类用于生成唯一ID
import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.IdWorker;
// 导入MyBatis Plus框架的服务实现类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入项目中定义的作业组枚举
import com.yf.exam.ability.job.enums.JobGroup; import com.yf.exam.ability.job.enums.JobGroup;
// 导入项目中定义的作业前缀枚举
import com.yf.exam.ability.job.enums.JobPrefix; import com.yf.exam.ability.job.enums.JobPrefix;
// 导入项目中的作业服务接口
import com.yf.exam.ability.job.service.JobService; import com.yf.exam.ability.job.service.JobService;
// 导入项目中定义的API错误码类
import com.yf.exam.core.api.ApiError; import com.yf.exam.core.api.ApiError;
// 导入项目中定义的DTO类用于分页请求
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO;
// 导入项目中的服务异常类
import com.yf.exam.core.exception.ServiceException; import com.yf.exam.core.exception.ServiceException;
// 导入项目中定义的Bean映射工具类
import com.yf.exam.core.utils.BeanMapper; import com.yf.exam.core.utils.BeanMapper;
// 导入项目中定义的Cron表达式工具类
import com.yf.exam.core.utils.CronUtils; import com.yf.exam.core.utils.CronUtils;
// 导入项目中定义的考试DTO类
import com.yf.exam.modules.exam.dto.ExamDTO; import com.yf.exam.modules.exam.dto.ExamDTO;
// 导入项目中定义的考试题库DTO类
import com.yf.exam.modules.exam.dto.ExamRepoDTO; import com.yf.exam.modules.exam.dto.ExamRepoDTO;
// 导入项目中定义的扩展考试题库DTO类
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
// 导入项目中的考试服务接口
import com.yf.exam.modules.exam.service.ExamRepoService; import com.yf.exam.modules.exam.service.ExamRepoService;
// 导入项目中的考试服务接口
import com.yf.exam.modules.exam.service.ExamService; import com.yf.exam.modules.exam.service.ExamService;
// 导入项目中定义的试卷DTO类
import com.yf.exam.modules.paper.dto.PaperDTO; import com.yf.exam.modules.paper.dto.PaperDTO;
// 导入项目中定义的试卷题目DTO类
import com.yf.exam.modules.paper.dto.PaperQuDTO; import com.yf.exam.modules.paper.dto.PaperQuDTO;
// 导入项目中定义的扩展试卷题目答案DTO类
import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO;
// 导入项目中定义的试卷题目详情DTO类
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
// 导入项目中定义的试卷答案DTO类
import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO;
// 导入项目中定义的试卷列表请求DTO类
import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; import com.yf.exam.modules.paper.dto.request.PaperListReqDTO;
// 导入项目中定义的试卷列表响应DTO类
import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO;
// 导入项目中定义的考试结果响应DTO类
import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO;
// 导入项目中定义的试卷列表响应DTO类
import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; import com.yf.exam.modules.paper.dto.response.PaperListRespDTO;
// 导入项目中定义的试卷实体类
import com.yf.exam.modules.paper.entity.Paper; import com.yf.exam.modules.paper.entity.Paper;
// 导入项目中定义的试卷题目实体类
import com.yf.exam.modules.paper.entity.PaperQu; import com.yf.exam.modules.paper.entity.PaperQu;
// 导入项目中定义的试卷题目答案实体类
import com.yf.exam.modules.paper.entity.PaperQuAnswer; import com.yf.exam.modules.paper.entity.PaperQuAnswer;
// 导入项目中定义的考试状态枚举
import com.yf.exam.modules.paper.enums.ExamState; import com.yf.exam.modules.paper.enums.ExamState;
// 导入项目中定义的试卷状态枚举
import com.yf.exam.modules.paper.enums.PaperState; import com.yf.exam.modules.paper.enums.PaperState;
// 导入项目中定义的强制交卷作业类
import com.yf.exam.modules.paper.job.BreakExamJob; import com.yf.exam.modules.paper.job.BreakExamJob;
// 导入项目中定义的试卷Mapper接口
import com.yf.exam.modules.paper.mapper.PaperMapper; import com.yf.exam.modules.paper.mapper.PaperMapper;
// 导入项目中定义的试卷题目服务接口
import com.yf.exam.modules.paper.service.PaperQuAnswerService; import com.yf.exam.modules.paper.service.PaperQuAnswerService;
// 导入项目中定义的试卷题目服务接口
import com.yf.exam.modules.paper.service.PaperQuService; import com.yf.exam.modules.paper.service.PaperQuService;
// 导入项目中定义的试卷服务接口
import com.yf.exam.modules.paper.service.PaperService; import com.yf.exam.modules.paper.service.PaperService;
// 导入项目中定义的题目实体类
import com.yf.exam.modules.qu.entity.Qu; import com.yf.exam.modules.qu.entity.Qu;
// 导入项目中定义的题目答案实体类
import com.yf.exam.modules.qu.entity.QuAnswer; import com.yf.exam.modules.qu.entity.QuAnswer;
// 导入项目中定义的题目类型枚举
import com.yf.exam.modules.qu.enums.QuType; import com.yf.exam.modules.qu.enums.QuType;
// 导入项目中定义的题目服务接口
import com.yf.exam.modules.qu.service.QuAnswerService; import com.yf.exam.modules.qu.service.QuAnswerService;
// 导入项目中定义的题目服务接口
import com.yf.exam.modules.qu.service.QuService; import com.yf.exam.modules.qu.service.QuService;
// 导入项目中定义的系统用户实体类
import com.yf.exam.modules.sys.user.entity.SysUser; import com.yf.exam.modules.sys.user.entity.SysUser;
// 导入项目中定义的系统用户服务接口
import com.yf.exam.modules.sys.user.service.SysUserService; import com.yf.exam.modules.sys.user.service.SysUserService;
// 导入项目中定义的用户书籍服务接口
import com.yf.exam.modules.user.book.service.UserBookService; import com.yf.exam.modules.user.book.service.UserBookService;
// 导入项目中定义的用户考试服务接口
import com.yf.exam.modules.user.exam.service.UserExamService; import com.yf.exam.modules.user.exam.service.UserExamService;
// 导入Apache Commons Lang库中的StringUtils类用于字符串操作
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
// 导入Spring框架中的注解用于自动注入依赖
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
// 导入Spring框架中的注解用于声明服务组件
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
// 导入Spring框架中的注解用于声明事务管理
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
// 导入Spring框架中的类用于工具操作
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
// 导入Java.util包下的类用于集合操作
import java.util.*; import java.util.*;
// 定义语言设置服务实现类继承自ServiceImpl并实现PaperService接口
/** /**
* <p> * <p>
* *
@ -64,40 +117,51 @@ import java.util.*;
@Service @Service
public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements PaperService { public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements PaperService {
// 自动注入系统用户服务
@Autowired @Autowired
private SysUserService sysUserService; private SysUserService sysUserService;
// 自动注入考试服务
@Autowired @Autowired
private ExamService examService; private ExamService examService;
// 自动注入题目服务
@Autowired @Autowired
private QuService quService; private QuService quService;
// 自动注入题目答案服务
@Autowired @Autowired
private QuAnswerService quAnswerService; private QuAnswerService quAnswerService;
// 自动注入试卷服务
@Autowired @Autowired
private PaperService paperService; private PaperService paperService;
// 自动注入试卷题目服务
@Autowired @Autowired
private PaperQuService paperQuService; private PaperQuService paperQuService;
// 自动注入试卷题目答案服务
@Autowired @Autowired
private PaperQuAnswerService paperQuAnswerService; private PaperQuAnswerService paperQuAnswerService;
// 自动注入用户书籍服务
@Autowired @Autowired
private UserBookService userBookService; private UserBookService userBookService;
// 自动注入考试题库服务
@Autowired @Autowired
private ExamRepoService examRepoService; private ExamRepoService examRepoService;
// 自动注入用户考试服务
@Autowired @Autowired
private UserExamService userExamService; private UserExamService userExamService;
// 自动注入作业服务
@Autowired @Autowired
private JobService jobService; private JobService jobService;
// 定义展示的选项如ABC这样的选项列表
/** /**
* ABC * ABC
*/ */
@ -106,68 +170,83 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
,"Y","Z" ,"Y","Z"
}); });
// 声明创建试卷的方法,使用事务管理,如果发生异常则回滚
/**
*
* @param userId ID
* @param examId ID
* @return ID
*/
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public String createPaper(String userId, String examId) { public String createPaper(String userId, String examId) {
// 校验是否有正在考试的试卷 // 构建查询条件,查询是否有正在考试的试卷
QueryWrapper<Paper> wrapper = new QueryWrapper<>(); QueryWrapper<Paper> wrapper = new QueryWrapper<>();
wrapper.lambda() wrapper.lambda()
.eq(Paper::getUserId, userId) .eq(Paper::getUserId, userId)
.eq(Paper::getState, PaperState.ING); .eq(Paper::getState, PaperState.ING);
// 统计符合条件的试卷数量
int exists = this.count(wrapper); int exists = this.count(wrapper);
// 如果存在正在考试的试卷,则抛出服务异常
if (exists > 0) { if (exists > 0) {
throw new ServiceException(ApiError.ERROR_20010002); throw new ServiceException(ApiError.ERROR_20010002);
} }
// 查找考试 // 根据考试ID查询考试信息
ExamDTO exam = examService.findById(examId); ExamDTO exam = examService.findById(examId);
// 如果考试信息不存在,则抛出服务异常
if(exam == null){ if(exam == null){
throw new ServiceException(1, "考试不存在!"); throw new ServiceException(1, "考试不存在!");
} }
// 如果考试状态不正确,则抛出服务异常
if(!ExamState.ENABLE.equals(exam.getState())){ if(!ExamState.ENABLE.equals(exam.getState())){
throw new ServiceException(1, "考试状态不正确!"); throw new ServiceException(1, "考试状态不正确!");
} }
// 考试题目列表 // 根据考试ID生成试卷题目列表
List<PaperQu> quList = this.generateByRepo(examId); List<PaperQu> quList = this.generateByRepo(examId);
// 如果题目列表为空,则抛出服务异常
if(CollectionUtils.isEmpty(quList)){ if(CollectionUtils.isEmpty(quList)){
throw new ServiceException(1, "规则不正确,无对应的考题!"); throw new ServiceException(1, "规则不正确,无对应的考题!");
} }
//保存试卷内容 // 保存试卷内容并返回试卷ID
Paper paper = this.savePaper(userId, exam, quList); Paper paper = this.savePaper(userId, exam, quList);
// 强制交卷任务 // 添加强制交卷任务
String jobName = JobPrefix.BREAK_EXAM + paper.getId(); String jobName = JobPrefix.BREAK_EXAM + paper.getId();
jobService.addCronJob(BreakExamJob.class, jobName, CronUtils.dateToCron(paper.getLimitTime()), paper.getId()); jobService.addCronJob(BreakExamJob.class, jobName, CronUtils.dateToCron(paper.getLimitTime()), paper.getId());
return paper.getId(); return paper.getId();
} }
// 声明查询试卷详情的方法
/**
*
* @param paperId ID
* @return DTO
*/
@Override @Override
public ExamDetailRespDTO paperDetail(String paperId) { public ExamDetailRespDTO paperDetail(String paperId) {
// 创建考试详情响应DTO对象
ExamDetailRespDTO respDTO = new ExamDetailRespDTO(); ExamDetailRespDTO respDTO = new ExamDetailRespDTO();
// 试题基本信息 // 根据试卷ID查询试卷基本信息并复制到响应DTO中
Paper paper = paperService.getById(paperId); Paper paper = paperService.getById(paperId);
BeanMapper.copy(paper, respDTO); BeanMapper.copy(paper, respDTO);
// 查找题目列表 // 根据试卷ID查询题目列表
List<PaperQuDTO> list = paperQuService.listByPaper(paperId); List<PaperQuDTO> list = paperQuService.listByPaper(paperId);
// 分类题目列表
List<PaperQuDTO> radioList = new ArrayList<>(); List<PaperQuDTO> radioList = new ArrayList<>();
List<PaperQuDTO> multiList = new ArrayList<>(); List<PaperQuDTO> multiList = new ArrayList<>();
List<PaperQuDTO> judgeList = new ArrayList<>(); List<PaperQuDTO> judgeList = new ArrayList<>();
@ -183,56 +262,73 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
} }
} }
// 设置分类后的题目列表到响应DTO中
respDTO.setRadioList(radioList); respDTO.setRadioList(radioList);
respDTO.setMultiList(multiList); respDTO.setMultiList(multiList);
respDTO.setJudgeList(judgeList); respDTO.setJudgeList(judgeList);
return respDTO; return respDTO;
} }
// 声明查询试卷结果的方法
/**
*
* @param paperId ID
* @return DTO
*/
@Override @Override
public ExamResultRespDTO paperResult(String paperId) { public ExamResultRespDTO paperResult(String paperId) {
// 创建考试结果响应DTO对象
ExamResultRespDTO respDTO = new ExamResultRespDTO(); ExamResultRespDTO respDTO = new ExamResultRespDTO();
// 试题基本信息 // 根据试卷ID查询试卷基本信息并复制到响应DTO中
Paper paper = paperService.getById(paperId); Paper paper = paperService.getById(paperId);
BeanMapper.copy(paper, respDTO); BeanMapper.copy(paper, respDTO);
// 根据试卷ID查询题目列表
List<PaperQuDetailDTO> quList = paperQuService.listForPaperResult(paperId); List<PaperQuDetailDTO> quList = paperQuService.listForPaperResult(paperId);
respDTO.setQuList(quList); respDTO.setQuList(quList);
return respDTO; return respDTO;
} }
// 声明查询题目详情的方法
/**
*
* @param paperId ID
* @param quId ID
* @return DTO
*/
@Override @Override
public PaperQuDetailDTO findQuDetail(String paperId, String quId) { public PaperQuDetailDTO findQuDetail(String paperId, String quId) {
// 创建题目详情DTO对象
PaperQuDetailDTO respDTO = new PaperQuDetailDTO(); PaperQuDetailDTO respDTO = new PaperQuDetailDTO();
// 问题 // 根据题目ID查询题目信息
Qu qu = quService.getById(quId); Qu qu = quService.getById(quId);
// 基本信息 // 根据试卷ID和题目ID查询试卷题目信息并复制到响应DTO中
PaperQu paperQu = paperQuService.findByKey(paperId, quId); PaperQu paperQu = paperQuService.findByKey(paperId, quId);
BeanMapper.copy(paperQu, respDTO); BeanMapper.copy(paperQu, respDTO);
respDTO.setContent(qu.getContent()); respDTO.setContent(qu.getContent());
respDTO.setImage(qu.getImage()); respDTO.setImage(qu.getImage());
// 答案列表 // 根据试卷ID和题目ID查询答案列表并设置到响应DTO中
List<PaperQuAnswerExtDTO> list = paperQuAnswerService.listForExam(paperId, quId); List<PaperQuAnswerExtDTO> list = paperQuAnswerService.listForExam(paperId, quId);
respDTO.setAnswerList(list); respDTO.setAnswerList(list);
return respDTO; return respDTO;
} }
// 声明题库组题方式产生题目列表的私有方法
/** /**
* *
* @param examId * @param examId ID
* @return * @return
*/ */
private List<PaperQu> generateByRepo(String examId){ private List<PaperQu> generateByRepo(String examId){
// 查找规则指定的题库 // 查询规则指定的题库
List<ExamRepoExtDTO> list = examRepoService.listByExam(examId); List<ExamRepoExtDTO> list = examRepoService.listByExam(examId);
// 最终的题目列表 // 最终的题目列表
@ -242,10 +338,11 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
List<String> excludes = new ArrayList<>(); List<String> excludes = new ArrayList<>();
excludes.add("none"); excludes.add("none");
// 如果题库列表不为空,则进行题目抽取
if (!CollectionUtils.isEmpty(list)) { if (!CollectionUtils.isEmpty(list)) {
for (ExamRepoExtDTO item : list) { for (ExamRepoExtDTO item : list) {
// 单选题 // 抽取单选题
if(item.getRadioCount() > 0){ if(item.getRadioCount() > 0){
List<Qu> radioList = quService.listByRandom(item.getRepoId(), QuType.RADIO, excludes, item.getRadioCount()); List<Qu> radioList = quService.listByRandom(item.getRepoId(), QuType.RADIO, excludes, item.getRadioCount());
for (Qu qu : radioList) { for (Qu qu : radioList) {
@ -255,7 +352,7 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
} }
} }
//多选题 // 抽取多选题
if(item.getMultiCount() > 0) { if(item.getMultiCount() > 0) {
List<Qu> multiList = quService.listByRandom(item.getRepoId(), QuType.MULTI, excludes, List<Qu> multiList = quService.listByRandom(item.getRepoId(), QuType.MULTI, excludes,
item.getMultiCount()); item.getMultiCount());
@ -266,7 +363,7 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
} }
} }
// 判断题 // 抽取判断题
if(item.getJudgeCount() > 0) { if(item.getJudgeCount() > 0) {
List<Qu> judgeList = quService.listByRandom(item.getRepoId(), QuType.JUDGE, excludes, List<Qu> judgeList = quService.listByRandom(item.getRepoId(), QuType.JUDGE, excludes,
item.getJudgeCount()); item.getJudgeCount());
@ -281,33 +378,35 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
return quList; return quList;
} }
// 声明填充试题题目信息的私有方法
/** /**
* *
* @param repo * @param repo DTO
* @param qu * @param qu
* @return * @return
*/ */
private PaperQu processPaperQu(ExamRepoDTO repo, Qu qu) { private PaperQu processPaperQu(ExamRepoDTO repo, Qu qu) {
//保存试题信息 // 创建试卷题目实体
PaperQu paperQu = new PaperQu(); PaperQu paperQu = new PaperQu();
paperQu.setQuId(qu.getId()); paperQu.setQuId(qu.getId());
paperQu.setAnswered(false); paperQu.setAnswered(false);
paperQu.setIsRight(false); paperQu.setIsRight(false);
paperQu.setQuType(qu.getQuType()); paperQu.setQuType(qu.getQuType());
// 设置单选题分数
if (QuType.RADIO.equals(qu.getQuType())) { if (QuType.RADIO.equals(qu.getQuType())) {
paperQu.setScore(repo.getRadioScore()); paperQu.setScore(repo.getRadioScore());
paperQu.setActualScore(repo.getRadioScore()); paperQu.setActualScore(repo.getRadioScore());
} }
// 设置多选题分数
if (QuType.MULTI.equals(qu.getQuType())) { if (QuType.MULTI.equals(qu.getQuType())) {
paperQu.setScore(repo.getMultiScore()); paperQu.setScore(repo.getMultiScore());
paperQu.setActualScore(repo.getMultiScore()); paperQu.setActualScore(repo.getMultiScore());
} }
// 设置判断题分数
if (QuType.JUDGE.equals(qu.getQuType())) { if (QuType.JUDGE.equals(qu.getQuType())) {
paperQu.setScore(repo.getJudgeScore()); paperQu.setScore(repo.getJudgeScore());
paperQu.setActualScore(repo.getJudgeScore()); paperQu.setActualScore(repo.getJudgeScore());
@ -316,21 +415,21 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
return paperQu; return paperQu;
} }
// 声明保存试卷的私有方法
/** /**
* *
* @param userId * @param userId ID
* @param exam * @param exam DTO
* @param quList * @param quList
* @return * @return
*/ */
private Paper savePaper(String userId, ExamDTO exam, List<PaperQu> quList) { private Paper savePaper(String userId, ExamDTO exam, List<PaperQu> quList) {
// 查找用户 // 根据用户ID查询用户信息
SysUser user = sysUserService.getById(userId); SysUser user = sysUserService.getById(userId);
//保存试卷基本信息 // 创建试卷基本信息,并设置属性
Paper paper = new Paper(); Paper paper = new Paper();
paper.setDepartId(user.getDepartId()); paper.setDepartId(user.getDepartId());
paper.setExamId(exam.getId()); paper.setExamId(exam.getId());
@ -345,14 +444,16 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
paper.setState(PaperState.ING); paper.setState(PaperState.ING);
paper.setHasSaq(false); paper.setHasSaq(false);
// 截止时间 // 计算截止时间
Calendar cl = Calendar.getInstance(); Calendar cl = Calendar.getInstance();
cl.setTimeInMillis(System.currentTimeMillis()); cl.setTimeInMillis(System.currentTimeMillis());
cl.add(Calendar.MINUTE, exam.getTotalTime()); cl.add(Calendar.MINUTE, exam.getTotalTime());
paper.setLimitTime(cl.getTime()); paper.setLimitTime(cl.getTime());
// 保存试卷基本信息
paperService.save(paper); paperService.save(paper);
// 如果题目列表不为空,则保存试卷题目列表
if (!CollectionUtils.isEmpty(quList)) { if (!CollectionUtils.isEmpty(quList)) {
this.savePaperQu(paper.getId(), quList); this.savePaperQu(paper.getId(), quList);
} }
@ -360,30 +461,34 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
return paper; return paper;
} }
// 声明保存试卷试题列表的私有方法
/** /**
* *
* @param paperId * @param paperId ID
* @param quList * @param quList
*/ */
private void savePaperQu(String paperId, List<PaperQu> quList){ private void savePaperQu(String paperId, List<PaperQu> quList){
// 创建批量保存的题目列表和答案列表
List<PaperQu> batchQuList = new ArrayList<>(); List<PaperQu> batchQuList = new ArrayList<>();
List<PaperQuAnswer> batchAnswerList = new ArrayList<>(); List<PaperQuAnswer> batchAnswerList = new ArrayList<>();
// 初始化排序号
int sort = 0; int sort = 0;
for (PaperQu item : quList) { for (PaperQu item : quList) {
// 设置试卷ID和排序号并生成ID
item.setPaperId(paperId); item.setPaperId(paperId);
item.setSort(sort); item.setSort(sort);
item.setId(IdWorker.getIdStr()); item.setId(IdWorker.getIdStr());
//回答列表 // 查询题目的答案列表
List<QuAnswer> answerList = quAnswerService.listAnswerByRandom(item.getQuId()); List<QuAnswer> answerList = quAnswerService.listAnswerByRandom(item.getQuId());
// 如果答案列表不为空,则进行处理
if (!CollectionUtils.isEmpty(answerList)) { if (!CollectionUtils.isEmpty(answerList)) {
// 初始化答案排序号
int ii = 0; int ii = 0;
for (QuAnswer answer : answerList) { for (QuAnswer answer : answerList) {
PaperQuAnswer paperQuAnswer = new PaperQuAnswer(); PaperQuAnswer paperQuAnswer = new PaperQuAnswer();
@ -400,44 +505,50 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
} }
} }
// 添加到批量保存的题目列表中
batchQuList.add(item); batchQuList.add(item);
sort++; sort++;
} }
//添加 // 批量添加题
paperQuService.saveBatch(batchQuList); paperQuService.saveBatch(batchQuList);
//批量添加问题答案 // 批量添加答案
paperQuAnswerService.saveBatch(batchAnswerList); paperQuAnswerService.saveBatch(batchAnswerList);
} }
// 声明填充答案的方法,使用事务管理
/**
*
* @param reqDTO DTO
*/
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void fillAnswer(PaperAnswerDTO reqDTO) { public void fillAnswer(PaperAnswerDTO reqDTO) {
// 如果答案列表为空且答案字符串也为空,则直接返回
// 未作答
if(CollectionUtils.isEmpty(reqDTO.getAnswers()) if(CollectionUtils.isEmpty(reqDTO.getAnswers())
&& StringUtils.isBlank(reqDTO.getAnswer())){ && StringUtils.isBlank(reqDTO.getAnswer())){
return; return;
} }
//查找答案列表 // 查询答案列表
List<PaperQuAnswer> list = paperQuAnswerService.listForFill(reqDTO.getPaperId(), reqDTO.getQuId()); List<PaperQuAnswer> list = paperQuAnswerService.listForFill(reqDTO.getPaperId(), reqDTO.getQuId());
//是否正确 // 初始化是否正确的标记
boolean right = true; boolean right = true;
// 更新正确答案 // 更新正确答案
for (PaperQuAnswer item : list) { for (PaperQuAnswer item : list) {
// 设置答案是否被选中
if (reqDTO.getAnswers().contains(item.getId())) { if (reqDTO.getAnswers().contains(item.getId())) {
item.setChecked(true); item.setChecked(true);
} else { } else {
item.setChecked(false); item.setChecked(false);
} }
//有一个对不上就是错的 // 如果有一个答案不正确,则标记为错误
if (item.getIsRight()!=null && !item.getIsRight().equals(item.getChecked())) { if (item.getIsRight()!=null && !item.getIsRight().equals(item.getChecked())) {
right = false; right = false;
} }
@ -456,6 +567,11 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
} }
// 声明交卷的方法,使用事务管理
/**
*
* @param paperId ID
*/
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void handExam(String paperId) { public void handExam(String paperId) {
@ -463,20 +579,20 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
// 获取试卷信息 // 获取试卷信息
Paper paper = paperService.getById(paperId); Paper paper = paperService.getById(paperId);
//如果不是正常的,抛出异常 // 如果试卷状态不正确,则抛出服务异常
if(!PaperState.ING.equals(paper.getState())){ if(!PaperState.ING.equals(paper.getState())){
throw new ServiceException(1, "试卷状态不正确!"); throw new ServiceException(1, "试卷状态不正确!");
} }
// 客观分 // 计算客观
int objScore = paperQuService.sumObjective(paperId); int objScore = paperQuService.sumObjective(paperId);
paper.setObjScore(objScore); paper.setObjScore(objScore);
paper.setUserScore(objScore); paper.setUserScore(objScore);
// 主观分,因为要阅卷,所以给0 // 设置主观题分数为0
paper.setSubjScore(0); paper.setSubjScore(0);
// 待阅卷 // 如果有主观题,则设置状态为待阅卷
if(paper.getHasSaq()) { if(paper.getHasSaq()) {
paper.setState(PaperState.WAIT_OPT); paper.setState(PaperState.WAIT_OPT);
}else { }else {
@ -484,6 +600,7 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
// 同步保存考试成绩 // 同步保存考试成绩
userExamService.joinResult(paper.getUserId(), paper.getExamId(), objScore, objScore>=paper.getQualifyScore()); userExamService.joinResult(paper.getUserId(), paper.getExamId(), objScore, objScore>=paper.getQualifyScore());
// 设置状态为已完成
paper.setState(PaperState.FINISHED); paper.setState(PaperState.FINISHED);
} }
paper.setUpdateTime(new Date()); paper.setUpdateTime(new Date());
@ -497,10 +614,9 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
} }
paper.setUserTime(userTime); paper.setUserTime(userTime);
//更新试卷 // 更新试卷信息
paperService.updateById(paper); paperService.updateById(paper);
// 终止定时任务 // 终止定时任务
String name = JobPrefix.BREAK_EXAM + paperId; String name = JobPrefix.BREAK_EXAM + paperId;
jobService.deleteJob(name, JobGroup.SYSTEM); jobService.deleteJob(name, JobGroup.SYSTEM);
@ -517,26 +633,40 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements
} }
} }
// 声明分页查询试卷列表的方法
/**
*
* @param reqDTO DTO
* @return
*/
@Override @Override
public IPage<PaperListRespDTO> paging(PagingReqDTO<PaperListReqDTO> reqDTO) { public IPage<PaperListRespDTO> paging(PagingReqDTO<PaperListReqDTO> reqDTO) {
return baseMapper.paging(reqDTO.toPage(), reqDTO.getParams()); return baseMapper.paging(reqDTO.toPage(), reqDTO.getParams());
} }
// 声明检查考试进度的方法
/**
*
* @param userId ID
* @return DTO
*/
@Override @Override
public PaperDTO checkProcess(String userId) { public PaperDTO checkProcess(String userId) {
// 构建查询条件,查询是否有正在进行的考试
QueryWrapper<Paper> wrapper = new QueryWrapper<>(); QueryWrapper<Paper> wrapper = new QueryWrapper<>();
wrapper.lambda() wrapper.lambda()
.eq(Paper::getUserId, userId) .eq(Paper::getUserId, userId)
.eq(Paper::getState, PaperState.ING); .eq(Paper::getState, PaperState.ING);
// 查询正在进行的考试
Paper paper = this.getOne(wrapper, false); Paper paper = this.getOne(wrapper, false);
// 如果存在正在进行的考试则返回试卷DTO
if (paper != null) { if (paper != null) {
return BeanMapper.map(paper, PaperDTO.class); return BeanMapper.map(paper, PaperDTO.class);
} }
// 如果不存在正在进行的考试则返回null
return null; return null;
} }
}

@ -1,137 +1,129 @@
// 导入所需的包
package com.yf.exam.modules.qu.controller; package com.yf.exam.modules.qu.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 用于构建查询条件
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage; // 用于分页
import com.google.common.collect.Lists; import com.google.common.collect.Lists; // 用于操作列表
import com.yf.exam.core.api.ApiRest; import com.yf.exam.core.api.ApiRest; // API响应封装类
import com.yf.exam.core.api.controller.BaseController; import com.yf.exam.core.api.controller.BaseController; // 基础控制器类
import com.yf.exam.core.api.dto.BaseIdReqDTO; import com.yf.exam.core.api.dto.BaseIdReqDTO; // 基础ID请求DTO
import com.yf.exam.core.api.dto.BaseIdRespDTO; import com.yf.exam.core.api.dto.BaseIdRespDTO; // 基础ID响应DTO
import com.yf.exam.core.api.dto.BaseIdsReqDTO; import com.yf.exam.core.api.dto.BaseIdsReqDTO; // 基础ID数组请求DTO
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO; // 分页请求DTO
import com.yf.exam.core.exception.ServiceException; import com.yf.exam.core.exception.ServiceException; // 自定义服务异常
import com.yf.exam.core.utils.BeanMapper; import com.yf.exam.core.utils.BeanMapper; // Bean映射工具
import com.yf.exam.core.utils.excel.ExportExcel; import com.yf.exam.core.utils.excel.ExportExcel; // 导出Excel工具
import com.yf.exam.core.utils.excel.ImportExcel; import com.yf.exam.core.utils.excel.ImportExcel; // 导入Excel工具
import com.yf.exam.modules.qu.dto.QuDTO; import com.yf.exam.modules.qu.dto.QuDTO; // 问题DTO
import com.yf.exam.modules.qu.dto.export.QuExportDTO; import com.yf.exam.modules.qu.dto.export.QuExportDTO; // 问题导出DTO
import com.yf.exam.modules.qu.dto.ext.QuDetailDTO; import com.yf.exam.modules.qu.dto.ext.QuDetailDTO; // 问题详情DTO
import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; // 问题查询请求DTO
import com.yf.exam.modules.qu.entity.Qu; import com.yf.exam.modules.qu.entity.Qu; // 问题实体类
import com.yf.exam.modules.qu.service.QuService; import com.yf.exam.modules.qu.service.QuService; // 问题服务类
import io.swagger.annotations.Api; import io.swagger.annotations.Api; // Swagger API注释
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation; // Swagger API操作注释
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils; // 字符串操作工具类
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; // 用于处理Excel格式异常
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles; // Shiro权限控制注解
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired; // 自动注入依赖
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils; // 集合工具类
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody; // 请求体注解
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping; // 请求映射注解
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod; // 请求方法类型注解
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam; // 请求参数注解
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody; // 响应体注解
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController; // REST控制器注解
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile; // 用于处理文件上传
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse; // 用于处理HTTP响应
import java.io.IOException; import java.io.IOException; // IO异常处理
import java.util.Arrays; import java.util.Arrays; // 数组工具类
import java.util.List; import java.util.List; // 列表工具类
/** /**
* <p> * <p>
* *
* </p> * </p>
*
* *
* @author * @author
* @since 2020-05-25 13:25 * @since 2020-05-25 13:25
*/ */
@Api(tags={"问题题目"}) @Api(tags={"问题题目"}) // Swagger注解表示该控制器处理"问题题目"相关的请求
@RestController @RestController // Spring注解表示这是一个RESTful API控制器
@RequestMapping("/exam/api/qu/qu") @RequestMapping("/exam/api/qu/qu") // 设置基础路径
public class QuController extends BaseController { public class QuController extends BaseController {
@Autowired @Autowired
private QuService baseService; private QuService baseService; // 自动注入问题服务类
/** /**
* *
* *
* @param reqDTO * @param reqDTO
* @return * @return
*/ */
@RequiresRoles("sa") @RequiresRoles("sa") // 限制只有角色为"sa"的用户可以访问此方法
@ApiOperation(value = "添加或修改") @ApiOperation(value = "添加或修改") // Swagger注解描述该方法的功能
@RequestMapping(value = "/save", method = {RequestMethod.POST}) @RequestMapping(value = "/save", method = {RequestMethod.POST}) // POST请求表示保存操作
public ApiRest<BaseIdRespDTO> save(@RequestBody QuDetailDTO reqDTO) { public ApiRest<BaseIdRespDTO> save(@RequestBody QuDetailDTO reqDTO) {
baseService.save(reqDTO); baseService.save(reqDTO); // 调用服务层保存或更新问题数据
return super.success(); return super.success(); // 返回成功响应
} }
/** /**
* *
* *
* @param reqDTO * @param reqDTO IDID
* @return * @return
*/ */
@RequiresRoles("sa") @RequiresRoles("sa") // 限制只有角色为"sa"的用户可以访问此方法
@ApiOperation(value = "批量删除") @ApiOperation(value = "批量删除") // Swagger注解描述该方法的功能
@RequestMapping(value = "/delete", method = {RequestMethod.POST}) @RequestMapping(value = "/delete", method = {RequestMethod.POST}) // POST请求表示删除操作
public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) {
//根据ID删除 baseService.delete(reqDTO.getIds()); // 调用服务层进行批量删除
baseService.delete(reqDTO.getIds()); return super.success(); // 返回成功响应
return super.success();
} }
/** /**
* *
* *
* @param reqDTO * @param reqDTO IDID
* @return * @return
*/ */
@ApiOperation(value = "查找详情") @ApiOperation(value = "查找详情") // Swagger注解描述该方法的功能
@RequestMapping(value = "/detail", method = {RequestMethod.POST}) @RequestMapping(value = "/detail", method = {RequestMethod.POST}) // POST请求表示获取详情操作
public ApiRest<QuDetailDTO> detail(@RequestBody BaseIdReqDTO reqDTO) { public ApiRest<QuDetailDTO> detail(@RequestBody BaseIdReqDTO reqDTO) {
QuDetailDTO dto = baseService.detail(reqDTO.getId()); QuDetailDTO dto = baseService.detail(reqDTO.getId()); // 调用服务层获取问题题目详情
return super.success(dto); return super.success(dto); // 返回问题详情
} }
/** /**
* *
* *
* @param reqDTO * @param reqDTO
* @return * @return
*/ */
@RequiresRoles("sa") @RequiresRoles("sa") // 限制只有角色为"sa"的用户可以访问此方法
@ApiOperation(value = "分页查找") @ApiOperation(value = "分页查找") // Swagger注解描述该方法的功能
@RequestMapping(value = "/paging", method = {RequestMethod.POST}) @RequestMapping(value = "/paging", method = {RequestMethod.POST}) // POST请求表示分页查询操作
public ApiRest<IPage<QuDTO>> paging(@RequestBody PagingReqDTO<QuQueryReqDTO> reqDTO) { public ApiRest<IPage<QuDTO>> paging(@RequestBody PagingReqDTO<QuQueryReqDTO> reqDTO) {
IPage<QuDTO> page = baseService.paging(reqDTO); // 调用服务层进行分页查询
//分页查询并转换 return super.success(page); // 返回分页结果
IPage<QuDTO> page = baseService.paging(reqDTO);
return super.success(page);
} }
/** /**
* excel * Excel
*/ */
@RequiresRoles("sa") @RequiresRoles("sa") // 限制只有角色为"sa"的用户可以访问此方法
@ResponseBody @ResponseBody // 标明返回内容直接作为响应体
@RequestMapping(value = "/export") @RequestMapping(value = "/export") // 导出请求路径
public ApiRest exportFile(HttpServletResponse response, @RequestBody QuQueryReqDTO reqDTO) { public ApiRest exportFile(HttpServletResponse response, @RequestBody QuQueryReqDTO reqDTO) {
String fileName = "导出的试题-" + System.currentTimeMillis() + ".xlsx"; // 设置导出的文件名
// 导出文件名
String fileName = "导出的试题-" + System.currentTimeMillis() + ".xlsx";
try { try {
int no = 0; int no = 0;
String quId = ""; String quId = "";
List<QuExportDTO> list = baseService.listForExport(reqDTO); List<QuExportDTO> list = baseService.listForExport(reqDTO); // 获取导出数据
for (QuExportDTO item : list) { for (QuExportDTO item : list) {
if (!quId.equals(item.getQId())) { if (!quId.equals(item.getQId())) {
quId = item.getQId(); quId = item.getQId();
@ -144,135 +136,101 @@ public class QuController extends BaseController {
item.setQImage(""); item.setQImage("");
item.setQVideo(""); item.setQVideo("");
} }
item.setNo(String.valueOf(no)); item.setNo(String.valueOf(no)); // 设置题目序号
} }
new ExportExcel("试题", QuExportDTO.class).setDataList(list).write(response, fileName).dispose(); new ExportExcel("试题", QuExportDTO.class).setDataList(list).write(response, fileName).dispose(); // 导出数据到Excel文件
return super.success(); return super.success(); // 返回成功响应
} catch (Exception e) { } catch (Exception e) {
return failure(e.getMessage()); return failure(e.getMessage()); // 捕获异常并返回失败响应
} }
} }
/** /**
* Excel * Excel
* *
* @param file * @param file Excel
* @return * @return
*/ */
@RequiresRoles("sa") @RequiresRoles("sa") // 限制只有角色为"sa"的用户可以访问此方法
@ResponseBody @ResponseBody // 标明返回内容直接作为响应体
@RequestMapping(value = "/import") @RequestMapping(value = "/import") // 导入请求路径
public ApiRest importFile(@RequestParam("file") MultipartFile file) { public ApiRest importFile(@RequestParam("file") MultipartFile file) {
try { try {
ImportExcel ei = new ImportExcel(file, 1, 0); // 创建导入Excel对象
ImportExcel ei = new ImportExcel(file, 1, 0); List<QuExportDTO> list = ei.getDataList(QuExportDTO.class); // 获取Excel中的数据列表
List<QuExportDTO> list = ei.getDataList(QuExportDTO.class); this.checkExcel(list); // 校验数据
baseService.importExcel(list); // 调用服务层进行导入操作
// 校验数据 return super.success(); // 返回成功响应
this.checkExcel(list); } catch (IOException | InvalidFormatException | IllegalAccessException | InstantiationException e) {
return super.failure(); // 捕获各种异常并返回失败响应
// 导入数据条数
baseService.importExcel(list);
// 导入成功
return super.success();
} catch (IOException e) {
} catch (InvalidFormatException e) {
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} }
return super.failure();
} }
/** /**
* Excel * Excel
* *
* @param list * @param list
* @throws Exception * @throws ServiceException
*/ */
private void checkExcel(List<QuExportDTO> list) throws ServiceException { private void checkExcel(List<QuExportDTO> list) throws ServiceException {
// 校验Excel数据的逻辑检查每一行数据的有效性
// 约定第三行开始导入
int line = 3; int line = 3;
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
if (CollectionUtils.isEmpty(list)) { if (CollectionUtils.isEmpty(list)) {
throw new ServiceException(1, "您导入的数据似乎是一个空表格!"); throw new ServiceException(1, "您导入的数据似乎是一个空表格!"); // 如果表格为空,抛出异常
} }
Integer quNo = null; Integer quNo = null;
for (QuExportDTO item : list) { for (QuExportDTO item : list) {
System.out.println(item.getNo());
if (StringUtils.isBlank(item.getNo())) { if (StringUtils.isBlank(item.getNo())) {
line++; line++;
continue; continue;
} }
System.out.println(item.getQContent());
Integer no; Integer no;
try { try {
no = Integer.parseInt(item.getNo()); no = Integer.parseInt(item.getNo()); // 转换题目序号
} catch (Exception e) { } catch (Exception e) {
line++; line++;
continue; continue;
} }
if (no == null) { if (no == null) {
sb.append("第" + line + "行,题目序号不能为空!<br>"); sb.append("第" + line + "行,题目序号不能为空!<br>");
} }
// 校验题目内容和其他字段是否为空
if (quNo == null || !quNo.equals(no)) { if (quNo == null || !quNo.equals(no)) {
if (item.getQuType() == null) { if (item.getQuType() == null) {
sb.append("第" + line + "行,题目类型不能为空<br>"); sb.append("第" + line + "行,题目类型不能为空<br>");
} }
if (StringUtils.isBlank(item.getQContent())) { if (StringUtils.isBlank(item.getQContent())) {
sb.append("第" + line + "行,题目内容不能为空<br>"); sb.append("第" + line + "行,题目内容不能为空<br>");
} }
if (CollectionUtils.isEmpty(item.getRepoList())) { if (CollectionUtils.isEmpty(item.getRepoList())) {
sb.append("第" + line + "行,题目必须包含一个题库<br>"); sb.append("第" + line + "行,题目必须包含一个题库<br>");
} }
} }
if (StringUtils.isBlank(item.getAIsRight())) { if (StringUtils.isBlank(item.getAIsRight())) {
sb.append("第" + line + "行,选项是否正确不能为空<br>"); sb.append("第" + line + "行,选项是否正确不能为空<br>");
} }
if (StringUtils.isBlank(item.getAContent()) && StringUtils.isBlank(item.getAImage())) { if (StringUtils.isBlank(item.getAContent()) && StringUtils.isBlank(item.getAImage())) {
sb.append("第" + line + "行,选项内容和选项图片必须有一个不为空<br>"); sb.append("第" + line + "行,选项内容和选项图片必须有一个不为空<br>");
} }
quNo = no; quNo = no;
line++; line++;
} }
// 存在错误
if (!"".equals(sb.toString())) { if (!"".equals(sb.toString())) {
throw new ServiceException(1, sb.toString()); throw new ServiceException(1, sb.toString()); // 如果有校验错误,抛出异常
} }
} }
/** /**
* *
*/ */
@ResponseBody @ResponseBody
@RequestMapping(value = "import/template") @RequestMapping(value = "import/template") // 导入模板下载路径
public ApiRest importFileTemplate(HttpServletResponse response) { public ApiRest importFileTemplate(HttpServletResponse response) {
try { try {
String fileName = "试题导入模板.xlsx"; String fileName = "试题导入模板.xlsx"; // 设置文件名
List<QuExportDTO> list = Lists.newArrayList(); List<QuExportDTO> list = Lists.newArrayList(); // 创建模板数据列表
// 模板数据(包含问题内容、题型、选项等)
QuExportDTO l1 = new QuExportDTO(); QuExportDTO l1 = new QuExportDTO();
l1.setNo("正式导入,请删除此说明行:数字,相同的数字表示同一题的序列"); l1.setNo("正式导入,请删除此说明行:数字,相同的数字表示同一题的序列");
l1.setQContent("问题内容"); l1.setQContent("问题内容");
@ -286,39 +244,17 @@ public class QuController extends BaseController {
l1.setAIsRight("只能填写0或10表示否1表示是"); l1.setAIsRight("只能填写0或10表示否1表示是");
l1.setAAnalysis("这个项是正确的"); l1.setAAnalysis("这个项是正确的");
// 添加模板示例数据
QuExportDTO l2 = new QuExportDTO();
l2.setQContent("找出以下可以被2整除的数多选");
l2.setQAnalysis("最基本的数学题,不做过多解析");
l2.setQuType("2");
l2.setNo("1");
l2.setAIsRight("1");
l2.setAContent("数字2");
l2.setAAnalysis("2除以2=1对的");
QuExportDTO l3 = new QuExportDTO();
l3.setNo("1");
l3.setAIsRight("0");
l3.setAContent("数字3");
l3.setAAnalysis("3除以2=1.5,不能被整除");
QuExportDTO l4 = new QuExportDTO();
l4.setNo("1");
l4.setAIsRight("1");
l4.setAContent("数字6");
l4.setAAnalysis("6除以2=3对的");
list.add(l1); list.add(l1);
list.add(l2); list.add(l2);
list.add(l3); list.add(l3);
list.add(l4); list.add(l4);
// 导出模板文件
new ExportExcel("试题数据", QuExportDTO.class, 1).setDataList(list).write(response, fileName).dispose(); new ExportExcel("试题数据", QuExportDTO.class, 1).setDataList(list).write(response, fileName).dispose();
return super.success(); return super.success(); // 返回成功响应
} catch (Exception e) { } catch (Exception e) {
return super.failure("导入模板下载失败!失败信息:"+e.getMessage()); return super.failure("导入模板下载失败!失败信息:"+e.getMessage()); // 返回失败响应
} }
} }
} }

@ -1,42 +1,66 @@
package com.yf.exam.modules.qu.dto; package com.yf.exam.modules.qu.dto;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel; // Swagger注解用于生成API文档
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty; // Swagger注解用于描述API模型的属性
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
import java.io.Serializable; import java.io.Serializable; // 可序列化接口
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
*
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@ApiModel(value="候选答案", description="候选答案") @ApiModel(value="候选答案", description="候选答案")
public class QuAnswerDTO implements Serializable { public class QuAnswerDTO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
@ApiModelProperty(value = "答案ID", required=true) /**
private String id; * ID
*
@ApiModelProperty(value = "问题ID", required=true) */
private String quId; @ApiModelProperty(value = "答案ID", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String id; // 答案ID
@ApiModelProperty(value = "是否正确", required=true) /**
private Boolean isRight; * ID
* ID
*/
@ApiModelProperty(value = "问题ID", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String quId; // 题目ID
@ApiModelProperty(value = "选项图片", required=true) /**
private String image; *
* `true``false`
*/
@ApiModelProperty(value = "是否正确", required=true) // Swagger注解描述字段信息标明该字段是必填项
private Boolean isRight; // 是否正确
@ApiModelProperty(value = "答案内容", required=true) /**
private String content; *
* URL
*/
@ApiModelProperty(value = "选项图片", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String image; // 选项图片URL
@ApiModelProperty(value = "答案分析", required=true) /**
private String analysis; *
*
*/
@ApiModelProperty(value = "答案内容", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String content; // 答案内容
/**
*
*
*/
@ApiModelProperty(value = "答案分析", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String analysis; // 答案分析
} }

@ -1,53 +1,88 @@
package com.yf.exam.modules.qu.dto; package com.yf.exam.modules.qu.dto;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel; // Swagger注解用于生成API文档
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty; // Swagger注解用于描述API模型的属性
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
import java.io.Serializable; import java.io.Serializable; // 可序列化接口
import java.util.Date; import java.util.Date; // 日期类型
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
*
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@ApiModel(value="问题题目", description="问题题目") @ApiModel(value="问题题目", description="问题题目")
public class QuDTO implements Serializable { public class QuDTO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
@ApiModelProperty(value = "题目ID", required=true)
private String id;
@ApiModelProperty(value = "题目类型", required=true) /**
private Integer quType; * ID
* ID
@ApiModelProperty(value = "1普通,2较难", required=true) */
private Integer level; @ApiModelProperty(value = "题目ID", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String id; // 题目ID
@ApiModelProperty(value = "题目图片", required=true) /**
private String image; *
*
*/
@ApiModelProperty(value = "题目类型", required=true) // Swagger注解描述字段信息标明该字段是必填项
private Integer quType; // 题目类型
@ApiModelProperty(value = "题目内容", required=true) /**
private String content; *
* 12
*/
@ApiModelProperty(value = "1普通,2较难", required=true) // Swagger注解描述字段信息标明该字段是必填项
private Integer level; // 题目难度
/**
*
* URL
*/
@ApiModelProperty(value = "题目图片", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String image; // 题目图片URL
@ApiModelProperty(value = "创建时间", required=true) /**
private Date createTime; *
*
*/
@ApiModelProperty(value = "题目内容", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String content; // 题目内容
@ApiModelProperty(value = "更新时间", required=true) /**
private Date updateTime; *
*
*/
@ApiModelProperty(value = "创建时间", required=true) // Swagger注解描述字段信息标明该字段是必填项
private Date createTime; // 创建时间
@ApiModelProperty(value = "题目备注", required=true) /**
private String remark; *
*
*/
@ApiModelProperty(value = "更新时间", required=true) // Swagger注解描述字段信息标明该字段是必填项
private Date updateTime; // 更新时间
@ApiModelProperty(value = "整题解析", required=true) /**
private String analysis; *
*
*/
@ApiModelProperty(value = "题目备注", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String remark; // 题目备注
/**
*
*
*/
@ApiModelProperty(value = "整题解析", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String analysis; // 整题解析
} }

@ -1,38 +1,58 @@
package com.yf.exam.modules.qu.dto; package com.yf.exam.modules.qu.dto;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel; // Swagger注解用于生成API文档
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty; // Swagger注解用于描述API模型的属性
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
import java.io.Serializable; import java.io.Serializable; // 可序列化接口
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* IDID
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@ApiModel(value="试题题库", description="试题题库") @ApiModel(value="试题题库", description="试题题库")
public class QuRepoDTO implements Serializable { public class QuRepoDTO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
private String id; /**
* ID
@ApiModelProperty(value = "试题", required=true) *
private String quId; */
private String id; // 试题ID
@ApiModelProperty(value = "归属题库", required=true) /**
private String repoId; * ID
* ID
*/
@ApiModelProperty(value = "试题", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String quId; // 题目ID
@ApiModelProperty(value = "题目类型", required=true) /**
private Integer quType; * ID
*
*/
@ApiModelProperty(value = "归属题库", required=true) // Swagger注解描述字段信息标明该字段是必填项
private String repoId; // 题库ID
@ApiModelProperty(value = "排序", required=true) /**
private Integer sort; *
*
*/
@ApiModelProperty(value = "题目类型", required=true) // Swagger注解描述字段信息标明该字段是必填项
private Integer quType; // 题目类型
/**
*
*
*/
@ApiModelProperty(value = "排序", required=true) // Swagger注解描述字段信息标明该字段是必填项
private Integer sort; // 排序
} }

@ -1,45 +1,59 @@
package com.yf.exam.modules.qu.dto.export; package com.yf.exam.modules.qu.dto.export;
import com.yf.exam.core.utils.excel.annotation.ExcelField; import com.yf.exam.core.utils.excel.annotation.ExcelField; // Excel导出注解
import com.yf.exam.core.utils.excel.fieldtype.ListType; import com.yf.exam.core.utils.excel.fieldtype.ListType; // 用于处理List类型字段的特殊注解
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter、toString等方法
import java.util.List; import java.util.List; // 用于表示列表类型的字段
/** /**
* *
*
* 使DTO
* Excel
*
* @author bool * @author bool
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
public class QuExportDTO { public class QuExportDTO {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
/** /**
* * ID
*/ */
private String qId; private String qId; // 题目的唯一标识符
@ExcelField(title="题目序号", align=2, sort=1) @ExcelField(title="题目序号", align=2, sort=1) // 导出Excel时的列标题和排序align为居中对齐sort为排序位置
private String no; private String no; // 题目序号,表示题目的编号
@ExcelField(title="题目类型", align=2, sort=2)
private String quType; @ExcelField(title="题目类型", align=2, sort=2) // Excel导出列的标题和排序
@ExcelField(title="题目内容", align=2, sort=3) private String quType; // 题目类型,可能是单选题、多选题等
private String qContent;
@ExcelField(title="整体解析", align=2, sort=4) @ExcelField(title="题目内容", align=2, sort=3) // Excel导出列的标题和排序
private String qAnalysis; private String qContent; // 题目内容,包含问题的具体描述
@ExcelField(title="题目图片", align=2, sort=5)
private String qImage; @ExcelField(title="整体解析", align=2, sort=4) // Excel导出列的标题和排序
@ExcelField(title="题目视频", align=2, sort=6) private String qAnalysis; // 整个题目的解析说明
private String qVideo;
@ExcelField(title="所属题库", align=2, sort=7, fieldType = ListType.class) @ExcelField(title="题目图片", align=2, sort=5) // Excel导出列的标题和排序
private List<String> repoList; private String qImage; // 题目图片存储图片URL或路径
@ExcelField(title="是否正确项", align=2, sort=8)
private String aIsRight; @ExcelField(title="题目视频", align=2, sort=6) // Excel导出列的标题和排序
@ExcelField(title="选项内容", align=2, sort=9) private String qVideo; // 题目视频存储视频URL或路径
private String aContent;
@ExcelField(title="选项解析", align=2, sort=10) @ExcelField(title="所属题库", align=2, sort=7, fieldType = ListType.class) // 题库列表,支持导出多个题库
private String aAnalysis; private List<String> repoList; // 题目所属的题库列表
@ExcelField(title="选项图片", align=2, sort=11)
private String aImage; @ExcelField(title="是否正确项", align=2, sort=8) // Excel导出列的标题和排序
private String aIsRight; // 是否为正确选项0或1
@ExcelField(title="选项内容", align=2, sort=9) // Excel导出列的标题和排序
private String aContent; // 选项内容,表示答案的具体内容
@ExcelField(title="选项解析", align=2, sort=10) // Excel导出列的标题和排序
private String aAnalysis; // 选项解析,说明该选项的正确性或相关分析
@ExcelField(title="选项图片", align=2, sort=11) // Excel导出列的标题和排序
private String aImage; // 选项图片存储图片URL或路径
} }

@ -1,23 +1,56 @@
package com.yf.exam.modules.qu.dto.export; package com.yf.exam.modules.qu.dto.export;
import com.yf.exam.modules.qu.dto.QuAnswerDTO; import com.yf.exam.modules.qu.dto.QuAnswerDTO; // 导入的选项答案DTO
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter、toString等方法
import java.util.List; import java.util.List; // 用于表示列表类型的字段
/** /**
* *
*
* DTO
*
*
* @author bool * @author bool
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
public class QuImportDTO { public class QuImportDTO {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
private String quType; /**
private String qContent; *
private String qAnalysis; * 1234
private String qImage; */
private String repoName; private String quType; // 题目类型,表示题目的类别
private List<QuAnswerDTO> answerList;
/**
*
*
*/
private String qContent; // 题目内容,表示题目的实际问题
/**
*
*
*/
private String qAnalysis; // 题目解析,解释题目的答案或相关说明
/**
*
* URL
*/
private String qImage; // 题目图片存储图片URL或路径
/**
*
*
*/
private String repoName; // 题目所属的题库名称
/**
*
*
*/
private List<QuAnswerDTO> answerList; // 答案选项列表,包含该题目的所有答案选项
} }

@ -1,33 +1,43 @@
package com.yf.exam.modules.qu.dto.ext; package com.yf.exam.modules.qu.dto.ext;
import com.yf.exam.modules.qu.dto.QuAnswerDTO; import com.yf.exam.modules.qu.dto.QuAnswerDTO; // 引入选项答案DTO
import com.yf.exam.modules.qu.dto.QuDTO; import com.yf.exam.modules.qu.dto.QuDTO; // 引入问题题目DTO
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel; // Swagger注解用于生成API文档
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty; // Swagger注解用于描述API模型的属性
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
import java.util.List; import java.util.List; // 用于表示列表类型的字段
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* `QuDTO`
*
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@ApiModel(value="问题题目详情", description="问题题目详情") @ApiModel(value="问题题目详情", description="问题题目详情")
public class QuDetailDTO extends QuDTO { public class QuDetailDTO extends QuDTO {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
@ApiModelProperty(value = "备选项列表", required=true)
private List<QuAnswerDTO> answerList;
@ApiModelProperty(value = "题库列表", required=true)
private List<String> repoIds;
/**
*
*
*
*/
@ApiModelProperty(value = "备选项列表", required=true) // Swagger注解用于生成文档
private List<QuAnswerDTO> answerList; // 备选项列表,包含该题目的所有答案选项
/**
*
* ID
*
*/
@ApiModelProperty(value = "题库列表", required=true) // Swagger注解用于生成文档
private List<String> repoIds; // 题库列表存储题库ID的列表
} }

@ -1,38 +1,54 @@
package com.yf.exam.modules.qu.dto.request; package com.yf.exam.modules.qu.dto.request;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel; // Swagger注解用于生成API文档
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty; // Swagger注解用于描述API模型的属性
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
import java.io.Serializable; import java.io.Serializable; // 可序列化接口
import java.util.List; import java.util.List; // 用于表示列表类型的字段
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
*
* ID便
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@ApiModel(value="题目查询请求类", description="题目查询请求类") @ApiModel(value="题目查询请求类", description="题目查询请求类")
public class QuQueryReqDTO implements Serializable { public class QuQueryReqDTO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
@ApiModelProperty(value = "题目类型")
private Integer quType;
@ApiModelProperty(value = "归属题库") /**
private List<String> repoIds; *
*
@ApiModelProperty(value = "题目内容") */
private String content; @ApiModelProperty(value = "题目类型") // Swagger注解描述字段信息
private Integer quType; // 题目类型,通常是数字表示不同题型
@ApiModelProperty(value = "排除ID列表") /**
private List<String> excludes; *
* ID
*/
@ApiModelProperty(value = "归属题库") // Swagger注解描述字段信息
private List<String> repoIds; // 题库ID列表题目可以归属于多个题库
/**
*
*
*/
@ApiModelProperty(value = "题目内容") // Swagger注解描述字段信息
private String content; // 题目内容,支持模糊查询
/**
* ID
* IDID
*/
@ApiModelProperty(value = "排除ID列表") // Swagger注解描述字段信息
private List<String> excludes; // 排除的题目ID列表
} }

@ -1,34 +1,47 @@
package com.yf.exam.modules.qu.dto.request; package com.yf.exam.modules.qu.dto.request;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel; // Swagger注解用于生成API文档
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty; // Swagger注解用于描述API模型的属性
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
import java.io.Serializable; import java.io.Serializable; // 可序列化接口
import java.util.List; import java.util.List; // 用于表示列表类型的字段
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
*
*
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@ApiModel(value="试题题库批量操作类", description="试题题库批量操作类") @ApiModel(value="试题题库批量操作类", description="试题题库批量操作类")
public class QuRepoBatchReqDTO implements Serializable { public class QuRepoBatchReqDTO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
@ApiModelProperty(value = "题目ID", required=true)
private List<String> quIds;
@ApiModelProperty(value = "题目类型", required=true) /**
private List<String> repoIds; * ID
* ID
*/
@ApiModelProperty(value = "题目ID", required=true) // Swagger注解描述字段信息标明该字段是必填项
private List<String> quIds; // 要操作的题目ID列表
@ApiModelProperty(value = "是否移除,否就新增;是就移除", required=true) /**
private Boolean remove; * ID
* ID
*/
@ApiModelProperty(value = "题目类型", required=true) // Swagger注解描述字段信息标明该字段是必填项
private List<String> repoIds; // 题库ID列表
/**
*
* `true` `false`
*/
@ApiModelProperty(value = "是否移除,否就新增;是就移除", required=true) // Swagger注解描述字段信息标明该字段是必填项
private Boolean remove; // `true`表示移除,`false`表示新增
} }

@ -1,75 +1,85 @@
package com.yf.exam.modules.qu.entity; package com.yf.exam.modules.qu.entity;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType; // MyBatis Plus注解指定ID生成策略
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField; // MyBatis Plus注解指定字段映射
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId; // MyBatis Plus注解指定主键字段
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName; // MyBatis Plus注解指定表名
import com.baomidou.mybatisplus.extension.activerecord.Model; import com.baomidou.mybatisplus.extension.activerecord.Model; // MyBatis Plus扩展的活动记录模式类
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
import java.util.Date; import java.util.Date; // 日期类型
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* MyBatis-Plus
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@TableName("el_qu") @TableName("el_qu") // MyBatis Plus注解指定与数据库表的映射关系
public class Qu extends Model<Qu> { public class Qu extends Model<Qu> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
/** /**
* ID * ID
*
*/ */
@TableId(value = "id", type = IdType.ASSIGN_ID) @TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定主键生成策略为自定义ID
private String id; private String id; // 题目ID
/** /**
* *
*
*/ */
@TableField("qu_type") @TableField("qu_type") // MyBatis Plus注解指定字段名与数据库表字段名映射
private Integer quType; private Integer quType; // 题目类型
/** /**
* 1,2 *
* 12
*/ */
private Integer level; private Integer level; // 题目难度1普通2较难
/** /**
* *
* URL
*/ */
private String image; private String image; // 题目图片
/** /**
* *
*
*/ */
private String content; private String content; // 题目内容
/** /**
* *
*
*/ */
@TableField("create_time") @TableField("create_time") // MyBatis Plus注解映射数据库中的字段
private Date createTime; private Date createTime; // 创建时间
/** /**
* *
*
*/ */
@TableField("update_time") @TableField("update_time") // MyBatis Plus注解映射数据库中的字段
private Date updateTime; private Date updateTime; // 更新时间
/** /**
* *
*
*/ */
private String remark; private String remark; // 题目备注
/** /**
* *
*
*/ */
private String analysis; private String analysis; // 整题解析
} }

@ -1,58 +1,64 @@
package com.yf.exam.modules.qu.entity; package com.yf.exam.modules.qu.entity;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType; // MyBatis Plus注解指定ID生成策略
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField; // MyBatis Plus注解指定字段映射
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId; // MyBatis Plus注解指定主键字段
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName; // MyBatis Plus注解指定表名
import com.baomidou.mybatisplus.extension.activerecord.Model; import com.baomidou.mybatisplus.extension.activerecord.Model; // MyBatis Plus扩展的活动记录模式类
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* MyBatis-Plus
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@TableName("el_qu_answer") @TableName("el_qu_answer") // MyBatis Plus注解指定与数据库表的映射关系
public class QuAnswer extends Model<QuAnswer> { public class QuAnswer extends Model<QuAnswer> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
/** /**
* ID * ID
*
*/ */
@TableId(value = "id", type = IdType.ASSIGN_ID) @TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定主键生成策略为自定义ID
private String id; private String id; // 答案ID
/** /**
* ID * ID
* ID
*/ */
@TableField("qu_id") @TableField("qu_id") // MyBatis Plus注解指定字段名与数据库表字段名映射
private String quId; private String quId; // 问题ID
/** /**
* *
* truefalse
*/ */
@TableField("is_right") @TableField("is_right") // MyBatis Plus注解映射数据库中的字段
private Boolean isRight; private Boolean isRight; // 是否正确
/** /**
* *
* URL
*/ */
private String image; private String image; // 选项图片
/** /**
* *
*
*/ */
private String content; private String content; // 答案内容
/** /**
* *
*
*/ */
private String analysis; private String analysis; // 答案分析
} }

@ -1,50 +1,59 @@
package com.yf.exam.modules.qu.entity; package com.yf.exam.modules.qu.entity;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType; // MyBatis Plus注解指定ID生成策略
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField; // MyBatis Plus注解指定字段映射
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId; // MyBatis Plus注解指定主键字段
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName; // MyBatis Plus注解指定表名
import com.baomidou.mybatisplus.extension.activerecord.Model; import com.baomidou.mybatisplus.extension.activerecord.Model; // MyBatis Plus扩展的活动记录模式类
import lombok.Data; import lombok.Data; // Lombok注解用于自动生成getter、setter等方法
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
*
*
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Data @Data // Lombok注解自动生成getter、setter等方法
@TableName("el_qu_repo") @TableName("el_qu_repo") // MyBatis Plus注解指定与数据库表的映射关系
public class QuRepo extends Model<QuRepo> { public class QuRepo extends Model<QuRepo> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L; // 序列化版本UID
@TableId(value = "id", type = IdType.ASSIGN_ID) /**
private String id; * ID
*
*/
@TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解指定主键生成策略为自定义ID
private String id; // 试题题库关系ID
/** /**
* * ID
* ID
*/ */
@TableField("qu_id") @TableField("qu_id") // MyBatis Plus注解指定字段名与数据库表字段名映射
private String quId; private String quId; // 试题ID
/** /**
* * ID
* ID
*/ */
@TableField("repo_id") @TableField("repo_id") // MyBatis Plus注解指定字段名与数据库表字段名映射
private String repoId; private String repoId; // 题库ID
/** /**
* *
*
*/ */
@TableField("qu_type") @TableField("qu_type") // MyBatis Plus注解映射数据库中的字段
private Integer quType; private Integer quType; // 题目类型
/** /**
* *
*
*/ */
private Integer sort; private Integer sort; // 排序
} }

@ -1,8 +1,10 @@
package com.yf.exam.modules.qu.enums; package com.yf.exam.modules.qu.enums;
/** /**
* *
*
*
*
* @author bool * @author bool
* @date 2019-10-30 13:11 * @date 2019-10-30 13:11
*/ */
@ -10,16 +12,19 @@ public interface QuType {
/** /**
* *
*
*/ */
Integer RADIO = 1; Integer RADIO = 1;
/** /**
* *
*
*/ */
Integer MULTI = 2; Integer MULTI = 2;
/** /**
* *
*
*/ */
Integer JUDGE = 3; Integer JUDGE = 3;

@ -1,15 +1,15 @@
package com.yf.exam.modules.qu.mapper; package com.yf.exam.modules.qu.mapper; // 定义包名,用于存放与问题题目相关的 Mapper 类
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入 MyBatis-Plus 的 BaseMapper用于提供通用的 CRUD 方法
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage; // 导入分页接口 IPage用于处理分页结果
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入分页插件的 Page 类,用于分页查询
import com.yf.exam.modules.qu.dto.QuDTO; import com.yf.exam.modules.qu.dto.QuDTO; // 导入 QuDTO 数据传输对象,用于封装题目数据
import com.yf.exam.modules.qu.dto.export.QuExportDTO; import com.yf.exam.modules.qu.dto.export.QuExportDTO; // 导入 QuExportDTO 用于题目导出的数据结构
import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; // 导入 QuQueryReqDTO 用于封装查询条件
import com.yf.exam.modules.qu.entity.Qu; import com.yf.exam.modules.qu.entity.Qu; // 导入 Qu 实体类,表示题目表的对应数据
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param; // 导入 MyBatis 的 Param 注解,用于 SQL 查询中的参数传递
import java.util.List; import java.util.List; // 导入 List用于返回多个对象的集合
/** /**
* <p> * <p>
@ -19,38 +19,34 @@ import java.util.List;
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
public interface QuMapper extends BaseMapper<Qu> { public interface QuMapper extends BaseMapper<Qu> { // QuMapper 继承自 BaseMapper提供基本的 CRUD 操作
/** /**
* *
* @param repoId * @param repoId ID
* @param quType * @param quType
* @param level * @param level
* @param excludes ID * @param excludes ID
* @param size * @param size
* @return * @return
*/ */
List<Qu> listByRandom(@Param("repoId") String repoId, List<Qu> listByRandom(@Param("repoId") String repoId, // 题库ID
@Param("quType") Integer quType, @Param("quType") Integer quType, // 题目类型
@Param("excludes") List<String> excludes, @Param("excludes") List<String> excludes, // 要排除的题目ID列表
@Param("size") Integer size); @Param("size") Integer size); // 抽取的题目数量
/** /**
* *
* @param query * @param query
* @return * @return
*/ */
List<QuExportDTO> listForExport(@Param("query") QuQueryReqDTO query); List<QuExportDTO> listForExport(@Param("query") QuQueryReqDTO query); // 根据查询条件查找导出数据
/** /**
* *
* @param page * @param page
* @param query * @param query
* @return * @return
*/ */
IPage<QuDTO> paging(Page page, @Param("query") QuQueryReqDTO query); IPage<QuDTO> paging(Page page, @Param("query") QuQueryReqDTO query); // 分页查询题目数据,返回 QuDTO 类型的数据
} }

@ -1,7 +1,7 @@
package com.yf.exam.modules.qu.mapper; package com.yf.exam.modules.qu.mapper; // 定义包名,用于存放与试题题库相关的 Mapper 类
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入 MyBatis-Plus 的 BaseMapper用于提供通用的 CRUD 方法
import com.yf.exam.modules.qu.entity.QuRepo; import com.yf.exam.modules.qu.entity.QuRepo; // 导入 QuRepo 实体类,表示试题题库表的数据
/** /**
* <p> * <p>
@ -11,6 +11,5 @@ import com.yf.exam.modules.qu.entity.QuRepo;
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
public interface QuRepoMapper extends BaseMapper<QuRepo> { public interface QuRepoMapper extends BaseMapper<QuRepo> { // QuRepoMapper 继承自 BaseMapper提供基本的 CRUD 操作
} }

@ -10,7 +10,7 @@ import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
@ -19,30 +19,30 @@ import java.util.List;
public interface QuAnswerService extends IService<QuAnswer> { public interface QuAnswerService extends IService<QuAnswer> {
/** /**
* *
* @param reqDTO * @param reqDTO
* @return * @return
*/ */
IPage<QuAnswerDTO> paging(PagingReqDTO<QuAnswerDTO> reqDTO); IPage<QuAnswerDTO> paging(PagingReqDTO<QuAnswerDTO> reqDTO);
/** /**
* ID * ID
* @param quId * @param quId ID
* @return * @return
*/ */
List<QuAnswer> listAnswerByRandom(String quId); List<QuAnswer> listAnswerByRandom(String quId);
/** /**
* * ID
* @param quId * @param quId ID
* @return * @return
*/ */
List<QuAnswerDTO> listByQu(String quId); List<QuAnswerDTO> listByQu(String quId);
/** /**
* *
* @param quId * @param quId ID
* @param list * @param list
*/ */
void saveAll(String quId, List<QuAnswerDTO> list); void saveAll(String quId, List<QuAnswerDTO> list);
} }

@ -1,17 +1,24 @@
// 定义包名表示该接口属于com.yf.exam.modules.qu.service包下
package com.yf.exam.modules.qu.service; package com.yf.exam.modules.qu.service;
// 导入MyBatis Plus框架的分页功能相关类
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
// 导入MyBatis Plus框架的服务接口
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
// 导入项目中定义的分页请求DTO类
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO;
// 导入项目中定义的题库DTO类
import com.yf.exam.modules.qu.dto.QuRepoDTO; import com.yf.exam.modules.qu.dto.QuRepoDTO;
// 导入项目中定义的批量请求DTO类
import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO; import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO;
// 导入项目中定义的题库实体类
import com.yf.exam.modules.qu.entity.QuRepo; import com.yf.exam.modules.qu.entity.QuRepo;
// 导入Java.util包下的List接口用于操作列表
import java.util.List; import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
@ -20,39 +27,39 @@ import java.util.List;
public interface QuRepoService extends IService<QuRepo> { public interface QuRepoService extends IService<QuRepo> {
/** /**
* *
* @param reqDTO * @param reqDTO DTO
* @return * @return
*/ */
IPage<QuRepoDTO> paging(PagingReqDTO<QuRepoDTO> reqDTO); IPage<QuRepoDTO> paging(PagingReqDTO<QuRepoDTO> reqDTO);
/** /**
* *
* @param quId * @param quId ID
* @param quType * @param quType
* @param ids * @param ids ID
*/ */
void saveAll(String quId, Integer quType, List<String> ids); void saveAll(String quId, Integer quType, List<String> ids);
/** /**
* *
* @param quId * @param quId ID
* @return * @return ID
*/ */
List<String> listByQu(String quId); List<String> listByQu(String quId);
/** /**
* ID * ID
* @param repoId * @param repoId ID
* @param quType * @param quType
* @param rand * @param rand
* @return * @return ID
*/ */
List<String> listByRepo(String repoId, Integer quType, boolean rand); List<String> listByRepo(String repoId, Integer quType, boolean rand);
/** /**
* *
* @param reqDTO * @param reqDTO DTO
*/ */
void batchAction(QuRepoBatchReqDTO reqDTO); void batchAction(QuRepoBatchReqDTO reqDTO);

@ -1,19 +1,28 @@
// 定义包名表示该接口属于com.yf.exam.modules.qu.service包下
package com.yf.exam.modules.qu.service; package com.yf.exam.modules.qu.service;
// 导入MyBatis Plus框架的分页功能相关类
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
// 导入MyBatis Plus框架的服务接口
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
// 导入项目中定义的分页请求DTO类
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO;
// 导入项目中定义的题目DTO类
import com.yf.exam.modules.qu.dto.QuDTO; import com.yf.exam.modules.qu.dto.QuDTO;
// 导入项目中定义的题目导出DTO类
import com.yf.exam.modules.qu.dto.export.QuExportDTO; import com.yf.exam.modules.qu.dto.export.QuExportDTO;
// 导入项目中定义的扩展题目详情DTO类
import com.yf.exam.modules.qu.dto.ext.QuDetailDTO; import com.yf.exam.modules.qu.dto.ext.QuDetailDTO;
// 导入项目中定义的题目查询请求DTO类
import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO;
// 导入项目中定义的题目实体类
import com.yf.exam.modules.qu.entity.Qu; import com.yf.exam.modules.qu.entity.Qu;
// 导入Java.util包下的List接口用于操作列表
import java.util.List; import java.util.List;
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
@ -22,25 +31,25 @@ import java.util.List;
public interface QuService extends IService<Qu> { public interface QuService extends IService<Qu> {
/** /**
* *
* @param reqDTO * @param reqDTO DTO
* @return * @return
*/ */
IPage<QuDTO> paging(PagingReqDTO<QuQueryReqDTO> reqDTO); IPage<QuDTO> paging(PagingReqDTO<QuQueryReqDTO> reqDTO);
/** /**
* *
* @param ids * @param ids ID
*/ */
void delete(List<String> ids); void delete(List<String> ids);
/** /**
* *
* @param repoId * @param repoId ID
* @param quType * @param quType
* @param excludes ID * @param excludes ID
* @param size * @param size
* @return * @return
*/ */
List<Qu> listByRandom(String repoId, List<Qu> listByRandom(String repoId,
Integer quType, Integer quType,
@ -48,29 +57,29 @@ public interface QuService extends IService<Qu> {
Integer size); Integer size);
/** /**
* *
* @param id * @param id ID
* @return * @return DTO
*/ */
QuDetailDTO detail(String id); QuDetailDTO detail(String id);
/** /**
* *
* @param reqDTO * @param reqDTO DTO
*/ */
void save(QuDetailDTO reqDTO); void save(QuDetailDTO reqDTO);
/** /**
* *
* @param query * @param query DTO
* @return * @return
*/ */
List<QuExportDTO> listForExport(QuQueryReqDTO query); List<QuExportDTO> listForExport(QuQueryReqDTO query);
/** /**
* Excel * Excel
* @param dtoList * @param dtoList DTO
* @return * @return
*/ */
int importExcel(List<QuExportDTO> dtoList); int importExcel(List<QuExportDTO> dtoList);
} }

@ -1,24 +1,24 @@
package com.yf.exam.modules.qu.service.impl; package com.yf.exam.modules.qu.service.impl; // 定义包名,表示这是实现类部分,专注于处理与试题答案相关的逻辑
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON; // 导入 fastjson 库,用于 JSON 序列化和反序列化
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference; // 导入 fastjson 库的 TypeReference用于处理泛型类型
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 导入 MyBatis-Plus 的 QueryWrapper用于构造查询条件
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage; // 导入 MyBatis-Plus 的 IPage 接口,用于分页查询
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入 MyBatis-Plus 的分页 Page 类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; // 导入 MyBatis-Plus 的 ServiceImpl 基类
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO; // 导入分页请求数据传输对象类
import com.yf.exam.core.utils.BeanMapper; import com.yf.exam.core.utils.BeanMapper; // 导入 BeanMapper 工具类,用于对象之间的映射
import com.yf.exam.modules.qu.dto.QuAnswerDTO; import com.yf.exam.modules.qu.dto.QuAnswerDTO; // 导入试题答案的 DTO 类
import com.yf.exam.modules.qu.entity.QuAnswer; import com.yf.exam.modules.qu.entity.QuAnswer; // 导入试题答案的实体类
import com.yf.exam.modules.qu.mapper.QuAnswerMapper; import com.yf.exam.modules.qu.mapper.QuAnswerMapper; // 导入试题答案的 Mapper 接口
import com.yf.exam.modules.qu.service.QuAnswerService; import com.yf.exam.modules.qu.service.QuAnswerService; // 导入试题答案的服务接口
import com.yf.exam.modules.qu.utils.ImageCheckUtils; import com.yf.exam.modules.qu.utils.ImageCheckUtils; // 导入图片校验工具类
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired; // 导入 Spring 的注解,自动注入依赖
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service; // 导入 Spring 的服务注解,标识这是一个服务类
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils; // 导入 Spring 的集合工具类,用于检查集合是否为空
import java.util.ArrayList; import java.util.ArrayList; // 导入 ArrayList用于动态数组
import java.util.List; import java.util.List; // 导入 List 接口,作为列表类型
/** /**
* <p> * <p>
@ -28,65 +28,77 @@ import java.util.List;
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Service @Service // 表示这是一个服务类Spring 会自动扫描并管理该类
public class QuAnswerServiceImpl extends ServiceImpl<QuAnswerMapper, QuAnswer> implements QuAnswerService { public class QuAnswerServiceImpl extends ServiceImpl<QuAnswerMapper, QuAnswer> implements QuAnswerService {
@Autowired @Autowired
private ImageCheckUtils imageCheckUtils; private ImageCheckUtils imageCheckUtils; // 自动注入图片校验工具类,用于校验图片地址是否合法
@Override @Override
public IPage<QuAnswerDTO> paging(PagingReqDTO<QuAnswerDTO> reqDTO) { public IPage<QuAnswerDTO> paging(PagingReqDTO<QuAnswerDTO> reqDTO) {
// 创建分页对象,传入当前页和每页大小
//创建分页对象
IPage<QuAnswer> query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); IPage<QuAnswer> query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
//查询条件 // 创建查询条件包装器
QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>(); QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>();
//获得数据 // 执行分页查询,获取分页结果
IPage<QuAnswer> page = this.page(query, wrapper); IPage<QuAnswer> page = this.page(query, wrapper);
//转换结果
// 将查询结果转换为 QuAnswerDTO 类型的分页结果
IPage<QuAnswerDTO> pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference<Page<QuAnswerDTO>>(){}); IPage<QuAnswerDTO> pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference<Page<QuAnswerDTO>>(){});
return pageData; return pageData;
} }
@Override @Override
public List<QuAnswer> listAnswerByRandom(String quId) { public List<QuAnswer> listAnswerByRandom(String quId) {
// 创建查询条件包装器
QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>(); QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>();
// 设置查询条件,过滤出与 quId 相同的记录
wrapper.lambda().eq(QuAnswer::getQuId, quId); wrapper.lambda().eq(QuAnswer::getQuId, quId);
// 使用 SQL 的随机排序来随机获取答案
wrapper.last(" ORDER BY RAND() "); wrapper.last(" ORDER BY RAND() ");
// 执行查询并返回结果
return this.list(wrapper); return this.list(wrapper);
} }
@Override @Override
public List<QuAnswerDTO> listByQu(String quId) { public List<QuAnswerDTO> listByQu(String quId) {
// 创建查询条件包装器
QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>(); QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>();
// 设置查询条件,过滤出与 quId 相同的记录
wrapper.lambda().eq(QuAnswer::getQuId, quId); wrapper.lambda().eq(QuAnswer::getQuId, quId);
// 执行查询,获取答案列表
List<QuAnswer> list = this.list(wrapper); List<QuAnswer> list = this.list(wrapper);
if(!CollectionUtils.isEmpty(list)){ if(!CollectionUtils.isEmpty(list)){
// 将 QuAnswer 实体对象列表转换为 QuAnswerDTO 对象列表
return BeanMapper.mapList(list, QuAnswerDTO.class); return BeanMapper.mapList(list, QuAnswerDTO.class);
} }
// 如果没有找到记录,返回 null
return null; return null;
} }
/** /**
* *
* @param quId * @param quId ID
* @return * @return ID
*/ */
public List<String> findExistsList(String quId) { public List<String> findExistsList(String quId) {
//返回结果 // 创建空的结果列表
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();
// 创建查询条件包装器
QueryWrapper<QuAnswer> wrapper = new QueryWrapper(); QueryWrapper<QuAnswer> wrapper = new QueryWrapper();
// 设置查询条件,过滤出与 quId 相同的记录
wrapper.lambda().eq(QuAnswer::getQuId, quId); wrapper.lambda().eq(QuAnswer::getQuId, quId);
// 执行查询,获取答案列表
List<QuAnswer> list = this.list(wrapper); List<QuAnswer> list = this.list(wrapper);
if (!CollectionUtils.isEmpty(list)) { if (!CollectionUtils.isEmpty(list)) {
// 将已有的答案 ID 添加到结果列表
for (QuAnswer item : list) { for (QuAnswer item : list) {
ids.add(item.getId()); ids.add(item.getId());
} }
@ -96,49 +108,49 @@ public class QuAnswerServiceImpl extends ServiceImpl<QuAnswerMapper, QuAnswer> i
@Override @Override
public void saveAll(String quId, List<QuAnswerDTO> list) { public void saveAll(String quId, List<QuAnswerDTO> list) {
// 创建保存的答案列表
//最终要保存的列表
List<QuAnswer> saveList = new ArrayList<>(); List<QuAnswer> saveList = new ArrayList<>();
//已存在的标签列表 // 获取已有的答案 ID 列表
List<String> ids = this.findExistsList(quId); List<String> ids = this.findExistsList(quId);
// 如果答案列表不为空,则进行处理
if(!CollectionUtils.isEmpty(list)){ if(!CollectionUtils.isEmpty(list)){
for(QuAnswerDTO item: list){ for(QuAnswerDTO item: list){
// 校验图片地址 // 校验选项图片地址是否合法
imageCheckUtils.checkImage(item.getImage(), "选项图片地址错误!"); imageCheckUtils.checkImage(item.getImage(), "选项图片地址错误!");
//标签ID // 获取答案 ID
String id = item.getId(); String id = item.getId();
QuAnswer answer = new QuAnswer(); QuAnswer answer = new QuAnswer();
// 将 DTO 转换为实体类
BeanMapper.copy(item, answer); BeanMapper.copy(item, answer);
answer.setQuId(quId); answer.setQuId(quId); // 设置试题 ID
//补全ID避免新增 // 如果该答案已存在,则从 IDs 列表中移除
if(ids.contains(id)){ if(ids.contains(id)){
ids.remove(id); ids.remove(id);
} }
// 添加答案到保存列表
saveList.add(answer); saveList.add(answer);
} }
//保存标签列表 // 如果有待保存的答案,则批量保存或更新
if(!CollectionUtils.isEmpty(saveList)) { if(!CollectionUtils.isEmpty(saveList)) {
this.saveOrUpdateBatch(saveList); this.saveOrUpdateBatch(saveList);
} }
//除已移 // 如果有被移除的答案,则批量删除
if(!ids.isEmpty()){ if(!ids.isEmpty()){
this.removeByIds(ids); this.removeByIds(ids);
} }
}else{ }else{
// 如果答案列表为空,则删除所有与该试题 ID 相关的答案
QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>(); QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(QuAnswer::getQuId, quId); wrapper.lambda().eq(QuAnswer::getQuId, quId);
this.remove(wrapper); this.remove(wrapper);
} }
} }
} }

@ -1,175 +1,182 @@
package com.yf.exam.modules.qu.service.impl; package com.yf.exam.modules.qu.service.impl; // 定义包名,表示这是服务实现类,负责处理与试题题库相关的业务逻辑
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON; // 导入 fastjson 库,用于 JSON 序列化和反序列化
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference; // 导入 fastjson 库的 TypeReference用于处理泛型类型
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 导入 MyBatis-Plus 的 QueryWrapper用于构造查询条件
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage; // 导入 MyBatis-Plus 的 IPage 接口,用于分页查询
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入 MyBatis-Plus 的分页 Page 类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; // 导入 MyBatis-Plus 的 ServiceImpl 基类
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO; // 导入分页请求数据传输对象类
import com.yf.exam.modules.qu.dto.QuRepoDTO; import com.yf.exam.modules.qu.dto.QuRepoDTO; // 导入试题题库的 DTO 类
import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO; import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO; // 导入试题题库批量操作请求类
import com.yf.exam.modules.qu.entity.Qu; import com.yf.exam.modules.qu.entity.Qu; // 导入试题实体类
import com.yf.exam.modules.qu.entity.QuRepo; import com.yf.exam.modules.qu.entity.QuRepo; // 导入试题题库实体类
import com.yf.exam.modules.qu.mapper.QuMapper; import com.yf.exam.modules.qu.mapper.QuMapper; // 导入试题的 Mapper 接口
import com.yf.exam.modules.qu.mapper.QuRepoMapper; import com.yf.exam.modules.qu.mapper.QuRepoMapper; // 导入试题题库的 Mapper 接口
import com.yf.exam.modules.qu.service.QuRepoService; import com.yf.exam.modules.qu.service.QuRepoService; // 导入试题题库服务接口
import com.yf.exam.modules.repo.service.RepoService; import com.yf.exam.modules.repo.service.RepoService; // 导入题库服务接口
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired; // 导入 Spring 的注解,自动注入依赖
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service; // 导入 Spring 的服务注解,标识这是一个服务类
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils; // 导入 Spring 的集合工具类,用于检查集合是否为空
import java.util.ArrayList; import java.util.ArrayList; // 导入 ArrayList用于动态数组
import java.util.List; import java.util.List; // 导入 List 接口,作为列表类型
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
* @since 2020-05-25 13:23 * @since 2020-05-25 13:23
*/ */
@Service @Service // 表示这是一个 Spring 服务类Spring 会自动扫描并管理该类
public class QuRepoServiceImpl extends ServiceImpl<QuRepoMapper, QuRepo> implements QuRepoService { public class QuRepoServiceImpl extends ServiceImpl<QuRepoMapper, QuRepo> implements QuRepoService {
@Autowired @Autowired
private QuMapper quMapper; private QuMapper quMapper; // 自动注入试题的 Mapper 接口
@Autowired @Autowired
private RepoService repoService; private RepoService repoService; // 自动注入题库服务接口
@Override @Override
public IPage<QuRepoDTO> paging(PagingReqDTO<QuRepoDTO> reqDTO) { public IPage<QuRepoDTO> paging(PagingReqDTO<QuRepoDTO> reqDTO) {
// 创建分页对象,传入当前页和每页大小
//创建分页对象
IPage<QuRepo> query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); IPage<QuRepo> query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
//查询条件 // 创建查询条件包装器
QueryWrapper<QuRepo> wrapper = new QueryWrapper<>(); QueryWrapper<QuRepo> wrapper = new QueryWrapper<>();
//获得数据 // 执行分页查询,获取分页结果
IPage<QuRepo> page = this.page(query, wrapper); IPage<QuRepo> page = this.page(query, wrapper);
//转换结果
// 将查询结果转换为 QuRepoDTO 类型的分页结果
IPage<QuRepoDTO> pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference<Page<QuRepoDTO>>(){}); IPage<QuRepoDTO> pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference<Page<QuRepoDTO>>(){});
return pageData; return pageData;
} }
@Override @Override
public void saveAll(String quId, Integer quType, List<String> ids) { public void saveAll(String quId, Integer quType, List<String> ids) {
// 先删除 // 先删除已有的试题题库记录
QueryWrapper<QuRepo> wrapper = new QueryWrapper<>(); QueryWrapper<QuRepo> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(QuRepo::getQuId, quId); wrapper.lambda().eq(QuRepo::getQuId, quId);
this.remove(wrapper); this.remove(wrapper);
// 保存全部 // 如果题库 ID 列表不为空,保存新的记录
if(!CollectionUtils.isEmpty(ids)){ if(!CollectionUtils.isEmpty(ids)){
List<QuRepo> list = new ArrayList<>(); List<QuRepo> list = new ArrayList<>();
for(String id: ids){ for(String id: ids){
QuRepo ref = new QuRepo(); QuRepo ref = new QuRepo();
ref.setQuId(quId); ref.setQuId(quId); // 设置试题 ID
ref.setRepoId(id); ref.setRepoId(id); // 设置题库 ID
ref.setQuType(quType); ref.setQuType(quType); // 设置题目类型
list.add(ref); list.add(ref);
} }
// 批量保存试题题库记录
this.saveBatch(list); this.saveBatch(list);
// 对每个题库进行排序
for(String id: ids){ for(String id: ids){
this.sortRepo(id); this.sortRepo(id);
} }
} }
} }
@Override @Override
public List<String> listByQu(String quId) { public List<String> listByQu(String quId) {
// 先删除 // 根据试题 ID 查找题库记录
QueryWrapper<QuRepo> wrapper = new QueryWrapper<>(); QueryWrapper<QuRepo> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(QuRepo::getQuId, quId); wrapper.lambda().eq(QuRepo::getQuId, quId);
List<QuRepo> list = this.list(wrapper); List<QuRepo> list = this.list(wrapper);
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();
if(!CollectionUtils.isEmpty(list)){ if(!CollectionUtils.isEmpty(list)){
// 提取题库 ID 列表
for(QuRepo item: list){ for(QuRepo item: list){
ids.add(item.getRepoId()); ids.add(item.getRepoId());
} }
} }
return ids; return ids; // 返回题库 ID 列表
} }
@Override @Override
public List<String> listByRepo(String repoId, Integer quType, boolean rand) { public List<String> listByRepo(String repoId, Integer quType, boolean rand) {
// 根据题库 ID 和题目类型查询题库记录
QueryWrapper<QuRepo> wrapper = new QueryWrapper<>(); QueryWrapper<QuRepo> wrapper = new QueryWrapper<>();
wrapper.lambda() wrapper.lambda().eq(QuRepo::getRepoId, repoId);
.eq(QuRepo::getRepoId, repoId);
// 如果有题目类型,添加过滤条件
if(quType != null){ if(quType != null){
wrapper.lambda().eq(QuRepo::getQuType, quType); wrapper.lambda().eq(QuRepo::getQuType, quType);
} }
// 根据是否需要随机排序决定排序方式
if(rand){ if(rand){
wrapper.orderByAsc(" RAND() "); wrapper.orderByAsc(" RAND() "); // 随机排序
}else{ }else{
wrapper.lambda().orderByAsc(QuRepo::getSort); wrapper.lambda().orderByAsc(QuRepo::getSort); // 按照排序字段排序
} }
// 执行查询,获取题库记录列表
List<QuRepo> list = this.list(wrapper); List<QuRepo> list = this.list(wrapper);
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();
if(!CollectionUtils.isEmpty(list)){ if(!CollectionUtils.isEmpty(list)){
// 提取试题 ID 列表
for(QuRepo item: list){ for(QuRepo item: list){
ids.add(item.getQuId()); ids.add(item.getQuId());
} }
} }
return ids; return ids; // 返回试题 ID 列表
} }
@Override @Override
public void batchAction(QuRepoBatchReqDTO reqDTO) { public void batchAction(QuRepoBatchReqDTO reqDTO) {
// 如果需要移除记录
// 移除的
if(reqDTO.getRemove() != null && reqDTO.getRemove()){ if(reqDTO.getRemove() != null && reqDTO.getRemove()){
// 删除满足条件的题库记录
QueryWrapper<QuRepo> wrapper = new QueryWrapper<>(); QueryWrapper<QuRepo> wrapper = new QueryWrapper<>();
wrapper.lambda() wrapper.lambda()
.in(QuRepo::getRepoId, reqDTO.getRepoIds()) .in(QuRepo::getRepoId, reqDTO.getRepoIds())
.in(QuRepo::getQuId, reqDTO.getQuIds()); .in(QuRepo::getQuId, reqDTO.getQuIds());
this.remove(wrapper); this.remove(wrapper);
}else{ }else{
// 如果是新增记录,处理新增逻辑
// 新增的
for(String quId : reqDTO.getQuIds()){ for(String quId : reqDTO.getQuIds()){
// 根据试题 ID 查询试题类型
Qu q = quMapper.selectById(quId); Qu q = quMapper.selectById(quId);
// 保存新的题库记录
this.saveAll(quId, q.getQuType(), reqDTO.getRepoIds()); this.saveAll(quId, q.getQuType(), reqDTO.getRepoIds());
} }
} }
// 对每个题库进行排序
for(String id: reqDTO.getRepoIds()){ for(String id: reqDTO.getRepoIds()){
this.sortRepo(id); this.sortRepo(id);
} }
} }
/** /**
* *
* @param repoId * @param repoId ID
*/ */
private void sortRepo(String repoId){ private void sortRepo(String repoId){
// 查询题库下的所有试题
QueryWrapper<QuRepo> wrapper = new QueryWrapper<>(); QueryWrapper<QuRepo> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(QuRepo::getRepoId, repoId); wrapper.lambda().eq(QuRepo::getRepoId, repoId);
List<QuRepo> list = this.list(wrapper); List<QuRepo> list = this.list(wrapper);
// 如果题库下没有试题,返回
if(CollectionUtils.isEmpty(list)){ if(CollectionUtils.isEmpty(list)){
return; return;
} }
// 按照顺序设置每个试题的排序值
int sort = 1; int sort = 1;
for(QuRepo item: list){ for(QuRepo item: list){
item.setSort(sort); item.setSort(sort);
sort++; sort++;
} }
// 批量更新排序值
this.updateBatchById(list); this.updateBatchById(list);
} }
} }

@ -1,9 +1,12 @@
package com.yf.exam.modules.qu.service.impl; package com.yf.exam.modules.qu.service.impl;
// 导入MyBatis Plus相关类
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入其他相关类
import com.yf.exam.ability.upload.config.UploadConfig; import com.yf.exam.ability.upload.config.UploadConfig;
import com.yf.exam.core.api.dto.PagingReqDTO; import com.yf.exam.core.api.dto.PagingReqDTO;
import com.yf.exam.core.exception.ServiceException; import com.yf.exam.core.exception.ServiceException;
@ -23,6 +26,7 @@ import com.yf.exam.modules.qu.service.QuRepoService;
import com.yf.exam.modules.qu.service.QuService; import com.yf.exam.modules.qu.service.QuService;
import com.yf.exam.modules.qu.utils.ImageCheckUtils; import com.yf.exam.modules.qu.utils.ImageCheckUtils;
import com.yf.exam.modules.repo.service.RepoService; import com.yf.exam.modules.repo.service.RepoService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -37,7 +41,7 @@ import java.util.Map;
/** /**
* <p> * <p>
* *
* </p> * </p>
* *
* @author * @author
@ -46,125 +50,136 @@ import java.util.Map;
@Service @Service
public class QuServiceImpl extends ServiceImpl<QuMapper, Qu> implements QuService { public class QuServiceImpl extends ServiceImpl<QuMapper, Qu> implements QuService {
// 注入QuAnswerService服务
@Autowired @Autowired
private QuAnswerService quAnswerService; private QuAnswerService quAnswerService;
// 注入QuRepoService服务
@Autowired @Autowired
private QuRepoService quRepoService; private QuRepoService quRepoService;
// 注入图片校验工具类
@Autowired @Autowired
private ImageCheckUtils imageCheckUtils; private ImageCheckUtils imageCheckUtils;
// 分页查询题目列表
@Override @Override
public IPage<QuDTO> paging(PagingReqDTO<QuQueryReqDTO> reqDTO) { public IPage<QuDTO> paging(PagingReqDTO<QuQueryReqDTO> reqDTO) {
// 创建分页对象 // 创建分页对象
Page page = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); Page page = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
//转换结果 // 调用baseMapper的分页查询方法获取分页数据
IPage<QuDTO> pageData = baseMapper.paging(page, reqDTO.getParams()); IPage<QuDTO> pageData = baseMapper.paging(page, reqDTO.getParams());
return pageData; return pageData;
} }
// 删除题目、答案和题库绑定
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void delete(List<String> ids) { public void delete(List<String> ids) {
// 除题目 // 除题目
this.removeByIds(ids); this.removeByIds(ids);
// 移除选项 // 删除与题目相关的选项
QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>(); QueryWrapper<QuAnswer> wrapper = new QueryWrapper<>();
wrapper.lambda().in(QuAnswer::getQuId, ids); wrapper.lambda().in(QuAnswer::getQuId, ids);
quAnswerService.remove(wrapper); quAnswerService.remove(wrapper);
// 移除题库绑定 // 删除题库与题目的绑定
QueryWrapper<QuRepo> wrapper1 = new QueryWrapper<>(); QueryWrapper<QuRepo> wrapper1 = new QueryWrapper<>();
wrapper1.lambda().in(QuRepo::getQuId, ids); wrapper1.lambda().in(QuRepo::getQuId, ids);
quRepoService.remove(wrapper1); quRepoService.remove(wrapper1);
} }
// 随机获取题目
@Override @Override
public List<Qu> listByRandom(String repoId, Integer quType, List<String> excludes, Integer size) { public List<Qu> listByRandom(String repoId, Integer quType, List<String> excludes, Integer size) {
return baseMapper.listByRandom(repoId, quType, excludes, size); return baseMapper.listByRandom(repoId, quType, excludes, size);
} }
// 获取题目的详细信息
@Override @Override
public QuDetailDTO detail(String id) { public QuDetailDTO detail(String id) {
QuDetailDTO respDTO = new QuDetailDTO(); QuDetailDTO respDTO = new QuDetailDTO();
// 获取题目信息
Qu qu = this.getById(id); Qu qu = this.getById(id);
BeanMapper.copy(qu, respDTO); BeanMapper.copy(qu, respDTO);
// 获取题目的选项信息
List<QuAnswerDTO> answerList = quAnswerService.listByQu(id); List<QuAnswerDTO> answerList = quAnswerService.listByQu(id);
respDTO.setAnswerList(answerList); respDTO.setAnswerList(answerList);
// 获取题目所属的题库
List<String> repoIds = quRepoService.listByQu(id); List<String> repoIds = quRepoService.listByQu(id);
respDTO.setRepoIds(repoIds); respDTO.setRepoIds(repoIds);
return respDTO; return respDTO;
} }
// 保存题目信息
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void save(QuDetailDTO reqDTO) { public void save(QuDetailDTO reqDTO) {
// 校验题目信息
// 校验数据
this.checkData(reqDTO, ""); this.checkData(reqDTO, "");
Qu qu = new Qu(); Qu qu = new Qu();
// 将题目详情复制到实体类
BeanMapper.copy(reqDTO, qu); BeanMapper.copy(reqDTO, qu);
// 校验图片地址 // 校验图片地址是否正确
imageCheckUtils.checkImage(qu.getImage(), "题干图片地址错误!"); imageCheckUtils.checkImage(qu.getImage(), "题干图片地址错误!");
// 更新 // 保存或更新题目信息
this.saveOrUpdate(qu); this.saveOrUpdate(qu);
// 保存全部问 // 保存目的选项
quAnswerService.saveAll(qu.getId(), reqDTO.getAnswerList()); quAnswerService.saveAll(qu.getId(), reqDTO.getAnswerList());
// 保存到题库 // 保存题目与题库的绑定
quRepoService.saveAll(qu.getId(), qu.getQuType(), reqDTO.getRepoIds()); quRepoService.saveAll(qu.getId(), qu.getQuType(), reqDTO.getRepoIds());
} }
// 获取题目导出的列表
@Override @Override
public List<QuExportDTO> listForExport(QuQueryReqDTO query) { public List<QuExportDTO> listForExport(QuQueryReqDTO query) {
return baseMapper.listForExport(query); return baseMapper.listForExport(query);
} }
// 导入Excel数据
@Override @Override
public int importExcel(List<QuExportDTO> dtoList) { public int importExcel(List<QuExportDTO> dtoList) {
// 根据题目名称分组 // 根据题目名称分组
Map<Integer, List<QuExportDTO>> anMap = new HashMap<>(16); Map<Integer, List<QuExportDTO>> anMap = new HashMap<>(16);
//题目本体信息 // 存储题目信息
Map<Integer, QuExportDTO> quMap = new HashMap<>(16); Map<Integer, QuExportDTO> quMap = new HashMap<>(16);
//数据分组 // 分组数据
for (QuExportDTO item : dtoList) { for (QuExportDTO item : dtoList) {
// 空白的ID // 如果题目ID为空跳过
if (StringUtils.isEmpty(item.getNo())) { if (StringUtils.isEmpty(item.getNo())) {
continue; continue;
} }
Integer key; Integer key;
//序号 // 获取题目序号
try { try {
key = Integer.parseInt(item.getNo()); key = Integer.parseInt(item.getNo());
} catch (Exception e) { } catch (Exception e) {
continue; continue;
} }
//如果已经有题目了,直接处理选项 // 如果题目已存在,直接处理选项
if (anMap.containsKey(key)) { if (anMap.containsKey(key)) {
anMap.get(key).add(item); anMap.get(key).add(item);
} else { } else {
//如果没有,将题目内容和选项一起 // 如果没有,将题目内容和选项一起放入
List<QuExportDTO> subList = new ArrayList<>(); List<QuExportDTO> subList = new ArrayList<>();
subList.add(item); subList.add(item);
anMap.put(key, subList); anMap.put(key, subList);
@ -174,49 +189,46 @@ public class QuServiceImpl extends ServiceImpl<QuMapper, Qu> implements QuServic
int count = 0; int count = 0;
try { try {
// 遍历题目插入
//循环题目插入
for (Integer key : quMap.keySet()) { for (Integer key : quMap.keySet()) {
QuExportDTO im = quMap.get(key); QuExportDTO im = quMap.get(key);
//题目基本信息 // 处理题目基本信息
QuDetailDTO qu = new QuDetailDTO(); QuDetailDTO qu = new QuDetailDTO();
qu.setContent(im.getQContent()); qu.setContent(im.getQContent());
qu.setAnalysis(im.getQAnalysis()); qu.setAnalysis(im.getQAnalysis());
qu.setQuType(Integer.parseInt(im.getQuType())); qu.setQuType(Integer.parseInt(im.getQuType()));
qu.setCreateTime(new Date()); qu.setCreateTime(new Date());
//设置回答列表 // 设置题目的回答列表
List<QuAnswerDTO> answerList = this.processAnswerList(anMap.get(key)); List<QuAnswerDTO> answerList = this.processAnswerList(anMap.get(key));
//设置题目
qu.setAnswerList(answerList); qu.setAnswerList(answerList);
//设置引用题库
// 设置题目所属的题库
qu.setRepoIds(im.getRepoList()); qu.setRepoIds(im.getRepoList());
// 保存答案
// 保存题目
this.save(qu); this.save(qu);
count++; count++;
} }
} catch (ServiceException e) { } catch (ServiceException e) {
e.printStackTrace(); e.printStackTrace();
// 异常处理,抛出导入失败的异常
throw new ServiceException(1, "导入出现问题,行:" + count + "" + e.getMessage()); throw new ServiceException(1, "导入出现问题,行:" + count + "" + e.getMessage());
} }
return count; return count;
} }
/** // 处理题目的回答列表
*
*
* @param importList
* @return
*/
private List<QuAnswerDTO> processAnswerList(List<QuExportDTO> importList) { private List<QuAnswerDTO> processAnswerList(List<QuExportDTO> importList) {
List<QuAnswerDTO> list = new ArrayList<>(16); List<QuAnswerDTO> list = new ArrayList<>(16);
for (QuExportDTO item : importList) { for (QuExportDTO item : importList) {
QuAnswerDTO a = new QuAnswerDTO(); QuAnswerDTO a = new QuAnswerDTO();
// 设置选项是否正确
a.setIsRight("1".equals(item.getAIsRight())); a.setIsRight("1".equals(item.getAIsRight()));
a.setContent(item.getAContent()); a.setContent(item.getAContent());
a.setAnalysis(item.getAAnalysis()); a.setAnalysis(item.getAAnalysis());
@ -226,58 +238,52 @@ public class QuServiceImpl extends ServiceImpl<QuMapper, Qu> implements QuServic
return list; return list;
} }
/** // 校验题目信息
*
*
* @param qu
* @param no
* @throws Exception
*/
public void checkData(QuDetailDTO qu, String no) { public void checkData(QuDetailDTO qu, String no) {
// 校验题目内容不能为空
if (StringUtils.isEmpty(qu.getContent())) { if (StringUtils.isEmpty(qu.getContent())) {
throw new ServiceException(1, no + "题目内容不能为空!"); throw new ServiceException(1, no + "题目内容不能为空!");
} }
// 校验至少选择一个题库
if (CollectionUtils.isEmpty(qu.getRepoIds())) { if (CollectionUtils.isEmpty(qu.getRepoIds())) {
throw new ServiceException(1, no + "至少要选择一个题库!"); throw new ServiceException(1, no + "至少要选择一个题库!");
} }
// 校验回答选项
List<QuAnswerDTO> answers = qu.getAnswerList(); List<QuAnswerDTO> answers = qu.getAnswerList();
if (CollectionUtils.isEmpty(answers)) { if (CollectionUtils.isEmpty(answers)) {
throw new ServiceException(1, no + "客观题至少要包含一个备选答案!"); throw new ServiceException(1, no + "客观题至少要包含一个备选答案!");
} }
int trueCount = 0; int trueCount = 0;
for (QuAnswerDTO a : answers) { for (QuAnswerDTO a : answers) {
// 校验选项是否定义了正确标志
if (a.getIsRight() == null) { if (a.getIsRight() == null) {
throw new ServiceException(1, no + "必须定义选项是否正确项!"); throw new ServiceException(1, no + "必须定义选项是否正确项!");
} }
// 校验选项内容不能为空
if (StringUtils.isEmpty(a.getContent())) { if (StringUtils.isEmpty(a.getContent())) {
throw new ServiceException(1, no + "选项内容不为空!"); throw new ServiceException(1, no + "选项内容不为空!");
} }
// 统计正确选项的个数
if (a.getIsRight()) { if (a.getIsRight()) {
trueCount += 1; trueCount += 1;
} }
} }
// 校验至少包含一个正确选项
if (trueCount == 0) { if (trueCount == 0) {
throw new ServiceException(1, no + "至少要包含一个正确项!"); throw new ServiceException(1, no + "至少要包含一个正确项!");
} }
// 单选题不能包含多个正确选项
//单选题
if (qu.getQuType().equals(QuType.RADIO) && trueCount > 1) { if (qu.getQuType().equals(QuType.RADIO) && trueCount > 1) {
throw new ServiceException(1, no + "单选题不能包含多个正确项!"); throw new ServiceException(1, no + "单选题不能包含多个正确项!");
} }
} }
} }

@ -1,30 +1,41 @@
// 定义包名表示该类属于com.yf.exam.modules.qu.utils包下
package com.yf.exam.modules.qu.utils; package com.yf.exam.modules.qu.utils;
// 导入项目中定义的上传配置类
import com.yf.exam.ability.upload.config.UploadConfig; import com.yf.exam.ability.upload.config.UploadConfig;
// 导入项目中定义的服务异常类
import com.yf.exam.core.exception.ServiceException; import com.yf.exam.core.exception.ServiceException;
// 导入Apache Commons Lang库中的StringUtils类用于字符串操作
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
// 导入Spring框架中的注解用于自动注入依赖
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
// 导入Spring框架中的注解用于声明组件
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/**
*
*/
@Component @Component
public class ImageCheckUtils { public class ImageCheckUtils {
// 自动注入上传配置,用于获取图片上传的相关配置
@Autowired @Autowired
private UploadConfig conf; private UploadConfig conf;
/** /**
* *
* @param image * @param image
* @param throwMsg * @param throwMsg
*/ */
public void checkImage(String image, String throwMsg) { public void checkImage(String image, String throwMsg) {
// 如果图片地址为空或空白,则直接返回,不进行校验
if(StringUtils.isBlank(image)){ if(StringUtils.isBlank(image)){
return; return;
} }
// 校验图片地址 // 校验图片地址是否以配置的URL开头确保图片地址是合法的
if(!image.startsWith(conf.getUrl())){ if(!image.startsWith(conf.getUrl())){
// 如果图片地址不合法,则抛出服务异常
throw new ServiceException(throwMsg); throw new ServiceException(throwMsg);
} }
} }

Loading…
Cancel
Save