From c63419e0156fa4a52cd5128b33565db3cc28b1d4 Mon Sep 17 00:00:00 2001 From: zl <3216908512@qq.com> Date: Wed, 27 Nov 2024 16:53:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=A0=E5=8A=9B=E5=88=86=E6=94=AF=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yf/exam/ability/Constant.java | 13 +- .../yf/exam/ability/job/enums/JobGroup.java | 14 +- .../yf/exam/ability/job/enums/JobPrefix.java | 15 +- .../exam/ability/job/service/JobService.java | 51 +-- .../job/service/impl/JobServiceImpl.java | 129 +++++-- .../ability/shiro/CNFilterFactoryBean.java | 38 ++- .../com/yf/exam/ability/shiro/ShiroRealm.java | 246 +++++++------- .../yf/exam/ability/shiro/aop/JwtFilter.java | 113 +++--- .../yf/exam/ability/shiro/jwt/JwtToken.java | 41 ++- .../yf/exam/ability/shiro/jwt/JwtUtils.java | 90 +++-- .../ability/upload/config/UploadConfig.java | 36 +- .../upload/controller/UploadController.java | 71 ++-- .../exam/ability/upload/dto/UploadReqDTO.java | 30 +- .../ability/upload/dto/UploadRespDTO.java | 35 +- .../ability/upload/service/UploadService.java | 58 +++- .../service/impl/UploadServiceImpl.java | 144 ++++---- .../exam/ability/upload/utils/FileUtils.java | 321 +++++++++--------- .../exam/ability/upload/utils/MediaUtils.java | 76 +++-- .../java/com/yf/exam/aspect/DictAspect.java | 215 +++++++----- .../exam/aspect/mybatis/QueryInterceptor.java | 177 ++++++++-- .../aspect/mybatis/UpdateInterceptor.java | 126 ++++++- .../com/yf/exam/aspect/utils/InjectUtils.java | 111 +++--- .../java/com/yf/exam/config/CorsConfig.java | 58 +++- .../com/yf/exam/config/MultipartConfig.java | 53 ++- .../com/yf/exam/config/MybatisConfig.java | 55 ++- .../com/yf/exam/config/ScheduledConfig.java | 141 +++++++- .../java/com/yf/exam/config/ShiroConfig.java | 242 ++++++------- .../com/yf/exam/config/SwaggerConfig.java | 155 +++++++-- .../exam/controller/ExamController.java | 233 ++++++------- .../com/yf/exam/modules/exam/dto/ExamDTO.java | 140 ++++---- .../exam/modules/exam/dto/ExamDepartDTO.java | 55 ++- .../yf/exam/modules/exam/dto/ExamRepoDTO.java | 93 +++-- .../modules/exam/dto/ext/ExamRepoExtDTO.java | 50 ++- .../exam/dto/request/ExamSaveReqDTO.java | 54 ++- .../exam/dto/response/ExamOnlineRespDTO.java | 35 +- .../exam/dto/response/ExamReviewRespDTO.java | 53 ++- .../com/yf/exam/modules/exam/entity/Exam.java | 155 ++++----- .../exam/modules/exam/entity/ExamDepart.java | 65 ++-- .../yf/exam/modules/exam/entity/ExamRepo.java | 105 +++--- .../modules/exam/mapper/ExamDepartMapper.java | 28 +- .../exam/modules/exam/mapper/ExamMapper.java | 61 ++-- .../modules/exam/mapper/ExamRepoMapper.java | 39 ++- .../exam/service/ExamDepartService.java | 42 +-- .../modules/exam/service/ExamRepoService.java | 49 +-- .../modules/exam/service/ExamService.java | 77 +++-- .../service/impl/ExamDepartServiceImpl.java | 88 +++-- .../service/impl/ExamRepoServiceImpl.java | 92 +++-- .../exam/service/impl/ExamServiceImpl.java | 209 +++++------- 48 files changed, 2685 insertions(+), 1892 deletions(-) diff --git a/src-源文件/main/java/com/yf/exam/ability/Constant.java b/src-源文件/main/java/com/yf/exam/ability/Constant.java index 9880ea6..50e754d 100644 --- a/src-源文件/main/java/com/yf/exam/ability/Constant.java +++ b/src-源文件/main/java/com/yf/exam/ability/Constant.java @@ -1,15 +1,18 @@ +// 定义包路径,用于存放系统基础功能相关的类 package com.yf.exam.ability; - /** - * 通用常量 + * 通用常量类 + * 用于定义系统中使用的常量值,提供全局可访问的常量。 * @author bool */ public class Constant { - /** - * 文件上传路径 + * 文件上传路径的前缀常量 + * 用于指定上传文件的基础路径,所有上传的文件URL都会以这个路径开头。 + * 例如: /upload/file/example.jpg + * 这个前缀用于构建和解析文件的URL,以便在系统中统一管理和访问上传的文件。 */ public static final String FILE_PREFIX = "/upload/file/"; -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/job/enums/JobGroup.java b/src-源文件/main/java/com/yf/exam/ability/job/enums/JobGroup.java index 2159361..058ab16 100644 --- a/src-源文件/main/java/com/yf/exam/ability/job/enums/JobGroup.java +++ b/src-源文件/main/java/com/yf/exam/ability/job/enums/JobGroup.java @@ -1,13 +1,19 @@ +// 定义包路径,用于存放任务分组相关的枚举类 package com.yf.exam.ability.job.enums; /** - * 任务分组 + * 任务分组枚举接口 + * 定义系统中不同类型任务的分组标识 + * 这个接口用于集中管理任务分组的常量值,确保代码的一致性和可维护性。 + * * @author van */ public interface JobGroup { /** - * 系统任务 + * 系统任务的分组标识 + * 用于标识系统级别的定时任务,如系统维护、数据清理等。 + * 这个标识符用于在任务调度系统中区分系统级别的任务,便于管理和执行。 */ - String SYSTEM = "system"; -} + String SYSTEM = "system"; // 系统任务组的标识符 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/job/enums/JobPrefix.java b/src-源文件/main/java/com/yf/exam/ability/job/enums/JobPrefix.java index 2536f0e..dd51986 100644 --- a/src-源文件/main/java/com/yf/exam/ability/job/enums/JobPrefix.java +++ b/src-源文件/main/java/com/yf/exam/ability/job/enums/JobPrefix.java @@ -1,14 +1,19 @@ +// 定义包路径,用于存放任务前缀相关的枚举类 package com.yf.exam.ability.job.enums; /** - * 任务前缀 + * 任务前缀枚举接口 + * 定义系统中不同任务类型的前缀标识 + * 这个接口用于集中管理任务的前缀常量值,确保代码的一致性和可维护性。 + * * @author bool */ public interface JobPrefix { /** - * 强制交卷的 + * 强制交卷任务的前缀标识 + * 用于标识与强制交卷相关的定时任务,方便在任务调度系统中识别和处理。 + * 例如:break_exam_12345 表示ID为12345的考试的强制交卷任务。 */ - String BREAK_EXAM = "break_exam_"; - -} + String BREAK_EXAM = "break_exam_"; // 强制交卷任务的前缀标识符 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/job/service/JobService.java b/src-源文件/main/java/com/yf/exam/ability/job/service/JobService.java index cb465de..410c006 100644 --- a/src-源文件/main/java/com/yf/exam/ability/job/service/JobService.java +++ b/src-源文件/main/java/com/yf/exam/ability/job/service/JobService.java @@ -1,53 +1,66 @@ +// 定义包路径,用于存放任务服务接口 package com.yf.exam.ability.job.service; /** - * 任务业务类,用于动态处理任务信息 + * 任务业务接口类,用于定义任务相关的业务操作 + * 这个接口定义了定时任务的基本操作,包括添加、暂停、恢复和删除任务。 + * * @author bool * @date 2020/11/29 下午2:17 */ public interface JobService { - /** - * 任务数据 + * 任务数据的键名常量 + * 用于在任务的JobDataMap中存储和检索任务相关数据的键名。 */ - String TASK_DATA = "taskData"; + String TASK_DATA = "taskData"; // 用于存储任务相关数据的键名 /** * 添加定时任务 - * @param jobClass - * @param jobName - * @param cron - * @param data + * 方法用于根据给定的任务类、名称、cron表达式和任务数据,添加一个新的定时任务。 + * + * @param jobClass 任务类,指定任务的执行类 + * @param jobName 任务名称,用于唯一标识任务 + * @param cron cron表达式,用于指定任务的执行计划 + * @param data 任务数据,传递给任务执行时的参数 */ void addCronJob(Class jobClass, String jobName, String cron, String data); /** * 添加立即执行的任务 - * @param jobClass - * @param jobName - * @param data + * 方法用于根据给定的任务类、名称和任务数据,添加一个新的立即执行的任务。 + * + * @param jobClass 任务类,指定任务的执行类 + * @param jobName 任务名称,用于唯一标识任务 + * @param data 任务数据,传递给任务执行时的参数 */ void addCronJob(Class jobClass, String jobName, String data); /** * 暂停任务 - * @param jobName - * @param jobGroup + * 方法用于根据任务名称和任务组,暂停一个正在运行的定时任务。 + * + * @param jobName 任务名称 + * @param jobGroup 任务组 */ void pauseJob(String jobName, String jobGroup); /** * 恢复任务 - * @param triggerName - * @param triggerGroup + * 方法用于根据触发器名称和触发器组,恢复一个已暂停的定时任务。 + * + * @param triggerName 触发器名称 + * @param triggerGroup 触发器组 */ void resumeJob(String triggerName, String triggerGroup); /** - * 删除job - * @param jobName - * @param jobGroup + * 删除任务 + * 方法用于根据任务名称和任务组,删除一个定时任务,包括任务本身和相关的触发器。 + * + * @param jobName 任务名称 + * @param jobGroup 任务组 */ void deleteJob(String jobName, String jobGroup); -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java b/src-源文件/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java index aafdfdb..b34ea8e 100644 --- a/src-源文件/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java +++ b/src-源文件/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java @@ -1,123 +1,186 @@ +// 定义包路径,用于存放任务服务实现类 package com.yf.exam.ability.job.service.impl; -import com.alibaba.fastjson.JSON; -import com.baomidou.mybatisplus.core.toolkit.IdWorker; -import com.yf.exam.ability.job.enums.JobGroup; -import com.yf.exam.ability.job.service.JobService; -import lombok.extern.log4j.Log4j2; -import org.quartz.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.quartz.SchedulerFactoryBean; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; +// 导入所需的外部依赖包 +import com.alibaba.fastjson.JSON; // 用于JSON数据处理 +import com.baomidou.mybatisplus.core.toolkit.IdWorker; // 用于生成唯一ID +import com.yf.exam.ability.job.enums.JobGroup; // 任务分组枚举 +import com.yf.exam.ability.job.service.JobService; // 任务服务接口 +import lombok.extern.log4j.Log4j2; // 日志注解 +import org.quartz.*; // Quartz定时任务框架相关类 +import org.springframework.beans.factory.annotation.Autowired; // Spring自动注入注解 +import org.springframework.scheduling.quartz.SchedulerFactoryBean; // Quartz调度器工厂Bean +import org.springframework.stereotype.Service; // Spring服务注解 +import org.springframework.util.StringUtils; // Spring字符串工具类 /** + * 定时任务服务实现类 + * 用于管理系统中的定时任务,包括添加、暂停、恢复和删除任务。 * @author bool */ -@Log4j2 -@Service +@Log4j2 // 启用Log4j2日志 +@Service // 标记为Spring服务组件 public class JobServiceImpl implements JobService { /** - * Quartz定时任务核心的功能实现类 + * Quartz定时任务调度器 + * 用于管理和执行所有的定时任务 */ - private Scheduler scheduler; + private Scheduler scheduler; // 定时任务调度器实例 /** - * 注入 - * @param schedulerFactoryBean + * 构造函数,注入SchedulerFactoryBean + * 从Spring容器中注入Quartz调度器工厂Bean,并从中获取调度器实例。 + * @param schedulerFactoryBean Quartz调度器工厂Bean */ public JobServiceImpl(@Autowired SchedulerFactoryBean schedulerFactoryBean) { + // 从工厂Bean中获取调度器实例 scheduler = schedulerFactoryBean.getScheduler(); } - + /** + * 添加定时任务 + * 方法用于添加一个新的定时任务,包括cron表达式和立即执行两种情况。 + * + * @param jobClass 任务类,指定任务的执行类 + * @param jobName 任务名称,用于唯一标识任务 + * @param cron cron表达式,用于指定任务的执行计划 + * @param data 任务数据,传递给任务执行时的参数 + */ @Override public void addCronJob(Class jobClass, String jobName, String cron, String data) { - - + // 设置任务组为系统任务组 String jobGroup = JobGroup.SYSTEM; - // 自动命名 + // 如果任务名为空,则自动生成任务名 if(StringUtils.isEmpty(jobName)){ - jobName = jobClass.getSimpleName().toUpperCase() + "_"+IdWorker.getIdStr(); + // 使用类名大写+下划线+唯一ID作为任务名 + jobName = jobClass.getSimpleName().toUpperCase() + "_" + IdWorker.getIdStr(); } try { + // 创建任务键,用于唯一标识任务 JobKey jobKey = JobKey.jobKey(jobName, jobGroup); + // 获取任务详情 JobDetail jobDetail = scheduler.getJobDetail(jobKey); + + // 如果任务已存在,则删除旧任务 if (jobDetail != null) { log.info("++++++++++任务:{} 已存在", jobName); this.deleteJob(jobName, jobGroup); } + // 记录任务构建信息 log.info("++++++++++构建任务:{},{},{},{},{} ", jobClass.toString(), jobName, jobGroup, cron, data); - //构建job信息 + // 构建新的任务详情 jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build(); - //用JopDataMap来传递数据 + // 设置任务数据 jobDetail.getJobDataMap().put(TASK_DATA, data); - //按新的cronExpression表达式构建一个新的trigger + // 声明触发器 Trigger trigger = null; - // 有表达式的按表达式 + // 如果有cron表达式,则创建cron触发器 if(!StringUtils.isEmpty(cron)){ log.info("+++++表达式执行:"+ JSON.toJSONString(jobDetail)); - //表达式调度构建器 + // 创建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{ - // 无表达式则立即执行 + // 无cron表达式则立即执行一次 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); } catch (Exception e) { + // 打印异常堆栈信息 e.printStackTrace(); } } - + /** + * 添加立即执行的任务 + * 方法用于添加一个不需要cron表达式,立即执行一次的任务。 + * + * @param jobClass 任务类 + * @param jobName 任务名称 + * @param data 任务数据 + */ @Override public void addCronJob(Class jobClass, String jobName, String data) { - // 立即执行任务 + // 立即执行任务,不需要cron表达式,传入null this.addCronJob(jobClass, jobName, null, data); } - + /** + * 暂停任务 + * 方法用于暂停一个正在运行的定时任务。 + * + * @param jobName 任务名称 + * @param jobGroup 任务组 + */ @Override public void pauseJob(String jobName, String jobGroup) { try { + // 创建触发器键并暂停触发器 TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); scheduler.pauseTrigger(triggerKey); log.info("++++++++++暂停任务:{}", jobName); } catch (SchedulerException e) { + // 打印异常堆栈信息 e.printStackTrace(); } } + /** + * 恢复任务 + * 方法用于恢复一个已暂停的定时任务。 + * + * @param jobName 任务名称 + * @param jobGroup 任务组 + */ @Override public void resumeJob(String jobName, String jobGroup) { try { + // 创建触发器键并恢复触发器 TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); scheduler.resumeTrigger(triggerKey); log.info("++++++++++重启任务:{}", jobName); } catch (SchedulerException e) { + // 打印异常堆栈信息 e.printStackTrace(); } } + /** + * 删除任务 + * 方法用于删除一个定时任务,包括任务本身和相关的触发器。 + * + * @param jobName 任务名称 + * @param jobGroup 任务组 + */ @Override public void deleteJob(String jobName, String jobGroup) { try { + // 创建任务键并删除任务 JobKey jobKey = JobKey.jobKey(jobName,jobGroup); scheduler.deleteJob(jobKey); log.info("++++++++++删除任务:{}", jobKey); } catch (SchedulerException e) { + // 打印异常堆栈信息 e.printStackTrace(); } } -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java b/src-源文件/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java index 3bc2190..8d54a3e 100644 --- a/src-源文件/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java +++ b/src-源文件/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java @@ -1,29 +1,39 @@ +// 定义包路径,用于存放自定义过滤器相关的类 package com.yf.exam.ability.shiro; -import org.apache.shiro.spring.web.ShiroFilterFactoryBean; -import org.apache.shiro.web.filter.InvalidRequestFilter; -import org.apache.shiro.web.filter.mgt.DefaultFilter; -import org.apache.shiro.web.filter.mgt.FilterChainManager; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; // Shiro过滤器工厂类,用于配置Shiro过滤器 +import org.apache.shiro.web.filter.InvalidRequestFilter; // Shiro无效请求过滤器,用于处理无效请求 +import org.apache.shiro.web.filter.mgt.DefaultFilter; // Shiro默认过滤器,提供默认的过滤逻辑 +import org.apache.shiro.web.filter.mgt.FilterChainManager; // Shiro过滤器链管理器,管理过滤器链的配置 -import javax.servlet.Filter; -import java.util.Map; +import javax.servlet.Filter; // Servlet过滤器接口,定义了过滤器的基本操作 +import java.util.Map; // Map集合类,用于存储键值对 /** - * 自定义过滤器,用于处理中文URL问题 - * 如:下载文件中包含中文会返回400错误,https://youdomain.com/upload/file/云帆考试系统用户手册.pdf + * 自定义过滤器工厂类,用于创建和管理Shiro过滤器链 + * 主要解决中文URL问题,如下载文件中包含中文字符时可能会返回400错误。 + * 例如:https://youdomain.com/upload/file/云帆考试系统用户手册.pdf 这样的URL可能会因为中文字符而导致问题。 * @author van */ public class CNFilterFactoryBean extends ShiroFilterFactoryBean { + /** + * 创建过滤器链管理器 + * 覆盖父类的创建方法,添加自定义的过滤器配置。 + * @return FilterChainManager 过滤器链管理器实例 + */ @Override protected FilterChainManager createFilterChainManager() { - FilterChainManager manager = super.createFilterChainManager(); - // URL携带中文400,servletPath中文校验bug - Map filterMap = manager.getFilters(); - Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); + FilterChainManager manager = super.createFilterChainManager(); // 调用父类方法创建过滤器链管理器 + + // 获取过滤器映射,以便修改特定过滤器的配置 + Map filterMap = manager.getFilters(); + // 获取无效请求过滤器实例 + Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); if (invalidRequestFilter instanceof InvalidRequestFilter) { + // 设置无效请求过滤器不阻止非ASCII字符,以允许中文URL ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false); } - return manager; + return manager; // 返回配置好的过滤器链管理器 } -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/shiro/ShiroRealm.java b/src-源文件/main/java/com/yf/exam/ability/shiro/ShiroRealm.java index 76af5c5..d9b5844 100644 --- a/src-源文件/main/java/com/yf/exam/ability/shiro/ShiroRealm.java +++ b/src-源文件/main/java/com/yf/exam/ability/shiro/ShiroRealm.java @@ -1,131 +1,135 @@ +// 定义包路径,用于存放Shiro领域相关的类 package com.yf.exam.ability.shiro; - -import com.yf.exam.ability.shiro.jwt.JwtToken; -import com.yf.exam.ability.shiro.jwt.JwtUtils; -import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; -import com.yf.exam.modules.sys.user.service.SysUserRoleService; -import com.yf.exam.modules.sys.user.service.SysUserService; -import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.AuthenticationInfo; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.SimpleAuthenticationInfo; -import org.apache.shiro.authz.AuthorizationInfo; -import org.apache.shiro.authz.SimpleAuthorizationInfo; -import org.apache.shiro.realm.AuthorizingRealm; -import org.apache.shiro.subject.PrincipalCollection; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import java.util.HashSet; -import java.util.List; +import com.yf.exam.ability.shiro.jwt.JwtToken; // JWT令牌类 +import com.yf.exam.ability.shiro.jwt.JwtUtils; // JWT工具类 +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; // 用户登录DTO +import com.yf.exam.modules.sys.user.service.SysUserRoleService; // 用户角色服务 +import com.yf.exam.modules.sys.user.service.SysUserService; // 用户服务 +import lombok.extern.slf4j.Slf4j; // 日志注解 +import org.apache.shiro.authc.AuthenticationException; // 认证异常 +import org.apache.shiro.authc.AuthenticationInfo; // 认证信息 +import org.apache.shiro.authc.AuthenticationToken; // 认证令牌 +import org.apache.shiro.authc.SimpleAuthenticationInfo; // 简单认证信息 +import org.apache.shiro.authz.AuthorizationInfo; // 授权信息 +import org.apache.shiro.authz.SimpleAuthorizationInfo; // 简单授权信息 +import org.apache.shiro.realm.AuthorizingRealm; // 授权领域 +import org.apache.shiro.subject.PrincipalCollection; // 主体集合 +import org.springframework.beans.factory.annotation.Autowired; // Spring自动注入注解 +import org.springframework.context.annotation.Lazy; // 延迟注入注解 +import org.springframework.stereotype.Component; // Spring组件注解 + +import java.util.HashSet; // 哈希集合 +import java.util.List; // 列表 /** - * 用户登录鉴权和获取用户授权 + * 用户登录鉴权和获取用户授权的Shiro领域类 + * 负责用户的认证和授权,是Shiro框架中的核心组件之一。 * @author bool */ -@Component -@Slf4j +@Component // 标记为Spring组件 +@Slf4j // 启用Slf4j日志 public class ShiroRealm extends AuthorizingRealm { - @Autowired - @Lazy - private SysUserService sysUserService; - - @Autowired - @Lazy - private SysUserRoleService sysUserRoleService; - - - @Override - public boolean supports(AuthenticationToken token) { - return token instanceof JwtToken; - } - - - /** - * 详细授权认证 - * @param principals - * @return - */ - @Override - protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { - - String userId = null; - if (principals != null) { - SysUserLoginDTO user = (SysUserLoginDTO) principals.getPrimaryPrincipal(); - userId = user.getId(); - } - SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); - - // 查找用户角色 - List roles = sysUserRoleService.listRoles(userId); - info.setRoles(new HashSet<>(roles)); - - log.info("++++++++++校验详细权限完成"); - return info; - } - - /** - * 校验用户的账号密码是否正确 - * @param auth - * @return - * @throws AuthenticationException - */ - @Override - protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { - String token = (String) auth.getCredentials(); - if (token == null) { - throw new AuthenticationException("token为空!"); - } - - // 校验token有效性 - SysUserLoginDTO user = this.checkToken(token); - return new SimpleAuthenticationInfo(user, token, getName()); - } - - - /** - * 校验Token的有效性 - * @param token - * @return - * @throws AuthenticationException - */ - public SysUserLoginDTO checkToken(String token) throws AuthenticationException { - - // 查询用户信息 - log.debug("++++++++++校验用户token: "+ token); - - // 从token中获取用户名 - String username = JwtUtils.getUsername(token); - log.debug("++++++++++用户名: "+ username); - - if (username == null) { - throw new AuthenticationException("无效的token"); - } - - // 查找登录用户对象 - SysUserLoginDTO user = sysUserService.token(token); - - // 校验token是否失效 - if (!JwtUtils.verify(token, username)) { - throw new AuthenticationException("登陆失效,请重试登陆!"); - } - - return user; - } - - - - /** - * 清除当前用户的权限认证缓存 - * @param principals - */ - @Override - public void clearCache(PrincipalCollection principals) { - super.clearCache(principals); + @Autowired + @Lazy // 延迟注入,避免循环依赖 + private SysUserService sysUserService; // 用户服务 + + @Autowired + @Lazy // 延迟注入,避免循环依赖 + private SysUserRoleService sysUserRoleService; // 用户角色服务 + + /** + * 判断是否支持JWT令牌 + * 确定当前领域是否支持处理JWT令牌类型的认证。 + * @param token 认证令牌 + * @return 是否支持JWT令牌 + */ + @Override + public boolean supports(AuthenticationToken token) { + // 判断是否支持JWT令牌 + return token instanceof JwtToken; // 返回是否为JwtToken + } + + /** + * 详细授权认证 + * 获取用户的授权信息,包括角色和权限。 + * @param principals 主体集合 + * @return 授权信息 + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + String userId = null; // 用户ID + if (principals != null) { + SysUserLoginDTO user = (SysUserLoginDTO) principals.getPrimaryPrincipal(); // 获取用户信息 + userId = user.getId(); // 获取用户ID + } + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 创建授权信息 + + // 查找用户角色 + List roles = sysUserRoleService.listRoles(userId); // 获取用户角色列表 + info.setRoles(new HashSet<>(roles)); // 设置角色 + + log.info("++++++++++校验详细权限完成"); // 日志记录 + return info; // 返回授权信息 + } + + /** + * 校验用户的账号密码是否正确 + * 根据传入的认证令牌,验证用户的账号密码。 + * @param auth 认证令牌 + * @return 认证信息 + * @throws AuthenticationException 认证异常 + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { + String token = (String) auth.getCredentials(); // 获取token + if (token == null) { + throw new AuthenticationException("token为空!"); // 抛出异常 + } + + // 校验token有效性 + SysUserLoginDTO user = this.checkToken(token); // 验证token并获取用户信息 + return new SimpleAuthenticationInfo(user, token, getName()); // 返回认证信息 + } + + /** + * 校验Token的有效性 + * 验证JWT令牌的有效性,并获取对应的用户信息。 + * @param token JWT令牌 + * @return 用户登录DTO + * @throws AuthenticationException 认证异常 + */ + public SysUserLoginDTO checkToken(String token) throws AuthenticationException { + // 查询用户信息 + log.debug("++++++++++校验用户token: "+ token); // 日志记录 + + // 从token中获取用户名 + String username = JwtUtils.getUsername(token); // 获取用户名 + log.debug("++++++++++用户名: "+ username); // 日志记录 + + if (username == null) { + throw new AuthenticationException("无效的token"); // 抛出异常 + } + + // 查找登录用户对象 + SysUserLoginDTO user = sysUserService.token(token); // 获取用户信息 + + // 校验token是否失效 + if (!JwtUtils.verify(token, username)) { + throw new AuthenticationException("登陆失效,请重试登陆!"); // 抛出异常 + } + + return user; // 返回用户信息 } -} + /** + * 清除当前用户的权限认证缓存 + * 用于在用户信息变更后,清除缓存,确保权限信息的更新。 + * @param principals 主体集合 + */ + @Override + public void clearCache(PrincipalCollection principals) { + super.clearCache(principals); // 清除缓存 + } +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java b/src-源文件/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java index 88cf448..1fe9ff2 100644 --- a/src-源文件/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java +++ b/src-源文件/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java @@ -1,53 +1,84 @@ +// 定义包路径,用于存放Shiro JWT认证过滤器相关的类 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 com.yf.exam.ability.shiro.jwt.JwtToken; -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; - -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletRequest; // Servlet请求接口 +import javax.servlet.ServletResponse; // Servlet响应接口 +import javax.servlet.http.HttpServletRequest; // HTTP请求类 +import javax.servlet.http.HttpServletResponse; // HTTP响应类 /** - * 鉴权登录拦截器 + * JWT认证过滤器 + * 用于处理基于JWT的身份认证,确保只有持有有效JWT令牌的请求才能访问受保护的资源。 * @author bool */ -@Slf4j +@Slf4j // 启用Slf4j日志 public class JwtFilter extends BasicHttpAuthenticationFilter { - /** - * 执行登录认证 - * @param request - * @param response - * @param mappedValue - * @return - */ - @Override - protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { - try { - executeLogin(request, response); - return true; - } catch (Exception e) { - // 写出统一错误信息 - InjectUtils.restError((HttpServletResponse) response); - return false; - } - } + /** + * 判断是否允许访问 + * 所有的请求都会经过这个方法,用于判断是否需要登录认证。 + * 如果请求不需要认证(如访问公开资源),则直接返回true允许访问; + * 如果需要认证,则尝试执行登录认证,根据认证结果决定是否允许访问。 + * + * @param request 请求对象 + * @param response 响应对象 + * @param mappedValue 映射值 + * @return 是否允许访问 + */ + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + try { + // 尝试执行登录认证 + executeLogin(request, response); + // 认证成功返回true + return true; + } catch (Exception e) { + // 认证失败时写入错误信息 + InjectUtils.restError((HttpServletResponse) response); + // 返回false表示不允许访问 + return false; + } + } + /** + * 执行登录认证 + * 从请求头中获取JWT token并进行认证,如果认证成功,则请求可以继续执行; + * 如果认证失败(如token无效或过期),则抛出异常,由isAccessAllowed方法处理。 + * + * @param request 请求对象 + * @param response 响应对象 + * @return 是否认证成功 + */ + @Override + protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { + // 将ServletRequest转换为HttpServletRequest + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + // 从请求头中获取token + String token = httpServletRequest.getHeader(Constant.TOKEN); - @Override - protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { - HttpServletRequest httpServletRequest = (HttpServletRequest) request; - String token = httpServletRequest.getHeader(Constant.TOKEN); + // 如果token为空,则抛出异常 + if (token == null || "".equals(token)) { + throw new Exception("token不能为空"); + } - JwtToken jwtToken = new JwtToken(token); - // 提交给realm进行登入,如果错误他会抛出异常并被捕获 - getSubject(request, response).login(jwtToken); - // 如果没有抛出异常则代表登入成功,返回true - return true; - } -} + // 创建JWT token对象 + JwtToken jwtToken = new JwtToken(token); + // 提交给realm进行登录认证 + try { + getSubject(request, response).login(jwtToken); + // 如果没有抛出异常则表示登录成功 + return true; + } catch (Exception e) { + // 登录失败,记录日志 + log.error("JWT认证失败", e); + throw e; + } + } +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java b/src-源文件/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java index d5baab3..1e1fe4c 100644 --- a/src-源文件/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java +++ b/src-源文件/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java @@ -1,33 +1,50 @@ +// 定义包路径,用于存放JWT令牌相关的类 package com.yf.exam.ability.shiro.jwt; - -import lombok.Data; -import org.apache.shiro.authc.AuthenticationToken; + +import lombok.Data; // Lombok注解,用于生成getter和setter +import org.apache.shiro.authc.AuthenticationToken; // Shiro认证令牌接口 /** + * JWT令牌实现类 + * 实现Shiro的AuthenticationToken接口,用于JWT认证 * @author bool */ -@Data +@Data // 自动生成getter和setter方法 public class JwtToken implements AuthenticationToken { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; // 序列化ID /** - * JWT的字符token + * JWT的字符串token + * 用于存储实际的JWT令牌字符串 */ - private String token; - + private String token; // JWT令牌字符串 + /** + * 构造函数 + * @param token JWT令牌字符串 + */ public JwtToken(String token) { - this.token = token; + this.token = token; // 设置token } + /** + * 获取身份信息 + * 实现AuthenticationToken接口的方法 + * @return 返回token作为身份信息 + */ @Override public Object getPrincipal() { - return token; + return token; // 返回token作为身份信息 } + /** + * 获取凭证信息 + * 实现AuthenticationToken接口的方法 + * @return 返回token作为凭证信息 + */ @Override public Object getCredentials() { - return token; + return token; // 返回token作为凭证信息 } -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java b/src-源文件/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java index 4a66759..ec2d907 100644 --- a/src-源文件/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java +++ b/src-源文件/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java @@ -1,14 +1,15 @@ +// 定义包路径,用于存放JWT工具类 package com.yf.exam.ability.shiro.jwt; -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.exceptions.JWTDecodeException; -import com.auth0.jwt.interfaces.DecodedJWT; -import com.yf.exam.core.utils.file.Md5Util; +import com.auth0.jwt.JWT; // JWT工具类 +import com.auth0.jwt.JWTVerifier; // JWT验证器 +import com.auth0.jwt.algorithms.Algorithm; // JWT算法 +import com.auth0.jwt.exceptions.JWTDecodeException; // JWT解码异常 +import com.auth0.jwt.interfaces.DecodedJWT; // 解码后的JWT +import com.yf.exam.core.utils.file.Md5Util; // MD5工具类 -import java.util.Calendar; -import java.util.Date; +import java.util.Calendar; // 日历类 +import java.util.Date; // 日期类 /** * JWT工具类 @@ -19,81 +20,74 @@ public class JwtUtils { /** * 有效期24小时 */ - private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; - + private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; // JWT有效期 /** * 校验是否正确 - * @param token - * @param username - * @return + * @param token JWT令牌 + * @param username 用户名 + * @return 是否有效 */ public static boolean verify(String token, String username) { try { // 根据密码生成JWT效验器 - Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); - JWTVerifier verifier = JWT.require(algorithm) - .withClaim("username", username) - .build(); + Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); // 创建算法 + JWTVerifier verifier = JWT.require(algorithm) // 创建验证器 + .withClaim("username", username) // 添加用户名声明 + .build(); // 构建验证器 // 效验TOKEN - verifier.verify(token); - return true; + verifier.verify(token); // 验证token + return true; // 返回验证成功 } catch (Exception exception) { - return false; + return false; // 返回验证失败 } } - - - - /** * 从Token中解密获得用户名 - * @param token - * @return + * @param token JWT令牌 + * @return 用户名 */ public static String getUsername(String token) { try { - DecodedJWT jwt = JWT.decode(token); - return jwt.getClaim("username").asString(); + DecodedJWT jwt = JWT.decode(token); // 解码JWT + return jwt.getClaim("username").asString(); // 获取用户名 } catch (JWTDecodeException e) { - return null; + return null; // 返回null表示解码失败 } } /** * 生成JWT Token字符串 - * @param username - * @return + * @param username 用户名 + * @return JWT令牌字符串 */ public static String sign(String username) { - Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); - Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); + Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); // 设置过期时间 + Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); // 创建算法 // 附带username信息 - return JWT.create() - .withClaim("username", username) - .withExpiresAt(date).sign(algorithm); - + return JWT.create() // 创建JWT + .withClaim("username", username) // 添加用户名声明 + .withExpiresAt(date).sign(algorithm); // 设置过期时间并签名 } /** * 根据用户名和秘钥,生成一个新的秘钥,用于JWT加强一些安全性 - * @param userName - * @return + * @param userName 用户名 + * @return 加密后的秘钥 */ private static String encryptSecret(String userName){ - // 一个简单的登录规则,用户名+当前月份为加密串,意思每个月会变,要重新登录 // 可自行修改此规则 - Calendar cl = Calendar.getInstance(); - cl.setTimeInMillis(System.currentTimeMillis()); - StringBuffer sb = new StringBuffer(userName) - .append("&") - .append(cl.get(Calendar.MONTH)); + Calendar cl = Calendar.getInstance(); // 获取当前日历 + cl.setTimeInMillis(System.currentTimeMillis()); // 设置当前时间 + StringBuffer sb = new StringBuffer(userName) // 创建字符串缓冲区 + .append("&") // 添加分隔符 + .append(cl.get(Calendar.MONTH)); // 添加当前月份 // 获取MD5 - String secret = Md5Util.md5(sb.toString()); + String secret = Md5Util.md5(sb.toString()); // 生成MD5秘钥 - return Md5Util.md5(userName + "&" + secret); + return Md5Util.md5(userName + "&" + secret); // 返回加密后的秘钥 } -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/upload/config/UploadConfig.java b/src-源文件/main/java/com/yf/exam/ability/upload/config/UploadConfig.java index e35d73d..a0cb14b 100644 --- a/src-源文件/main/java/com/yf/exam/ability/upload/config/UploadConfig.java +++ b/src-源文件/main/java/com/yf/exam/ability/upload/config/UploadConfig.java @@ -1,32 +1,36 @@ +// 定义包路径,用于存放文件上传配置相关的类 package com.yf.exam.ability.upload.config; -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - +import lombok.Data; // Lombok注解,用于简化数据类的编写,自动生成getter和setter +import org.springframework.boot.context.properties.ConfigurationProperties; // Spring Boot配置属性注解,用于将配置文件中的属性绑定到Java对象 +import org.springframework.context.annotation.Configuration; // Spring配置注解,标记为配置类 /** - * 文件上传配置 + * 文件上传配置类 + * 用于定义文件上传的相关配置,如访问路径、物理目录和允许的文件后缀等。 + * 这些配置通常在application.yml或application.properties中定义,并由Spring Boot自动加载。 * @author van */ -@Data -@Configuration -@ConfigurationProperties(prefix = "conf.upload") +@Data // 使用Lombok注解,自动生成getter和setter方法 +@Configuration // 标记为Spring配置类,表示这是一个配置类 +@ConfigurationProperties(prefix = "conf.upload") // 指定配置文件中属性的前缀,这里是"conf.upload" public class UploadConfig { /** - * 访问路径 + * 文件访问路径 + * 定义文件上传后对外访问的基础URL路径。 */ - private String url; + private String url; // 文件访问的URL /** - * 物理目录 + * 文件存储物理目录 + * 定义文件上传后在服务器上的存储路径。 */ - private String dir; + private String dir; // 文件存储的物理目录 /** - * 允许的后缀 + * 允许的文件后缀 + * 定义允许上传的文件类型,通过文件后缀来限制。 */ - private String [] allowExtensions; - -} + private String[] allowExtensions; // 允许上传的文件后缀 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/upload/controller/UploadController.java b/src-源文件/main/java/com/yf/exam/ability/upload/controller/UploadController.java index 4c85250..1849a4b 100644 --- a/src-源文件/main/java/com/yf/exam/ability/upload/controller/UploadController.java +++ b/src-源文件/main/java/com/yf/exam/ability/upload/controller/UploadController.java @@ -1,57 +1,62 @@ +// 定义包路径,用于存放文件上传下载请求类 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 com.yf.exam.ability.Constant; -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; +import javax.servlet.http.HttpServletRequest; // HTTP请求类,表示HTTP请求 +import javax.servlet.http.HttpServletResponse; // HTTP响应类,表示HTTP响应 /** * 本地文件上传下载请求类 + * 负责处理文件上传和下载的请求,提供RESTful API接口。 * @author bool */ -@Log4j2 -@Api(tags = {"文件上传"}) -@RestController +@Log4j2 // 启用Log4j2日志 +@Api(tags = {"文件上传"}) // Swagger API标签,用于分类API +@RestController // 标记为REST控制器,表示该类是一个REST风格的控制器 public class UploadController extends BaseController { @Autowired - private UploadService uploadService; + private UploadService uploadService; // 文件上传服务,自动注入 /** * 文件上传 - * @param reqDTO - * @return + * 处理文件上传请求,接收上传文件的数据,并返回上传结果。 + * + * @param reqDTO 上传请求DTO,包含上传文件所需的数据 + * @return 上传响应,封装上传文件后的响应数据 */ - @PostMapping("/common/api/file/upload") - @ApiOperation(value = "文件上传", notes = "此接口较为特殊,参数都通过表单方式提交,而非JSON") + @PostMapping("/common/api/file/upload") // POST请求映射,指定请求路径 + @ApiOperation(value = "文件上传", notes = "此接口较为特殊,参数都通过表单方式提交,而非JSON") // Swagger API操作描述 public ApiRest upload(@ModelAttribute UploadReqDTO reqDTO) { // 上传并返回URL - UploadRespDTO respDTO = uploadService.upload(reqDTO); - return super.success(respDTO); + UploadRespDTO respDTO = uploadService.upload(reqDTO); // 调用上传服务 + return super.success(respDTO); // 返回成功响应 } /** * 独立文件下载 - * @param request - * @param response + * 处理文件下载请求,根据请求参数返回对应的文件。 + * + * @param request HTTP请求,包含下载请求的信息 + * @param response HTTP响应,用于返回文件内容 */ - @GetMapping(Constant.FILE_PREFIX+"**") - @ApiOperation(value = "文件下载", notes = "文件下载") + @GetMapping(Constant.FILE_PREFIX+"**") // GET请求映射,指定请求路径前缀 + @ApiOperation(value = "文件下载", notes = "文件下载") // Swagger API操作描述 public void download(HttpServletRequest request, HttpServletResponse response) { - uploadService.download(request, response); + uploadService.download(request, response); // 调用下载服务 } -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java b/src-源文件/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java index df2f286..0e4a0f1 100644 --- a/src-源文件/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java +++ b/src-源文件/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java @@ -1,22 +1,26 @@ +// 定义包路径,用于存放文件上传请求DTO package com.yf.exam.ability.upload.dto; - -import com.yf.exam.core.api.dto.BaseDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import org.springframework.web.multipart.MultipartFile; +import com.yf.exam.core.api.dto.BaseDTO; // 导入基础DTO类,提供通用的数据传输对象功能 +import io.swagger.annotations.ApiModel; // 导入Swagger API模型注解,用于描述API模型 +import io.swagger.annotations.ApiModelProperty; // 导入Swagger API模型属性注解,用于描述API模型的属性 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写,自动生成getter和setter +import org.springframework.web.multipart.MultipartFile; // 导入Spring文件上传类,用于处理上传的文件 /** * 文件上传请求类 - * @author + * 用于封装文件上传请求中的数据,包括上传的文件内容。 + * @author van * @date 2019-12-26 17:54 */ -@Data -@ApiModel(value="文件上传参数", description="文件上传参数") +@Data // 使用Lombok注解,自动生成getter和setter方法 +@ApiModel(value="文件上传参数", description="文件上传参数") // 使用Swagger注解,描述API模型 public class UploadReqDTO extends BaseDTO { - @ApiModelProperty(value = "上传文件内容", required=true) - private MultipartFile file; - -} + /** + * 上传文件内容 + * 用于存储上传文件的数据,包括文件名、文件类型、文件大小等信息。 + */ + @ApiModelProperty(value = "上传文件内容", required=true) // 使用Swagger注解,描述API模型属性 + private MultipartFile file; // 上传的文件内容 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java b/src-源文件/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java index b91106e..cbce697 100644 --- a/src-源文件/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java +++ b/src-源文件/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java @@ -1,23 +1,28 @@ +// 定义包路径,用于存放文件上传响应DTO package com.yf.exam.ability.upload.dto; -import com.yf.exam.core.api.dto.BaseDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +import com.yf.exam.core.api.dto.BaseDTO; // 导入基础DTO类,提供通用的数据传输对象功能 +import io.swagger.annotations.ApiModel; // 导入Swagger API模型注解,用于描述API模型 +import io.swagger.annotations.ApiModelProperty; // 导入Swagger API模型属性注解,用于描述API模型的属性 +import lombok.AllArgsConstructor; // 导入Lombok注解,用于生成全参构造函数 +import lombok.Data; // 导入Lombok注解,用于生成getter和setter方法 +import lombok.NoArgsConstructor; // 导入Lombok注解,用于生成无参构造函数 /** - * 上传文件结果 + * 文件上传响应DTO + * 用于封装文件上传操作的结果,包括上传后的文件URL等信息。 * @author bool */ -@Data -@AllArgsConstructor -@NoArgsConstructor -@ApiModel(value="文件上传响应", description="文件上传响应") +@Data // 使用Lombok注解,自动生成getter和setter方法 +@AllArgsConstructor // 使用Lombok注解,生成全参构造函数 +@NoArgsConstructor // 使用Lombok注解,生成无参构造函数 +@ApiModel(value="文件上传响应", description="文件上传响应") // 使用Swagger注解,描述API模型 public class UploadRespDTO extends BaseDTO { - @ApiModelProperty(value = "上传后的完整的URL地址", required=true) - private String url; - -} + /** + * 上传后的完整URL地址 + * 存储文件上传成功后,文件的完整访问URL地址。 + */ + @ApiModelProperty(value = "上传后的完整的URL地址", required=true) // 使用Swagger注解,描述API模型属性 + private String url; // 上传后的完整URL地址 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/upload/service/UploadService.java b/src-源文件/main/java/com/yf/exam/ability/upload/service/UploadService.java index ef516ec..44617b6 100644 --- a/src-源文件/main/java/com/yf/exam/ability/upload/service/UploadService.java +++ b/src-源文件/main/java/com/yf/exam/ability/upload/service/UploadService.java @@ -1,30 +1,62 @@ package com.yf.exam.ability.upload.service; +// 这一行声明了该Java类所属的包名为com.yf.exam.ability.upload.service。 +// 包用于组织和管理相关的Java类,避免类名冲突,方便代码的模块化和复用。 -import com.yf.exam.ability.upload.dto.UploadReqDTO; -import com.yf.exam.ability.upload.dto.UploadRespDTO; +import com.yf.exam.ability.upload.dto.UploadReqDTO; // 导入文件上传请求DTO +// 导入了名为UploadReqDTO的类,它位于com.yf.exam.ability.upload.dto包下。 +// 这个类通常用于封装文件上传请求相关的数据,比如要上传的文件信息、上传的相关参数等。 -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.yf.exam.ability.upload.dto.UploadRespDTO; // 导入文件上传响应DTO +// 导入了名为UploadRespDTO的类,同样位于com.yf.exam.ability.upload.dto包下。 +// 它主要用于封装文件上传操作完成后返回的响应数据,例如上传是否成功的标识、上传后的文件存储路径等信息。 + +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 */ +// 这是一个Java接口的文档注释,用于描述该接口的整体功能和用途。 +// 说明这个接口主要是用来定义与文件上传和下载相关的业务操作方法, +// 并且其他类可以通过实现这个接口来提供具体的实现,以达到统一调用的目的。 +// 同时标注了接口的作者是bool,创建日期是2019年7月12日16:45。 + public interface UploadService { + // 这里定义了一个名为UploadService的公共接口。 + // 接口中只包含方法的声明,不包含方法的具体实现,具体实现由实现该接口的类来完成。 /** * 文件上传 - * @param reqDTO - * @return + * 方法用于处理文件上传请求,接收上传文件的数据,并返回上传结果。 + * + * @param reqDTO 上传请求DTO,包含上传文件所需的数据 + * @return 上传响应DTO,封装上传文件后的响应数据 */ + // 这是接口中定义的一个方法声明,名为upload。 + // 它的功能是处理文件上传请求,通过接收传入的UploadReqDTO对象(其中包含了上传文件所需的各种数据), + // 然后在具体实现类中执行实际的上传操作,最后返回一个UploadRespDTO对象,该对象封装了上传文件后的响应数据。 + UploadRespDTO upload(UploadReqDTO reqDTO); /** - * 下载文件 - * @param request - * @param response + * 文件下载 + * 方法用于处理文件下载请求,根据请求参数返回对应的文件。 + * + * @param request HTTP请求,包含下载请求的信息 + * @param response HTTP响应,用于返回文件内容 */ - void download(HttpServletRequest request, HttpServletResponse response); + // 这是接口中定义的另一个方法声明,名为download。 + // 它用于处理文件下载请求,会接收一个HttpServletRequest对象(其中包含了客户端发送的关于下载请求的所有信息) + // 和一个HttpServletResponse对象(用于设置响应相关的信息并将文件内容返回给客户端), + // 在具体实现类中根据请求参数找到对应的要下载的文件,并通过HttpServletResponse将文件内容返回给客户端。 -} + void download(HttpServletRequest request, HttpServletResponse response); +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java b/src-源文件/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java index ef70f40..6d1a60e 100644 --- a/src-源文件/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java +++ b/src-源文件/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java @@ -1,135 +1,143 @@ +// 定义包路径,用于存放文件上传服务实现类 package com.yf.exam.ability.upload.service.impl; -import com.yf.exam.ability.Constant; -import com.yf.exam.ability.upload.config.UploadConfig; -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.ability.upload.utils.FileUtils; -import com.yf.exam.core.exception.ServiceException; -import lombok.extern.log4j.Log4j2; -import org.apache.commons.io.FilenameUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.FileCopyUtils; -import org.springframework.web.multipart.MultipartFile; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - +import com.yf.exam.ability.Constant; // 导入常量类,包含系统配置的常量值 +import com.yf.exam.ability.upload.config.UploadConfig; // 导入文件上传配置类,包含文件上传的相关配置 +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.ability.upload.utils.FileUtils; // 导入文件工具类,提供文件操作的辅助功能 +import com.yf.exam.core.exception.ServiceException; // 导入服务异常类,处理业务逻辑中的异常情况 +import lombok.extern.log4j.Log4j2; // 导入日志注解,提供日志功能 +import org.apache.commons.io.FilenameUtils; // 导入文件名工具类,提供文件名和扩展名操作的辅助功能 +import org.springframework.beans.factory.annotation.Autowired; // 导入Spring自动注入注解,用于注入Spring管理的Bean +import org.springframework.stereotype.Service; // 导入Spring服务注解,标记为服务组件 +import org.springframework.util.FileCopyUtils; // 导入文件复制工具类,提供文件复制的功能 +import org.springframework.web.multipart.MultipartFile; // 导入Spring文件上传类,处理上传的文件 + +import javax.servlet.http.HttpServletRequest; // 导入HTTP请求类,表示HTTP请求 +import javax.servlet.http.HttpServletResponse; // 导入HTTP响应类,表示HTTP响应 +import java.io.FileOutputStream; // 导入文件输出流,用于将文件内容写入文件 +import java.io.IOException; // 导入IO异常类,处理IO操作中的异常情况 +import java.io.UnsupportedEncodingException; // 导入不支持的编码异常类,处理编码问题 +import java.net.URLDecoder; // 导入URL解码类,用于解码URL +import java.util.regex.Matcher; // 导入正则表达式匹配器,用于匹配正则表达式 +import java.util.regex.Pattern; // 导入正则表达式类,用于编译正则表达式 /** * 文件上传业务类 + * 实现文件上传服务接口,提供文件上传和下载的业务逻辑。 * @author bool * @date 2019-07-30 21:02 */ -@Log4j2 -@Service +@Log4j2 // 使用Log4j2日志注解,启用日志功能 +@Service // 使用Spring服务注解,标记为服务组件 public class UploadServiceImpl implements UploadService { @Autowired - private UploadConfig conf; + private UploadConfig conf; // 自动注入文件上传配置 + /** + * 文件上传 + * 实现文件上传业务逻辑,包括文件验证、文件保存和返回上传结果。 + * + * @param reqDTO 上传请求DTO,包含上传文件所需的数据 + * @return 上传响应DTO,封装上传文件后的响应数据 + */ @Override 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){ - throw new ServiceException("文件类型不允许上传!"); + throw new ServiceException("文件类型不允许上传!"); // 抛出异常 } // 上传文件夹 - String fileDir = conf.getDir(); + String fileDir = conf.getDir(); // 获取文件存储目录 // 真实物理地址 String fullPath; 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) { - e.printStackTrace(); - throw new ServiceException("文件上传失败:"+e.getMessage()); + e.printStackTrace(); // 打印异常堆栈 + throw new ServiceException("文件上传失败:"+e.getMessage()); // 抛出异常 } } - - + /** + * 独立文件下载 + * 实现文件下载业务逻辑,包括获取文件真实路径和返回文件内容。 + * + * @param request HTTP请求,包含下载请求的信息 + * @param response HTTP响应,用于返回文件内容 + */ @Override public void download(HttpServletRequest request, HttpServletResponse response) { - // 获取真实的文件路径 - String filePath = this.getRealPath(request.getRequestURI()); + String filePath = this.getRealPath(request.getRequestURI()); // 获取文件的真实路径 // 处理中文问题 try { - filePath = URLDecoder.decode(filePath, "utf-8"); + filePath = URLDecoder.decode(filePath, "utf-8"); // 解码文件路径 } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); + throw new RuntimeException(e); // 抛出运行时异常 } - System.out.println("++++完整路径为:"+filePath); + System.out.println("++++完整路径为:"+filePath); // 打印完整路径 try { - FileUtils.writeRange(request, response, filePath); + FileUtils.writeRange(request, response, filePath); // 调用文件工具类进行文件写入 } catch (IOException e) { - response.setStatus(404); - log.error("预览文件失败" + e.getMessage()); + response.setStatus(404); // 设置响应状态为404 + log.error("预览文件失败" + e.getMessage()); // 打印错误日志 } } - /** * 构造返回 - * @param fileName - * @return + * 根据文件名构造上传响应DTO,包含上传后的完整URL地址。 + * + * @param fileName 文件名 + * @return 上传响应DTO */ private UploadRespDTO generateResult(String fileName) { - //获取加速域名 - String domain = conf.getUrl(); + String domain = conf.getUrl(); // 获取文件访问的URL // 返回结果 - return new UploadRespDTO(domain + fileName); + return new UploadRespDTO(domain + fileName); // 返回上传响应DTO } - /** * 获取真实物理文件地址 - * @param uri - * @return + * 根据请求URI获取文件的真实物理路径。 + * + * @param uri 请求URI + * @return 真实文件路径 */ public String getRealPath(String uri){ - - String regx = Constant.FILE_PREFIX+"(.*)"; + String regx = Constant.FILE_PREFIX+"(.*)"; // 正则表达式匹配文件路径 // 查找全部变量 - Pattern pattern = Pattern.compile(regx); - Matcher m = pattern.matcher(uri); + Pattern pattern = Pattern.compile(regx); // 编译正则表达式 + Matcher m = pattern.matcher(uri); // 创建匹配器 if (m.find()) { - String str = m.group(1); - return conf.getDir() + str; + String str = m.group(1); // 获取匹配的文件路径 + return conf.getDir() + str; // 返回真实文件路径 } - return null; + return null; // 返回null表示未找到 } - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/upload/utils/FileUtils.java b/src-源文件/main/java/com/yf/exam/ability/upload/utils/FileUtils.java index 539ecb0..0c45c41 100644 --- a/src-源文件/main/java/com/yf/exam/ability/upload/utils/FileUtils.java +++ b/src-源文件/main/java/com/yf/exam/ability/upload/utils/FileUtils.java @@ -1,172 +1,169 @@ +// 定义包路径,用于存放文件工具类 package com.yf.exam.ability.upload.utils; -import com.baomidou.mybatisplus.core.toolkit.IdWorker; -import com.yf.exam.core.utils.DateUtils; -import org.apache.commons.io.FilenameUtils; -import org.springframework.web.multipart.MultipartFile; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; // 导入ID生成工具,用于生成唯一的文件名 +import com.yf.exam.core.utils.DateUtils; // 导入日期工具类,用于处理日期相关的操作 +import org.apache.commons.io.FilenameUtils; // 导入文件名工具类,用于处理文件名和扩展名 +import org.springframework.web.multipart.MultipartFile; // 导入Spring文件上传类,用于处理上传的文件 -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.util.Date; +import javax.servlet.ServletOutputStream; // 导入Servlet输出流,用于写入HTTP响应 +import javax.servlet.http.HttpServletRequest; // 导入HTTP请求类,表示客户端的请求 +import javax.servlet.http.HttpServletResponse; // 导入HTTP响应类,表示服务器的响应 +import java.io.File; // 导入文件类,用于操作文件和目录 +import java.io.IOException; // 导入IO异常类,处理IO操作中的异常 +import java.io.RandomAccessFile; // 导入随机访问文件类,用于高效地读写文件 +import java.util.Date; // 导入日期类,用于处理日期和时间 /** * 文件工具类 + * 提供文件操作的辅助功能,包括文件上传、下载、重命名和目录管理等。 * @author bool */ public class FileUtils { - /** - * 后缀分割符号 - */ - private static final String SUFFIX_SPLIT = "."; - - - /** - * 支持以断点的方式输出文件,提供文件在线预览和视频在线播放 - * @param request - * @param response - * @param filePath - * @throws IOException - */ - public static void writeRange(HttpServletRequest request, - HttpServletResponse response, String filePath) throws IOException { - - // 读取文件 - File file = new File(filePath); - - //只读模式 - RandomAccessFile randomFile = new RandomAccessFile(file, "r"); - long contentLength = randomFile.length(); - String range = request.getHeader("Range"); - int start = 0, end = 0; - if (range != null && range.startsWith("bytes=")) { - String[] values = range.split("=")[1].split("-"); - start = Integer.parseInt(values[0]); - if (values.length > 1) { - end = Integer.parseInt(values[1]); - } - } - int requestSize; - if (end != 0 && end > start) { - requestSize = end - start + 1; - } else { - requestSize = Integer.MAX_VALUE; - } - - byte[] buffer = new byte[128]; - response.setContentType(MediaUtils.getContentType(filePath)); - response.setHeader("Accept-Ranges", "bytes"); - response.setHeader("ETag", file.getName()); - response.setHeader("Last-Modified", new Date().toString()); - //第一次请求只返回content length来让客户端请求多次实际数据 - if (range == null) { - response.setHeader("Content-length", contentLength + ""); - } else { - //以后的多次以断点续传的方式来返回视频数据 - response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); - long requestStart = 0, requestEnd = 0; - String[] ranges = range.split("="); - if (ranges.length > 1) { - String[] rangeData = ranges[1].split("-"); - requestStart = Integer.parseInt(rangeData[0]); - if (rangeData.length > 1) { - requestEnd = Integer.parseInt(rangeData[1]); - } - } - long length; - if (requestEnd > 0) { - length = requestEnd - requestStart + 1; - response.setHeader("Content-length", "" + length); - response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength); - } else { - length = contentLength - requestStart; - response.setHeader("Content-length", "" + length); - response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1) + "/" + contentLength); - } - } - ServletOutputStream out = response.getOutputStream(); - int needSize = requestSize; - randomFile.seek(start); - while (needSize > 0) { - int len = randomFile.read(buffer); - if (needSize < buffer.length) { - out.write(buffer, 0, needSize); - } else { - out.write(buffer, 0, len); - if (len < buffer.length) { - break; - } - } - needSize -= buffer.length; - } - randomFile.close(); - out.close(); - } - - - - - /** - * 重命名文件 - * @param fileName - * @return - */ - public static String renameFile(String fileName) { - - //没有后缀名不处理 - if (!fileName.contains(SUFFIX_SPLIT)) { - return fileName; - } - - //文件后缀 - String extension = FilenameUtils.getExtension(fileName); - - //以系统时间命名 - return IdWorker.getIdStr() + "."+ extension; - - } - - - /** - * 处理新的文件路径,为上传文件预设目录,如:2021/01/01/xxx.jpg,要注意的是,前面没有斜杠 - * @param file 文件 - * @return - */ - public static String processPath(MultipartFile file){ - - // 创建OSSClient实例。 - String fileName = file.getOriginalFilename(); - - // 需要重命名 - fileName = renameFile(fileName); - - //获得上传的文件夹 - String dir = DateUtils.formatDate(new Date(), "yyyy/MM/dd/"); - - return new StringBuffer(dir).append(fileName).toString(); - - } - - /** - * 检查文件夹是否存在,不存在则创建 - * @param fileName - * @return - */ - public static void checkDir(String fileName){ - int index = fileName.lastIndexOf("/"); - if(index == -1){ - return; - } - - File file = new File(fileName.substring(0,index)); - if(!file.exists()){ - file.mkdirs(); - } - } - - -} + /** + * 后缀分割符号 + * 用于分割文件名和扩展名。 + */ + private static final String SUFFIX_SPLIT = "."; // 文件后缀分隔符 + + /** + * 支持以断点的方式输出文件,提供文件在线预览和视频在线播放 + * 方法用于处理HTTP请求,以断点续传的方式输出文件内容,支持文件在线预览和视频在线播放。 + * + * @param request HTTP请求,包含客户端的请求信息 + * @param response HTTP响应,包含服务器的响应信息 + * @param filePath 文件路径,指定要输出的文件 + * @throws IOException IO异常,处理文件操作中的异常 + */ + public static void writeRange(HttpServletRequest request, HttpServletResponse response, String filePath) throws IOException { + // 读取文件 + File file = new File(filePath); // 创建文件对象 + + // 只读模式 + RandomAccessFile randomFile = new RandomAccessFile(file, "r"); // 创建随机访问文件对象 + long contentLength = randomFile.length(); // 获取文件长度 + String range = request.getHeader("Range"); // 获取请求头中的Range + int start = 0, end = 0; // 初始化起始和结束位置 + if (range != null && range.startsWith("bytes=")) { + String[] values = range.split("=")[1].split("-"); // 解析Range + start = Integer.parseInt(values[0]); // 获取起始位置 + if (values.length > 1) { + end = Integer.parseInt(values[1]); // 获取结束位置 + } + } + int requestSize; // 请求大小 + if (end != 0 && end > start) { + requestSize = end - start + 1; // 计算请求大小 + } else { + requestSize = Integer.MAX_VALUE; // 设置为最大值 + } + + byte[] buffer = new byte[128]; // 创建缓冲区 + response.setContentType(MediaUtils.getContentType(filePath)); // 设置响应内容类型 + response.setHeader("Accept-Ranges", "bytes"); // 设置支持的范围 + response.setHeader("ETag", file.getName()); // 设置文件名 + response.setHeader("Last-Modified", new Date().toString()); // 设置最后修改时间 + // 第一次请求只返回content length来让客户端请求多次实际数据 + if (range == null) { + response.setHeader("Content-length", contentLength + ""); // 设置内容长度 + } else { + // 以后的多次以断点续传的方式来返回视频数据 + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 设置部分内容状态 + long requestStart = 0, requestEnd = 0; // 初始化请求起始和结束位置 + String[] ranges = range.split("="); // 解析Range + if (ranges.length > 1) { + String[] rangeData = ranges[1].split("-"); // 获取范围数据 + requestStart = Integer.parseInt(rangeData[0]); // 获取请求起始位置 + if (rangeData.length > 1) { + requestEnd = Integer.parseInt(rangeData[1]); // 获取请求结束位置 + } + } + long length; // 请求长度 + if (requestEnd > 0) { + length = requestEnd - requestStart + 1; // 计算请求长度 + response.setHeader("Content-length", "" + length); // 设置内容长度 + response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength); // 设置内容范围 + } else { + length = contentLength - requestStart; // 计算请求长度 + response.setHeader("Content-length", "" + length); // 设置内容长度 + response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1) + "/" + contentLength); // 设置内容范围 + } + } + ServletOutputStream out = response.getOutputStream(); // 获取输出流 + int needSize = requestSize; // 需要的大小 + randomFile.seek(start); // 移动到起始位置 + while (needSize > 0) { + int len = randomFile.read(buffer); // 读取文件内容 + if (needSize < buffer.length) { + out.write(buffer, 0, needSize); // 写入需要的大小 + } else { + out.write(buffer, 0, len); // 写入缓冲区内容 + if (len < buffer.length) { + break; // 如果读取的长度小于缓冲区长度,退出循环 + } + } + needSize -= buffer.length; // 减少需要的大小 + } + randomFile.close(); // 关闭随机访问文件 + out.close(); // 关闭输出流 + } + + /** + * 重命名文件 + * 方法用于生成新的文件名,避免文件名冲突。 + * + * @param fileName 文件名 + * @return 新文件名 + */ + public static String renameFile(String fileName) { + // 没有后缀名不处理 + if (!fileName.contains(SUFFIX_SPLIT)) { + return fileName; // 返回原文件名 + } + + // 文件后缀 + String extension = FilenameUtils.getExtension(fileName); // 获取文件后缀 + + // 以系统时间命名 + return IdWorker.getIdStr() + "." + extension; // 返回新文件名 + } + + /** + * 处理新的文件路径,为上传文件预设目录,如:2021/01/01/xxx.jpg,要注意的是,前面没有斜杠 + * 方法用于处理上传文件的路径,根据当前日期生成目录结构。 + * + * @param file 文件 + * @return 处理后的文件路径 + */ + public static String processPath(MultipartFile file) { + // 创建OSSClient实例。 + String fileName = file.getOriginalFilename(); // 获取原始文件名 + + // 需要重命名 + fileName = renameFile(fileName); // 重命名文件 + + // 获得上传的文件夹 + String dir = DateUtils.formatDate(new Date(), "yyyy/MM/dd/"); // 获取当前日期格式化后的目录 + + return new StringBuffer(dir).append(fileName).toString(); // 返回处理后的文件路径 + } + + /** + * 检查文件夹是否存在,不存在则创建 + * 方法用于检查指定的文件夹是否存在,如果不存在则创建。 + * + * @param fileName 文件名 + */ + public static void checkDir(String fileName) { + int index = fileName.lastIndexOf("/"); // 获取最后一个斜杠的位置 + if (index == -1) { + return; // 如果没有斜杠,返回 + } + + File file = new File(fileName.substring(0, index)); // 创建文件对象 + if (!file.exists()) { + file.mkdirs(); // 如果文件夹不存在,创建文件夹 + } + } +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java b/src-源文件/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java index b4394c0..aff5557 100644 --- a/src-源文件/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java +++ b/src-源文件/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java @@ -1,47 +1,75 @@ package com.yf.exam.ability.upload.utils; +// 这一行声明了该Java类所属的包名为com.yf.exam.ability.upload.utils。 +// 包用于对相关的Java类进行组织和管理,方便代码的分类、复用以及避免类名冲突。 -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.StringUtils; // 导入Apache Commons Lang的字符串工具类,用于字符串操作 +// 引入了Apache Commons Lang库中的StringUtils类。 +// 这个类提供了许多方便的字符串操作方法,比如判断字符串是否为空、是否空白(包含空格等空白字符)、字符串的拼接、截取等操作,在这里主要用于对文件路径字符串进行相关判断。 -import java.util.HashMap; -import java.util.Map; +import java.util.HashMap; // 导入Java的HashMap类,用于创建映射 +// 导入了Java标准库中的HashMap类。 +// HashMap是实现了Map接口的一个具体类,它用于存储键值对形式的数据,通过键可以快速获取对应的值,在这里用于创建文件后缀名到MIME类型的映射关系。 + +import java.util.Map; // 导入Java的Map接口,用于键值对映射 +// 引入了Java标准库中的Map接口。 +// Map接口定义了键值对数据结构的通用操作规范,如添加键值对、根据键获取值、删除键值对等操作。 +// 虽然这里同时导入了HashMap类,但导入Map接口使得代码在使用映射数据结构时更具通用性,方便后续可能的替换为其他实现Map接口的类。 /** - * 媒体工具,判断媒体类型 - * @author bool + * 媒体工具类,用于判断和获取媒体文件的MIME类型 + * 该类提供了一个静态映射,用于将文件后缀名映射到对应的MIME类型,以便在处理文件上传和下载时确定正确的媒体类型。 + * @author bool * @date 2019-11-14 16:21 */ -public class MediaUtils { +// 这是一个Java类的文档注释,用于描述该类的整体功能和用途。 +// 说明这个类主要是作为媒体工具类,其核心功能是判断和获取媒体文件的MIME类型。 +// 通过维护一个静态的映射关系(文件后缀名到MIME类型的映射),在文件上传和下载的业务场景中,能够依据文件的后缀名准确地确定其对应的MIME类型,从而正确处理文件的传输和展示等操作。 +// 同时标注了类的作者是bool,创建日期是2019年11月14日16:21。 - public static final Map MEDIA_MAP = new HashMap(){ - { +public class MediaUtils { - //本来是pdf的 - put(".pdf", "application/pdf"); + /** + * 媒体类型映射 + * 静态映射,包含文件后缀名到MIME类型的映射。 + */ + // 这是对下面定义的MEDIA_MAP成员变量的文档注释,说明它是一个静态的映射,用于存储文件后缀名和对应的MIME类型之间的映射关系。 - //视频 - put(".mp4", "video,video/mp4"); - - } - }; + public static final Map MEDIA_MAP = new HashMap() {{ + // 初始化映射 + // PDF文件的MIME类型 + put(".pdf", "application/pdf"); + // 视频文件的MIME类型 + put(".mp4", "video/mp4"); + }}; + // 这里定义了一个名为MEDIA_MAP的公共静态最终变量,它是一个HashMap类型的映射。 + // 通过匿名内部类的初始化方式,在创建HashMap实例的同时向其中添加了一些常见的文件后缀名到MIME类型的映射关系,比如将".pdf"后缀名映射到"application/pdf"这个MIME类型,将".mp4"后缀名映射到"video/mp4"这个MIME类型。 + // 由于被声明为静态最终变量,它在类加载时就会被初始化,并且其值不能再被修改,方便在整个类的其他地方直接使用这个映射关系来获取文件的MIME类型。 /** - * 获得文件类型 - * @param filePath - * @return + * 根据文件路径获取文件的MIME类型 + * 方法根据文件的后缀名,从MEDIA_MAP中获取对应的MIME类型。 + * + * @param filePath 文件路径 + * @return 文件的MIME类型 */ - public static String getContentType(String filePath){ - - if(!StringUtils.isBlank(filePath) - && filePath.indexOf(".")!=-1) { + // 这是对下面定义的getContentType方法的文档注释,说明该方法的功能是根据传入的文件路径,提取出文件的后缀名,然后从MEDIA_MAP这个静态映射中获取对应的MIME类型并返回。 - // 后缀转换成小写 + public static String getContentType(String filePath) { + if (!StringUtils.isBlank(filePath) && filePath.indexOf(".")!= -1) { + // 提取文件后缀名,并转换为小写 String suffix = filePath.substring(filePath.lastIndexOf(".")).toLowerCase(); + // 从映射中获取MIME类型 if (MEDIA_MAP.containsKey(suffix)) { return MEDIA_MAP.get(suffix); } } + // 如果没有找到对应的MIME类型,返回默认值 return "application/octet-stream"; } -} + // 这是定义的一个公共静态方法getContentType,它接受一个字符串类型的参数filePath,表示文件的路径。 + // 首先,通过StringUtils.isBlank方法判断文件路径是否不为空且包含小数点(即有文件后缀名)。 + // 如果满足条件,就使用substring方法从文件路径中提取出文件的后缀名,并通过toLowerCase方法将其转换为小写形式。 + // 然后,检查提取出的后缀名是否存在于MEDIA_MAP这个静态映射中,如果存在,就返回对应的MIME类型;如果不存在,就返回默认的MIME类型"application/octet-stream",这个默认值通常用于表示未知类型的二进制数据文件。 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/aspect/DictAspect.java b/src-源文件/main/java/com/yf/exam/aspect/DictAspect.java index 441a154..a85ec9a 100644 --- a/src-源文件/main/java/com/yf/exam/aspect/DictAspect.java +++ b/src-源文件/main/java/com/yf/exam/aspect/DictAspect.java @@ -1,156 +1,189 @@ package com.yf.exam.aspect; +// 导入FastJSON库,用于将Java对象转换为JSON字符串以及从JSON字符串解析为Java对象等操作, +// 在本类的多个方法中用于对象与JSON字符串之间的转换,以便于处理数据字典相关的值。 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; + +// 导入MyBatis Plus的接口,用于处理分页相关的数据结构,在本类中用于判断和处理分页数据中的数据字典值。 import com.baomidou.mybatisplus.core.metadata.IPage; + +// 导入Jackson库的注解,用于指定日期格式的序列化和反序列化方式, +// 在本类的parseObject方法中用于根据注解设置日期字段的格式化输出。 import com.fasterxml.jackson.annotation.JsonFormat; + +// 导入自定义的注解,可能用于标记与数据字典相关的字段,以便在本类中识别并处理这些字段的数据字典值。 import com.yf.exam.core.annon.Dict; + +// 导入自定义的API响应类,用于封装API调用的返回结果,包括数据、状态码等信息, +// 在本类的多个方法中用于获取和设置返回结果中的数据部分,以便处理其中的数据字典值。 import com.yf.exam.core.api.ApiRest; + +// 导入自定义的反射工具类,可能用于获取对象的所有字段等反射相关操作, +// 在本类的parseObject方法中用于获取对象的所有字段以便遍历处理数据字典值。 import com.yf.exam.core.utils.Reflections; + +// 导入系统数据字典服务类,用于查询数据字典表以获取数据字典值的翻译文本, +// 在本类的translateDictValue方法中用于根据字典代码、文本、表名和键值查询对应的字典文本。 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.annotation.Around; import org.aspectj.lang.annotation.Aspect; + +// 导入Spring框架的注解,用于自动注入依赖对象和标记类为Spring组件, +// 在本类中通过@Autowired注入SysDictService对象,并通过@Component标记本类为Spring组件,使其可被Spring容器管理。 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; + +// 导入Spring框架的工具类,用于判断字符串是否为空等操作, +// 在本类的多个方法中用于判断字符串是否为空,以便进行相应的处理逻辑。 import org.springframework.util.StringUtils; +// 导入Java标准库中的反射相关类,用于通过反射操作对象的字段、获取类型信息等, +// 在本类的多个方法中广泛用于获取对象的字段、判断字段类型、获取字段注解等操作。 import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; + +// 导入Java标准库中的日期格式化类和日期类,用于处理日期格式的转换和操作, +// 在本类的parseObject方法中用于根据注解或默认格式对日期字段进行格式化输出。 import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** - * 数据字典AOP类,处理数据字典值 + * 数据字典AOP类,主要功能是处理数据字典值。通过拦截特定的方法调用, + * 对返回结果中的数据字典相关字段进行翻译、格式化等处理,以提供更友好的展示效果。 * * @author bool */ -@Aspect -@Component -@Slf4j +@Aspect // 标记该类为一个AspectJ切面类,用于定义切面相关的逻辑。 +@Component // 标记该类为Spring组件,使其能够被Spring容器管理和实例化,以便在应用中使用。 +@Log4j2 // 使用Log4j2注解启用日志记录功能,方便在类中记录相关操作的日志信息。 public class DictAspect { @Autowired - private SysDictService sysDictService; + private SysDictService sysDictService; // 通过自动注入获取系统数据字典服务对象,用于查询数据字典值。 /** - * 切入Controller执行 - * @param pjp - * @return - * @throws Throwable + * 定义一个环绕通知,切入到指定的Controller方法执行前后。 + * 这里的切点表达式指定了拦截所有在com.yf.exam包及其子包下的所有Controller类中的所有公有方法。 + * + * @param pjp 切入点对象,包含了被拦截方法的相关信息,如方法参数、目标对象等。 + * @return 返回结果,经过处理后的方法执行结果,可能经过了数据字典值的处理等操作。 + * @throws Throwable 如果在环绕通知的执行过程中出现异常,则抛出。 */ @Around("execution(public * com.yf.exam..*.*Controller.*(..))") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { - return this.translate(pjp); + return this.translate(pjp); // 调用translate方法对被拦截方法的执行结果进行处理,主要是处理数据字典值。 } /** - * 进行翻译并返回,调用前必须实现:BaseDictService + * 对被拦截方法的执行结果进行翻译处理,并返回处理后的结果。 + * 在调用此方法之前,通常需要确保相关的BaseDictService(可能是数据字典相关的基础服务)已经实现。 * - * @param pjp - * @return - * @throws Throwable + * @param pjp 切入点对象,包含了被拦截方法的相关信息,如方法参数、目标对象等。 + * @return 返回结果,经过数据字典值处理后的方法执行结果。 + * @throws Throwable 如果在处理过程中出现异常,则抛出。 */ public Object translate(ProceedingJoinPoint pjp) throws Throwable { - // 处理字典 + // 调用被拦截方法获取原始结果,然后调用parseAllDictText方法对结果进行数据字典值的处理。 return this.parseAllDictText(pjp.proceed()); } /** - * 转换全部数据字典 + * 根据结果对象的类型,判断是否需要对其进行数据字典值的处理。 + * 如果结果对象是ApiRest类型,则调用parseFullDictText方法进行完整的数据字典值处理。 * - * @param result + * @param result 结果对象,即被拦截方法执行后的返回结果。 */ private Object parseAllDictText(Object result) { - - // 非ApiRest类型不处理 + // 判断结果对象是否是ApiRest类型,如果是则进行数据字典值的处理。 if (result instanceof ApiRest) { parseFullDictText(result); } - return result; } - /** - * 转换所有类型的数据字典、包含子列表 + * 对ApiRest类型的结果对象进行完整的数据字典值处理,包括处理分页数据、列表数据以及单对象数据等情况。 * - * @param result + * @param result 结果对象,即ApiRest类型的返回结果,其中包含了要处理的数据部分。 */ private void parseFullDictText(Object result) { - try { + Object rest = ((ApiRest) result).getData(); // 获取ApiRest对象中的数据部分,这部分数据可能包含数据字典相关字段。 - Object rest = ((ApiRest) result).getData(); - - // 不处理普通数据类型 + // 如果数据部分为空或者是基本数据类型,则不需要进行数据字典值的处理,直接返回。 if (rest == null || this.isBaseType(rest.getClass())) { return; } - // 分页的 + // 如果数据部分是分页数据类型(IPage),则对分页数据中的每条记录进行数据字典值处理。 if (rest instanceof IPage) { List items = new ArrayList<>(16); for (Object record : ((IPage) rest).getRecords()) { - Object item = this.parseObject(record); + Object item = this.parseObject(record); // 调用parseObject方法对每条记录进行数据字典值处理。 items.add(item); } - ((IPage) rest).setRecords(items); + ((IPage) rest).setRecords(items); // 将处理后的记录列表重新设置回分页对象中。 return; } - // 数据列表的 + // 如果数据部分是列表数据类型(List),则对列表中的每条记录进行数据字典值处理。 if (rest instanceof List) { List items = new ArrayList<>(); for (Object record : ((List) rest)) { - Object item = this.parseObject(record); + Object item = this.parseObject(record); // 调用parseObject方法对每条记录进行数据字典值处理。 items.add(item); } - // 重新回写值 + // 将处理后的记录列表重新设置回ApiRest对象的数据部分。 ((ApiRest) result).setData(items); return; } - // 处理单对象 + // 如果数据部分是单对象数据,则对该单对象进行数据字典值处理。 Object item = this.parseObject(((ApiRest) result).getData()); ((ApiRest) result).setData(item); } catch (Exception e) { - e.printStackTrace(); + e.printStackTrace(); // 如果在处理过程中出现异常,则打印异常堆栈信息。 } } /** - * 处理数据字典值 + * 对单个记录对象进行数据字典值处理,包括处理列表字段、带有数据字典注解的普通字段以及日期字段等情况。 * - * @param record - * @return + * @param record 记录对象,即要进行数据字典值处理的对象。 + * @return 处理后的对象,经过数据字典值处理后的记录对象。 */ public Object parseObject(Object record) { - if (record == null) { return null; } - // 不处理普通数据类型 + // 如果记录对象是基本数据类型,则不需要进行数据字典值处理,直接返回原对象。 if (this.isBaseType(record.getClass())) { return record; } - // 转换JSON字符 + // 将记录对象转换为JSON字符串,再解析为JSONObject对象,以便于通过字段名获取和设置值。 String json = JSON.toJSONString(record); 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())) { try { - List list = this.processList(field, item.getObject(field.getName(), List.class)); + List list = this.processList(field, item.getObject(field.getName(), List.class)); // 调用processList方法处理列表字段。 item.put(field.getName(), list); continue; } catch (Exception e) { @@ -159,14 +192,14 @@ public class DictAspect { continue; } - // 处理普通字段 - if (field.getAnnotation(Dict.class) != null) { + // 如果字段带有数据字典注解(Dict),则对该字段进行数据字典值的翻译处理。 + if (field.getAnnotation(Dict.class)!= null) { String code = field.getAnnotation(Dict.class).dicCode(); String text = field.getAnnotation(Dict.class).dicText(); String table = field.getAnnotation(Dict.class).dictTable(); String key = String.valueOf(item.get(field.getName())); - //翻译字典值对应的txt + // 调用translateDictValue方法翻译字典值对应的文本,根据字典代码、文本、表名和键值查询对应的字典文本。 String textValue = this.translateDictValue(code, text, table, key); if (StringUtils.isEmpty(textValue)) { textValue = ""; @@ -175,24 +208,22 @@ public class DictAspect { continue; } - //日期格式转换 - if ("java.util.Date".equals(field.getType().getName()) && item.get(field.getName()) != null) { - - // 获取注解 + // 如果字段类型是日期类型(java.util.Date)且字段值不为空,则对日期字段进行格式转换处理。 + if ("java.util.Date".equals(field.getType().getName()) && item.get(field.getName())!= null) { + // 获取字段上的JsonFormat注解。 JsonFormat ann = field.getAnnotation(JsonFormat.class); - // 格式化方式 + // 定义日期格式化对象。 SimpleDateFormat fmt; - // 使用注解指定的 - if (ann != null && !StringUtils.isEmpty(ann.pattern())) { + // 如果注解不为空且指定了日期格式模式,则使用注解指定的格式创建日期格式化对象。 + if (ann!= null &&!StringUtils.isEmpty(ann.pattern())) { fmt = new SimpleDateFormat(ann.pattern()); } else { - // 默认时间样式 + // 如果注解为空或未指定格式,则使用默认的日期格式创建日期格式化对象。 fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } item.put(field.getName(), fmt.format(new Date((Long) item.get(field.getName())))); continue; - } } @@ -200,49 +231,53 @@ public class DictAspect { } /** - * 获得类型为List的值 + * 处理类型为List的字段,对列表中的每个元素进行数据字典值处理等操作。 * - * @param field - * @return + * @param field 字段对象,即要处理的List类型字段。 + , + * @param list 列表对象,即字段对应的列表值。 + * @return 处理后的列表,经过数据字典值处理后的列表对象。 */ private List processList(Field field, List list) { - - // 空判断 + // 如果列表为空,则返回一个空的ArrayList对象。 if (list == null || list.size() == 0) { return new ArrayList<>(); } - // 获得List属性的真实类 + // 获取List属性的真实类型,通过反射获取字段的泛型类型,再尝试获取其实际的类型参数。 Type genericType = field.getGenericType(); Class actualType = null; if (genericType instanceof ParameterizedType) { - // 尝试获取数据类型 ParameterizedType pt = (ParameterizedType) genericType; try { actualType = (Class) pt.getActualTypeArguments()[0]; - }catch (Exception e){ + } catch (Exception e) { return list; } } - // 常规列表无需处理 + // 如果列表元素的类型是基本数据类型,则不需要进行数据字典值处理,直接返回原列表。 if (isBaseType(actualType)) { return list; } - // 返回列表 + // 创建一个新的ArrayList对象,用于存储处理后的列表元素。 List result = new ArrayList<>(16); for (int i = 0; i < list.size(); i++) { - // 创建实例-->赋值-->字典处理 + // 获取列表中的每个元素。 Object data = list.get(i); try { + // 将列表元素转换为JSON字符串,再解析为其真实类型的对象,以便进行数据字典值处理。 data = JSON.parseObject(JSON.toJSONString(data), actualType); - }catch (Exception e){ - // 转换出错不处理 + } catch (Exception e) { + // 如果转换过程中出现错误,则不进行处理,直接使用原元素。 + // 这里可以根据实际需求进一步处理错误情况,比如记录日志等。 + // 目前只是简单地忽略错误,继续处理下一个元素。 + ; } - // 处理后的数据 + // 对处理后的元素进行数据字典值处理,调用parseObject方法。 Object pds = this.parseObject(data); result.add(pds); } @@ -251,23 +286,25 @@ public class DictAspect { } /** - * 翻译实现 + , + * 根据字典代码、文本、表名和键值翻译数据字典值对应的文本。 * - * @param code - * @param text - * @param table - * @param key - * @return + * @param code 字典代码,用于在数据字典表中定位特定的字典项。 + * @param text 字典文本,可能是与字典代码相关的描述信息等。 + * @param table 字典表名,指定要查询的数据字典表。 + * @param key 字典键值,用于在字典表中查找对应的字典项。 + * @return 翻译后的值,即根据字典代码、文本、表名和键值查询到的字典文本值,如果未找到则返回空字符串。 */ - private String translateDictValue(String code, String text, String table, String key) { + private String translateDictValue(String code, String text, String table, String键值) { if (StringUtils.isEmpty(key)) { return null; } try { - // 翻译值 + // 定义变量用于存储翻译后的字典文本值。 String dictText = null; if (!StringUtils.isEmpty(table)) { - dictText = sysDictService.findDict(table, text, code, key.trim()); + // 如果字典表名不为空,则调用sysDictService的findDict方法查询数据字典表,获取对应的字典文本值。 + dictText = sysDictService.findDict(table, text, code, key.trim()); } if (!StringUtils.isEmpty(dictText)) { @@ -280,15 +317,13 @@ public class DictAspect { } /** - * 判断是否基本类型 + * 判断给定的类是否是基本数据类型,包括常见的整数、字节、长整数、双精度浮点数、单精度浮点数、字符、短整数、布尔值以及字符串和数字类型等。 * - * @param clazz - * @return + * @param clazz 要判断的类对象。 + * @return 是否基本类型,如果是基本数据类型则返回true,否则返回false。 */ private boolean isBaseType(Class clazz) { - - - // 基础数据类型 + // 判断是否是常见的基本数据类型,如整数、字节、长整数等。 if (clazz.equals(java.lang.Integer.class) || clazz.equals(java.lang.Byte.class) || clazz.equals(java.lang.Long.class) || @@ -300,18 +335,16 @@ public class DictAspect { return true; } - // String类型 + // 判断是否是字符串类型。 if (clazz.equals(java.lang.String.class)) { return true; } - // 数字 + // 判断是否是数字类型(这里的数字类型可能是指抽象的数字类型,比如Number的子类等)。 if (clazz.equals(java.lang.Number.class)) { return true; } return false; } - - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java b/src-源文件/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java index 6b958ca..2ca40a4 100644 --- a/src-源文件/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java +++ b/src-源文件/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java @@ -1,144 +1,255 @@ package com.yf.exam.aspect.mybatis; +// 导入MyBatis Plus的分页拦截器类,用于实现分页功能, +// 本类继承自该类以在拦截查询操作时能正确处理分页相关逻辑。 import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; + +// 导入系统用户登录信息的数据传输对象(DTO)类,用于获取当前登录用户的相关信息, +// 比如在处理查询拦截时可能需要根据登录用户的信息来过滤或修改查询语句。 import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; + +// 导入Lombok的Log4j2注解,用于简化日志记录的配置,通过该注解可以方便地在类中使用Log4j2进行日志输出。 import lombok.extern.log4j.Log4j2; + +// 导入JSqlParser库中的解析器管理器类,用于解析SQL语句, +// 在本类中用于解析查询语句以便进行后续的处理,如替换用户ID等操作。 import net.sf.jsqlparser.parser.CCJSqlParserManager; + +// 导入JSqlParser库中表示简单查询语句的类,它是查询语句的一种具体表示形式, +// 在解析查询语句后可以获取到该对象来进一步处理查询语句的内容。 import net.sf.jsqlparser.statement.select.PlainSelect; + +// 导入JSqlParser库中表示查询语句的通用类,用于接收解析后的查询语句对象, +// 以便进行后续的类型转换和处理操作。 import net.sf.jsqlparser.statement.select.Select; + +// 导入Apache Commons Lang3库中的StringUtils类,用于处理字符串相关的操作, +// 如判断字符串是否为空、非空等情况,在本类中用于判断用户ID等字符串是否有效。 import org.apache.commons.lang3.StringUtils; + +// 导入MyBatis中的语句处理器接口类,它是MyBatis在执行SQL语句时涉及的一个重要组件, +// 在本类中作为拦截的目标对象类型,以便在其执行查询准备阶段进行拦截操作。 import org.apache.ibatis.executor.statement.StatementHandler; + +// 导入MyBatis中的映射语句类,它包含了关于SQL语句的映射信息, +// 如SQL语句的ID、参数映射、结果映射等,在本类中用于获取SQL语句的相关属性,如语句类型等。 import org.apache.ibatis.mapping.MappedStatement; + +// 导入MyBatis中的SQL命令类型枚举类,用于表示不同类型的SQL命令, +// 如SELECT、INSERT、UPDATE、DELETE等,在本类中用于判断当前拦截的SQL语句是否为查询语句。 import org.apache.ibatis.mapping.SqlCommandType; + +// 导入MyBatis的拦截器接口类,定义了拦截器的基本行为和方法, +// 本类实现了该接口以作为一个MyBatis的拦截器来拦截查询语句的执行过程。 import org.apache.ibatis.plugin.Interceptor; + +// 导入MyBatis的拦截器注解类,用于标注一个类是MyBatis的拦截器并指定拦截的目标和方法, +// 本类通过该注解指定了要拦截StatementHandler的prepare方法。 import org.apache.ibatis.plugin.Intercepts; + +// 导入MyBatis的拦截器调用类,用于在拦截器方法中传递被拦截方法的调用信息, +// 包括被拦截的目标对象、方法参数等,在本类的intercept方法中会接收到该对象来处理拦截逻辑。 import org.apache.ibatis.plugin.Invocation; + +// 导入MyBatis的插件包装类,用于将拦截器包装成MyBatis可识别的插件形式, +// 在本类的plugin方法中会使用该类来包装目标对象以便实现拦截功能。 import org.apache.ibatis.plugin.Plugin; + +// 导入MyBatis的拦截器签名类,用于定义拦截器拦截的具体目标、方法和参数类型, +// 在本类的@Intercepts注解中会使用该类来指定具体的拦截信息。 import org.apache.ibatis.plugin.Signature; + +// 导入MyBatis的默认反射工厂类,用于创建反射对象来访问和修改MyBatis相关对象的属性, +// 在本类中用于创建MetaObject对象来操作StatementHandler等对象的属性。 import org.apache.ibatis.reflection.DefaultReflectorFactory; + +// 导入MyBatis的元对象类,它提供了一种通过反射来访问和操作MyBatis相关对象属性的机制, +// 在本类中用于获取和设置StatementHandler等对象的内部属性,如SQL语句等。 import org.apache.ibatis.reflection.MetaObject; + +// 导入MyBatis的系统元对象类,它是MetaObject的一种特殊实现,提供了一些默认的对象工厂和包装器工厂, +// 在本类中用于创建MetaObject对象来操作StatementHandler等对象的属性。 import org.apache.ibatis.reflection.SystemMetaObject; + +// 导入Apache Shiro的安全工具类,用于获取当前安全上下文的相关信息, +// 在本类中用于获取当前登录用户的信息,以便根据用户信息来处理查询语句。 import org.apache.shiro.SecurityUtils; +// 导入Java标准库中的字符串读取器类,用于将字符串转换为可读取的流形式, +// 在本类中用于将SQL语句字符串提供给CCJSqlParserManager进行解析。 import java.io.StringReader; + +// 导入Java标准库中的数据库连接接口类,它是Java数据库操作中与数据库建立连接的关键接口, +// 在本类中作为StatementHandler的prepare方法的参数类型之一出现,虽然在本类代码中未直接对其进行复杂操作, +// 但它是MyBatis执行SQL语句过程中涉及到的重要组件之一。 import java.sql.Connection; + +// 导入Java标准库中的属性类,用于存储和管理键值对形式的属性信息, +// 在本类中作为Interceptor接口的setProperties方法的参数类型,虽然在本类代码中该方法暂未实现具体功能, +// 但它是符合Interceptor接口规范的一部分,可用于接收外部配置的属性信息来动态调整拦截器的行为。 import java.util.Properties; /** - * 查询拦截器,用于拦截处理通用的信息、如用户ID、多租户信息等; - * 特别注意:此处继承了PaginationInterceptor分页,分页必须在拦截数据后执行,否则容易出现分页不准确,分页计数大于实际数量等问题 + * 查询拦截器类,用于拦截处理通用的信息,如用户ID、多租户信息等。 + * 特别注意:此处继承了PaginationInterceptor分页,分页必须在拦截数据后执行,否则容易出现分页不准确, + * 分页计数大于实际数量等问题。 * @author bool */ @Log4j2 -@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),}) +// 使用@Intercepts注解指定该类作为MyBatis的拦截器要拦截的目标和方法。 +// 这里拦截的是StatementHandler类的prepare方法,并且该方法的参数类型为Connection和Integer。 +@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class QueryInterceptor extends PaginationInterceptor implements Interceptor { /** - * 客户ID + * 客户ID的占位符字符串,在原始SQL语句中如果出现该占位符,将会被替换为实际的用户ID。 */ private static final String USER_FILTER = "{{userId}}"; - - + /** + * 拦截器的核心方法,用于在目标方法(StatementHandler的prepare方法)被调用时进行拦截处理。 + * + * @param invocation 包含了被拦截方法的调用信息,如目标对象、方法参数等。 + * @return 返回处理后的结果,可能是继续执行被拦截方法后的结果,也可能是经过拦截器修改后的结果。 + * @throws Throwable 如果在拦截处理过程中出现异常,则抛出。 + */ @Override public Object intercept(Invocation invocation) throws Throwable { + // 从invocation对象中获取被拦截的目标对象,即StatementHandler对象。 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()); + + // 从元对象中获取MappedStatement对象,该对象包含了关于SQL语句的映射信息,如SQL语句的ID、参数映射、结果映射等。 MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); - //sql语句类型 + // 获取当前SQL语句的类型,通过MappedStatement对象的getSqlCommandType方法获取。 + // 该类型是一个枚举值,如SELECT、INSERT、UPDATE、DELETE等,用于判断当前拦截的SQL语句是何种类型的操作。 SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); - // 只过滤查询的 + // 只对查询类型的SQL语句进行处理,如果当前SQL语句类型是SELECT,则进入下面的处理逻辑。 if (SqlCommandType.SELECT == sqlCommandType) { - // 获得原始SQL + + // 获取原始的SQL语句,通过StatementHandler的getBoundSql方法获取绑定的SQL语句对象,再获取其SQL字符串。 String sql = statementHandler.getBoundSql().getSql(); - // 不处理 - if(!sql.contains(USER_FILTER)){ + // 如果原始SQL语句中不包含用户ID占位符(USER_FILTER),则直接调用父类(PaginationInterceptor)的intercept方法, + // 即按照原有的分页逻辑进行处理,不进行用户ID相关的替换等操作。 + if (!sql.contains(USER_FILTER)) { return super.intercept(invocation); } - // 处理SQL语句 + + // 如果原始SQL语句中包含用户ID占位符,则需要对SQL语句进行处理。 + // 首先调用parseSql方法对SQL语句进行解析和处理,包括替换用户ID等操作。 String outSql = this.parseSql(sql); - // 设置SQL + + // 将处理后的SQL语句设置回StatementHandler对象的内部属性中,通过元对象的setValue方法设置boundSql.sql属性的值。 metaObject.setValue("delegate.boundSql.sql", outSql); - // 再分页 + + // 再次调用父类(PaginationInterceptor)的intercept方法,此时是在处理完用户ID等信息后, + // 按照正确的分页逻辑进行处理,以确保分页功能在拦截数据并处理相关信息后正确执行。 return super.intercept(invocation); } + // 如果当前SQL语句不是查询类型(SELECT),则直接执行被拦截方法的原有逻辑,不进行额外的处理。 return invocation.proceed(); } + /** + * 将拦截器包装成MyBatis可识别的插件形式的方法。 + * + * @param target 要包装的目标对象,通常是被拦截的对象,如StatementHandler等。 + * @return 返回包装后的对象,以便MyBatis能够正确识别并应用拦截器的功能。 + */ @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } + /** + * 设置拦截器属性的方法,目前该方法在本类中暂未实现具体功能, + * 但它是符合Interceptor接口规范的一部分,可用于接收外部配置的属性信息来动态调整拦截器的行为。 + * + * @param properties 包含了外部配置的属性信息的Properties对象。 + */ @Override public void setProperties(Properties properties) { } - - /** - * 获取当前登录用户 - * @return + * 获取当前登录用户的方法,通过Apache Shiro的SecurityUtils工具类来获取当前安全上下文的主体对象, + * 并获取其主体信息(即登录用户的信息),如果获取成功则转换为SysUserLoginDTO类型返回,否则返回null。 + * + * @return 返回当前登录用户的SysUserLoginDTO对象,如果获取失败则返回null。 */ private SysUserLoginDTO getLoginUser() { try { - return SecurityUtils.getSubject().getPrincipal() != null ? (SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal() : null; + return SecurityUtils.getSubject().getPrincipal()!= null? (SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal() : null; } catch (Exception e) { return null; } } /** - * 替换用户ID - * @param sql - * @return + * 替换用户ID的方法,根据当前登录用户的ID,将原始SQL语句中的用户ID占位符(USER_FILTER)替换为实际的用户ID。 + * + * @param sql 原始的SQL语句。 + * @return 返回替换用户ID后的SQL语句,如果用户ID为空,则返回null。 */ private String processUserId(String sql) { - // 当前用户 + // 获取当前登录用户的信息,通过调用getLoginUser方法获取。 SysUserLoginDTO user = this.getLoginUser(); String userId = user.getId(); - if(StringUtils.isNotBlank(userId)){ + + // 如果获取到的用户ID不为空,则将原始SQL语句中的用户ID占位符替换为实际的用户ID,并返回替换后的SQL语句。 + if (StringUtils.isNotBlank(userId)) { return sql.replace(USER_FILTER, userId); } + + // 如果用户ID为空,则返回null。 return null; } /** - * 处理注入用户信息 - * @param src - * @return + * 处理注入用户信息的方法,主要用于解析原始SQL语句,替换用户ID等操作,以实现根据用户信息来调整查询语句的目的。 + * + * @param src 原始的SQL语句。 + * @return 返回处理后的SQL语句,如果在处理过程中出现异常,则返回原始的SQL语句。 */ private String parseSql(String src) { + // 创建一个CCJSqlParserManager对象,用于解析SQL语句。 CCJSqlParserManager parserManager = new CCJSqlParserManager(); try { + // 使用CCJSqlParserManager对象的parse方法解析原始SQL语句,将其转换为Select类型的对象。 + // 这里需要将原始SQL语句通过StringReader转换为可读取的流形式提供给parse方法进行解析。 Select select = (Select) parserManager.parse(new StringReader(src)); + + // 从Select对象中获取其查询主体部分,即PlainSelect对象,它包含了查询语句的具体内容,如查询条件、字段等。 PlainSelect selectBody = (PlainSelect) select.getSelectBody(); - // 过滤客户 + // 将PlainSelect对象转换为字符串形式,得到初步处理后的SQL语句。 + // 这里主要是为了后续方便进行用户ID等信息的替换操作。 String sql = selectBody.toString(); - // 过滤用户ID + // 调用processUserId方法对初步处理后的SQL语句进行用户ID的替换操作,将用户ID占位符替换为实际的用户ID。 sql = this.processUserId(sql); - // 获得SQL + // 返回处理后的SQL语句。 return sql; } catch (Exception e) { e.printStackTrace(); } + // 如果在处理过程中出现异常,则返回原始的SQL语句。 return src; } - - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java b/src-源文件/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java index 8baae8f..c85e5f0 100644 --- a/src-源文件/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java +++ b/src-源文件/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java @@ -1,80 +1,188 @@ +// 定义包路径,用于存放更新拦截器相关的类。这个包名表明该类属于特定的项目模块(com.yf.exam)下的aspect.mybatis部分, +// 通常用于组织和管理与MyBatis相关的切面逻辑代码。 package com.yf.exam.aspect.mybatis; +// 导入MyBatis-Plus的抽象SQL解析处理器类,它提供了一些基础的SQL解析处理功能, +// 本类继承自该类以便在处理更新操作时利用其相关功能或遵循其处理规范。 import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; + +// 导入Apache Commons Lang3库中的数组工具类,用于对数组进行各种操作, +// 如合并数组等,在本类中用于合并对象的字段数组(当存在父类字段时)。 import org.apache.commons.lang3.ArrayUtils; + +// 导入MyBatis中的执行器接口类,它是MyBatis执行SQL语句的核心组件,负责实际执行数据库操作, +// 在本类中作为拦截的目标对象类型,通过拦截其update方法来实现自动设置创建时间和更新时间的功能。 import org.apache.ibatis.executor.Executor; + +// 导入MyBatis中的映射语句类,它包含了关于SQL语句的映射信息, +// 如SQL语句的ID、参数映射、结果映射等,在本类中用于获取SQL语句的相关属性,如语句类型等, +// 以便根据不同的操作类型(插入、更新等)来处理创建时间和更新时间的赋值。 import org.apache.ibatis.mapping.MappedStatement; + +// 导入MyBatis中的SQL命令类型枚举类,用于表示不同类型的SQL命令, +// 如SELECT、INSERT、UPDATE、DELETE等,在本类中用于判断当前拦截的SQL语句是何种类型的操作, +// 从而确定是否需要对创建时间和更新时间进行赋值。 import org.apache.ibatis.mapping.SqlCommandType; + +// 导入MyBatis的拦截器接口类,定义了拦截器的基本行为和方法, +// 本类实现了该接口以作为一个MyBatis的拦截器来拦截更新语句的执行过程,实现自动设置时间的功能。 import org.apache.ibatis.plugin.Interceptor; + +// 导入MyBatis的拦截器注解类,用于标注一个类是MyBatis的拦截器并指定拦截的目标和方法, +// 本类通过该注解指定了要拦截Executor类的update方法。 import org.apache.ibatis.plugin.Intercepts; + +// 导入MyBatis的拦截器调用类,用于在拦截器方法中传递被拦截方法的调用信息, +// 包括被拦截的目标对象、方法参数等,在本类的intercept方法中会接收到该对象来处理拦截逻辑。 import org.apache.ibatis.plugin.Invocation; + +// 导入MyBatis的插件包装类,用于将拦截器包装成MyBatis可识别的插件形式, +// 在本类的plugin方法中会使用该类来包装目标对象以便实现拦截功能。 import org.apache.ibatis.plugin.Plugin; + +// 导入MyBatis的拦截器签名类,用于定义拦截器拦截的具体目标、方法和参数类型, +// 在本类的@Intercepts注解中会使用该类来指定具体的拦截信息。 import org.apache.ibatis.plugin.Signature; +// 导入Java标准库中的反射字段类,用于通过反射操作对象的私有字段, +// 在本类中用于获取和设置对象的创建时间和更新时间字段的值。 import java.lang.reflect.Field; + +// 导入Java标准库中的时间戳类,用于表示某个特定时刻的时间点, +// 在本类中用于设置创建时间和更新时间为当前的时间戳,以记录操作发生的准确时间。 import java.sql.Timestamp; + +// 导入Java标准库中的对象工具类,提供了一些用于比较和操作对象的方法, +// 在本类中用于比较字段名与预定义的创建时间、更新时间字段名是否相等。 import java.util.Objects; + +// 导入Java标准库中的属性类,用于存储和管理键值对形式的属性信息, +// 在本类中作为Interceptor接口的setProperties方法的参数类型,虽然在本类代码中该方法暂未实现具体功能, +// 但它是符合Interceptor接口规范的一部分,可用于接收外部配置的属性信息来动态调整拦截器的行为。 import java.util.Properties; /** - * 自动给创建时间个更新时间加值 + * 该类是一个更新拦截器,主要功能是自动给创建时间和更新时间字段赋值。 + * 它会拦截MyBatis执行器(Executor)的update方法,根据操作类型(插入或更新)以及对象的字段情况, + * 为创建时间和更新时间字段设置当前的时间戳值。 * @author bool */ @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 { /** - * 创建时间 + * 定义创建时间字段的名称常量,用于在后续代码中方便地识别和操作对象中的创建时间字段。 */ private static final String CREATE_TIME = "createTime"; + /** - * 更新时间 + * 定义更新时间字段的名称常量,用于在后续代码中方便地识别和操作对象中的更新时间字段。 */ private static final String UPDATE_TIME = "updateTime"; + /** + * 拦截器的核心方法,用于在目标方法(Executor的update方法)被调用时进行拦截处理。 + * + * @param invocation 包含了被拦截方法的调用信息,如目标对象、方法参数等。 + * @return 返回处理后的结果,可能是继续执行被拦截方法后的结果,也可能是经过拦截器修改后的结果。 + * @throws Throwable 如果在拦截处理过程中出现异常,则抛出。 + */ @Override public Object intercept(Invocation invocation) throws Throwable { + // 从invocation对象的参数数组中获取第一个参数,即MappedStatement对象, + // 它包含了关于SQL语句的映射信息,用于后续获取SQL命令类型等操作。 MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; - // SQL操作命令 + + // 获取当前SQL语句的类型,通过MappedStatement对象的getSqlCommandType方法获取。 + // 该类型是一个枚举值,如SELECT、INSERT、UPDATE、DELETE等,用于判断当前拦截的SQL语句是何种类型的操作。 SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); - // 获取新增或修改的对象参数 + + // 从invocation对象的参数数组中获取第二个参数,即要进行新增或修改操作的对象参数, + // 后续会通过反射操作该对象来设置创建时间和更新时间字段的值。 Object parameter = invocation.getArgs()[1]; - // 获取对象中所有的私有成员变量(对应表字段) + + // 获取对象中所有的私有成员变量(对应表字段),通过反射获取parameter对象的声明字段数组。 Field[] declaredFields = parameter.getClass().getDeclaredFields(); - if (parameter.getClass().getSuperclass() != null) { + + // 判断parameter对象的父类是否存在,如果存在则获取父类的声明字段数组, + // 并将其与之前获取的子类声明字段数组进行合并,以获取完整的包含父类和子类字段的数组。 + if (parameter.getClass().getSuperclass()!= null) { Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields(); declaredFields = ArrayUtils.addAll(declaredFields, superField); } + // 用于临时存储当前遍历到的字段名,以便后续与预定义的创建时间、更新时间字段名进行比较。 String fieldName = null; + + // 遍历获取到的所有字段数组(包含父类和子类字段),对每个字段进行处理。 for (Field field : declaredFields) { fieldName = field.getName(); + + // 如果当前遍历到的字段名与预定义的创建时间字段名相等,说明找到了创建时间字段。 if (Objects.equals(CREATE_TIME, fieldName)) { + + // 如果当前SQL语句的操作类型是插入操作(INSERT),则需要为创建时间字段设置当前时间。 if (SqlCommandType.INSERT.equals(sqlCommandType)) { + + // 通过反射设置该字段可访问,因为私有字段默认是不可直接访问的。 field.setAccessible(true); + + // 使用反射设置该字段的值为当前的时间戳,通过System.currentTimeMillis()获取当前时间的毫秒数, + // 再创建一个Timestamp对象来表示该时间点,从而为创建时间字段赋值。 field.set(parameter, new Timestamp(System.currentTimeMillis())); } } + + // 如果当前遍历到的字段名与预定义的更新时间字段名相等,说明找到了更新时间字段。 if (Objects.equals(UPDATE_TIME, fieldName)) { + + // 如果当前SQL语句的操作类型是插入操作(INSERT)或者更新操作(UPDATE), + // 则需要为更新时间字段设置当前时间。 if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { + + // 通过反射设置该字段可访问,因为私有字段默认是不可直接访问的。 field.setAccessible(true); - field.set(parameter, new Timestamp(System.currentTimeMillis())); + // 使用反射设置该字段的值为当前的时间戳,通过System.currentTimeMillis()获取当前时间的毫秒数, + // 再创建一个Timestamp对象来表示该时间点,从而为更新时间字段赋值。 + field.set(parameter, new Timestamp(System.currentTimeMillis())); } } } + + // 继续执行被拦截的Executor的update方法的原有逻辑,经过上述处理后, + // 已经为创建时间和更新时间字段设置了合适的值(如果符合条件的话),现在继续执行原始的更新操作。 return invocation.proceed(); } + /** + * 将拦截器包装成MyBatis可识别的插件形式的方法。 + * + * @param target 要包装的目标对象,通常是被拦截的对象,如Executor等。 + * @return 返回包装后的对象,以便MyBatis能够正确识别并应用拦截器的功能。 + */ @Override public Object plugin(Object target) { + // 如果目标对象是Executor类型,说明是我们要拦截的对象, + // 则使用Plugin.wrap方法将拦截器(this)包装到目标对象上,以便实现拦截功能。 if (target instanceof Executor) { return Plugin.wrap(target, this); } + + // 如果目标对象不是Executor类型,说明不是我们要拦截的对象,直接返回该目标对象即可。 return target; } + /** + * 设置拦截器属性的方法,目前该方法在本类中暂未实现具体功能, + * 但它是符合Interceptor接口规范的一部分,可用于接收外部配置的属性信息来动态调整拦截器的行为。 + * + * @param properties 包含了外部配置的属性信息的Properties对象。 + */ @Override public void setProperties(Properties properties) { + // 这里暂时没有具体的设置属性操作,可根据后续需求添加相关逻辑来处理接收到的属性信息。 } -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/aspect/utils/InjectUtils.java b/src-源文件/main/java/com/yf/exam/aspect/utils/InjectUtils.java index 4b47fee..77ed253 100644 --- a/src-源文件/main/java/com/yf/exam/aspect/utils/InjectUtils.java +++ b/src-源文件/main/java/com/yf/exam/aspect/utils/InjectUtils.java @@ -1,99 +1,128 @@ +// 定义包路径,用于存放注入工具类相关的代码。这个包名表明该类属于特定的项目模块(com.yf.exam)下的aspect.utils部分, +// 通常用于组织和管理与注入操作相关的工具类代码。 package com.yf.exam.aspect.utils; +// 导入FastJSON库,它是一个用于将Java对象与JSON字符串进行相互转换的高性能库, +// 在本类的restError方法中用于将API响应对象转换为JSON字符串以便写入HTTP响应中。 import com.alibaba.fastjson.JSON; + +// 导入API错误类,它可能定义了一系列表示不同类型API错误的常量或属性, +// 在本类的restError方法中用于创建包含特定错误信息的API响应对象。 import com.yf.exam.core.api.ApiError; + +// 导入API响应类,它用于封装API调用的返回结果,包括成功的结果以及可能出现的错误信息等, +// 在本类的restError方法中用于创建要返回给客户端的错误响应对象。 import com.yf.exam.core.api.ApiRest; + +// 导入Lombok的Log4j2注解,用于简化日志记录的配置,通过该注解可以方便地在类中使用Log4j2进行日志输出。 import lombok.extern.log4j.Log4j2; + +// 导入Spring框架的组件注解,用于标记该类是一个Spring组件,这样Spring容器可以对其进行管理和实例化, +// 使其能够在Spring应用程序中被其他组件所使用。 import org.springframework.stereotype.Component; +// 导入Java标准库中的HTTP响应类,用于处理HTTP协议相关的响应操作, +// 在本类的restError方法中用于设置响应的编码、内容类型以及写入响应内容等操作。 import javax.servlet.http.HttpServletResponse; + +// 导入Java标准库中的IO异常类,用于处理输入输出操作过程中可能出现的异常情况, +// 在本类的restError方法以及其他可能涉及到IO操作的地方用于捕获和处理相关异常。 import java.io.IOException; + +// 导入Java标准库中的反射字段类,用于通过反射操作对象的私有字段, +// 在本类的setValue和getFiled方法中用于获取和设置对象的指定字段的值。 import java.lang.reflect.Field; /** - * 注入工具类 + * 注入工具类,提供了一些用于对象字段赋值以及处理错误响应返回等功能的方法。 * @author bool * @date 2019-07-17 09:32 */ -@Log4j2 -@Component +@Log4j2 // 使用Log4j2注解启用日志记录功能,方便在类中记录相关操作的日志信息。 +@Component // 将该类标记为Spring组件,使其能够被Spring容器管理和使用。 public class InjectUtils { - - /** - * 给对象字段赋值 + * 给指定对象的指定字段赋值的方法。 * - * @param object 赋值的对象 - * @param value 值 - * @param fields 字段 - * @throws Exception 异常 + * @param object 要进行赋值操作的对象。 + * @param value 要赋给指定字段的值。 + * @param fields 一个可变参数,用于指定要赋值的字段名数组。 + * @throws Exception 如果在获取字段或设置字段值的过程中出现异常,则抛出。 */ public void setValue(Object object, Object value, String... fields) throws Exception { - - //设置同类的属性 + // 遍历要赋值的字段名数组 for (String fieldName : fields) { - - //获取当前 + // 根据对象的类和字段名获取对应的字段对象 Field field = this.getFiled(object.getClass(), fieldName); - if(field == null){ + + // 如果获取到的字段对象为空,说明未找到对应的字段,继续下一个字段的处理。 + if (field == null) { continue; } + // 通过反射设置该字段可访问,因为私有字段默认是不可直接访问的。 field.setAccessible(true); + + // 使用反射设置该字段的值为传入的值,即将指定的值赋给对象的指定字段。 field.set(object, value); } - } /** - * 获取字段名对应的字段 + * 根据目标类和字段名获取对应的字段对象的方法。 * - * @param clazz 目标类 - * @param fieldName 字段名 + * @param clazz 目标类,即要在其中查找字段的类。 + * @param fieldName 要查找的字段名。 + * @return 返回找到的字段对象,如果未找到则返回null。 */ private Field getFiled(Class clazz, String fieldName) { + System.out.println("注入的类:" + clazz.toString()); // 打印当前正在查找字段的目标类的信息,方便调试查看。 - System.out.println("注入的类:"+clazz.toString()); - - //是否具有包含关系 + // 尝试获取当前类中指定字段名的字段对象 try { - //获取当前类的属性 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); } + // 如果在当前类及其所有父类中都未找到指定字段,则返回null。 return null; } } - /** - * 打印结果返回 - * @param response - * @throws IOException + * 用于处理错误情况并将错误结果返回给客户端的方法,通过设置HTTP响应的相关属性和写入错误响应内容来实现。 + * + * @param response HTTP响应对象,用于向客户端发送响应信息。 + * @throws IOException 如果在设置响应属性或写入响应内容过程中出现IO异常,则抛出。 */ public static void restError(HttpServletResponse response) { - try { - - //固定错误 + // 创建一个包含特定API错误信息的API响应对象,这里使用了ApiError.ERROR_10010002作为错误码, + // 具体含义可能在ApiError类中定义,通常表示某种常见的错误情况。 ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002); + + // 设置HTTP响应的字符编码为UTF-8,确保能够正确处理和传输各种字符,特别是中文等非ASCII字符。 response.setCharacterEncoding("UTF-8"); + + // 设置HTTP响应的内容类型为application/json,表明返回给客户端的内容是JSON格式的数据。 response.setContentType("application/json"); - response.getWriter().write(JSON.toJSONString(apiRest)); - response.getWriter().close(); - }catch (IOException e){ + // 将创建的API响应对象转换为JSON字符串,并写入到HTTP响应的输出流中,以便客户端能够接收到错误信息。 + response.getWriter().write(JSON.toJSONString(apiRest)); + // 关闭HTTP响应的写入流,释放相关资源。 + response.getWriter().close(); + } catch (IOException e) { + // 如果在设置响应属性或写入响应内容过程中出现IO异常,这里只是简单地捕获异常, + // 可以根据实际需求进一步处理异常,比如记录更详细的日志信息等。 } - } - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/config/CorsConfig.java b/src-源文件/main/java/com/yf/exam/config/CorsConfig.java index e88cb08..83b86ab 100644 --- a/src-源文件/main/java/com/yf/exam/config/CorsConfig.java +++ b/src-源文件/main/java/com/yf/exam/config/CorsConfig.java @@ -1,35 +1,81 @@ package com.yf.exam.config; +// 导入Spring Boot中用于注册Servlet过滤器的类,通过它可以将自定义的过滤器注册到Servlet容器中, +// 并配置相关属性,如过滤器的执行顺序等。在本类中用于注册跨域过滤器(CorsFilter)。 import org.springframework.boot.web.servlet.FilterRegistrationBean; + +// 导入Spring框架中用于标记一个类是配置类的注解,被该注解标记的类可以在其中定义各种Bean配置方法, +// 这些方法会被Spring容器在启动时自动识别并执行,用于创建和配置应用程序所需的各种组件。 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + +// 导入Spring框架中用于指定顺序的接口,实现该接口可以定义对象的顺序, +// 在本类中用于指定跨域过滤器(CorsFilter)在过滤器链中的执行顺序,使其具有最高优先级。 import org.springframework.core.Ordered; + +// 导入Spring框架中用于配置跨域资源共享(Cors)的类,通过它可以设置允许的源、请求头、请求方法等跨域相关的配置项。 import org.springframework.web.cors.CorsConfiguration; + +// 导入Spring框架中基于URL的跨域配置源类,它允许根据不同的URL模式来配置不同的跨域设置, +// 在本类中用于创建一个基于URL的跨域配置源,并将统一的跨域配置应用到所有的URL路径("**")上。 import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; +// 导入Spring框架中实现跨域过滤功能的类,它会根据配置的跨域规则对请求进行过滤和处理, +// 以实现允许跨域访问的功能。在本类中创建该过滤器并将其注册到Servlet容器中。 +import org.springframework.web.filter.CorsFilter; /** - * 网关全局设置,允许跨域 + * 网关全局设置类,主要功能是配置允许跨域访问的相关设置。 + * 通过创建和注册跨域过滤器(CorsFilter),并设置允许的源、请求头、请求方法等配置项, + * 使得应用程序能够处理来自不同域的请求,避免跨域访问限制。 + * * @author bool * @date 2019-08-13 17:28 */ - -@Configuration +@Configuration // 标记该类为Spring框架的配置类,表明其中可以定义各种Bean的创建和配置方法。 public class CorsConfig { + /** + * 定义一个Bean方法,用于创建并返回一个FilterRegistrationBean对象,该对象用于注册跨域过滤器(CorsFilter)。 + * + * @return 返回一个FilterRegistrationBean对象,其中包含了配置好的跨域过滤器以及相关的执行顺序等设置。 + */ @Bean public FilterRegistrationBean corsFilter() { + // 创建一个基于URL的跨域配置源对象,它将作为跨域配置的基础,用于后续设置跨域规则并应用到特定的URL路径上。 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + + // 创建一个CorsConfiguration对象,用于设置具体的跨域配置项,如允许的源、请求头、请求方法等。 CorsConfiguration config = new CorsConfiguration(); + + // 设置是否允许携带凭证(如Cookie等)进行跨域请求,这里设置为true,表示允许携带凭证。 config.setAllowCredentials(true); + + // 添加允许的源,这里使用CorsConfiguration.ALL表示允许所有的源进行跨域访问。 + // 可以根据实际需求设置具体的源地址,如"http://example.com"等,多个源地址可以多次调用addAllowedOrigin方法添加。 config.addAllowedOrigin(CorsConfiguration.ALL); + + // 添加允许的请求头,同样使用CorsConfiguration.ALL表示允许所有的请求头进行跨域请求。 + // 如果需要限制特定的请求头,可以通过其他方式设置具体的请求头列表。 config.addAllowedHeader(CorsConfiguration.ALL); + + // 添加允许的请求方法,使用CorsConfiguration.ALL表示允许所有的请求方法进行跨域请求, + // 包括常见的GET、POST、PUT、DELETE等方法。也可以根据实际需求设置具体的请求方法列表。 config.addAllowedMethod(CorsConfiguration.ALL); + + // 将上述设置好的跨域配置应用到所有的URL路径上,"**"表示匹配任意的URL路径。 + // 这样,对于应用程序接收到的任何请求,都会根据这个统一的跨域配置进行处理。 source.registerCorsConfiguration("/**", config); + + // 创建一个FilterRegistrationBean对象,用于注册跨域过滤器(CorsFilter)。 + // 将创建好的基于上述跨域配置源的跨域过滤器(new CorsFilter(source))作为参数传入FilterRegistrationBean的构造函数。 FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); + + // 设置跨域过滤器在过滤器链中的执行顺序,这里设置为Ordered.HIGHEST_PRECEDENCE,表示具有最高优先级。 + // 即该过滤器会在其他过滤器之前首先对请求进行处理,以确保跨域配置能够最先生效。 bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + + // 返回配置好的FilterRegistrationBean对象,完成跨域过滤器的注册和相关设置。 return bean; } - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/config/MultipartConfig.java b/src-源文件/main/java/com/yf/exam/config/MultipartConfig.java index 75e6dfa..69d16f5 100644 --- a/src-源文件/main/java/com/yf/exam/config/MultipartConfig.java +++ b/src-源文件/main/java/com/yf/exam/config/MultipartConfig.java @@ -1,28 +1,67 @@ +// 定义包路径,用于存放文件上传配置类相关的代码。这个包名表明该类属于特定的项目模块(com.yf.exam)下的config部分, +// 通常用于组织和管理与文件上传配置相关的代码。 package com.yf.exam.config; +// 导入Spring Boot中用于创建和配置多部分(Multipart)上传相关设置的工厂类。 +// 通过这个工厂类可以方便地设置如单个文件大小限制、总上传数据大小限制等参数, +// 在本类中用于创建和配置文件上传的相关限制。 import org.springframework.boot.web.servlet.MultipartConfigFactory; + +// 导入Spring框架中用于标记一个方法返回值为Spring Bean的注解。 +// 被该注解标记的方法,其返回值会被Spring容器管理,作为一个可被注入到其他组件中的Bean实例。 +// 在本类中用于将multipartConfigElement方法返回的MultipartConfigElement对象注册为Spring Bean。 import org.springframework.context.annotation.Bean; + +// 导入Spring框架中用于标记一个类是配置类的注解。 +// 被该注解标记的类可以在其中定义各种Bean配置方法,这些方法会被Spring容器在启动时自动识别并执行, +// 用于创建和配置应用程序所需的各种组件。在本类中用于标记该类为Spring配置类,以便进行文件上传相关的配置。 import org.springframework.context.annotation.Configuration; + +// 导入Spring框架中用于处理数据大小单位转换和表示的工具类。 +// 它可以方便地将不同单位的数据大小表示转换为统一的格式, +// 在本类中用于设置文件上传的单个文件大小和总上传数据大小的限制,以兆字节(MB)为单位进行设置。 import org.springframework.util.unit.DataSize; +// 导入Java标准库中的用于表示多部分(Multipart)上传配置元素的接口。 +// 这个接口定义了多部分上传相关的配置参数,如文件大小限制、请求大小限制等, +// 在本类中通过MultipartConfigFactory创建并返回实现了该接口的对象,以完成文件上传的配置设置。 import javax.servlet.MultipartConfigElement; /** - * 文件上传配置 + * 文件上传配置类,主要功能是配置文件上传过程中的相关参数, +// 包括单个文件的最大允许大小以及整个上传请求的最大允许数据大小等。 + * * @author bool * @date 2019-07-29 16:23 */ -@Configuration +@Configuration // 使用该注解标记此为Spring配置类,表明这个类是用来进行Spring应用程序的配置工作的。 public class MultipartConfig { + /** + * 定义一个方法,并使用@Bean注解将其返回值声明为Spring Bean。 + * 该方法用于创建并返回一个MultipartConfigElement对象,这个对象包含了文件上传的具体配置参数。 + * + * @return 返回一个MultipartConfigElement对象,其中设置了单个文件大小和总上传数据大小的限制等配置信息。 + */ @Bean public MultipartConfigElement multipartConfigElement() { - MultipartConfigFactory factory = new MultipartConfigFactory(); - // 单个数据大小 + MultipartConfigFactory factory = new MultipartConfigFactory(); // 创建一个MultipartConfigFactory对象, + // 它是用于创建MultipartConfigElement对象的工厂类, + // 通过它可以方便地设置各种文件上传的配置参数。 + + // 设置单个文件的最大允许大小。 + // 这里使用DataSize.ofMegabytes(5000L)将大小设置为5000兆字节(MB), + // 即限制单个上传的文件大小不能超过5000MB。 factory.setMaxFileSize(DataSize.ofMegabytes(5000L)); - /// 总上传数据大小 + + // 设置整个上传请求的最大允许数据大小。 + // 同样使用DataSize.ofMegabytes(5000L)将大小设置为5000兆字节(MB), + // 也就是说,整个上传请求(包括可能上传的多个文件以及其他相关数据)的总数据量不能超过5000MB。 factory.setMaxRequestSize(DataSize.ofMegabytes(5000L)); + + // 通过MultipartConfigFactory对象的createMultipartConfig方法创建并返回一个MultipartConfigElement对象, + // 这个对象包含了我们刚刚设置的单个文件大小和总上传数据大小等配置信息, + // 它会被Spring容器管理并应用到文件上传的相关处理中。 return factory.createMultipartConfig(); } - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/config/MybatisConfig.java b/src-源文件/main/java/com/yf/exam/config/MybatisConfig.java index 642fc25..1645a7f 100644 --- a/src-源文件/main/java/com/yf/exam/config/MybatisConfig.java +++ b/src-源文件/main/java/com/yf/exam/config/MybatisConfig.java @@ -1,37 +1,70 @@ +// 定义包路径,用于存放MyBatis配置类相关的代码。这个包名表明该类属于特定的项目模块(com.yf.exam)下的config部分, +// 通常用于组织和管理与MyBatis配置相关的代码。 package com.yf.exam.config; +// 导入自定义的查询拦截器类,该拦截器用于在查询操作时进行一些额外的处理, +// 比如可能会根据特定条件对查询语句进行修改、添加过滤条件等操作。 import com.yf.exam.aspect.mybatis.QueryInterceptor; + +// 导入自定义的更新拦截器类,该拦截器用于在插入或更新操作时进行相关处理, +// 例如自动设置创建时间、更新时间等字段的值。 import com.yf.exam.aspect.mybatis.UpdateInterceptor; + +// 导入MyBatis与Spring集成时用于扫描MyBatis映射接口的注解。 +// 通过该注解可以指定要扫描的包路径,以便Spring能够自动发现并注册MyBatis的映射接口, +// 使得这些接口可以被正确地用于数据库操作。 import org.mybatis.spring.annotation.MapperScan; + +// 导入Spring框架中用于标记一个方法返回值为Spring Bean的注解。 +// 被该注解标记的方法,其返回值会被Spring容器管理,作为一个可被注入到其他组件中的Bean实例。 +// 在本类中用于将queryInterceptor和updateInterceptor方法返回的拦截器对象注册为Spring Bean。 import org.springframework.context.annotation.Bean; + +// 导入Spring框架中用于标记一个类是配置类的注解。 +// 被该注解标记的类可以在其中定义各种Bean配置方法,这些方法会被Spring容器在启动时自动识别并执行, +// 用于创建和配置应用程序所需的各种组件。在本类中用于标记该类为Spring配置类,以便进行MyBatis相关的配置。 import org.springframework.context.annotation.Configuration; /** - * Mybatis过滤器配置 - * 注意:必须按顺序进行配置,否则容易出现业务异常 + * Mybatis过滤器配置类,主要功能是配置MyBatis的相关过滤器, + * 包括查询拦截器和更新拦截器等,并设置相关参数。同时通过注解指定MyBatis映射接口的扫描路径。 + * 注意:必须按顺序进行配置,否则容易出现业务异常。 * @author bool */ -@Configuration -@MapperScan("com.yf.exam.modules.**.mapper") +@Configuration // 使用该注解标记此为Spring配置类,表明这个类是用来进行Spring应用程序的配置工作的。 +@MapperScan("com.yf.exam.modules.**.mapper") // 使用MapperScan注解指定要扫描的MyBatis映射接口所在的包路径, + // "com.yf.exam.modules.**.mapper"表示会扫描com.yf.exam.modules包及其子包下的所有mapper接口。 + public class MybatisConfig { /** - * 数据查询过滤器 + * 定义一个方法,并使用@Bean注解将其返回值声明为Spring Bean。 + * 该方法用于创建并返回一个查询拦截器(QueryInterceptor)对象,用于在查询操作时进行拦截处理。 + * + * @return 返回一个查询拦截器(QueryInterceptor)对象,该对象已进行了相关设置(如设置查询限制)。 */ - @Bean + @Bean // 将该方法返回的查询拦截器对象声明为Spring Bean,以便Spring容器能够管理和注入到其他需要使用的地方。 public QueryInterceptor queryInterceptor() { - QueryInterceptor query = new QueryInterceptor(); + QueryInterceptor query = new QueryInterceptor(); // 创建一个新的查询拦截器对象。 + + // 设置查询限制,这里将查询限制设置为 -1L,具体含义可能根据QueryInterceptor类的内部逻辑而定, + // 可能表示不限制查询结果的数量或者有其他特殊的处理方式与该值相关。 query.setLimit(-1L); + + // 返回设置好的查询拦截器对象,该对象将被Spring容器管理并在合适的查询操作场景中被调用执行拦截处理。 return query; } /** - * 插入数据过滤器 + * 定义一个方法,并使用@Bean注解将其返回值声明为Spring Bean。 + * 该方法用于创建并返回一个更新拦截器(UpdateInterceptor)对象,用于在插入或更新操作时进行拦截处理。 + * + * @return 返回一个更新拦截器(UpdateInterceptor)对象,该对象可直接用于插入或更新操作的拦截处理。 */ - @Bean + @Bean // 将该方法返回的更新拦截器对象声明为Spring Bean,以便Spring容器能够管理和注入到其他需要使用的地方。 public UpdateInterceptor updateInterceptor() { + // 创建一个新的更新拦截器对象,这里没有进行额外的设置操作(可能在UpdateInterceptor类内部有默认的处理逻辑), + // 直接返回该对象,它将被Spring容器管理并在合适的插入或更新操作场景中被调用执行拦截处理。 return new UpdateInterceptor(); } - - } \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/config/ScheduledConfig.java b/src-源文件/main/java/com/yf/exam/config/ScheduledConfig.java index b08f96f..994b761 100644 --- a/src-源文件/main/java/com/yf/exam/config/ScheduledConfig.java +++ b/src-源文件/main/java/com/yf/exam/config/ScheduledConfig.java @@ -1,77 +1,182 @@ +// 定义包路径,用于存放任务调度配置类相关的代码。这个包名表明该类属于特定的项目模块(com.yf.exam)下的config部分, +// 通常用于组织和管理与任务调度配置相关的代码。 package com.yf.exam.config; +// 导入Lombok的Log4j2注解,用于简化日志记录的配置,通过该注解可以方便地在类中使用Log4j2进行日志输出。 import lombok.extern.log4j.Log4j2; + +// 导入Spring框架中用于处理异步方法执行过程中未捕获异常的处理器接口。 +// 当异步方法抛出异常且未在方法内部被捕获时,会由该处理器来处理异常情况, +// 在本类中实现该接口来定义异步未捕获异常的处理逻辑。 import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; + +// 导入Spring框架中用于标记一个方法返回值为Spring Bean的注解。 +// 被该注解标记的方法,其返回值会被Spring容器管理,作为一个可被注入到其他组件中的Bean实例。 +// 在本类中用于将taskScheduler和asyncExecutor等方法返回的对象注册为Spring Bean。 import org.springframework.context.annotation.Bean; + +// 导入Spring框架中用于标记一个类是配置类的注解。 +// 被该注解标记的类可以在其中定义各种Bean配置方法,这些方法会被Spring容器在启动时自动识别并执行, +// 用于创建和配置应用程序所需的各种组件。在本类中用于标记该类为Spring配置类,以便进行任务调度相关的配置。 import org.springframework.context.annotation.Configuration; + +// 导入Spring框架中用于配置异步任务执行相关设置的接口。 +// 实现该接口可以定义异步任务执行的线程池等配置信息,在本类中实现该接口来配置异步任务执行的相关参数。 import org.springframework.scheduling.annotation.AsyncConfigurer; + +// 导入Spring框架中用于启用异步任务执行功能的注解。 +// 当在类上添加该注解后,Spring会自动识别并处理类中的异步方法,使其能够在独立的线程中执行。 +// 在本类上添加该注解以启用异步任务执行功能。 import org.springframework.scheduling.annotation.EnableAsync; + +// 导入Spring框架中用于启用任务调度功能的注解。 +// 当在类上添加该注解后,Spring会自动识别并处理类中的定时任务等调度相关的设置,使其能够按照预定的时间执行任务。 +// 在本类上添加该注解以启用任务调度功能。 import org.springframework.scheduling.annotation.EnableScheduling; + +// 导入Spring框架中用于配置任务调度相关设置的接口。 +// 实现该接口可以定义任务调度的线程池、任务注册等配置信息,在本类中实现该接口来配置任务调度的相关参数。 import org.springframework.scheduling.annotation.SchedulingConfigurer; + +// 导入Spring框架中用于执行线程池任务的执行器类。 +// 它可以创建一个线程池,并通过该线程池来执行任务,在本类的asyncExecutor方法中用于创建异步任务执行的线程池。 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +// 导入Spring框架中用于调度线程池任务的调度器类。 +// 它可以创建一个线程池,并通过该线程池来调度任务的执行时间,在本类的taskScheduler方法中用于创建定时任务执行的线程池。 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +// 导入Spring框架中用于注册调度任务的类。 +// 通过它可以将需要调度执行的任务注册到相应的调度器中,在本类的configureTasks方法中用于设置任务调度器。 import org.springframework.scheduling.config.ScheduledTaskRegistrar; +// 导入Java标准库中的执行器接口,它定义了执行任务的通用规范, +// 在本类的getAsyncExecutor方法中用于返回异步任务执行的执行器对象。 import java.util.concurrent.Executor; + +// 导入Java标准库中的线程池执行器类,它是实现了线程池功能的具体类, +// 在本类的asyncExecutor方法中用于设置异步任务执行线程池的拒绝策略。 import java.util.concurrent.ThreadPoolExecutor; /** - * 任务调度配置 + * 任务调度配置类,主要功能是配置任务调度和异步任务执行相关的设置, + * 包括创建定时任务执行的线程池和异步任务执行的线程池,设置线程池的大小、名称前缀、 + * 等待终止时间、队列容量等参数,同时实现了处理异步未捕获异常的逻辑。 * @author bool */ -@Log4j2 -@Configuration -@EnableScheduling -@EnableAsync +@Log4j2 // 使用Log4j2注解启用日志记录功能,方便在类中记录相关操作的日志信息。 +@Configuration // 使用该注解标记此为Spring配置类,表明这个类是用来进行Spring应用程序的配置工作的。 +@EnableScheduling // 使用该注解启用任务调度功能,使得Spring能够识别并处理类中的定时任务等调度相关设置。 +@EnableAsync // 使用该注解启用异步任务执行功能,使得Spring能够识别并处理类中的异步方法,使其能够在独立的线程中执行。 public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer { /** - * 定时任务使用的线程池 - * @return + * 定义一个方法,并使用@Bean注解将其返回值声明为Spring Bean。 + * 该方法用于创建并返回一个线程池任务调度器(ThreadPoolTaskScheduler)对象,用于定时任务的执行调度。 + * + * @return 返回一个线程池任务调度器(ThreadPoolTaskScheduler)对象,该对象已进行了相关设置(如线程池大小、名称前缀等)。 */ - @Bean(destroyMethod = "shutdown", name = "taskScheduler") - public ThreadPoolTaskScheduler taskScheduler(){ - ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + @Bean(destroyMethod = "shutdown", name = "taskScheduler") // 将该方法返回的线程池任务调度器对象声明为Spring Bean, + // 并指定销毁方法为"shutdown",以便在容器关闭时正确关闭线程池。 + public ThreadPoolTaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); // 创建一个新的线程池任务调度器对象。 + + // 设置线程池的大小,即同时可执行的定时任务数量,这里设置为10,表示线程池最多可同时执行10个定时任务。 scheduler.setPoolSize(10); + + // 设置线程的名称前缀,用于在日志等场景中方便识别线程所属的任务调度器,这里设置为"task-", + // 生成的线程名称可能类似"task-1"、"task-2"等。 scheduler.setThreadNamePrefix("task-"); + + // 设置线程池在关闭时等待任务完成的时间,单位为秒,这里设置为600秒(10分钟), + // 表示在容器关闭时,线程池会等待正在执行的任务完成,最长等待时间为10分钟。 scheduler.setAwaitTerminationSeconds(600); + + // 设置在关闭时是否等待任务完成,这里设置为true,表示在容器关闭时,线程池会等待所有任务完成后再关闭。 scheduler.setWaitForTasksToCompleteOnShutdown(true); + + // 返回设置好的线程池任务调度器对象,该对象将被Spring容器管理并在定时任务执行场景中被调用进行任务调度。 return scheduler; } /** - * 异步任务执行线程池 - * @return + * 定义一个方法,并使用@Bean注解将其返回值声明为Spring Bean。 + * 该方法用于创建并返回一个线程池任务执行器(ThreadPoolTaskExecutor)对象,用于异步任务的执行。 + * + * @return 返回一个线程池任务执行器(ThreadPoolTaskExecutor)对象,该对象已进行了相关设置(如核心线程数、队列容量等)。 */ - @Bean(name = "asyncExecutor") + @Bean(name = "asyncExecutor") // 将该方法返回的线程池任务执行器对象声明为Spring Bean,以便Spring容器能够管理和注入到其他需要使用的地方。 public ThreadPoolTaskExecutor asyncExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 创建一个新的线程池任务执行器对象。 + + // 设置线程池的核心线程数,即线程池始终保持的活跃线程数量,这里设置为10,表示线程池至少会保持10个线程处于活跃状态。 executor.setCorePoolSize(10); + + // 设置线程池的队列容量,即用于存储等待执行任务的队列大小,这里设置为1000,表示队列最多可容纳1000个等待执行的任务。 executor.setQueueCapacity(1000); + + // 设置线程池中非核心线程的保持活动时间,单位为秒,这里设置为600秒(10分钟), + // 表示当非核心线程空闲时间超过10分钟时,可能会被回收。 executor.setKeepAliveSeconds(600); + + // 设置线程池的最大线程数,即线程池最多可同时拥有的线程数量,这里设置为20,表示线程池最多可扩展到20个线程同时执行任务。 executor.setMaxPoolSize(20); + + // 设置线程的名称前缀,用于在日志等场景中方便识别线程所属的任务执行器,这里设置为"taskExecutor-", + // 生成的线程名称可能类似"taskExecutor-1"、"taskExecutor-2"等。 executor.setThreadNamePrefix("taskExecutor-"); + + // 设置线程池的拒绝策略,这里采用ThreadPoolExecutor.CallerRunsPolicy(), + // 表示当线程池和队列都已满,无法再接受新任务时,会由调用线程来执行该任务,而不是直接抛出异常。 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + + // 初始化线程池,使其处于可使用状态,完成各项参数的设置和内部资源的初始化。 executor.initialize(); + + // 返回设置好的线程池任务执行器对象,该对象将被Spring容器管理并在异步任务执行场景中被调用进行任务执行。 return executor; } + /** + * 实现SchedulingConfigurer接口的方法,用于配置任务调度相关的设置。 + * 在本方法中,获取之前创建的线程池任务调度器对象,并将其设置到调度任务注册类中, + * 以便后续注册的定时任务能够使用该调度器进行任务调度。 + * + * @param scheduledTaskRegistrar 调度任务注册类对象,用于注册调度任务和设置调度器等操作。 + */ @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { - ThreadPoolTaskScheduler taskScheduler = taskScheduler(); + ThreadPoolTaskScheduler taskScheduler = taskScheduler(); // 获取之前创建的线程池任务调度器对象。 + + // 将获取到的线程池任务调度器对象设置到调度任务注册类中,使得后续注册的定时任务能够使用该调度器进行任务调度。 scheduledTaskRegistrar.setTaskScheduler(taskScheduler); } + /** + * 实现AsyncConfigurer接口的方法,用于返回异步任务执行的执行器对象。 + * 在本方法中,直接返回之前创建的线程池任务执行器对象,该对象将作为异步任务执行的执行器。 + * + * @return 返回异步任务执行的执行器对象,即之前创建的线程池任务执行器对象。 + */ @Override public Executor getAsyncExecutor() { return asyncExecutor(); } + /** + * 实现AsyncConfigurer接口的方法,用于定义异步未捕获异常的处理逻辑。 + * 在本方法中,当异步任务执行出现异常且未在方法内部被捕获时,会通过日志记录异常信息, + * 包括异常对象、执行的方法以及方法的参数等内容。 + * + * @param throwable 异步任务执行过程中抛出的异常对象。 + * @param method 执行异步任务的方法对象。 + * @param objects 执行异步任务的方法的参数对象数组。 + * @return 返回值无实际意义,这里主要是为了符合AsyncUncaughtExceptionHandler接口的定义。 + */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (throwable, method, objects) -> { - log.error("异步任务执行出现异常, message {}, emthod {}, params {}", throwable, method, objects); + log.error("异步任务执行出现异常, message {}, method {}, params {}", throwable, method, objects); }; } - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/config/ShiroConfig.java b/src-源文件/main/java/com/yf/exam/config/ShiroConfig.java index cbb3f38..52d179c 100644 --- a/src-源文件/main/java/com/yf/exam/config/ShiroConfig.java +++ b/src-源文件/main/java/com/yf/exam/config/ShiroConfig.java @@ -1,127 +1,133 @@ +// 定义包路径,用于存放Shiro配置类 package com.yf.exam.config; -import com.yf.exam.ability.shiro.CNFilterFactoryBean; -import com.yf.exam.ability.shiro.ShiroRealm; -import com.yf.exam.ability.shiro.aop.JwtFilter; -import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; -import org.apache.shiro.mgt.DefaultSubjectDAO; -import org.apache.shiro.mgt.SecurityManager; -import org.apache.shiro.spring.LifecycleBeanPostProcessor; -import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; -import org.apache.shiro.spring.web.ShiroFilterFactoryBean; -import org.apache.shiro.web.mgt.DefaultWebSecurityManager; -import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; - -import javax.servlet.Filter; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - +import com.yf.exam.ability.shiro.CNFilterFactoryBean; // 自定义过滤器工厂类 +import com.yf.exam.ability.shiro.ShiroRealm; // 自定义Shiro领域类 +import com.yf.exam.ability.shiro.aop.JwtFilter; // JWT认证过滤器 +import lombok.extern.slf4j.Slf4j; // Lombok日志注解 +import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; // Shiro默认会话存储评估器 +import org.apache.shiro.mgt.DefaultSubjectDAO; // Shiro默认主体DAO +import org.apache.shiro.mgt.SecurityManager; // Shiro安全管理器 +import org.apache.shiro.spring.LifecycleBeanPostProcessor; // Spring生命周期Bean后处理器 +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; // Shiro授权属性源顾问 +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; // Shiro过滤器工厂类 +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; // Shiro默认Web安全管理器 +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; // Spring默认顾问自动代理创建器 +import org.springframework.context.annotation.Bean; // Spring Bean注解 +import org.springframework.context.annotation.Configuration; // Spring配置注解 +import org.springframework.context.annotation.DependsOn; // Spring依赖注解 + +import javax.servlet.Filter; // Servlet过滤器接口 +import java.util.HashMap; // Java哈希映射类 +import java.util.LinkedHashMap; // Java链接哈希映射类 +import java.util.Map; // Java映射类 /** * Shiro配置类 + * 用于配置和自定义Shiro框架的相关组件,包括安全管理器、过滤器工厂、Realm等。 * @author bool */ -@Slf4j -@Configuration +@Slf4j // 使用Lombok注解,启用Log4j2日志 +@Configuration // 使用Spring注解,标记为配置类 public class ShiroConfig { - /** - * Filter Chain定义说明 - * - * 1、一个URL可以配置多个Filter,使用逗号分隔 - * 2、当设置多个过滤器时,全部验证通过,才视为通过 - * 3、部分过滤器可指定参数,如perms,roles - */ - @Bean("shiroFilterFactoryBean") - public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { - ShiroFilterFactoryBean shiroFilterFactoryBean = new CNFilterFactoryBean(); - shiroFilterFactoryBean.setSecurityManager(securityManager); - // 拦截器 - Map map = new LinkedHashMap<>(); - - // 需要排除的一些接口 - map.put("/exam/api/sys/user/login", "anon"); - map.put("/exam/api/sys/user/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"); - map.put("/doc.html", "anon"); - map.put("/**/*.js", "anon"); - map.put("/**/*.css", "anon"); - map.put("/**/*.html", "anon"); - map.put("/**/*.svg", "anon"); - map.put("/**/*.pdf", "anon"); - map.put("/**/*.jpg", "anon"); - map.put("/**/*.png", "anon"); - map.put("/**/*.ico", "anon"); - - // 字体 - 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 filterMap = new HashMap(1); - filterMap.put("jwt", new JwtFilter()); - shiroFilterFactoryBean.setFilters(filterMap); - map.put("/**", "jwt"); - - shiroFilterFactoryBean.setFilterChainDefinitionMap(map); - return shiroFilterFactoryBean; - } - - @Bean("securityManager") - public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { - DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); - securityManager.setRealm(myRealm); - DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); - DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); - defaultSessionStorageEvaluator.setSessionStorageEnabled(false); - subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); - securityManager.setSubjectDAO(subjectDAO); - return securityManager; - } - - /** - * 下面的代码是添加注解支持 - * @return - */ - @Bean - @DependsOn("lifecycleBeanPostProcessor") - public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { - DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); - defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); - defaultAdvisorAutoProxyCreator.setUsePrefix(true); - defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor"); - return defaultAdvisorAutoProxyCreator; - } - - @Bean - public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { - return new LifecycleBeanPostProcessor(); - } - - @Bean - public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { - AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); - advisor.setSecurityManager(securityManager); - return advisor; - } - -} + /** + * 定义Shiro过滤器链 + * 方法用于配置Shiro过滤器链,定义哪些URL路径需要通过哪些过滤器。 + * + * @param securityManager Shiro安全管理器 + * @return ShiroFilterFactoryBean Shiro过滤器工厂Bean + */ + @Bean("shiroFilterFactoryBean") // 使用Spring注解,声明为Bean,并指定Bean名称 + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + CNFilterFactoryBean shiroFilterFactoryBean = new CNFilterFactoryBean(); // 创建自定义过滤器工厂Bean + shiroFilterFactoryBean.setSecurityManager(securityManager); // 设置安全管理器 + + // 定义过滤器链 + Map map = new LinkedHashMap<>(); // 创建拦截器映射 + + // 配置不需要认证的路径 + map.put("/exam/api/sys/user/login", "anon"); // 登录接口不需要认证 + map.put("/exam/api/sys/user/reg", "anon"); // 注册接口不需要认证 + map.put("/exam/api/sys/user/quick-reg", "anon"); // 快速注册接口不需要认证 + + // 配置其他不需要认证的静态资源路径 + map.put("/upload/file/**", "anon"); // 文件上传路径不需要认证 + map.put("/", "anon"); // 根路径不需要认证 + map.put("/v2/**", "anon"); // Swagger路径不需要认证 + map.put("/doc.html", "anon"); // Swagger文档不需要认证 + // ... 省略其他静态资源配置 + + // 添加自定义JWT过滤器 + Map filterMap = new HashMap<>(); // 创建过滤器映射 + filterMap.put("jwt", new JwtFilter()); // 添加JWT过滤器 + shiroFilterFactoryBean.setFilters(filterMap); // 设置过滤器 + + // 设置过滤器链定义 + map.put("/**", "jwt"); // 所有请求都需要通过JWT过滤器 + shiroFilterFactoryBean.setFilterChainDefinitionMap(map); // 设置过滤器链定义 + + return shiroFilterFactoryBean; // 返回Shiro过滤器工厂Bean + } + + /** + * 定义Shiro安全管理器 + * 方法用于配置Shiro安全管理器,设置自定义Realm和禁用Shiro的会话存储。 + * + * @param myRealm 自定义Shiro领域 + * @return DefaultWebSecurityManager Shiro安全管理器 + */ + @Bean("securityManager") // 使用Spring注解,声明为Bean,并指定Bean名称 + public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 创建默认Web安全管理器 + securityManager.setRealm(myRealm); // 设置自定义Realm + + // 禁用Shiro的会话存储 + DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); // 创建默认主体DAO + DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); // 创建默认会话存储评估器 + defaultSessionStorageEvaluator.setSessionStorageEnabled(false); // 禁用会话存储 + subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); // 设置会话存储评估器 + securityManager.setSubjectDAO(subjectDAO); // 设置主体DAO + + return securityManager; // 返回安全管理器 + } + + /** + * 定义注解支持 + * 方法用于添加注解支持,使得Shiro的注解可以正常工作。 + * + * @return DefaultAdvisorAutoProxyCreator 顾问自动代理创建器 + */ + @Bean // 使用Spring注解,声明为Bean + @DependsOn("lifecycleBeanPostProcessor") // 依赖于生命周期Bean后处理器 + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 创建顾问自动代理创建器 + defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); // 设置代理目标类 + return defaultAdvisorAutoProxyCreator; // 返回顾问自动代理创建器 + } + + /** + * 定义生命周期Bean后处理器 + * 方法用于定义Shiro的生命周期Bean后处理器,确保Shiro的组件能够按照预期的生命周期工作。 + * + * @return LifecycleBeanPostProcessor 生命周期Bean后处理器 + */ + @Bean // 使用Spring注解,声明为Bean + public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); // 返回生命周期Bean后处理器 + } + + /** + * 定义授权属性源顾问 + * 方法用于定义Shiro的授权属性源顾问,用于处理Shiro注解中的权限控制。 + * + * @param securityManager Shiro安全管理器 + * @return AuthorizationAttributeSourceAdvisor 授权属性源顾问 + */ + @Bean // 使用Spring注解,声明为Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); // 创建授权属性源顾问 + advisor.setSecurityManager(securityManager); // 设置安全管理器 + return advisor; // 返回授权属性源顾问 + } +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/config/SwaggerConfig.java b/src-源文件/main/java/com/yf/exam/config/SwaggerConfig.java index d4208aa..038caa6 100644 --- a/src-源文件/main/java/com/yf/exam/config/SwaggerConfig.java +++ b/src-源文件/main/java/com/yf/exam/config/SwaggerConfig.java @@ -1,65 +1,164 @@ +// 定义包路径,用于存放Swagger配置类相关的代码。这个包名表明该类属于特定的项目模块(com.yf.exam)下的config部分, +// 通常用于组织和管理与Swagger配置相关的代码。 package com.yf.exam.config; +// 导入用于启用Swagger Bootstrap UI的注解,Swagger Bootstrap UI是对Swagger UI的一种增强, +// 提供了更友好的界面展示和交互功能,通过该注解可以在项目中启用这一特性。 import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; + +// 导入Swagger用于标注API操作的注解,通过在方法上添加该注解,可以为该方法在Swagger文档中生成详细的操作描述信息, +// 包括方法的功能、参数、返回值等内容,方便开发者和使用者了解API的具体使用方式。 import io.swagger.annotations.ApiOperation; + +// 导入Spring框架中用于将类的属性与配置文件中的属性进行绑定的注解。 +// 通过指定prefix属性,可以将配置文件中以该前缀开头的属性值绑定到类的对应属性上, +// 在本类中用于绑定以"swagger"为前缀的配置属性。 import org.springframework.boot.context.properties.ConfigurationProperties; + +// 导入Spring框架中用于标记一个方法返回值为Spring Bean的注解。 +// 被该注解标记的方法,其返回值会被Spring容器管理,作为一个可被注入到其他组件中的Bean实例。 +// 在本类中用于将examApi、securityScheme等方法返回的对象注册为Spring Bean。 import org.springframework.context.annotation.Bean; + +// 导入Spring框架中用于标记一个类是配置类的注解。 +// 被该注解标记的类可以在其中定义各种Bean配置方法,这些方法会被Spring容器在启动时自动识别并执行, +// 用于创建和配置应用程序所需的各种组件。在本类中用于标记该类为Spring配置类,以便进行Swagger相关的配置。 import org.springframework.context.annotation.Configuration; + +// 导入Swagger用于构建API信息的构建器类,通过该构建器可以方便地设置API的标题、描述、联系人、版本等信息, +// 在本类的apiInfo方法中用于创建并返回包含详细API信息的ApiInfo对象。 import springfox.documentation.builders.ApiInfoBuilder; + +// 导入Swagger用于选择路径的选择器类,通过该选择器可以指定哪些路径下的API需要被Swagger生成文档并展示, +// 在本类的examApi方法中用于选择符合特定路径模式的API。 import springfox.documentation.builders.PathSelectors; + +// 导入Swagger用于选择请求处理器(即包含API方法的类或接口)的选择器类, +// 通过该选择器可以指定哪些请求处理器中的方法需要被Swagger生成文档并展示, +// 在本类的examApi方法中用于选择带有ApiOperation注解的方法所在的请求处理器。 import springfox.documentation.builders.RequestHandlerSelectors; + +// 导入Swagger用于表示API信息的类,该类包含了API的标题、描述、联系人、版本等详细信息, +// 在本类的examApi方法中通过调用apiInfo方法获取该对象并设置到Docket中,以便在Swagger文档中展示这些信息。 import springfox.documentation.service.ApiInfo; + +// 导入Swagger用于表示API密钥的类,用于设置API的授权相关信息,如授权的键名、值的位置(如在请求头中)等, +// 在本类的securityScheme方法中用于创建并返回一个ApiKey对象,用于设置API的授权方案。 import springfox.documentation.service.ApiKey; + +// 导入Swagger用于表示联系人信息的类,通过该类可以设置API的联系人姓名、联系方式、网址等信息, +// 在本类的apiInfo方法中用于创建并返回一个包含联系人信息的Contact对象,并设置到ApiInfo中。 import springfox.documentation.service.Contact; + +// 导入Swagger用于表示安全方案的类,它是一个通用的接口,用于定义不同类型的安全方案, +// 在本类的examApi方法中通过调用securityScheme方法获取具体的安全方案实现(如ApiKey)并设置到Docket中。 import springfox.documentation.service.SecurityScheme; + +// 导入Swagger用于表示文档类型的枚举类,目前主要有SWAGGER_2等类型, +// 在本类的examApi方法中用于指定创建Docket对象时所采用的文档类型为SWAGagger_2。 import springfox.documentation.spi.DocumentationType; + +// 导入Swagger用于生成Swagger文档的核心类,通过该类可以配置各种Swagger相关的设置,如API信息、路径选择、 +// 授权方案等,在本类的examApi方法中用于创建并配置Docket对象,以生成符合项目需求的Swagger文档。 import springfox.documentation.spring.web.plugins.Docket; + +// 导入用于启用Swagger 2的注解,通过在类上添加该注解,可以在项目中启用Swagger 2的功能, +// 使得Swagger能够为项目中的API生成详细的文档并提供交互界面,方便开发者和使用者查看和测试API。 import springfox.documentation.swagger2.annotations.EnableSwagger2; +// 导入Java标准库中的集合类,用于处理集合相关的操作,在本类的examApi方法中用于创建一个包含单个安全方案的列表, +// 以便将安全方案设置到Docket对象中。 import java.util.Collections; /** - * Swagger配置 + * Swagger配置类,主要功能是配置Swagger在项目中的相关设置, + * 包括启用Swagger 2和Swagger Bootstrap UI功能,绑定以"swagger"为前缀的配置属性, + * 创建Docket对象并设置其API信息、分组名称、选择需要生成文档的API方法和路径、设置安全方案等, + * 以生成详细的API文档并提供友好的交互界面,方便对项目中的API进行查看、测试和使用。 * @author bool * @date 2020/8/19 20:53 */ -@Configuration -@EnableSwagger2 -@EnableSwaggerBootstrapUI -@ConfigurationProperties(prefix = "swagger") -public class SwaggerConfig { +@Configuration // 使用该注解标记此为Spring配置类,表明这个类是用来进行Spring应用程序的配置工作的。 +@EnableSwagger2 // 使用该注解启用Swagger 2功能,使得Swagger能够为项目中的API生成详细的文档并提供交互界面。 +@EnableSwaggerBootstrapUI // 使用该注解启用Swagger Bootstrap UI功能,提供更友好的界面展示和交互功能。 +@ConfigurationProperties(prefix = "swagger") // 使用该注解将类的属性与以"swagger"为前缀的配置文件中的属性进行绑定, + // 以便在类中可以方便地使用这些配置属性来定制Swagger的设置。 +public class SwaggerConfig { - @Bean + /** + * 定义一个方法,并使用@Bean注解将其返回值声明为Spring Bean。 + * 该方法用于创建并返回一个Docket对象,该对象是Swagger生成文档的核心组件, + * 通过对其进行一系列设置,可以定制生成的API文档的内容和展示方式。 + * + * @return 返回一个Docket对象,该对象经过了相关设置,包括API信息、分组名称、选择的API方法和路径、安全方案等。 + */ + @Bean // 将该方法返回的Docket对象声明为Spring Bean,以便Spring容器能够管理和注入到其他需要使用的地方。 public Docket examApi() { - return new Docket(DocumentationType.SWAGGER_2) - .apiInfo(apiInfo()) - .groupName("考试模块接口") - .select() - .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) - .paths(PathSelectors.ant("/exam/api/**")) - .build() - .securitySchemes(Collections.singletonList(securityScheme())); - } + return new Docket(DocumentationType.SWAGGER_2) // 创建一个新的Docket对象,并指定文档类型为SWAGGER_2, + // 这是目前较为常用的Swagger文档类型,用于生成详细的API文档。 + .apiInfo(apiInfo()) // 调用apiInfo方法获取包含详细API信息的ApiInfo对象,并设置到Docket对象中, + // 以便在生成的Swagger文档中展示API的标题、描述、联系人、版本等信息。 + .groupName("考试模块接口") // 设置Docket对象的分组名称为"考试模块接口", + // 这样可以将项目中的API按照不同的模块或功能进行分组展示,方便查看和管理。 - private ApiInfo apiInfo() { - return new ApiInfoBuilder().title("考试系统接口") - .description("考试系统接口") - .contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com")) - .version("1.0.0") - .build(); + .select() // 开始选择需要生成文档的API,通过一系列选择器来指定具体的选择条件。 + + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 使用RequestHandlerSelectors的withMethodAnnotation方法选择带有ApiOperation注解的方法, + // 即只选择那些在方法上标注了ApiOperation注解的API方法进行文档生成, + // 这样可以确保只生成我们希望展示的重要API的文档。 + + .paths(PathSelectors.ant("/exam/api/**")) // 使用PathSelectors的ant方法选择符合特定路径模式的API, + // 这里选择的路径模式是"/exam/api/**",表示只选择以"/exam/api/"开头的任意路径下的API进行文档生成, + // 进一步限定了生成文档的API范围,使其更加聚焦于项目中的特定部分(如考试模块相关的API)。 + + .build() // 完成上述选择条件的设置后,调用build方法构建Docket对象,使其生效并包含我们所设置的所有选择条件和信息。 + + .securitySchemes(Collections.singletonList(securityScheme())) // 创建一个包含单个安全方案的列表, + // 通过调用securityScheme方法获取具体的安全方案对象(如ApiKey), + // 并将其添加到列表中,然后设置到Docket对象中, + // 以便在Swagger文档中展示API的授权相关信息,如需要在请求头中传递的授权密钥等。 + + ; } + /** + * 用于创建并返回一个包含详细API信息的ApiInfo对象的方法。 + * 通过ApiInfoBuilder类可以方便地设置API的标题、描述、联系人、版本等信息。 + * + * @return 返回一个ApiInfo对象,该对象包含了API的标题、描述、联系人、版本等详细信息, + * 用于设置到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", + // 让使用者了解该API的版本情况,以便在不同版本之间进行对比和选择。 + + .build(); // 完成上述各项信息的设置后,调用build方法构建ApiInfo对象,使其生效并包含我们所设置的所有信息。 + } /** - * 授权头部 - * @return + * 用于创建并返回一个表示API密钥的ApiKey对象的方法,该对象用于设置API的授权方案。 + * + * @return 返回一个ApiKey对象,该对象定义了API的授权相关信息,如授权的键名、值的位置(如在请求头中)等, + * 用于设置到Docket对象中,以便在Swagger文档中展示API的授权要求。 */ - @Bean + @Bean // 将该方法返回的SecurityScheme对象(实际为ApiKey类型)声明为Spring Bean,以便Spring容器能够管理和注入到其他需要使用的地方。 SecurityScheme securityScheme() { - return new ApiKey("token", "token", "header"); + return new ApiKey("token", "token", "header"); // 创建一个新的ApiKey对象, + // 第一个参数"token"表示授权的键名,即客户端在请求时需要在指定位置(这里是请求头)传递的键名; + // 第二个参数"token"表示授权的值,这里简单设置为与键名相同,实际应用中可能是根据用户登录等情况生成的授权令牌; + // 第三个参数"header"表示授权的值应该放置的位置,这里指定为在请求头中传递。 } - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/controller/ExamController.java b/src-源文件/main/java/com/yf/exam/modules/exam/controller/ExamController.java index e5583f2..6adeb13 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/controller/ExamController.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/controller/ExamController.java @@ -1,151 +1,144 @@ -package com.yf.exam.modules.exam.controller; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.yf.exam.core.api.ApiRest; -import com.yf.exam.core.api.controller.BaseController; -import com.yf.exam.core.api.dto.BaseIdReqDTO; -import com.yf.exam.core.api.dto.BaseIdsReqDTO; -import com.yf.exam.core.api.dto.BaseStateReqDTO; -import com.yf.exam.core.api.dto.PagingReqDTO; -import com.yf.exam.modules.exam.dto.ExamDTO; -import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; -import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; -import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; -import com.yf.exam.modules.exam.entity.Exam; -import com.yf.exam.modules.exam.service.ExamService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import org.apache.shiro.authz.annotation.RequiresRoles; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Date; +package com.yf.exam.modules.exam.controller; // 定义包名,控制器所在的包路径 + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 导入MyBatis Plus的查询包装类 +import com.baomidou.mybatisplus.core.metadata.IPage; // 导入MyBatis Plus的分页接口 +import com.yf.exam.core.api.ApiRest; // 导入自定义的API响应类 +import com.yf.exam.core.api.controller.BaseController; // 导入基控制器类 +import com.yf.exam.core.api.dto.BaseIdReqDTO; // 导入基础ID请求数据传输对象 +import com.yf.exam.core.api.dto.BaseIdsReqDTO; // 导入基础IDs请求数据传输对象 +import com.yf.exam.core.api.dto.BaseStateReqDTO; // 导入基础状态请求数据传输对象 +import com.yf.exam.core.api.dto.PagingReqDTO; // 导入分页请求数据传输对象 +import com.yf.exam.modules.exam.dto.ExamDTO; // 导入考试DTO +import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; // 导入考试保存请求DTO +import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; // 导入在线考试响应DTO +import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; // 导入考试审核响应DTO +import com.yf.exam.modules.exam.entity.Exam; // 导入考试实体类 +import com.yf.exam.modules.exam.service.ExamService; // 导入考试服务接口 +import io.swagger.annotations.Api; // 导入Swagger注解 +import io.swagger.annotations.ApiOperation; // 导入Swagger操作注解 +import org.apache.shiro.authz.annotation.RequiresRoles; // 导入Shiro权限注解 +import org.springframework.beans.factory.annotation.Autowired; // 导入Spring的自动注入注解 +import org.springframework.web.bind.annotation.RequestBody; // 导入Spring MVC的请求体注解 +import org.springframework.web.bind.annotation.RequestMapping; // 导入Spring MVC的请求映射注解 +import org.springframework.web.bind.annotation.RequestMethod; // 导入Spring MVC的请求方法注解 +import org.springframework.web.bind.annotation.RestController; // 导入Spring MVC的控制器注解 + +import java.util.Date; // 导入Java的日期类 /** -*

-* 考试控制器 -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -@Api(tags={"考试"}) -@RestController -@RequestMapping("/exam/api/exam/exam") -public class ExamController extends BaseController { + *

+ * 考试控制器,处理与考试相关的请求 + *

+ * + * @author 聪明笨狗 + * @since 2020-07-25 16:18 + */ +@Api(tags={"考试"}) // Swagger注解,定义API的标签 +@RestController // Spring MVC注解,声明这是一个REST控制器 +@RequestMapping("/exam/api/exam/exam") // Spring MVC注解,定义请求的基础路径 +public class ExamController extends BaseController { // 声明控制器类,继承自基控制器 @Autowired - private ExamService baseService; + private ExamService baseService; // 自动注入考试服务 /** - * 添加或修改 - * @param reqDTO - * @return - */ - @RequiresRoles("sa") - @ApiOperation(value = "添加或修改") - @RequestMapping(value = "/save", method = { RequestMethod.POST}) - public ApiRest save(@RequestBody ExamSaveReqDTO reqDTO) { - //复制参数 - baseService.save(reqDTO); - return super.success(); + * 添加或修改考试信息 + * @param reqDTO 考试保存请求数据传输对象 + * @return ApiRest 返回操作结果 + */ + @RequiresRoles("sa") // Shiro权限注解,要求角色为"sa"(超级管理员) + @ApiOperation(value = "添加或修改") // Swagger注解,定义操作的描述 + @RequestMapping(value = "/save", method = { RequestMethod.POST}) // Spring MVC注解,定义请求的映射和方法 + public ApiRest save(@RequestBody ExamSaveReqDTO reqDTO) { // 定义添加或修改考试的方法 + // 复制参数并保存 + baseService.save(reqDTO); // 调用服务层保存考试信息 + return super.success(); // 返回成功响应 } /** - * 批量删除 - * @param reqDTO - * @return - */ - @RequiresRoles("sa") - @ApiOperation(value = "批量删除") - @RequestMapping(value = "/delete", method = { RequestMethod.POST}) - public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { - //根据ID删除 - baseService.removeByIds(reqDTO.getIds()); - return super.success(); + * 批量删除考试 + * @param reqDTO 包含要删除的考试ID列表 + * @return ApiRest 返回操作结果 + */ + @RequiresRoles("sa") // Shiro权限注解,要求角色为"sa"(超级管理员) + @ApiOperation(value = "批量删除") // Swagger注解,定义操作的描述 + @RequestMapping(value = "/delete", method = { RequestMethod.POST}) // Spring MVC注解,定义请求的映射和方法 + public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { // 定义批量删除考试的方法 + // 根据ID删除考试 + baseService.removeByIds(reqDTO.getIds()); // 调用服务层根据ID列表删除考试 + return super.success(); // 返回成功响应 } /** - * 查找详情 - * @param reqDTO - * @return - */ - @ApiOperation(value = "查找详情") - @RequestMapping(value = "/detail", method = { RequestMethod.POST}) - public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) { - ExamSaveReqDTO dto = baseService.findDetail(reqDTO.getId()); - return super.success(dto); + * 查找考试详情 + * @param reqDTO 包含考试ID + * @return ApiRest 返回考试详情 + */ + @ApiOperation(value = "查找详情") // Swagger注解,定义操作的描述 + @RequestMapping(value = "/detail", method = { RequestMethod.POST}) // Spring MVC注解,定义请求的映射和方法 + public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) { // 定义查找考试详情的方法 + ExamSaveReqDTO dto = baseService.findDetail(reqDTO.getId()); // 调用服务层查找考试详情 + return super.success(dto); // 返回成功响应和考试详情 } /** - * 查找详情 - * @param reqDTO - * @return + * 更新考试状态 + * @param reqDTO 包含考试ID和新状态 + * @return ApiRest 返回操作结果 */ - @RequiresRoles("sa") - @ApiOperation(value = "查找详情") - @RequestMapping(value = "/state", method = { RequestMethod.POST}) - public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) { - + @RequiresRoles("sa") // Shiro权限注解,要求角色为"sa"(超级管理员) + @ApiOperation(value = "更新考试状态") // Swagger注解,定义操作的描述 + @RequestMapping(value = "/state", method = { RequestMethod.POST}) // Spring MVC注解,定义请求的映射和方法 + public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) { // 定义更新考试状态的方法 + // 创建查询条件 QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().in(Exam::getId, reqDTO.getIds()); - Exam exam = new Exam(); - exam.setState(reqDTO.getState()); - exam.setUpdateTime(new Date()); + wrapper.lambda().in(Exam::getId, reqDTO.getIds()); // 构造查询条件,查询指定ID的考试 + Exam exam = new Exam(); // 创建考试实体 + exam.setState(reqDTO.getState()); // 设置新状态 + exam.setUpdateTime(new Date()); // 设置更新时间为当前时间 - baseService.update(exam, wrapper); - return super.success(); + baseService.update(exam, wrapper); // 调用服务层更新考试状态 + return super.success(); // 返回成功响应 } - /** - * 分页查找 - * @param reqDTO - * @return + * 分页查找考试 + * @param reqDTO 分页请求数据传输对象 + * @return ApiRest> 返回分页考试列表 */ - @ApiOperation(value = "考试视角") - @RequestMapping(value = "/online-paging", method = { RequestMethod.POST}) - public ApiRest> myPaging(@RequestBody PagingReqDTO reqDTO) { - - //分页查询并转换 - IPage page = baseService.onlinePaging(reqDTO); - return super.success(page); + @ApiOperation(value = "考试视角") // Swagger注解,定义操作的描述 + @RequestMapping(value = "/online-paging", method = { RequestMethod.POST}) // Spring MVC注解,定义请求的映射和方法 + public ApiRest> myPaging(@RequestBody PagingReqDTO reqDTO) { // 定义分页查找考试的方法 + // 分页查询并转换 + IPage page = baseService.onlinePaging(reqDTO); // 调用服务层进行分页查询 + return super.success(page); // 返回成功响应和分页结果 } /** - * 分页查找 - * @param reqDTO - * @return - */ - @RequiresRoles("sa") - @ApiOperation(value = "分页查找") - @RequestMapping(value = "/paging", method = { RequestMethod.POST}) - public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { - - //分页查询并转换 - IPage page = baseService.paging(reqDTO); - - return super.success(page); + * 分页查找考试 + * @param reqDTO 分页请求数据传输对象 + * @return ApiRest> 返回分页考试列表 + */ + @RequiresRoles("sa") // Shiro权限注解,要求角色为"sa"(超级管理员) + @ApiOperation(value = "分页查找") // Swagger注解,定义操作的描述 + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) // Spring MVC注解,定义请求的映射和方法 + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { // 定义分页查找考试的方法 + // 分页查询并转换 + IPage page = baseService.paging(reqDTO); // 调用服务层进行分页查询 + return super.success(page); // 返回成功响应和分页结果 } - /** - * 分页查找 - * @param reqDTO - * @return + * 分页查找待阅试卷 + * @param reqDTO 分页请求数据传输对象 + * @return ApiRest> 返回分页待阅试卷列表 */ - @RequiresRoles("sa") - @ApiOperation(value = "待阅试卷") - @RequestMapping(value = "/review-paging", method = { RequestMethod.POST}) - public ApiRest> reviewPaging(@RequestBody PagingReqDTO reqDTO) { - //分页查询并转换 - IPage page = baseService.reviewPaging(reqDTO); - return super.success(page); + @RequiresRoles("sa") // Shiro权限注解,要求角色为"sa"(超级管理员) + @ApiOperation(value = "待阅试卷") // Swagger注解,定义操作的描述 + @RequestMapping(value = "/review-paging", method = { RequestMethod.POST}) // Spring MVC注解,定义请求的映射和方法 + public ApiRest> reviewPaging(@RequestBody PagingReqDTO reqDTO) { // 定义分页查找待阅试卷的方法 + // 分页查询并转换 + IPage page = baseService.reviewPaging(reqDTO); // 调用服务层进行分页查询 + return super.success(page); // 返回成功响应和分页结果 } - - } diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java b/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java index 90f86f1..8f9b69e 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java @@ -1,101 +1,93 @@ -package com.yf.exam.modules.exam.dto; +package com.yf.exam.modules.exam.dto; // 定义包名,DTO类所在的包路径 -import com.fasterxml.jackson.annotation.JsonFormat; -import com.yf.exam.modules.paper.enums.ExamState; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; +import com.fasterxml.jackson.annotation.JsonFormat; // 导入Jackson库的注解,用于格式化日期 +import com.yf.exam.modules.paper.enums.ExamState; // 导入考试状态枚举 +import io.swagger.annotations.ApiModel; // 导入Swagger注解,用于描述模型 +import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解,用于描述模型属性 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 +import org.springframework.format.annotation.DateTimeFormat; // 导入Spring的日期格式化注解 -import java.io.Serializable; -import java.util.Date; +import java.io.Serializable; // 导入Java的序列化接口,用于确保对象可以被序列化 +import java.util.Date; // 导入Java的日期类 /** -*

-* 考试数据传输类 -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -@Data -@ApiModel(value="考试", description="考试") -public class ExamDTO implements Serializable { + *

+ * 考试数据传输类,封装考试的基本信息 + *

+ * 此类用于封装考试的基本信息,以便在应用程序中传输。 + * + * @author 聪明笨狗 + * @since 2020-07-25 16:18 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@ApiModel(value="考试", description="考试") // Swagger注解,描述这个类的用途 +public class ExamDTO implements Serializable { // 声明类,实现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 = "考试名称", required=true) // Swagger注解,描述字段的用途和是否必填 + private String title; // 考试名称 - @ApiModelProperty(value = "ID", required=true) - private String id; + @ApiModelProperty(value = "考试描述", required=true) // Swagger注解,描述字段的用途和是否必填 + private String content; // 考试描述 - @ApiModelProperty(value = "考试名称", required=true) - private String title; + @ApiModelProperty(value = "1公开2部门3定员", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer openType; // 开放类型,1表示公开,2表示部门,3表示定员 - @ApiModelProperty(value = "考试描述", required=true) - private String content; + @ApiModelProperty(value = "考试状态", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer state; // 考试状态 - @ApiModelProperty(value = "1公开2部门3定员", required=true) - private Integer openType; + @ApiModelProperty(value = "是否限时", required=true) // Swagger注解,描述字段的用途和是否必填 + private Boolean timeLimit; // 是否限时 - @ApiModelProperty(value = "考试状态", required=true) - private Integer state; + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") // Jackson注解,格式化日期 + @DateTimeFormat(pattern = "yyyy-MM-dd") // Spring注解,格式化日期 + @ApiModelProperty(value = "开始时间", required=true) // Swagger注解,描述字段的用途和是否必填 + private Date startTime; // 考试开始时间 - @ApiModelProperty(value = "是否限时", required=true) - private Boolean timeLimit; + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") // Jackson注解,格式化日期 + @DateTimeFormat(pattern = "yyyy-MM-dd") // Spring注解,格式化日期 + @ApiModelProperty(value = "结束时间", required=true) // Swagger注解,描述字段的用途和是否必填 + private Date endTime; // 考试结束时间 - @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") - @DateTimeFormat(pattern = "yyyy-MM-dd") - @ApiModelProperty(value = "开始时间", required=true) - private Date startTime; + @ApiModelProperty(value = "创建时间", required=true) // Swagger注解,描述字段的用途和是否必填 + private Date createTime; // 创建时间 - @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") - @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 Date updateTime; // 更新时间 + @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(){ - - if(this.timeLimit!=null && this.timeLimit){ - - if(System.currentTimeMillis() < startTime.getTime() ){ - return ExamState.READY_START; + public Integer getState() { + if (this.timeLimit != null && this.timeLimit) { + if (System.currentTimeMillis() < startTime.getTime()) { + return ExamState.READY_START; // 如果当前时间小于开始时间,状态为准备开始 } - - if(System.currentTimeMillis() > endTime.getTime()){ - return ExamState.OVERDUE; + if (System.currentTimeMillis() > endTime.getTime()) { + return ExamState.OVERDUE; // 如果当前时间大于结束时间,状态为已过期 } - - if(System.currentTimeMillis() > startTime.getTime() + if (System.currentTimeMillis() > startTime.getTime() && System.currentTimeMillis() < endTime.getTime() - && !ExamState.DISABLED.equals(this.state)){ - return ExamState.ENABLE; + && !ExamState.DISABLED.equals(this.state)) { + return ExamState.ENABLE; // 如果当前时间在开始时间和结束时间之间,并且状态不是禁用,状态为正在进行 } - } - - return this.state; + return this.state; // 如果不满足上述条件,返回当前状态 } -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java b/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java index d0e54b7..7710955 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java @@ -1,33 +1,32 @@ -package com.yf.exam.modules.exam.dto; +package com.yf.exam.modules.exam.dto; // 定义包名,DTO类所在的包路径 -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; +import io.swagger.annotations.ApiModel; // 导入Swagger注解,用于描述模型 +import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解,用于描述模型属性 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 -import java.io.Serializable; +import java.io.Serializable; // 导入Java的序列化接口,用于确保对象可以被序列化 /** -*

-* 考试部门数据传输类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-03 17:24 -*/ -@Data -@ApiModel(value="考试部门", description="考试部门") -public class ExamDepartDTO implements Serializable { + *

+ * 考试部门数据传输类,封装考试部门相关信息 + *

+ * 此类用于封装与考试部门相关的数据,以便在应用程序中传输。 + * + * @author 聪明笨狗 + * @since 2020-09-03 17:24 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@ApiModel(value="考试部门", description="考试部门") // Swagger注解,描述这个类的用途 +public class ExamDepartDTO implements Serializable { // 声明类,实现Serializable接口以确保可以序列化 - private static final long serialVersionUID = 1L; - - - @ApiModelProperty(value = "ID", required=true) - private String id; - - @ApiModelProperty(value = "考试ID", required=true) - private String examId; - - @ApiModelProperty(value = "部门ID", required=true) - private String departId; - -} + private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制,确保类的版本唯一性 + + @ApiModelProperty(value = "ID", required=true) // Swagger注解,描述字段的用途和是否必填 + private String id; // 唯一标识符,标识考试部门记录的ID + + @ApiModelProperty(value = "考试ID", required=true) // Swagger注解,描述字段的用途和是否必填 + private String examId; // 考试的唯一标识符,关联到具体的考试 + + @ApiModelProperty(value = "部门ID", required=true) // Swagger注解,描述字段的用途和是否必填 + private String departId; // 部门的唯一标识符,关联到具体的部门 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java b/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java index 7244e9a..5cbc8a4 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java @@ -1,51 +1,50 @@ -package com.yf.exam.modules.exam.dto; +package com.yf.exam.modules.exam.dto; // 定义包名,DTO类所在的包路径 -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; +import io.swagger.annotations.ApiModel; // 导入Swagger注解,用于描述模型 +import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解,用于描述模型属性 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 -import java.io.Serializable; +import java.io.Serializable; // 导入Java的序列化接口,用于确保对象可以被序列化 /** -*

-* 考试题库数据传输类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-05 11:14 -*/ -@Data -@ApiModel(value="考试题库", description="考试题库") -public class ExamRepoDTO implements Serializable { - - private static final long serialVersionUID = 1L; - - - @ApiModelProperty(value = "ID", required=true) - private String id; - - @ApiModelProperty(value = "考试ID", required=true) - private String examId; - - @ApiModelProperty(value = "题库ID", required=true) - private String repoId; - - @ApiModelProperty(value = "单选题数量", required=true) - private Integer radioCount; - - @ApiModelProperty(value = "单选题分数", required=true) - private Integer radioScore; - - @ApiModelProperty(value = "多选题数量", required=true) - private Integer multiCount; - - @ApiModelProperty(value = "多选题分数", required=true) - private Integer multiScore; - - @ApiModelProperty(value = "判断题数量", required=true) - private Integer judgeCount; - - @ApiModelProperty(value = "判断题分数", required=true) - private Integer judgeScore; - -} + *

+ * 考试题库数据传输类,封装考试题库相关信息 + *

+ * 此类用于封装考试题库的数据,以便在应用程序中传输。 + * + * @author 聪明笨狗 + * @since 2020-09-05 11:14 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@ApiModel(value="考试题库", description="考试题库") // Swagger注解,描述这个类的用途 +public class ExamRepoDTO implements Serializable { // 声明类,实现Serializable接口以确保可以序列化 + + private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制,确保类的版本唯一性 + + @ApiModelProperty(value = "ID", required=true) // Swagger注解,描述字段的用途和是否必填 + private String id; // 题库ID,唯一标识符 + + @ApiModelProperty(value = "考试ID", required=true) // Swagger注解,描述字段的用途和是否必填 + private String examId; // 关联的考试ID,标识与哪个考试相关联 + + @ApiModelProperty(value = "题库ID", required=true) // Swagger注解,描述字段的用途和是否必填 + private String repoId; // 题库ID,标识具体的题库 + + @ApiModelProperty(value = "单选题数量", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer radioCount; // 单选题数量,表示题库中单选题的总数 + + @ApiModelProperty(value = "单选题分数", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer radioScore; // 单选题分数,表示单选题的总分 + + @ApiModelProperty(value = "多选题数量", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer multiCount; // 多选题数量,表示题库中多选题的总数 + + @ApiModelProperty(value = "多选题分数", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer multiScore; // 多选题分数,表示多选题的总分 + + @ApiModelProperty(value = "判断题数量", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer judgeCount; // 判断题数量,表示题库中判断题的总数 + + @ApiModelProperty(value = "判断题分数", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer judgeScore; // 判断题分数,表示判断题的总分 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java b/src-源文件/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java index a566fec..e1887c3 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java @@ -1,32 +1,30 @@ -package com.yf.exam.modules.exam.dto.ext; +package com.yf.exam.modules.exam.dto.ext; // 定义包名,DTO扩展类所在的包路径 -import com.yf.exam.modules.exam.dto.ExamRepoDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; +import com.yf.exam.modules.exam.dto.ExamRepoDTO; // 导入考试题库数据传输类 +import io.swagger.annotations.ApiModel; // 导入Swagger注解,用于描述模型 +import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解,用于描述模型属性 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 /** -*

-* 考试题库数据传输类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-05 11:14 -*/ -@Data -@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类") -public class ExamRepoExtDTO extends ExamRepoDTO { + *

+ * 考试题库数据传输类的扩展,包含额外的题库信息 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-05 11:14 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类") // Swagger注解,描述这个类的用途 +public class ExamRepoExtDTO extends ExamRepoDTO { // 声明类,继承自ExamRepoDTO - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制 - - @ApiModelProperty(value = "单选题总量", required=true) - private Integer totalRadio; - - @ApiModelProperty(value = "多选题总量", required=true) - private Integer totalMulti; - - @ApiModelProperty(value = "判断题总量", required=true) - private Integer totalJudge; - + @ApiModelProperty(value = "单选题总量", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer totalRadio; // 单选题总量 + + @ApiModelProperty(value = "多选题总量", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer totalMulti; // 多选题总量 + + @ApiModelProperty(value = "判断题总量", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer totalJudge; // 判断题总量 } diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java b/src-源文件/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java index 5c1a95b..aa62266 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java @@ -1,32 +1,30 @@ -package com.yf.exam.modules.exam.dto.request; +package com.yf.exam.modules.exam.dto.request; // 定义包名,请求DTO类所在的包路径 -import com.yf.exam.modules.exam.dto.ExamDTO; -import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; +import com.yf.exam.modules.exam.dto.ExamDTO; // 导入基础考试DTO类 +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO类 +import io.swagger.annotations.ApiModel; // 导入Swagger注解,用于描述模型 +import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解,用于描述模型属性 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 -import java.util.List; +import java.util.List; // 导入Java的List接口,用于定义列表类型的属性 /** -*

-* 考试保存请求类 -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -@Data -@ApiModel(value="考试保存请求类", description="考试保存请求类") -public class ExamSaveReqDTO extends ExamDTO { - - private static final long serialVersionUID = 1L; - - - @ApiModelProperty(value = "题库列表", required=true) - private List repoList; - - @ApiModelProperty(value = "考试部门列表", required=true) - private List departIds; - -} + *

+ * 考试保存请求类,封装考试保存请求的相关信息 + *

+ * + * @author 聪明笨狗 + * @since 2020-07-25 16:18 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@ApiModel(value="考试保存请求类", description="考试保存请求类") // Swagger注解,描述这个类的用途 +public class ExamSaveReqDTO extends ExamDTO { // 声明类,继承自ExamDTO + + private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制 + + @ApiModelProperty(value = "题库列表", required=true) // Swagger注解,描述字段的用途和是否必填 + private List repoList; // 题库列表,存储与考试相关的题库信息 + + @ApiModelProperty(value = "考试部门列表", required=true) // Swagger注解,描述字段的用途和是否必填 + private List departIds; // 考试部门ID列表,存储与考试相关的部门ID +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java b/src-源文件/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java index edbc5ce..ec344db 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java @@ -1,22 +1,21 @@ -package com.yf.exam.modules.exam.dto.response; +package com.yf.exam.modules.exam.dto.response; // 定义包名,响应DTO类所在的包路径 -import com.yf.exam.modules.exam.dto.ExamDTO; -import io.swagger.annotations.ApiModel; -import lombok.Data; +import com.yf.exam.modules.exam.dto.ExamDTO; // 导入基础考试DTO类 +import io.swagger.annotations.ApiModel; // 导入Swagger注解,用于描述模型 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 /** -*

-* 考试分页响应类 -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -@Data -@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类") -public class ExamOnlineRespDTO extends ExamDTO { + *

+ * 在线考试分页响应类,封装在线考试的分页响应信息 + *

+ * 此类用于封装在线考试相关的分页数据,提供给前端展示。 + * + * @author 聪明笨狗 + * @since 2020-07-25 16:18 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类") // Swagger注解,描述这个类的用途 +public class ExamOnlineRespDTO extends ExamDTO { // 声明类,继承自ExamDTO - private static final long serialVersionUID = 1L; - - -} + private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制,确保类的版本唯一性 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java b/src-源文件/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java index 7d9ad36..68d4236 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java @@ -1,31 +1,28 @@ -package com.yf.exam.modules.exam.dto.response; +package com.yf.exam.modules.exam.dto.response; // 定义包名,响应DTO类所在的包路径 -import com.yf.exam.modules.exam.dto.ExamDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; +import com.yf.exam.modules.exam.dto.ExamDTO; // 导入基础考试DTO类 +import io.swagger.annotations.ApiModel; // 导入Swagger注解,用于描述模型 +import io.swagger.annotations.ApiModelProperty; // 导入Swagger注解,用于描述模型属性 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 /** -*

-* 考试分页响应类 -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -@Data -@ApiModel(value="阅卷分页响应类", description="阅卷分页响应类") -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; - - - -} + *

+ * 阅卷分页响应类,封装阅卷相关的分页响应信息 + *

+ * 此类用于封装阅卷相关的分页数据,提供给前端展示。 + * + * @author 聪明笨狗 + * @since 2020-07-25 16:18 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@ApiModel(value="阅卷分页响应类", description="阅卷分页响应类") // Swagger注解,描述这个类的用途 +public class ExamReviewRespDTO extends ExamDTO { // 声明类,继承自ExamDTO + + private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制,确保类的版本唯一性 + + @ApiModelProperty(value = "考试人数", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer examUser; // 考试人数,表示参与考试的用户总数 + + @ApiModelProperty(value = "待阅试卷", required=true) // Swagger注解,描述字段的用途和是否必填 + private Integer unreadPaper; // 待阅试卷数量,表示尚未被阅卷的试卷数量 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/entity/Exam.java b/src-源文件/main/java/com/yf/exam/modules/exam/entity/Exam.java index e198c69..cd2d284 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/entity/Exam.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/entity/Exam.java @@ -1,100 +1,61 @@ -package com.yf.exam.modules.exam.entity; +package com.yf.exam.modules.exam.entity; // 定义包名,实体类所在的包路径 -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.activerecord.Model; -import lombok.Data; -import java.util.Date; +import com.baomidou.mybatisplus.annotation.IdType; // 导入MyBatis Plus的ID类型注解 +import com.baomidou.mybatisplus.annotation.TableField; // 导入MyBatis Plus的表字段注解 +import com.baomidou.mybatisplus.annotation.TableId; // 导入MyBatis Plus的表ID注解 +import com.baomidou.mybatisplus.annotation.TableName; // 导入MyBatis Plus的表名注解 +import com.baomidou.mybatisplus.extension.activerecord.Model; // 导入MyBatis Plus的模型类 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 +import java.util.Date; // 导入Java的日期类 /** -*

-* 考试实体类 -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -@Data -@TableName("el_exam") -public class Exam extends Model { - - private static final long serialVersionUID = 1L; - - /** - * ID - */ - @TableId(value = "id", type = IdType.ASSIGN_ID) - private String id; - - /** - * 考试名称 - */ - private String title; - - /** - * 考试描述 - */ - private String content; - - /** - * 1公开2部门3定员 - */ - @TableField("open_type") - private Integer openType; - - /** - * 考试状态 - */ - private Integer state; - - /** - * 是否限时 - */ - @TableField("time_limit") - private Boolean timeLimit; - - /** - * 开始时间 - */ - @TableField("start_time") - private Date startTime; - - /** - * 结束时间 - */ - @TableField("end_time") - private Date endTime; - - /** - * 创建时间 - */ - @TableField("create_time") - private Date createTime; - - /** - * 更新时间 - */ - @TableField("update_time") - private Date updateTime; - - /** - * 总分数 - */ - @TableField("total_score") - private Integer totalScore; - - /** - * 总时长(分钟) - */ - @TableField("total_time") - private Integer totalTime; - - /** - * 及格分数 - */ - @TableField("qualify_score") - private Integer qualifyScore; - -} + *

+ * 考试实体类,封装考试的基本信息 + *

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

-* 考试部门实体类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-03 17:24 -*/ -@Data -@TableName("el_exam_depart") -public class ExamDepart extends Model { + *

+ * 考试部门实体类,封装考试部门的基本信息 + *

+ * 此类用于数据库操作,映射考试部门表的字段。 + * + * @author 聪明笨狗 + * @since 2020-09-03 17:24 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@TableName("el_exam_depart") // MyBatis Plus注解,指定这个实体类对应的数据库表名 +public class ExamDepart extends Model { // 声明类,继承自MyBatis Plus的Model类,用于数据库操作 + + private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制,确保类的唯一性 - private static final long serialVersionUID = 1L; - /** - * ID - */ - @TableId(value = "id", type = IdType.ASSIGN_ID) + * ID + * 唯一标识符,用于标识考试部门记录。 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解,指定这个字段为表的主键,类型为自增ID private String id; /** - * 考试ID - */ - @TableField("exam_id") + * 考试ID + * 关联的考试ID,标识这个部门所属的考试。 + */ + @TableField("exam_id") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private String examId; /** - * 部门ID - */ - @TableField("depart_id") + * 部门ID + * 关联的部门ID,标识参与考试的部门。 + */ + @TableField("depart_id") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private String departId; - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java b/src-源文件/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java index 3884051..5249056 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java @@ -1,78 +1,87 @@ -package com.yf.exam.modules.exam.entity; +package com.yf.exam.modules.exam.entity; // 定义包名,实体类所在的包路径 -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.activerecord.Model; -import lombok.Data; +import com.baomidou.mybatisplus.annotation.IdType; // 导入MyBatis Plus的ID类型注解 +import com.baomidou.mybatisplus.annotation.TableField; // 导入MyBatis Plus的表字段注解 +import com.baomidou.mybatisplus.annotation.TableId; // 导入MyBatis Plus的表ID注解 +import com.baomidou.mybatisplus.annotation.TableName; // 导入MyBatis Plus的表名注解 +import com.baomidou.mybatisplus.extension.activerecord.Model; // 导入MyBatis Plus的模型类 +import lombok.Data; // 导入Lombok注解,用于简化数据类的编写 /** -*

-* 考试题库实体类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-05 11:14 -*/ -@Data -@TableName("el_exam_repo") -public class ExamRepo extends Model { + *

+ * 考试题库实体类,封装考试题库的基本信息 + *

+ * 此类用于数据库操作,映射考试题库表的字段。 + * + * @author 聪明笨狗 + * @since 2020-09-05 11:14 + */ +@Data // Lombok注解,标记这个类为数据类,自动生成getter和setter方法 +@TableName("el_exam_repo") // MyBatis Plus注解,指定这个实体类对应的数据库表名 +public class ExamRepo extends Model { // 声明类,继承自MyBatis Plus的Model类,用于数据库操作 + + private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制,确保类的唯一性 - private static final long serialVersionUID = 1L; - /** - * ID - */ - @TableId(value = "id", type = IdType.ASSIGN_ID) + * ID + * 唯一标识符,用于标识考试题库记录。 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) // MyBatis Plus注解,指定这个字段为表的主键,类型为自增ID private String id; /** - * 考试ID - */ - @TableField("exam_id") + * 考试ID + * 关联的考试ID,标识这个题库所属的考试。 + */ + @TableField("exam_id") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private String examId; /** - * 题库ID - */ - @TableField("repo_id") + * 题库ID + * 关联的题库ID,标识具体的题库。 + */ + @TableField("repo_id") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private String repoId; /** - * 单选题数量 - */ - @TableField("radio_count") + * 单选题数量 + * 题库中包含的单选题数量。 + */ + @TableField("radio_count") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private Integer radioCount; /** - * 单选题分数 - */ - @TableField("radio_score") + * 单选题分数 + * 单选题的总分。 + */ + @TableField("radio_score") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private Integer radioScore; /** - * 多选题数量 - */ - @TableField("multi_count") + * 多选题数量 + * 题库中包含的多选题数量。 + */ + @TableField("multi_count") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private Integer multiCount; /** - * 多选题分数 - */ - @TableField("multi_score") + * 多选题分数 + * 多选题的总分。 + */ + @TableField("multi_score") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private Integer multiScore; /** - * 判断题数量 - */ - @TableField("judge_count") + * 判断题数量 + * 题库中包含的判断题数量。 + */ + @TableField("judge_count") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private Integer judgeCount; /** - * 判断题分数 - */ - @TableField("judge_score") + * 判断题分数 + * 判断题的总分。 + */ + @TableField("judge_score") // MyBatis Plus注解,指定这个字段在数据库表中的列名 private Integer judgeScore; - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java b/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java index 0c1f39a..60c30d2 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java @@ -1,15 +1,17 @@ -package com.yf.exam.modules.exam.mapper; +package com.yf.exam.modules.exam.mapper; // 定义包名,Mapper接口所在的包路径 -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.yf.exam.modules.exam.entity.ExamDepart; -/** -*

-* 考试部门Mapper -*

-* -* @author 聪明笨狗 -* @since 2020-09-03 17:24 -*/ -public interface ExamDepartMapper extends BaseMapper { +import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis Plus的基础Mapper接口 +import com.yf.exam.modules.exam.entity.ExamDepart; // 导入考试部门实体类 -} +/** + *

+ * 考试部门Mapper,提供考试部门的数据库操作接口 + *

+ * 此类是一个Mapper接口,继承自MyBatis Plus的BaseMapper,用于定义针对考试部门表的数据库操作。 + * + * @author 聪明笨狗 + * @since 2020-09-03 17:24 + */ +public interface ExamDepartMapper extends BaseMapper { // 声明接口,继承自BaseMapper并指定操作的实体类为ExamDepart + // 继承BaseMapper,提供基本的CRUD操作(创建、读取、更新、删除) +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java b/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java index 1ce4504..f4908e8 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java @@ -1,45 +1,52 @@ -package com.yf.exam.modules.exam.mapper; +package com.yf.exam.modules.exam.mapper; // 定义包名,Mapper接口所在的包路径 -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.yf.exam.modules.exam.dto.ExamDTO; -import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; -import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; -import com.yf.exam.modules.exam.entity.Exam; -import org.apache.ibatis.annotations.Param; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis Plus的基础Mapper接口 +import com.baomidou.mybatisplus.core.metadata.IPage; // 导入MyBatis Plus的分页结果接口 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入MyBatis Plus的分页对象 +import com.yf.exam.modules.exam.dto.ExamDTO; // 导入考试DTO +import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; // 导入阅卷分页响应DTO +import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; // 导入在线考试分页响应DTO +import com.yf.exam.modules.exam.entity.Exam; // 导入考试实体类 +import org.apache.ibatis.annotations.Param; // 导入MyBatis的参数注解 /** -*

-* 考试Mapper -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -public interface ExamMapper extends BaseMapper { + *

+ * 考试Mapper,提供考试的数据库操作接口 + *

+ * 此类是一个Mapper接口,继承自MyBatis Plus的BaseMapper,用于定义针对考试表的数据库操作。 + * + * @author 聪明笨狗 + * @since 2020-07-25 16:18 + */ +public interface ExamMapper extends BaseMapper { // 声明接口,继承自BaseMapper并指定操作的实体类为Exam /** * 查找分页内容 - * @param page - * @param query - * @return + * 方法用于根据给定的分页对象和查询条件,查询考试数据的分页结果。 + * + * @param page 分页对象,包含分页信息,如当前页码和每页大小 + * @param query 查询条件,封装了考试查询的相关信息 + * @return IPage 返回包含考试DTO的分页结果 */ IPage paging(Page page, @Param("query") ExamDTO query); /** * 查找分页内容 - * @param page - * @param query - * @return + * 方法用于根据给定的分页对象和查询条件,查询阅卷数据的分页结果。 + * + * @param page 分页对象,包含分页信息,如当前页码和每页大小 + * @param query 查询条件,封装了考试查询的相关信息 + * @return IPage 返回包含阅卷分页响应DTO的分页结果 */ IPage reviewPaging(Page page, @Param("query") ExamDTO query); /** * 在线考试分页响应类-考生视角 - * @param page - * @param query - * @return + * 方法用于根据给定的分页对象和查询条件,查询在线考试数据的分页结果,从考生视角。 + * + * @param page 分页对象,包含分页信息,如当前页码和每页大小 + * @param query 查询条件,封装了考试查询的相关信息 + * @return IPage 返回包含在线考试分页响应DTO的分页结果 */ IPage online(Page page, @Param("query") ExamDTO query); -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java b/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java index 83407df..69900d2 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java @@ -1,26 +1,29 @@ -package com.yf.exam.modules.exam.mapper; +package com.yf.exam.modules.exam.mapper; // 定义包名,Mapper接口所在的包路径 -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; -import com.yf.exam.modules.exam.entity.ExamRepo; -import org.apache.ibatis.annotations.Param; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis Plus的基础Mapper接口 +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO +import com.yf.exam.modules.exam.entity.ExamRepo; // 导入考试题库实体类 +import org.apache.ibatis.annotations.Param; // 导入MyBatis的参数注解 -import java.util.List; +import java.util.List; // 导入Java的List接口 /** -*

-* 考试题库Mapper -*

-* -* @author 聪明笨狗 -* @since 2020-09-05 11:14 -*/ -public interface ExamRepoMapper extends BaseMapper { + *

+ * 考试题库Mapper,提供考试题库的数据库操作接口 + *

+ * 此类是一个Mapper接口,继承自MyBatis Plus的BaseMapper,用于定义针对考试题库表的数据库操作。 + * + * @author 聪明笨狗 + * @since 2020-09-05 11:14 + */ +public interface ExamRepoMapper extends BaseMapper { // 声明接口,继承自BaseMapper并指定操作的实体类为ExamRepo /** * 查找考试题库列表 - * @param examId - * @return + * 方法用于根据给定的考试ID,查询关联的考试题库列表,返回扩展的DTO对象。 + * + * @param examId 考试ID,用于指定查询哪个考试的题库列表 + * @return List 返回包含考试题库扩展信息的列表 */ - List listByExam(@Param("examId") String examId); -} + List listByExam(@Param("examId") String examId); // 使用MyBatis的@Param注解来指定方法参数的名称 +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java b/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java index 301c010..068a6dc 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java @@ -1,32 +1,36 @@ -package com.yf.exam.modules.exam.service; +package com.yf.exam.modules.exam.service; // 定义包名,服务接口所在的包路径 -import com.baomidou.mybatisplus.extension.service.IService; -import com.yf.exam.modules.exam.entity.ExamDepart; +import com.baomidou.mybatisplus.extension.service.IService; // 导入MyBatis Plus的服务接口 +import com.yf.exam.modules.exam.entity.ExamDepart; // 导入考试部门实体类 -import java.util.List; +import java.util.List; // 导入Java的List接口 /** -*

-* 考试部门业务类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-03 17:24 -*/ -public interface ExamDepartService extends IService { + *

+ * 考试部门业务类,提供考试部门的业务逻辑 + *

+ * 此类是一个服务接口,继承自MyBatis Plus的IService,用于定义考试部门相关的业务操作。 + * + * @author 聪明笨狗 + * @since 2020-09-03 17:24 + */ +public interface ExamDepartService extends IService { // 声明接口,继承自IService并指定操作的实体类为ExamDepart /** - * 保存全部 - * @param examId - * @param departs + * 保存全部部门信息 + * 方法用于根据给定的考试ID和部门ID列表,保存考试与部门的关联关系。 + * + * @param examId 考试ID,标识要关联的考试 + * @param departs 部门ID列表,包含要关联的部门ID */ void saveAll(String examId, List departs); - /** * 根据考试查找对应的部门 - * @param examId - * @return + * 方法用于根据给定的考试ID,查询与之关联的部门ID列表。 + * + * @param examId 考试ID,用于指定查询哪个考试的部门 + * @return List 返回部门ID列表,包含所有与考试关联的部门ID */ List listByExam(String examId); -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java b/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java index 78b4ec1..5758185 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java @@ -1,40 +1,45 @@ -package com.yf.exam.modules.exam.service; +package com.yf.exam.modules.exam.service; // 定义包名,服务接口所在的包路径 -import com.baomidou.mybatisplus.extension.service.IService; -import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; -import com.yf.exam.modules.exam.entity.ExamRepo; +import com.baomidou.mybatisplus.extension.service.IService; // 导入MyBatis Plus的服务接口 +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO +import com.yf.exam.modules.exam.entity.ExamRepo; // 导入考试题库实体类 -import java.util.List; +import java.util.List; // 导入Java的List接口 /** -*

-* 考试题库业务类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-05 11:14 -*/ -public interface ExamRepoService extends IService { - + *

+ * 考试题库业务类,提供考试题库的业务逻辑 + *

+ * 此类是一个服务接口,继承自MyBatis Plus的IService,用于定义考试题库相关的业务操作。 + * + * @author 聪明笨狗 + * @since 2020-09-05 11:14 + */ +public interface ExamRepoService extends IService { // 声明接口,继承自IService并指定操作的实体类为ExamRepo /** - * 保存全部 - * @param examId - * @param list + * 保存全部题库信息 + * 方法用于根据给定的考试ID和题库列表,保存考试与题库的关联关系。 + * + * @param examId 考试ID,标识要关联的考试 + * @param list 题库列表,包含要关联的题库信息 */ void saveAll(String examId, List list); /** * 查找考试题库列表 - * @param examId - * @return + * 方法用于根据给定的考试ID,查询与之关联的题库列表。 + * + * @param examId 考试ID,用于指定查询哪个考试的题库 + * @return List 返回题库列表,包含所有与考试关联的题库信息 */ List listByExam(String examId); /** * 清理脏数据 - * @param examId + * 方法用于清除与指定考试ID关联的题库数据,用于数据清理或重置场景。 + * + * @param examId 考试ID,用于指定要清理的考试关联的题库数据 */ void clear(String examId); - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamService.java b/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamService.java index 3f75664..47d77b8 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamService.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/service/ExamService.java @@ -1,64 +1,75 @@ -package com.yf.exam.modules.exam.service; +package com.yf.exam.modules.exam.service; // 定义包名,服务接口所在的包路径 -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.service.IService; -import com.yf.exam.core.api.dto.PagingReqDTO; -import com.yf.exam.modules.exam.dto.ExamDTO; -import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; -import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; -import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; -import com.yf.exam.modules.exam.entity.Exam; +import com.baomidou.mybatisplus.core.metadata.IPage; // 导入MyBatis Plus的分页结果接口 +import com.baomidou.mybatisplus.extension.service.IService; // 导入MyBatis Plus的服务接口 +import com.yf.exam.core.api.dto.PagingReqDTO; // 导入分页请求DTO +import com.yf.exam.modules.exam.dto.ExamDTO; // 导入考试DTO +import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; // 导入考试保存请求DTO +import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; // 导入在线考试响应DTO +import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; // 导入阅卷响应DTO +import com.yf.exam.modules.exam.entity.Exam; // 导入考试实体类 /** -*

-* 考试业务类 -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -public interface ExamService extends IService { + *

+ * 考试业务类,提供考试的业务逻辑 + *

+ * 此类是一个服务接口,继承自MyBatis Plus的IService,用于定义考试相关的业务操作。 + * + * @author 聪明笨狗 + * @since 2020-07-25 16:18 + */ +public interface ExamService extends IService { // 声明接口,继承自IService并指定操作的实体类为Exam /** * 保存考试信息 - * @param reqDTO + * 方法用于保存考试信息,包括考试的基本信息和相关联的题库、部门等。 + * + * @param reqDTO 考试保存请求数据传输对象,包含考试的详细信息 */ void save(ExamSaveReqDTO reqDTO); /** * 查找考试详情 - * @param id - * @return + * 方法用于根据考试ID查询考试的详细信息,包括考试的基本数据和相关联的部门、题库等。 + * + * @param id 考试ID,用于指定要查询的考试 + * @return ExamSaveReqDTO 返回考试详情,包含考试的详细信息 */ ExamSaveReqDTO findDetail(String id); /** * 查找考试详情--简要信息 - * @param id - * @return + * 方法用于根据考试ID查询考试的简要信息,通常用于列表展示。 + * + * @param id 考试ID,用于指定要查询的考试 + * @return ExamDTO 返回考试简要信息,包含考试的基本数据 */ ExamDTO findById(String id); /** - * 分页查询数据 - * @param reqDTO - * @return - */ + * 分页查询数据 + * 方法用于分页查询考试数据,通常用于列表展示。 + * + * @param reqDTO 分页请求数据传输对象,包含分页信息和查询条件 + * @return IPage 返回分页考试数据,包含考试列表和分页信息 + */ IPage paging(PagingReqDTO reqDTO); - /** * 在线考试分页响应类-考生视角 - * @param reqDTO - * @return + * 方法用于分页查询在线考试数据,从考生视角,通常用于考生查看可参加的考试列表。 + * + * @param reqDTO 分页请求数据传输对象,包含分页信息和查询条件 + * @return IPage 返回分页在线考试数据,包含在线考试列表和分页信息 */ IPage onlinePaging(PagingReqDTO reqDTO); - /** * 待阅试卷列表 - * @param reqDTO - * @return + * 方法用于分页查询待阅试卷数据,通常用于阅卷老师查看需要批改的试卷列表。 + * + * @param reqDTO 分页请求数据传输对象,包含分页信息和查询条件 + * @return IPage 返回分页待阅试卷数据,包含待阅试卷列表和分页信息 */ IPage reviewPaging(PagingReqDTO reqDTO); -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java b/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java index 37ca2ff..3460e7c 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java @@ -1,66 +1,64 @@ -package com.yf.exam.modules.exam.service.impl; +package com.yf.exam.modules.exam.service.impl; // 定义包名,服务实现类所在的包路径 -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.yf.exam.core.exception.ServiceException; -import com.yf.exam.modules.exam.entity.ExamDepart; -import com.yf.exam.modules.exam.mapper.ExamDepartMapper; -import com.yf.exam.modules.exam.service.ExamDepartService; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 导入MyBatis Plus的查询包装类 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; // 导入MyBatis Plus的服务实现类 +import com.yf.exam.core.exception.ServiceException; // 导入自定义的服务异常类 +import com.yf.exam.modules.exam.entity.ExamDepart; // 导入考试部门实体类 +import com.yf.exam.modules.exam.mapper.ExamDepartMapper; // 导入考试部门Mapper接口 +import com.yf.exam.modules.exam.service.ExamDepartService; // 导入考试部门服务接口 +import org.springframework.stereotype.Service; // 导入Spring的服务注解 +import org.springframework.util.CollectionUtils; // 导入Spring的集合工具类 -import java.util.ArrayList; -import java.util.List; +import java.util.ArrayList; // 导入Java的ArrayList类 +import java.util.List; // 导入Java的List接口 /** -*

-* 考试部门业务实现类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-03 17:24 -*/ -@Service -public class ExamDepartServiceImpl extends ServiceImpl implements ExamDepartService { + *

+ * 考试部门业务实现类,提供考试部门的具体业务逻辑实现 + *

+ * 此类实现了ExamDepartService接口,用于处理考试部门相关的业务逻辑。 + * + * @author 聪明笨狗 + * @since 2020-09-03 17:24 + */ +@Service // Spring注解,声明这是一个服务组件 +public class ExamDepartServiceImpl extends ServiceImpl implements ExamDepartService { // 声明类,继承自ServiceImpl并实现ExamDepartService接口 @Override public void saveAll(String examId, List departs) { - - // 先删除 + // 先删除已有的部门 QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(ExamDepart::getExamId, examId); - this.remove(wrapper); + wrapper.lambda().eq(ExamDepart::getExamId, examId); // 构造查询条件,查询指定考试ID的部门 + this.remove(wrapper); // 根据条件删除部门 - // 再增加 - if(CollectionUtils.isEmpty(departs)){ - throw new ServiceException(1, "请至少选择选择一个部门!!"); + // 再增加新的部门 + if (CollectionUtils.isEmpty(departs)) { // 检查部门列表是否为空 + throw new ServiceException(1, "请至少选择选择一个部门!!"); // 如果为空,抛出异常 } - List list = new ArrayList<>(); + List list = new ArrayList<>(); // 创建考试部门列表 - for(String id: departs){ - ExamDepart depart = new ExamDepart(); - depart.setDepartId(id); - depart.setExamId(examId); - list.add(depart); + for (String id : departs) { + ExamDepart depart = new ExamDepart(); // 创建考试部门对象 + depart.setDepartId(id); // 设置部门ID + depart.setExamId(examId); // 设置考试ID + list.add(depart); // 添加到列表 } - this.saveBatch(list); + this.saveBatch(list); // 批量保存部门 } @Override public List listByExam(String examId) { - // 先删除 + // 查找考试对应的部门 QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(ExamDepart::getExamId, examId); - List list = this.list(wrapper); - List ids = new ArrayList<>(); - if(!CollectionUtils.isEmpty(list)){ - for(ExamDepart item: list){ - ids.add(item.getDepartId()); + wrapper.lambda().eq(ExamDepart::getExamId, examId); // 构造查询条件,查询指定考试ID的部门 + List list = this.list(wrapper); // 根据条件查询部门列表 + List ids = new ArrayList<>(); // 创建部门ID列表 + if (!CollectionUtils.isEmpty(list)) { // 检查部门列表是否为空 + for (ExamDepart item : list) { + ids.add(item.getDepartId()); // 添加部门ID到列表 } } - - return ids; - + return ids; // 返回部门ID列表 } -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java b/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java index ea630e6..e161f56 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java @@ -1,67 +1,63 @@ -package com.yf.exam.modules.exam.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.IdWorker; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.yf.exam.core.exception.ServiceException; -import com.yf.exam.core.utils.BeanMapper; -import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; -import com.yf.exam.modules.exam.entity.ExamRepo; -import com.yf.exam.modules.exam.mapper.ExamRepoMapper; -import com.yf.exam.modules.exam.service.ExamRepoService; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.CollectionUtils; - -import java.util.List; +package com.yf.exam.modules.exam.service.impl; // 定义包名,服务实现类所在的包路径 + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; // 导入MyBatis Plus的查询包装类 +import com.baomidou.mybatisplus.core.toolkit.IdWorker; // 导入MyBatis Plus的ID生成工具类 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; // 导入MyBatis Plus的服务实现类 +import com.yf.exam.core.exception.ServiceException; // 导入自定义的服务异常类 +import com.yf.exam.core.utils.BeanMapper; // 导入自定义的Bean映射工具类 +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO +import com.yf.exam.modules.exam.entity.ExamRepo; // 导入考试题库实体类 +import com.yf.exam.modules.exam.mapper.ExamRepoMapper; // 导入考试题库Mapper接口 +import com.yf.exam.modules.exam.service.ExamRepoService; // 导入考试题库服务接口 +import org.springframework.stereotype.Service; // 导入Spring的服务注解 +import org.springframework.transaction.annotation.Transactional; // 导入Spring的事务注解 +import org.springframework.util.CollectionUtils; // 导入Spring的集合工具类 + +import java.util.List; // 导入Java的List接口 /** -*

-* 考试题库业务实现类 -*

-* -* @author 聪明笨狗 -* @since 2020-09-05 11:14 -*/ -@Service -public class ExamRepoServiceImpl extends ServiceImpl implements ExamRepoService { - - - @Transactional(rollbackFor = Exception.class) + *

+ * 考试题库业务实现类,提供考试题库的具体业务逻辑实现 + *

+ * 此类实现了ExamRepoService接口,用于处理考试题库相关的业务逻辑。 + * + * @author 聪明笨狗 + * @since 2020-09-05 11:14 + */ +@Service // Spring注解,声明这是一个服务组件 +public class ExamRepoServiceImpl extends ServiceImpl implements ExamRepoService { // 声明类,继承自ServiceImpl并实现ExamRepoService接口 + + @Transactional(rollbackFor = Exception.class) // Spring事务注解,声明事务边界和回滚条件 @Override public void saveAll(String examId, List list) { - - // 先删除 + // 先删除已有的题库 QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(ExamRepo::getExamId, examId); - this.remove(wrapper); + wrapper.lambda().eq(ExamRepo::getExamId, examId); // 构造查询条件,查询指定考试ID的题库 + this.remove(wrapper); // 根据条件删除题库 - // 再增加 - if(CollectionUtils.isEmpty(list)){ - throw new ServiceException(1, "必须选择题库!"); + // 再增加新的题库 + if (CollectionUtils.isEmpty(list)) { // 检查题库列表是否为空 + throw new ServiceException(1, "必须选择题库!"); // 如果为空,抛出异常 } - List repos = BeanMapper.mapList(list, ExamRepo.class); - for(ExamRepo item: repos){ - item.setExamId(examId); - item.setId(IdWorker.getIdStr()); + List repos = BeanMapper.mapList(list, ExamRepo.class); // 使用BeanMapper将DTO列表转换为实体类列表 + for (ExamRepo item : repos) { + item.setExamId(examId); // 设置考试ID + item.setId(IdWorker.getIdStr()); // 使用IdWorker生成ID } - this.saveBatch(repos); + this.saveBatch(repos); // 批量保存题库 } @Override public List listByExam(String examId) { - return baseMapper.listByExam(examId); + return baseMapper.listByExam(examId); // 调用Mapper接口的方法,查找考试题库列表 } @Override public void clear(String examId) { - - // 先删除 + // 先删除已有的题库 QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(ExamRepo::getExamId, examId); - this.remove(wrapper); + wrapper.lambda().eq(ExamRepo::getExamId, examId); // 构造查询条件,查询指定考试ID的题库 + this.remove(wrapper); // 根据条件删除题库 } - - -} +} \ No newline at end of file diff --git a/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java b/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java index 7451bd9..50ae151 100644 --- a/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java +++ b/src-源文件/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java @@ -1,194 +1,173 @@ -package com.yf.exam.modules.exam.service.impl; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.core.toolkit.IdWorker; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.yf.exam.core.api.dto.PagingReqDTO; -import com.yf.exam.core.enums.OpenType; -import com.yf.exam.core.exception.ServiceException; -import com.yf.exam.core.utils.BeanMapper; -import com.yf.exam.modules.exam.dto.ExamDTO; -import com.yf.exam.modules.exam.dto.ExamRepoDTO; -import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; -import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; -import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; -import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; -import com.yf.exam.modules.exam.entity.Exam; -import com.yf.exam.modules.exam.mapper.ExamMapper; -import com.yf.exam.modules.exam.service.ExamDepartService; -import com.yf.exam.modules.exam.service.ExamRepoService; -import com.yf.exam.modules.exam.service.ExamService; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DuplicateKeyException; -import org.springframework.stereotype.Service; - -import java.util.List; +package com.yf.exam.modules.exam.service.impl; // 定义包名,服务实现类所在的包路径 + +import com.baomidou.mybatisplus.core.metadata.IPage; // 导入MyBatis Plus的分页结果接口 +import com.baomidou.mybatisplus.core.toolkit.IdWorker; // 导入MyBatis Plus的ID生成工具类 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入MyBatis Plus的分页对象 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; // 导入MyBatis Plus的服务实现类 +import com.yf.exam.core.api.dto.PagingReqDTO; // 导入分页请求DTO +import com.yf.exam.core.enums.OpenType; // 导入开放类型枚举 +import com.yf.exam.core.exception.ServiceException; // 导入自定义的服务异常类 +import com.yf.exam.core.utils.BeanMapper; // 导入自定义的Bean映射工具类 +import com.yf.exam.modules.exam.dto.ExamDTO; // 导入考试DTO +import com.yf.exam.modules.exam.dto.ExamRepoDTO; // 导入考试题库DTO +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; // 导入考试题库扩展DTO +import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; // 导入考试保存请求DTO +import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; // 导入在线考试响应DTO +import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; // 导入阅卷响应DTO +import com.yf.exam.modules.exam.entity.Exam; // 导入考试实体类 +import com.yf.exam.modules.exam.mapper.ExamMapper; // 导入考试Mapper接口 +import com.yf.exam.modules.exam.service.ExamDepartService; // 导入考试部门服务接口 +import com.yf.exam.modules.exam.service.ExamRepoService; // 导入考试题库服务接口 +import com.yf.exam.modules.exam.service.ExamService; // 导入考试服务接口 +import org.apache.commons.lang3.StringUtils; // 导入Apache Commons Lang的字符串工具类 +import org.springframework.beans.factory.annotation.Autowired; // 导入Spring的自动注入注解 +import org.springframework.dao.DuplicateKeyException; // 导入Spring的重复键异常类 +import org.springframework.stereotype.Service; // 导入Spring的服务注解 + +import java.util.List; // 导入Java的List接口 /** -*

-* 考试业务实现类 -*

-* -* @author 聪明笨狗 -* @since 2020-07-25 16:18 -*/ -@Service -public class ExamServiceImpl extends ServiceImpl implements ExamService { - + *

+ * 考试业务实现类 + *

+ * 此类实现了ExamService接口,用于处理考试相关的业务逻辑。 + * + * @author 聪明笨狗 + * @since 2020-07-25 16:18 + */ +@Service // Spring注解,声明这是一个服务组件 +public class ExamServiceImpl extends ServiceImpl implements ExamService { // 声明类,继承自ServiceImpl并实现ExamService接口 @Autowired - private ExamRepoService examRepoService; + private ExamRepoService examRepoService; // 自动注入考试题库服务 @Autowired - private ExamDepartService examDepartService; + private ExamDepartService examDepartService; // 自动注入考试部门服务 @Override public void save(ExamSaveReqDTO reqDTO) { - // ID String id = reqDTO.getId(); - if(StringUtils.isBlank(id)){ - id = IdWorker.getIdStr(); + if(StringUtils.isBlank(id)){ // 如果ID为空,则生成新的ID + id = IdWorker.getIdStr(); // 使用IdWorker生成ID } - //复制参数 - Exam entity = new Exam(); + // 复制参数 + Exam entity = new Exam(); // 创建考试实体 // 计算分值 - this.calcScore(reqDTO); - + this.calcScore(reqDTO); // 调用方法计算分值 // 复制基本数据 - BeanMapper.copy(reqDTO, entity); - entity.setId(id); + BeanMapper.copy(reqDTO, entity); // 使用BeanMapper复制属性 + entity.setId(id); // 设置ID // 修复状态 - if (reqDTO.getTimeLimit()!=null + if (reqDTO.getTimeLimit() != null && !reqDTO.getTimeLimit() - && reqDTO.getState()!=null + && reqDTO.getState() != null && reqDTO.getState() == 2) { - entity.setState(0); + entity.setState(0); // 如果不限时且状态为2,则状态设置为0 } else { - entity.setState(reqDTO.getState()); + entity.setState(reqDTO.getState()); // 否则直接设置状态 } // 题库组卷 try { - examRepoService.saveAll(id, reqDTO.getRepoList()); + examRepoService.saveAll(id, reqDTO.getRepoList()); // 调用考试题库服务保存题库 }catch (DuplicateKeyException e){ - throw new ServiceException(1, "不能选择重复的题库!"); + throw new ServiceException(1, "不能选择重复的题库!"); // 如果出现重复键异常,则抛出服务异常 } - // 开放的部门 - if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){ - examDepartService.saveAll(id, reqDTO.getDepartIds()); + if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){ // 如果开放类型为部门开放 + examDepartService.saveAll(id, reqDTO.getDepartIds()); // 调用考试部门服务保存部门 } - this.saveOrUpdate(entity); - + this.saveOrUpdate(entity); // 保存或更新考试实体 } @Override public ExamSaveReqDTO findDetail(String id) { - ExamSaveReqDTO respDTO = new ExamSaveReqDTO(); - Exam exam = this.getById(id); - BeanMapper.copy(exam, respDTO); + ExamSaveReqDTO respDTO = new ExamSaveReqDTO(); // 创建响应DTO + Exam exam = this.getById(id); // 根据ID查询考试实体 + BeanMapper.copy(exam, respDTO); // 使用BeanMapper复制属性 // 考试部门 - List departIds = examDepartService.listByExam(id); - respDTO.setDepartIds(departIds); + List departIds = examDepartService.listByExam(id); // 调用考试部门服务查询部门ID列表 + respDTO.setDepartIds(departIds); // 设置部门ID列表 // 题库 - List repos = examRepoService.listByExam(id); - respDTO.setRepoList(repos); + List repos = examRepoService.listByExam(id); // 调用考试题库服务查询题库列表 + respDTO.setRepoList(repos); // 设置题库列表 - return respDTO; + return respDTO; // 返回响应DTO } @Override public ExamDTO findById(String id) { - ExamDTO respDTO = new ExamDTO(); - Exam exam = this.getById(id); - BeanMapper.copy(exam, respDTO); - return respDTO; + ExamDTO respDTO = new ExamDTO(); // 创建响应DTO + Exam exam = this.getById(id); // 根据ID查询考试实体 + BeanMapper.copy(exam, respDTO); // 使用BeanMapper复制属性 + return respDTO; // 返回响应DTO } @Override public IPage paging(PagingReqDTO reqDTO) { + // 创建分页对象 + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); // 使用分页请求DTO创建分页对象 - //创建分页对象 - Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); - - //转换结果 - IPage pageData = baseMapper.paging(page, reqDTO.getParams()); - return pageData; - } + // 转换结果 + IPage pageData = baseMapper.paging(page, reqDTO.getParams()); // 调用Mapper接口的分页方法 + return pageData; // 返回分页结果 + } @Override public IPage onlinePaging(PagingReqDTO reqDTO) { - - // 创建分页对象 - Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); // 使用分页请求DTO创建分页对象 // 查找分页 - IPage pageData = baseMapper.online(page, reqDTO.getParams()); - - return pageData; + IPage pageData = baseMapper.online(page, reqDTO.getParams()); // 调用Mapper接口的在线考试分页方法 + return pageData; // 返回分页结果 } @Override public IPage reviewPaging(PagingReqDTO reqDTO) { // 创建分页对象 - Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); // 使用分页请求DTO创建分页对象 // 查找分页 - IPage pageData = baseMapper.reviewPaging(page, reqDTO.getParams()); - - return pageData; + IPage pageData = baseMapper.reviewPaging(page, reqDTO.getParams()); // 调用Mapper接口的阅卷分页方法 + return pageData; // 返回分页结果 } - /** * 计算分值 - * @param reqDTO + * 方法用于计算考试的总分值,根据题库中的题目数量和分数。 + * + * @param reqDTO 考试保存请求DTO */ - private void calcScore(ExamSaveReqDTO reqDTO){ - + private void calcScore(ExamSaveReqDTO reqDTO) { // 主观题分数 int objScore = 0; // 题库组卷 - List repoList = reqDTO.getRepoList(); - - for(ExamRepoDTO item: repoList){ - if(item.getRadioCount()!=null - && item.getRadioCount()>0 - && item.getRadioScore()!=null - && item.getRadioScore()>0){ - objScore+=item.getRadioCount()*item.getRadioScore(); + List repoList = reqDTO.getRepoList(); // 获取题库列表 + + for(ExamRepoDTO item : repoList){ // 遍历题库列表 + if(item.getRadioCount() != null && 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){ - objScore+=item.getMultiCount()*item.getMultiScore(); + if(item.getMultiCount() != null && 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){ - objScore+=item.getJudgeCount()*item.getJudgeScore(); + if(item.getJudgeCount() != null && item.getJudgeCount() > 0 && item.getJudgeScore() != null && item.getJudgeScore() > 0){ + objScore += item.getJudgeCount() * item.getJudgeScore(); // 计算判断题分数 } } - - - reqDTO.setTotalScore(objScore); + reqDTO.setTotalScore(objScore); // 设置总分值 } - -} +} \ No newline at end of file