From 2ceaa01e1b0b6a90b8a5d567c6fb8c518de585e8 Mon Sep 17 00:00:00 2001 From: yutao <2930275373@qq.com> Date: Tue, 22 Apr 2025 19:36:26 +0800 Subject: [PATCH 1/4] 111 --- 1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 1.txt diff --git a/1.txt b/1.txt new file mode 100644 index 0000000..dc46e18 --- /dev/null +++ b/1.txt @@ -0,0 +1 @@ +654148951 \ No newline at end of file -- 2.34.1 From 9e2436cbc8357107d619f6cd0bc71e4df74591cc Mon Sep 17 00:00:00 2001 From: yutao <2930275373@qq.com> Date: Mon, 28 Apr 2025 21:10:39 +0800 Subject: [PATCH 2/4] LYT --- repo/controller/RepoController.java | 132 ++++++ repo/dto/RepoDTO.java | 67 +++ repo/dto/request/RepoReqDTO.java | 40 ++ repo/dto/response/RepoRespDTO.java | 45 ++ repo/entity/Repo.java | 62 +++ repo/mapper/RepoMapper.java | 31 ++ repo/service/RepoService.java | 38 ++ repo/service/impl/RepoServiceImpl.java | 53 +++ .../controller/SysConfigController.java | 92 +++++ sys/config/dto/SysConfigDTO.java | 61 +++ sys/config/entity/SysConfig.java | 53 +++ sys/config/mapper/SysConfigMapper.java | 25 ++ sys/config/service/SysConfigService.java | 34 ++ .../service/impl/SysConfigServiceImpl.java | 54 +++ .../controller/SysDepartController.java | 186 +++++++++ sys/depart/dto/SysDepartDTO.java | 73 ++++ sys/depart/dto/request/DepartSortReqDTO.java | 42 ++ sys/depart/dto/response/SysDepartTreeDTO.java | 41 ++ sys/depart/entity/SysDepart.java | 59 +++ sys/depart/mapper/SysDepartMapper.java | 28 ++ sys/depart/service/SysDepartService.java | 62 +++ .../service/impl/SysDepartServiceImpl.java | 368 +++++++++++++++++ sys/system/mapper/SysDictMapper.java | 40 ++ sys/system/service/SysDictService.java | 26 ++ .../service/impl/SysDictServiceImpl.java | 45 ++ sys/user/controller/SysRoleController.java | 106 +++++ sys/user/controller/SysUserController.java | 245 +++++++++++ sys/user/dto/SysRoleDTO.java | 45 ++ sys/user/dto/SysUserDTO.java | 108 +++++ sys/user/dto/SysUserRoleDTO.java | 55 +++ sys/user/dto/request/SysUserLoginReqDTO.java | 45 ++ sys/user/dto/request/SysUserSaveReqDTO.java | 83 ++++ sys/user/dto/request/SysUserTokenReqDTO.java | 38 ++ sys/user/dto/response/SysUserLoginDTO.java | 106 +++++ sys/user/entity/SysRole.java | 54 +++ sys/user/entity/SysUser.java | 103 +++++ sys/user/entity/SysUserRole.java | 53 +++ sys/user/mapper/SysRoleMapper.java | 32 ++ sys/user/mapper/SysUserMapper.java | 32 ++ sys/user/mapper/SysUserRoleMapper.java | 32 ++ sys/user/service/SysRoleService.java | 52 +++ sys/user/service/SysUserRoleService.java | 94 +++++ sys/user/service/SysUserService.java | 99 +++++ sys/user/service/impl/SysRoleServiceImpl.java | 62 +++ .../service/impl/SysUserRoleServiceImpl.java | 200 +++++++++ sys/user/service/impl/SysUserServiceImpl.java | 383 ++++++++++++++++++ 46 files changed, 3784 insertions(+) create mode 100644 repo/controller/RepoController.java create mode 100644 repo/dto/RepoDTO.java create mode 100644 repo/dto/request/RepoReqDTO.java create mode 100644 repo/dto/response/RepoRespDTO.java create mode 100644 repo/entity/Repo.java create mode 100644 repo/mapper/RepoMapper.java create mode 100644 repo/service/RepoService.java create mode 100644 repo/service/impl/RepoServiceImpl.java create mode 100644 sys/config/controller/SysConfigController.java create mode 100644 sys/config/dto/SysConfigDTO.java create mode 100644 sys/config/entity/SysConfig.java create mode 100644 sys/config/mapper/SysConfigMapper.java create mode 100644 sys/config/service/SysConfigService.java create mode 100644 sys/config/service/impl/SysConfigServiceImpl.java create mode 100644 sys/depart/controller/SysDepartController.java create mode 100644 sys/depart/dto/SysDepartDTO.java create mode 100644 sys/depart/dto/request/DepartSortReqDTO.java create mode 100644 sys/depart/dto/response/SysDepartTreeDTO.java create mode 100644 sys/depart/entity/SysDepart.java create mode 100644 sys/depart/mapper/SysDepartMapper.java create mode 100644 sys/depart/service/SysDepartService.java create mode 100644 sys/depart/service/impl/SysDepartServiceImpl.java create mode 100644 sys/system/mapper/SysDictMapper.java create mode 100644 sys/system/service/SysDictService.java create mode 100644 sys/system/service/impl/SysDictServiceImpl.java create mode 100644 sys/user/controller/SysRoleController.java create mode 100644 sys/user/controller/SysUserController.java create mode 100644 sys/user/dto/SysRoleDTO.java create mode 100644 sys/user/dto/SysUserDTO.java create mode 100644 sys/user/dto/SysUserRoleDTO.java create mode 100644 sys/user/dto/request/SysUserLoginReqDTO.java create mode 100644 sys/user/dto/request/SysUserSaveReqDTO.java create mode 100644 sys/user/dto/request/SysUserTokenReqDTO.java create mode 100644 sys/user/dto/response/SysUserLoginDTO.java create mode 100644 sys/user/entity/SysRole.java create mode 100644 sys/user/entity/SysUser.java create mode 100644 sys/user/entity/SysUserRole.java create mode 100644 sys/user/mapper/SysRoleMapper.java create mode 100644 sys/user/mapper/SysUserMapper.java create mode 100644 sys/user/mapper/SysUserRoleMapper.java create mode 100644 sys/user/service/SysRoleService.java create mode 100644 sys/user/service/SysUserRoleService.java create mode 100644 sys/user/service/SysUserService.java create mode 100644 sys/user/service/impl/SysRoleServiceImpl.java create mode 100644 sys/user/service/impl/SysUserRoleServiceImpl.java create mode 100644 sys/user/service/impl/SysUserServiceImpl.java diff --git a/repo/controller/RepoController.java b/repo/controller/RepoController.java new file mode 100644 index 0000000..7d1c420 --- /dev/null +++ b/repo/controller/RepoController.java @@ -0,0 +1,132 @@ +package com.yf.exam.modules.repo.controller; + +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.PagingReqDTO; +import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO; +import com.yf.exam.modules.qu.service.QuRepoService; +import com.yf.exam.modules.repo.dto.RepoDTO; +import com.yf.exam.modules.repo.dto.request.RepoReqDTO; +import com.yf.exam.modules.repo.dto.response.RepoRespDTO; +import com.yf.exam.modules.repo.entity.Repo; +import com.yf.exam.modules.repo.service.RepoService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.BeanUtils; +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; + +/** + *

+ * 题库控制器,处理与题库相关的 HTTP 请求 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:25 + */ +@Api(tags={"题库"}) +@RestController +@RequestMapping("/exam/api/repo") +public class RepoController extends BaseController { + + /** + * 注入题库服务,用于处理题库相关的业务逻辑 + */ + @Autowired + private RepoService baseService; + + /** + * 注入题目与题库关联服务,用于处理题目与题库的批量操作 + */ + @Autowired + private QuRepoService quRepoService; + + /** + * 添加或修改题库信息 + * @param reqDTO 包含题库信息的请求数据传输对象 + * @return 操作结果,封装在 ApiRest 对象中 + */ + @RequiresRoles("sa") + @ApiOperation(value = "添加或修改") + @RequestMapping(value = "/save", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody RepoDTO reqDTO) { + // 调用服务层方法保存或更新题库信息 + baseService.save(reqDTO); + // 返回操作成功的响应 + return super.success(); + } + + /** + * 批量删除题库 + * @param reqDTO 包含要删除的题库 ID 列表的请求数据传输对象 + * @return 操作结果,封装在 ApiRest 对象中 + */ + @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") + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/detail", method = { RequestMethod.POST}) + public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) { + // 根据 ID 获取题库实体 + Repo entity = baseService.getById(reqDTO.getId()); + // 创建一个新的 DTO 对象 + RepoDTO dto = new RepoDTO(); + // 将实体对象的属性复制到 DTO 对象中 + BeanUtils.copyProperties(entity, dto); + // 返回包含 DTO 对象的操作成功响应 + return super.success(dto); + } + + /** + * 分页查找题库信息 + * @param reqDTO 包含分页和查询条件的请求数据传输对象 + * @return 包含分页查询结果的操作结果,封装在 ApiRest 对象中 + */ + @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") + @ApiOperation(value = "批量操作", notes = "批量加入或从题库移除") + @RequestMapping(value = "/batch-action", method = { RequestMethod.POST}) + public ApiRest batchAction(@RequestBody QuRepoBatchReqDTO reqDTO) { + + // 调用服务层方法进行批量操作 + quRepoService.batchAction(reqDTO); + // 返回操作成功的响应 + return super.success(); + } +} diff --git a/repo/dto/RepoDTO.java b/repo/dto/RepoDTO.java new file mode 100644 index 0000000..c2063da --- /dev/null +++ b/repo/dto/RepoDTO.java @@ -0,0 +1,67 @@ +package com.yf.exam.modules.repo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * 题库请求类,用于在与题库相关的业务操作中传输数据,实现了 Serializable 接口,可进行序列化操作。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:23 + */ +@Data +@ApiModel(value="题库", description="题库") +public class RepoDTO implements Serializable { + + // 序列化版本号,确保序列化和反序列化的兼容性 + private static final long serialVersionUID = 1L; + + /** + * 题库的唯一标识 ID,在业务操作中用于唯一确定一个题库。 + * 该属性为必填项。 + */ + @ApiModelProperty(value = "题库ID", required=true) + private String id; + + /** + * 题库的编号,可用于对题库进行分类或标识。 + * 该属性为必填项。 + */ + @ApiModelProperty(value = "题库编号", required=true) + private String code; + + /** + * 题库的名称,用于直观地表示题库的主题或内容。 + * 该属性为必填项。 + */ + @ApiModelProperty(value = "题库名称", required=true) + private String title; + + /** + * 题库的备注信息,可用于记录关于题库的额外说明或注意事项。 + * 该属性为必填项。 + */ + @ApiModelProperty(value = "题库备注", required=true) + private String remark; + + /** + * 题库的创建时间,记录该题库创建的具体时间点。 + * 该属性为必填项。 + */ + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + /** + * 题库的更新时间,记录该题库最后一次被修改的具体时间点。 + * 该属性为必填项。 + */ + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + +} diff --git a/repo/dto/request/RepoReqDTO.java b/repo/dto/request/RepoReqDTO.java new file mode 100644 index 0000000..89cef56 --- /dev/null +++ b/repo/dto/request/RepoReqDTO.java @@ -0,0 +1,40 @@ +package com.yf.exam.modules.repo.dto.request; + +import com.yf.exam.modules.repo.dto.RepoDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + *

+ * 题库请求类,用于在分页查询题库信息时传递请求参数,继承自 RepoDTO 类,可复用其属性。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:23 + */ +@Data +@ApiModel(value="题库分页请求类", description="题库分页请求类") +public class RepoReqDTO extends RepoDTO { + + // 序列化版本号,确保序列化和反序列化的兼容性 + private static final long serialVersionUID = 1L; + + /** + * 排除题库 ID 列表,在查询题库时,会排除这些 ID 对应的题库。 + * 该参数为必填项。 + */ + @ApiModelProperty(value = "排除题库ID", required=true) + private List excludes; + + /** + * 题库标题,用于筛选具有特定标题的题库。 + * 此处注解中的描述与字段名不符,原注解描述为单选题数量,可能存在错误,应根据实际需求调整。 + * 该参数为必填项。 + */ + @ApiModelProperty(value = "单选题数量", required=true) + private String title; + +} diff --git a/repo/dto/response/RepoRespDTO.java b/repo/dto/response/RepoRespDTO.java new file mode 100644 index 0000000..e871768 --- /dev/null +++ b/repo/dto/response/RepoRespDTO.java @@ -0,0 +1,45 @@ +package com.yf.exam.modules.repo.dto.response; + +import com.yf.exam.modules.repo.dto.RepoDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 题库分页响应类,用于封装题库分页查询的响应数据。 + * 该类继承自 RepoDTO,在其基础上扩展了不同题型数量的属性。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:23 + */ +@Data +@ApiModel(value="题库分页响应类", description="题库分页响应类") +public class RepoRespDTO extends RepoDTO { + + // 序列化版本号,用于在序列化和反序列化过程中确保版本的兼容性 + private static final long serialVersionUID = 1L; + + /** + * 多选题数量,代表该题库中多选题的总数。 + * 该属性为必填项,在响应数据中必须包含。 + */ + @ApiModelProperty(value = "多选题数量", required=true) + private Integer multiCount; + + /** + * 单选题数量,代表该题库中单选题的总数。 + * 该属性为必填项,在响应数据中必须包含。 + */ + @ApiModelProperty(value = "单选题数量", required=true) + private Integer radioCount; + + /** + * 判断题数量,代表该题库中判断题的总数。 + * 该属性为必填项,在响应数据中必须包含。 + */ + @ApiModelProperty(value = "判断题数量", required=true) + private Integer judgeCount; + +} diff --git a/repo/entity/Repo.java b/repo/entity/Repo.java new file mode 100644 index 0000000..cb37a3c --- /dev/null +++ b/repo/entity/Repo.java @@ -0,0 +1,62 @@ +package com.yf.exam.modules.repo.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; + +/** + *

+ * 题库实体类,对应数据库中的 `el_repo` 表,用于封装题库相关的数据。 + * 继承自 MyBatis-Plus 的 Model 类,可使用其提供的 ActiveRecord 功能进行数据库操作。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:23 + */ +@Data +// 表明该实体类对应数据库中的 el_repo 表 +@TableName("el_repo") +public class Repo extends Model { + + // 序列化版本号,确保序列化和反序列化的兼容性 + private static final long serialVersionUID = 1L; + + /** + * 题库ID,作为表的主键,使用 MyBatis-Plus 的 ASSIGN_ID 策略自动分配 ID。 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 题库编号,用于标识不同的题库。 + */ + private String code; + + /** + * 题库名称,直观反映题库的主题或内容。 + */ + private String title; + + /** + * 题库备注,可用于记录关于题库的额外说明信息。 + */ + private String remark; + + /** + * 创建时间,记录题库创建的时间,对应数据库表中的 create_time 字段。 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间,记录题库最后一次更新的时间,对应数据库表中的 update_time 字段。 + */ + @TableField("update_time") + private Date updateTime; + +} diff --git a/repo/mapper/RepoMapper.java b/repo/mapper/RepoMapper.java new file mode 100644 index 0000000..f6d009e --- /dev/null +++ b/repo/mapper/RepoMapper.java @@ -0,0 +1,31 @@ +package com.yf.exam.modules.repo.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.repo.dto.request.RepoReqDTO; +import com.yf.exam.modules.repo.dto.response.RepoRespDTO; +import com.yf.exam.modules.repo.entity.Repo; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 题库Mapper接口,继承自 MyBatis-Plus 的 BaseMapper 接口,用于操作题库实体类与数据库的交互。 + * BaseMapper 接口提供了基本的增删改查操作,该接口在此基础上扩展了分页查询功能。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:23 + */ +public interface RepoMapper extends BaseMapper { + + /** + * 分页查询题库信息 + * + * @param page 分页对象,包含分页的相关信息,如当前页码、每页记录数等,用于控制查询结果的分页展示。 + * @param query 题库查询请求对象,包含查询条件,如排除题库ID、题库标题等,用于筛选符合条件的题库记录。 + * @return 返回一个包含 RepoRespDTO 对象的分页数据,RepoRespDTO 是题库分页响应类,封装了题库的相关信息以及不同题型的数量。 + */ + IPage paging(Page page, @Param("query") RepoReqDTO query); + +} diff --git a/repo/service/RepoService.java b/repo/service/RepoService.java new file mode 100644 index 0000000..85dbed7 --- /dev/null +++ b/repo/service/RepoService.java @@ -0,0 +1,38 @@ +package com.yf.exam.modules.repo.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.repo.dto.RepoDTO; +import com.yf.exam.modules.repo.dto.request.RepoReqDTO; +import com.yf.exam.modules.repo.dto.response.RepoRespDTO; +import com.yf.exam.modules.repo.entity.Repo; + +/** + *

+ * 题库业务类,定义了与题库相关的业务操作接口。 + * 该接口继承自 MyBatis-Plus 的 IService 接口,可使用其提供的基础 CRUD 操作方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:23 + */ +public interface RepoService extends IService { + + /** + * 分页查询题库数据 + * + * @param reqDTO 包含分页信息和查询条件的请求对象。 + * PagingReqDTO 封装了分页参数,RepoReqDTO 封装了具体的查询条件,如排除题库 ID、题库标题等。 + * @return 一个 IPage 对象,包含分页查询结果,结果元素为 RepoRespDTO 类型,该类型封装了题库的详细信息及题型数量。 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 保存或更新题库信息 + * + * @param reqDTO 包含题库信息的数据传输对象,包含题库的 ID、编号、名称、备注等信息。 + * 若 DTO 中的 ID 存在,则进行更新操作;若 ID 不存在,则进行新增操作。 + */ + void save(RepoDTO reqDTO); +} diff --git a/repo/service/impl/RepoServiceImpl.java b/repo/service/impl/RepoServiceImpl.java new file mode 100644 index 0000000..454d167 --- /dev/null +++ b/repo/service/impl/RepoServiceImpl.java @@ -0,0 +1,53 @@ +package com.yf.exam.modules.repo.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.repo.dto.RepoDTO; +import com.yf.exam.modules.repo.dto.request.RepoReqDTO; +import com.yf.exam.modules.repo.dto.response.RepoRespDTO; +import com.yf.exam.modules.repo.entity.Repo; +import com.yf.exam.modules.repo.mapper.RepoMapper; +import com.yf.exam.modules.repo.service.RepoService; +import org.springframework.stereotype.Service; + +/** + *

+ * 题库服务实现类,实现了 RepoService 接口,继承自 MyBatis-Plus 的 ServiceImpl, + * 用于处理题库相关的业务逻辑,与数据库进行交互。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:23 + */ +@Service +public class RepoServiceImpl extends ServiceImpl implements RepoService { + + /** + * 分页查询题库信息 + * + * @param reqDTO 包含分页信息和查询参数的请求对象 + * @return 包含分页结果的 IPage 对象,其中元素为 RepoRespDTO 类型 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + // 调用 RepoMapper 的 paging 方法进行分页查询,传入分页对象和查询参数 + return baseMapper.paging(reqDTO.toPage(), reqDTO.getParams()); + } + + /** + * 保存或更新题库信息 + * + * @param reqDTO 包含题库信息的请求数据传输对象 + */ + @Override + public void save(RepoDTO reqDTO) { + + // 复制 DTO 对象的属性到实体对象 + Repo entity = new Repo(); + BeanMapper.copy(reqDTO, entity); + // 调用 MyBatis-Plus 的 saveOrUpdate 方法保存或更新实体对象 + this.saveOrUpdate(entity); + } +} diff --git a/sys/config/controller/SysConfigController.java b/sys/config/controller/SysConfigController.java new file mode 100644 index 0000000..fa9cfe4 --- /dev/null +++ b/sys/config/controller/SysConfigController.java @@ -0,0 +1,92 @@ +package com.yf.exam.modules.sys.config.controller; + +// 导入 API 响应结果类,用于封装接口返回数据 +import com.yf.exam.core.api.ApiRest; +// 导入基础控制器类,提供一些通用的控制器方法 +import com.yf.exam.core.api.controller.BaseController; +// 导入基础 ID 响应数据传输对象类 +import com.yf.exam.core.api.dto.BaseIdRespDTO; +// 导入 Bean 映射工具类,用于对象属性的复制 +import com.yf.exam.core.utils.BeanMapper; +// 导入图片校验工具类 +import com.yf.exam.modules.qu.utils.ImageCheckUtils; +// 导入系统配置数据传输对象类 +import com.yf.exam.modules.sys.config.dto.SysConfigDTO; +// 导入系统配置实体类 +import com.yf.exam.modules.sys.config.entity.SysConfig; +// 导入系统配置服务接口 +import com.yf.exam.modules.sys.config.service.SysConfigService; +// 导入 Swagger 注解,用于生成 API 文档,标记控制器的标签 +import io.swagger.annotations.Api; +// 导入 Swagger 注解,用于生成 API 文档,标记接口的操作描述 +import io.swagger.annotations.ApiOperation; +// 导入 Shiro 注解,用于权限控制,要求用户具有指定角色才能访问接口 +import org.apache.shiro.authz.annotation.RequiresRoles; +// 导入 Spring 依赖注入注解 +import org.springframework.beans.factory.annotation.Autowired; +// 导入 Spring 请求体注解,用于将请求体中的数据绑定到方法参数上 +import org.springframework.web.bind.annotation.RequestBody; +// 导入 Spring 请求映射注解,用于映射请求路径 +import org.springframework.web.bind.annotation.RequestMapping; +// 导入 Spring 请求方法注解,用于指定请求的 HTTP 方法 +import org.springframework.web.bind.annotation.RequestMethod; +// 导入 Spring 控制器注解,标记该类为 RESTful 风格的控制器 +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 通用配置控制器,处理与系统通用配置相关的接口请求 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-17 09:12 + */ +@Api(tags={"通用配置"}) +@RestController +@RequestMapping("/exam/api/sys/config") +public class SysConfigController extends BaseController { + + // 自动注入系统配置服务实例,用于调用系统配置相关的业务逻辑 + @Autowired + private SysConfigService baseService; + + // 自动注入图片校验工具实例,用于校验图片地址 + @Autowired + private ImageCheckUtils imageCheckUtils; + + /** + * 添加或修改系统配置信息 + * @param reqDTO 系统配置数据传输对象,包含要添加或修改的配置信息 + * @return 封装了操作结果和配置 ID 的 API 响应对象 + */ + @RequiresRoles("sa") + @ApiOperation(value = "添加或修改") + @RequestMapping(value = "/save", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody SysConfigDTO reqDTO) { + + // 复制请求数据传输对象中的属性到系统配置实体对象中 + SysConfig entity = new SysConfig(); + BeanMapper.copy(reqDTO, entity); + + // 调用图片校验工具类的方法,校验系统配置中的背景 LOGO 地址是否合法 + imageCheckUtils.checkImage(entity.getBackLogo(), "系统LOGO地址错误!"); + + // 调用系统配置服务的方法,保存或更新系统配置信息 + baseService.saveOrUpdate(entity); + // 调用父类的成功响应方法,返回包含配置 ID 的响应对象 + return super.success(new BaseIdRespDTO(entity.getId())); + } + + /** + * 查找系统配置详情 + * @return 封装了系统配置详情数据传输对象的 API 响应对象 + */ + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/detail", method = { RequestMethod.POST}) + public ApiRest find() { + // 调用系统配置服务的方法,获取系统配置详情数据传输对象 + SysConfigDTO dto = baseService.find(); + // 调用父类的成功响应方法,返回包含系统配置详情的响应对象 + return super.success(dto); + } +} diff --git a/sys/config/dto/SysConfigDTO.java b/sys/config/dto/SysConfigDTO.java new file mode 100644 index 0000000..f85b824 --- /dev/null +++ b/sys/config/dto/SysConfigDTO.java @@ -0,0 +1,61 @@ +// 定义包名,表明该类所属的模块和功能目录 +package com.yf.exam.modules.sys.config.dto; + +// 导入 Swagger 注解,用于生成 API 文档,标记类或属性的描述信息 +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法 +import lombok.Data; + +// 导入 Serializable 接口,表明该类的对象可以被序列化 +import java.io.Serializable; + +/** + *

+ * 通用配置请求类,用于在不同层之间传输系统通用配置信息 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-17 09:12 + */ +// 自动生成 getter、setter、equals、hashCode 和 toString 方法 +@Data +// 为 Swagger 文档提供类的描述信息 +@ApiModel(value="通用配置", description="通用配置") +public class SysConfigDTO implements Serializable { + + // 序列化版本号,用于在反序列化时验证版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 系统配置的唯一标识 + * 该属性在请求和响应中是必需的 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 系统的名称 + */ + @ApiModelProperty(value = "系统名称") + private String siteName; + + /** + * 前端页面显示的 LOGO 地址 + */ + @ApiModelProperty(value = "前端LOGO") + private String frontLogo; + + /** + * 后台管理页面显示的 LOGO 地址 + */ + @ApiModelProperty(value = "后台LOGO") + private String backLogo; + + /** + * 系统的版权信息 + */ + @ApiModelProperty(value = "版权信息") + private String copyRight; + +} diff --git a/sys/config/entity/SysConfig.java b/sys/config/entity/SysConfig.java new file mode 100644 index 0000000..070ab06 --- /dev/null +++ b/sys/config/entity/SysConfig.java @@ -0,0 +1,53 @@ +package com.yf.exam.modules.sys.config.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; + +/** +*

+* 通用配置实体类 +*

+* +* @author 聪明笨狗 +* @since 2020-04-17 09:12 +*/ +@Data +@TableName("sys_config") +public class SysConfig extends Model { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 系统名称 + */ + @TableField("site_name") + private String siteName; + + /** + * 前端LOGO + */ + @TableField("front_logo") + private String frontLogo; + + /** + * 后台LOGO + */ + @TableField("back_logo") + private String backLogo; + + /** + * 版权信息 + */ + @TableField("copy_right") + private String copyRight; +} diff --git a/sys/config/mapper/SysConfigMapper.java b/sys/config/mapper/SysConfigMapper.java new file mode 100644 index 0000000..35eea21 --- /dev/null +++ b/sys/config/mapper/SysConfigMapper.java @@ -0,0 +1,25 @@ +/** + * 定义包名,表明该类所在的模块和目录结构,此包为系统配置模块的映射器包 + */ +package com.yf.exam.modules.sys.config.mapper; + +/** + * 导入 MyBatis-Plus 框架的 BaseMapper 接口,用于提供基本的数据库操作方法 + */ +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +/** + * 导入系统配置实体类,该实体类对应数据库中的系统配置表 + */ +import com.yf.exam.modules.sys.config.entity.SysConfig; + +/** + *

+ * 通用配置Mapper,用于与数据库中的系统配置表进行交互,继承自 BaseMapper 可使用其提供的基础 CRUD 操作 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-17 09:12 + */ +public interface SysConfigMapper extends BaseMapper { + // 该接口目前未自定义额外的数据库操作方法,可根据需求在此添加 +} diff --git a/sys/config/service/SysConfigService.java b/sys/config/service/SysConfigService.java new file mode 100644 index 0000000..321b533 --- /dev/null +++ b/sys/config/service/SysConfigService.java @@ -0,0 +1,34 @@ +/** + * 定义包名,表明该类所属的模块和功能目录,这里是系统配置服务模块 + */ +package com.yf.exam.modules.sys.config.service; + +/** + * 导入 MyBatis-Plus 框架的扩展服务接口,该接口提供了一些通用的服务层方法 + */ +import com.baomidou.mybatisplus.extension.service.IService; +/** + * 导入系统配置数据传输对象类,用于在不同层之间传输系统配置相关的数据 + */ +import com.yf.exam.modules.sys.config.dto.SysConfigDTO; +/** + * 导入系统配置实体类,对应数据库中的系统配置表 + */ +import com.yf.exam.modules.sys.config.entity.SysConfig; + +/** + *

+ * 通用配置业务类,定义了与系统通用配置相关的业务方法,继承自 IService 接口可使用其提供的基础服务方法 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-17 09:12 + */ +public interface SysConfigService extends IService { + + /** + * 查找配置信息,从数据库或其他数据源中获取系统配置信息并封装成 SysConfigDTO 对象返回 + * @return 系统配置数据传输对象,包含系统配置的相关信息 + */ + SysConfigDTO find(); +} diff --git a/sys/config/service/impl/SysConfigServiceImpl.java b/sys/config/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..c02cf8e --- /dev/null +++ b/sys/config/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,54 @@ +// 定义当前类所在的包 +package com.yf.exam.modules.sys.config.service.impl; + +// 导入 MyBatis-Plus 框架的查询条件构造器类 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +// 导入 MyBatis-Plus 框架的服务实现基类 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入自定义的 Bean 映射工具类,用于对象属性的复制 +import com.yf.exam.core.utils.BeanMapper; +// 导入系统配置数据传输对象类 +import com.yf.exam.modules.sys.config.dto.SysConfigDTO; +// 导入系统配置实体类 +import com.yf.exam.modules.sys.config.entity.SysConfig; +// 导入系统配置映射器接口 +import com.yf.exam.modules.sys.config.mapper.SysConfigMapper; +// 导入系统配置服务接口 +import com.yf.exam.modules.sys.config.service.SysConfigService; +// 导入 Spring 框架的服务注解,将该类标记为服务层组件 +import org.springframework.stereotype.Service; + +/** + *

+ * 语言设置 服务实现类,负责处理系统配置的具体业务逻辑 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-17 09:12 + */ +// 将该类标记为 Spring 服务组件,使其可以被 Spring 容器管理 +@Service +public class SysConfigServiceImpl extends ServiceImpl implements SysConfigService { + + /** + * 查找系统配置信息,返回第一个系统配置的 DTO 对象 + * + * @return 系统配置数据传输对象 + */ + @Override + public SysConfigDTO find() { + // 创建一个查询条件构造器,用于构建对 SysConfig 实体的查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + // 在查询语句末尾添加 "LIMIT 1",表示只查询一条记录 + wrapper.last(" LIMIT 1"); + + // 调用父类的 getOne 方法,根据查询条件获取一个 SysConfig 实体对象,false 表示不严格校验是否只返回一条记录 + SysConfig entity = this.getOne(wrapper, false); + // 创建一个新的系统配置数据传输对象 + SysConfigDTO dto = new SysConfigDTO(); + // 使用 BeanMapper 工具类将 SysConfig 实体对象的属性复制到 SysConfigDTO 对象中 + BeanMapper.copy(entity, dto); + // 返回填充好数据的系统配置数据传输对象 + return dto; + } +} diff --git a/sys/depart/controller/SysDepartController.java b/sys/depart/controller/SysDepartController.java new file mode 100644 index 0000000..fe7be06 --- /dev/null +++ b/sys/depart/controller/SysDepartController.java @@ -0,0 +1,186 @@ +// 定义当前类所在的包,表明该类属于系统部门控制器模块 +package com.yf.exam.modules.sys.depart.controller; + +// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +// 导入 MyBatis-Plus 的分页元数据接口,用于处理分页查询结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入自定义的 API 统一响应类,用于封装接口返回数据 +import com.yf.exam.core.api.ApiRest; +// 导入自定义的基础控制器类,提供一些通用的控制器方法 +import com.yf.exam.core.api.controller.BaseController; +// 导入自定义的基础单个 ID 请求数据传输对象类 +import com.yf.exam.core.api.dto.BaseIdReqDTO; +// 导入自定义的基础多个 ID 请求数据传输对象类 +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +// 导入自定义的分页请求数据传输对象类 +import com.yf.exam.core.api.dto.PagingReqDTO; +// 导入自定义的 Bean 映射工具类,用于对象属性的复制 +import com.yf.exam.core.utils.BeanMapper; +// 导入系统部门数据传输对象类,用于在不同层之间传输部门信息 +import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; +// 导入部门排序请求数据传输对象类,用于接收部门排序的请求信息 +import com.yf.exam.modules.sys.depart.dto.request.DepartSortReqDTO; +// 导入系统部门树状数据传输对象类,用于返回部门的树状结构信息 +import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; +// 导入系统部门实体类,对应数据库中的部门表 +import com.yf.exam.modules.sys.depart.entity.SysDepart; +// 导入系统部门服务接口,用于调用部门相关的业务逻辑 +import com.yf.exam.modules.sys.depart.service.SysDepartService; +// 导入 Swagger 注解,用于生成 API 文档,标记控制器的标签 +import io.swagger.annotations.Api; +// 导入 Swagger 注解,用于生成 API 文档,标记接口的操作描述 +import io.swagger.annotations.ApiOperation; +// 导入 Shiro 注解,用于权限控制,要求用户具有指定角色才能访问接口 +import org.apache.shiro.authz.annotation.RequiresRoles; +// 导入 Spring 框架的依赖注入注解,用于自动注入依赖的 Bean +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +// 导入 Spring 框架的请求体注解,用于将请求体中的数据绑定到方法参数上 +import org.springframework.web.bind.annotation.RequestBody; +// 导入 Spring 框架的请求映射注解,用于映射请求路径 +import org.springframework.web.bind.annotation.RequestMapping; +// 导入 Spring 框架的请求方法注解,用于指定请求的 HTTP 方法 +import org.springframework.web.bind.annotation.RequestMethod; +// 导入 Spring 框架的控制器注解,标记该类为 RESTful 风格的控制器 +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + *

+ * 部门信息控制器,处理与系统部门信息相关的接口请求 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-02 17:25 + */ +// 为 Swagger 文档标记该控制器的标签为 "部门信息" +@Api(tags={"部门信息"}) +// 标记该类为 RESTful 风格的控制器,返回数据直接作为 HTTP 响应体 +@RestController +// 定义该控制器处理的请求路径前缀 +@RequestMapping("/exam/api/sys/depart") +public class SysDepartController extends BaseController { + + // 自动注入系统部门服务实例,用于调用部门相关的业务逻辑 + @Autowired + private SysDepartService baseService; + + /** + * 添加或修改部门信息 + * @param reqDTO 部门信息数据传输对象,包含要添加或修改的部门信息 + * @return 封装了操作结果的 API 统一响应对象 + */ + // 要求用户具有 "sa" 角色才能访问该接口 + @RequiresRoles("sa") + // 为 Swagger 文档标记该接口的操作描述为 "添加或修改" + @ApiOperation(value = "添加或修改") + // 映射请求路径为 "/save",并指定请求方法为 POST + @RequestMapping(value = "/save", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody SysDepartDTO reqDTO) { + // 调用部门服务的保存方法,保存或更新部门信息 + baseService.save(reqDTO); + // 调用父类的成功响应方法,返回操作成功的响应对象 + return super.success(); + } + + /** + * 批量删除部门信息 + * @param reqDTO 包含多个部门 ID 的请求数据传输对象,用于指定要删除的部门 + * @return 封装了操作结果的 API 统一响应对象 + */ + @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 封装了部门详情信息的 API 统一响应对象 + */ + @RequiresRoles("sa") + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/detail", method = { RequestMethod.POST}) + public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) { + // 根据传入的部门 ID,调用部门服务的获取方法获取部门实体对象 + SysDepart entity = baseService.getById(reqDTO.getId()); + // 创建一个新的部门数据传输对象 + SysDepartDTO dto = new SysDepartDTO(); + // 将部门实体对象的属性复制到部门数据传输对象中 + BeanUtils.copyProperties(entity, dto); + return super.success(dto); + } + + /** + * 分页查找部门信息 + * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数 + * @return 封装了分页查询结果的 API 统一响应对象 + */ + @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); + } + + /** + * 查找部门列表,每次最多返回 200 条数据 + * @param reqDTO 部门信息数据传输对象,包含查询部门列表的条件 + * @return 封装了部门列表信息的 API 统一响应对象 + */ + @RequiresRoles("sa") + @ApiOperation(value = "查找列表") + @RequestMapping(value = "/list", method = { RequestMethod.POST}) + public ApiRest> list(@RequestBody SysDepartDTO reqDTO) { + + // 创建一个 MyBatis-Plus 的查询条件构造器 + QueryWrapper wrapper = new QueryWrapper<>(); + + // 调用部门服务的列表查询方法,根据查询条件获取部门实体列表 + List list = baseService.list(wrapper); + + // 使用 Bean 映射工具类将部门实体列表转换为部门数据传输对象列表 + List dtoList = BeanMapper.mapList(list, SysDepartDTO.class); + + return super.success(dtoList); + } + + + /** + * 获取部门树列表 + * @return 封装了部门树列表信息的 API 统一响应对象 + */ + @RequiresRoles("sa") + @ApiOperation(value = "树列表") + @RequestMapping(value = "/tree", method = { RequestMethod.POST}) + public ApiRest> tree() { + // 调用部门服务的查找树列表方法,获取部门树列表数据传输对象列表 + List dtoList = baseService.findTree(); + return super.success(dtoList); + } + + + /** + * 对部门进行分类排序 + * @param reqDTO 部门排序请求数据传输对象,包含要排序的部门 ID 和排序值 + * @return 封装了操作结果的 API 统一响应对象 + */ + @RequiresRoles("sa") + @ApiOperation(value = "分类排序") + @RequestMapping(value = "/sort", method = { RequestMethod.POST}) + public ApiRest sort(@RequestBody DepartSortReqDTO reqDTO) { + // 调用部门服务的排序方法,根据传入的部门 ID 和排序值对部门进行排序 + baseService.sort(reqDTO.getId(), reqDTO.getSort()); + return super.success(); + } +} diff --git a/sys/depart/dto/SysDepartDTO.java b/sys/depart/dto/SysDepartDTO.java new file mode 100644 index 0000000..70d8efb --- /dev/null +++ b/sys/depart/dto/SysDepartDTO.java @@ -0,0 +1,73 @@ +// 定义该类所在的包,表明其在项目模块中的位置 +package com.yf.exam.modules.sys.depart.dto; + +// 导入 Swagger 注解,用于在生成 API 文档时描述类的信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于在生成 API 文档时描述类属性的信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; + +// 导入 Serializable 接口,使该类的对象可以被序列化和反序列化 +import java.io.Serializable; + +/** + *

+ * 部门信息数据传输类,用于在不同层之间传输部门相关信息,如在控制器与服务层、服务层与数据访问层之间传递数据。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-02 17:25 + */ +// 使用 Lombok 的 Data 注解,自动生成常用方法 +@Data +// 使用 Swagger 的 ApiModel 注解,为 API 文档描述该类的信息 +@ApiModel(value="部门信息", description="部门信息") +public class SysDepartDTO implements Serializable { + + // 序列化版本号,确保序列化和反序列化时类的版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 部门的唯一标识 ID + * 在 API 文档中标记为必需项 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 部门类型,1 表示公司,2 表示部门 + * 在 API 文档中标记为必需项 + */ + @ApiModelProperty(value = "1公司2部门", required=true) + private Integer deptType; + + /** + * 该部门所属的上级部门 ID + * 在 API 文档中标记为必需项 + */ + @ApiModelProperty(value = "所属上级", required=true) + private String parentId; + + /** + * 部门的名称 + * 在 API 文档中标记为必需项 + */ + @ApiModelProperty(value = "部门名称", required=true) + private String deptName; + + /** + * 部门的编码 + * 在 API 文档中标记为必需项 + */ + @ApiModelProperty(value = "部门编码", required=true) + private String deptCode; + + /** + * 部门的排序值,用于对部门进行排序展示 + * 在 API 文档中标记为必需项 + */ + @ApiModelProperty(value = "排序", required=true) + private Integer sort; + +} diff --git a/sys/depart/dto/request/DepartSortReqDTO.java b/sys/depart/dto/request/DepartSortReqDTO.java new file mode 100644 index 0000000..40fd441 --- /dev/null +++ b/sys/depart/dto/request/DepartSortReqDTO.java @@ -0,0 +1,42 @@ +// 声明该类所在的包,表明其在项目模块中的位置 +package com.yf.exam.modules.sys.depart.dto.request; + +// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法 +import lombok.Data; + +// 导入 Serializable 接口,表明该类的对象可以被序列化 +import java.io.Serializable; + +/** + *

+ * 部门排序请求类,用于封装部门排序操作所需的请求参数 + *

+ * + * @author 聪明笨狗 + * @since 2020-03-14 10:37 + */ +// 自动生成 getter、setter、equals、hashCode 和 toString 方法 +@Data +// 为 Swagger 文档提供类的描述信息 +@ApiModel(value="部门排序请求类", description="部门排序请求类") +public class DepartSortReqDTO implements Serializable { + + // 序列化版本号,用于在反序列化时验证版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 分类 ID,标识要进行排序操作的部门 + */ + @ApiModelProperty(value = "分类ID") + private String id; + + /** + * 排序方式,0 表示下降排序,1 表示上升排序 + */ + @ApiModelProperty(value = "排序,0下降,1上升") + private Integer sort; +} diff --git a/sys/depart/dto/response/SysDepartTreeDTO.java b/sys/depart/dto/response/SysDepartTreeDTO.java new file mode 100644 index 0000000..bb7137b --- /dev/null +++ b/sys/depart/dto/response/SysDepartTreeDTO.java @@ -0,0 +1,41 @@ +// 定义包名,表明该类所属的模块和功能目录 +package com.yf.exam.modules.sys.depart.dto.response; + +// 导入 SysDepartDTO 类,该类是当前类的父类,包含部门的基本信息 +import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; +// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法 +import lombok.Data; + +// 导入 List 接口,用于存储子部门列表 +import java.util.List; + +/** + *

+ * 部门树结构响应类,继承自 SysDepartDTO,用于返回部门的树状结构信息 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-02 17:25 + */ +// 自动生成 getter、setter、equals、hashCode 和 toString 方法 +@Data +// 为 Swagger 文档提供类的描述信息 +@ApiModel(value="部门树结构响应类", description="部门树结构响应类") +public class SysDepartTreeDTO extends SysDepartDTO { + + // 序列化版本号,用于在反序列化时验证版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 子部门列表,存储当前部门的所有子部门信息 + * 该属性在 API 文档中标记为必需项 + */ + @ApiModelProperty(value = "子列表", required=true) + private List children; + + +} diff --git a/sys/depart/entity/SysDepart.java b/sys/depart/entity/SysDepart.java new file mode 100644 index 0000000..71ca156 --- /dev/null +++ b/sys/depart/entity/SysDepart.java @@ -0,0 +1,59 @@ +package com.yf.exam.modules.sys.depart.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; + +/** +*

+* 部门信息实体类 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +@Data +@TableName("sys_depart") +public class SysDepart extends Model { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 1公司2部门 + */ + @TableField("dept_type") + private Integer deptType; + + /** + * 所属上级 + */ + @TableField("parent_id") + private String parentId; + + /** + * 部门名称 + */ + @TableField("dept_name") + private String deptName; + + /** + * 部门编码 + */ + @TableField("dept_code") + private String deptCode; + + /** + * 排序 + */ + private Integer sort; + +} diff --git a/sys/depart/mapper/SysDepartMapper.java b/sys/depart/mapper/SysDepartMapper.java new file mode 100644 index 0000000..221ab89 --- /dev/null +++ b/sys/depart/mapper/SysDepartMapper.java @@ -0,0 +1,28 @@ +package com.yf.exam.modules.sys.depart.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.sys.depart.dto.SysDepartDTO; +import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; +import com.yf.exam.modules.sys.depart.entity.SysDepart; +import org.apache.ibatis.annotations.Param; + +/** +*

+* 部门信息Mapper +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +public interface SysDepartMapper extends BaseMapper { + + /** + * 部门树分页 + * @param page + * @param query + * @return + */ + IPage paging(Page page, @Param("query") SysDepartDTO query); +} diff --git a/sys/depart/service/SysDepartService.java b/sys/depart/service/SysDepartService.java new file mode 100644 index 0000000..1b70e83 --- /dev/null +++ b/sys/depart/service/SysDepartService.java @@ -0,0 +1,62 @@ +package com.yf.exam.modules.sys.depart.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.sys.depart.dto.SysDepartDTO; +import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; +import com.yf.exam.modules.sys.depart.entity.SysDepart; + +import java.util.List; + +/** +*

+* 部门信息业务类 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +public interface SysDepartService extends IService { + + /** + * 保存 + * @param reqDTO + */ + void save(SysDepartDTO reqDTO); + + /** + * 分页查询数据 + * @param reqDTO + * @return + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 查找部门树结构 + * @return + */ + List findTree(); + + /** + * 查找部门树 + * @param ids + * @return + */ + List findTree(List ids); + + /** + * 排序 + * @param id + * @param sort + */ + void sort(String id, Integer sort); + + + /** + * 获取某个部门ID下的所有子部门ID + * @param id + * @return + */ + List listAllSubIds( String id); +} diff --git a/sys/depart/service/impl/SysDepartServiceImpl.java b/sys/depart/service/impl/SysDepartServiceImpl.java new file mode 100644 index 0000000..e0a610a --- /dev/null +++ b/sys/depart/service/impl/SysDepartServiceImpl.java @@ -0,0 +1,368 @@ +package com.yf.exam.modules.sys.depart.service.impl; + +// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +// 导入 MyBatis-Plus 的分页元数据接口,用于处理分页查询结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入 MyBatis-Plus 的分页类,用于创建分页对象 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 导入 MyBatis-Plus 的服务实现基类,提供通用的服务层方法 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入自定义的分页请求数据传输对象类,用于封装分页查询请求参数 +import com.yf.exam.core.api.dto.PagingReqDTO; +// 导入自定义的 Bean 映射工具类,用于对象属性的复制 +import com.yf.exam.core.utils.BeanMapper; +// 导入系统部门数据传输对象类,用于在不同层之间传输部门信息 +import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; +// 导入系统部门树状数据传输对象类,用于返回部门的树状结构信息 +import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; +// 导入系统部门实体类,对应数据库中的部门表 +import com.yf.exam.modules.sys.depart.entity.SysDepart; +// 导入系统部门映射器接口,用于与数据库进行交互 +import com.yf.exam.modules.sys.depart.mapper.SysDepartMapper; +// 导入系统部门服务接口,定义部门相关的业务方法 +import com.yf.exam.modules.sys.depart.service.SysDepartService; +// 导入 Apache Commons Lang3 工具类,用于字符串操作 +import org.apache.commons.lang3.StringUtils; +// 导入 Spring 工具类,用于集合操作 +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * 部门信息业务实现类,实现了系统部门服务接口,处理部门相关的业务逻辑 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-02 17:25 + */ +// 将该类标记为 Spring 服务组件,使其可以被 Spring 容器管理 +@Service +public class SysDepartServiceImpl extends ServiceImpl implements SysDepartService { + + /** + * 0标识为顶级分类,作为根部门的标记 + */ + private static final String ROOT_TAG = "0"; + + /** + * 保存或更新部门信息 + * 如果传入的部门 ID 为空,则填充部门编码;否则,清空排序和部门编码信息 + * @param reqDTO 部门信息数据传输对象,包含要保存或更新的部门信息 + */ + @Override + public void save(SysDepartDTO reqDTO) { + // 判断传入的部门 ID 是否为空 + if(StringUtils.isBlank(reqDTO.getId())) { + // 若为空,则填充部门编码 + this.fillCode(reqDTO); + } else { + // 若不为空,则清空排序和部门编码信息 + reqDTO.setSort(null); + reqDTO.setDeptCode(null); + } + + // 创建一个新的部门实体对象 + SysDepart entity = new SysDepart(); + // 使用 BeanMapper 工具类将部门数据传输对象的属性复制到部门实体对象中 + BeanMapper.copy(reqDTO, entity); + // 调用 MyBatis-Plus 的保存或更新方法,保存或更新部门信息 + this.saveOrUpdate(entity); + } + + /** + * 分页查询部门信息,并将结果转换为树状结构 + * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数 + * @return 分页的部门树状结构数据传输对象列表 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + // 创建分页对象,传入当前页码和每页记录数 + Page query = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + // 获取请求参数中的部门信息 + SysDepartDTO params = reqDTO.getParams(); + // 调用映射器的分页查询方法,将结果转换为部门树状结构数据传输对象列表 + IPage pageData = baseMapper.paging(query, params); + + return pageData; + } + + /** + * 查找部门树状结构,调用重载方法,不传入部门 ID 列表 + * @return 部门树状结构数据传输对象列表 + */ + @Override + public List findTree() { + return this.findTree(null); + } + + /** + * 根据指定的部门 ID 列表查找部门树状结构 + * @param ids 部门 ID 列表,若为空则查找所有部门的树状结构 + * @return 部门树状结构数据传输对象列表 + */ + @Override + public List findTree(List ids) { + // 创建查询条件构造器 + QueryWrapper wrapper = new QueryWrapper(); + // 按部门排序字段升序排序 + wrapper.lambda().orderByAsc(SysDepart::getSort); + + // 判断传入的部门 ID 列表是否不为空 + if(!CollectionUtils.isEmpty(ids)) { + // 存储所有相关部门 ID 的列表 + List fullIds = new ArrayList<>(); + // 遍历传入的部门 ID 列表 + for(String id: ids) { + // 递归获取该部门及其所有父部门的 ID 并添加到 fullIds 列表中 + this.cycleAllParent(fullIds, id); + } + + // 判断 fullIds 列表是否不为空 + if(!CollectionUtils.isEmpty(fullIds)) { + // 将查询条件限制为 fullIds 列表中的部门 ID + wrapper.lambda().in(SysDepart::getId, fullIds); + } + } + + // 根据查询条件获取所有部门列表 + List list = this.list(wrapper); + // 使用 BeanMapper 工具类将部门实体列表转换为部门树状结构数据传输对象列表 + List dtoList = BeanMapper.mapList(list, SysDepartTreeDTO.class); + + // 存储每个父部门 ID 对应的子部门列表的映射 + Map> map = new HashMap<>(16); + + // 遍历部门树状结构数据传输对象列表 + for(SysDepartTreeDTO item: dtoList) { + // 判断映射中是否已存在该父部门 ID + if(map.containsKey(item.getParentId())) { + // 若存在,则将该部门添加到对应的子部门列表中 + map.get(item.getParentId()).add(item); + continue; + } + + // 若不存在,则创建一个新的子部门列表,并将该部门添加到列表中 + List a = new ArrayList<>(); + a.add(item); + // 将该父部门 ID 和对应的子部门列表添加到映射中 + map.put(item.getParentId(), a); + } + + // 获取根部门(父部门 ID 为 0)的子部门列表 + List topList = map.get(ROOT_TAG); + // 判断根部门的子部门列表是否不为空 + if(!CollectionUtils.isEmpty(topList)) { + // 遍历根部门的子部门列表 + for(SysDepartTreeDTO item: topList) { + // 递归填充每个部门的子部门信息 + this.fillChildren(map, item); + } + } + + return topList; + } + + /** + * 对部门进行排序操作,可实现部门的上升或下降排序 + * @param id 要排序的部门 ID + * @param sort 排序方式,0 表示上升,1 表示下降 + */ + @Override + public void sort(String id, Integer sort) { + // 根据部门 ID 获取要排序的部门实体对象 + SysDepart depart = this.getById(id); + // 用于交换排序的部门实体对象 + SysDepart exchange = null; + + // 创建查询条件构造器 + QueryWrapper wrapper = new QueryWrapper<>(); + // 限制查询条件为与要排序的部门同级(父部门相同) + wrapper.lambda() + .eq(SysDepart::getParentId, depart.getParentId()); + // 限制查询结果只返回一条记录 + wrapper.last("LIMIT 1"); + + // 判断排序方式是否为上升 + if(sort == 0) { + // 限制查询条件为排序值小于要排序部门的排序值,并按排序值降序排序 + wrapper.lambda() + .lt(SysDepart::getSort, depart.getSort()) + .orderByDesc(SysDepart::getSort); + // 获取满足条件的部门实体对象 + exchange = this.getOne(wrapper, false); + } + + // 判断排序方式是否为下降 + if(sort == 1) { + // 限制查询条件为排序值大于要排序部门的排序值,并按排序值升序排序 + wrapper.lambda() + .gt(SysDepart::getSort, depart.getSort()) + .orderByAsc(SysDepart::getSort); + // 获取满足条件的部门实体对象 + exchange = this.getOne(wrapper, false); + } + + // 判断是否找到可交换排序的部门实体对象 + if(exchange != null) { + // 创建一个新的部门实体对象,用于更新要排序的部门的排序值 + SysDepart a = new SysDepart(); + a.setId(id); + a.setSort(exchange.getSort()); + // 创建一个新的部门实体对象,用于更新可交换排序的部门的排序值 + SysDepart b = new SysDepart(); + b.setId(exchange.getId()); + b.setSort(depart.getSort()); + // 调用 MyBatis-Plus 的更新方法,更新两个部门的排序值 + this.updateById(a); + this.updateById(b); + } + } + + /** + * 填充部门编码,根据父部门信息和同级部门排序生成新的部门编码 + * @param reqDTO 部门信息数据传输对象,用于填充部门编码 + */ + private void fillCode(SysDepartDTO reqDTO) { + // 部门编码的前缀 + String code = ""; + + // 判断传入的父部门 ID 是否不为空且不为根部门 ID + if(StringUtils.isNotBlank(reqDTO.getParentId()) + && !ROOT_TAG.equals(reqDTO.getParentId())) { + // 根据父部门 ID 获取父部门实体对象 + SysDepart parent = this.getById(reqDTO.getParentId()); + // 将父部门的编码作为前缀 + code = parent.getDeptCode(); + } + + // 创建查询条件构造器 + QueryWrapper wrapper = new QueryWrapper<>(); + // 限制查询条件为与要填充编码的部门同级(父部门相同),并按排序值降序排序 + wrapper.lambda() + .eq(SysDepart::getParentId, reqDTO.getParentId()) + .orderByDesc(SysDepart::getSort); + // 限制查询结果只返回一条记录 + wrapper.last("LIMIT 1"); + // 获取满足条件的部门实体对象 + SysDepart depart = this.getOne(wrapper, false); + + // 判断是否找到同级部门实体对象 + if(depart != null) { + // 生成新的部门编码,将同级部门的排序值加 1 并格式化后添加到前缀后面 + code += this.formatCode(depart.getSort() + 1); + // 设置要填充编码的部门的排序值为同级部门的排序值加 1 + reqDTO.setSort(depart.getSort() + 1); + } else { + // 若未找到同级部门实体对象,则生成新的部门编码,排序值为 1 并格式化后添加到前缀后面 + code += this.formatCode(1); + // 设置要填充编码的部门的排序值为 1 + reqDTO.setSort(1); + } + + // 设置要填充编码的部门的部门编码 + reqDTO.setDeptCode(code); + } + + /** + * 格式化排序值,根据排序值生成对应的部门编码前缀 + * @param sort 排序值 + * @return 格式化后的部门编码前缀 + */ + private String formatCode(Integer sort) { + // 判断排序值是否小于 10 + if(sort < 10) { + // 若小于 10,则在排序值前添加 "A0" + return "A0" + sort; + } + // 若不小于 10,则在排序值前添加 "A" + return "A" + sort; + } + + /** + * 递归填充部门的子部门信息 + * @param map 存储每个父部门 ID 对应的子部门列表的映射 + * @param item 当前要填充子部门信息的部门树状结构数据传输对象 + */ + private void fillChildren(Map> map, SysDepartTreeDTO item) { + // 判断映射中是否存在该部门 ID 对应的子部门列表 + if(map.containsKey(item.getId())) { + // 获取该部门 ID 对应的子部门列表 + List children = map.get(item.getId()); + // 判断子部门列表是否不为空 + if(!CollectionUtils.isEmpty(children)) { + // 遍历子部门列表 + for(SysDepartTreeDTO sub: children) { + // 递归填充每个子部门的子部门信息 + this.fillChildren(map, sub); + } + } + // 设置该部门的子部门列表 + item.setChildren(children); + } + } + + /** + * 获取指定部门及其所有子部门的 ID 列表 + * @param id 要获取子部门 ID 列表的部门 ID + * @return 包含指定部门及其所有子部门 ID 的列表 + */ + @Override + public List listAllSubIds( String id) { + // 存储指定部门及其所有子部门 ID 的列表 + List ids = new ArrayList<>(); + // 递归获取指定部门及其所有子部门的 ID 并添加到列表中 + this.cycleAllSubs(ids, id); + return ids; + } + + /** + * 递归获取指定部门及其所有子部门的 ID 并添加到列表中 + * @param list 存储部门 ID 的列表 + * @param id 当前要处理的部门 ID + */ + private void cycleAllSubs(List list, String id) { + // 将当前部门 ID 添加到列表中 + list.add(id); + + // 创建查询条件构造器 + QueryWrapper wrapper = new QueryWrapper<>(); + // 限制查询条件为父部门 ID 等于当前部门 ID,并按排序值降序排序 + wrapper.lambda() + .eq(SysDepart::getParentId, id) + .orderByDesc(SysDepart::getSort); + // 获取满足条件的子部门列表 + List subList = this.list(wrapper); + // 判断子部门列表是否不为空 + if(!CollectionUtils.isEmpty(subList)) { + // 遍历子部门列表 + for(SysDepart item: subList) { + // 递归处理每个子部门 + this.cycleAllSubs(list, item.getId()); + } + } + } + + /** + * 递归获取指定部门及其所有父部门的 ID 并添加到列表中 + * @param list 存储部门 ID 的列表 + * @param id 当前要处理的部门 ID + */ + private void cycleAllParent(List list, String id) { + // 将当前部门 ID 添加到列表中 + list.add(id); + // 根据部门 ID 获取部门实体对象 + SysDepart depart = this.getById(id); + + // 判断该部门的父部门 ID 是否不为空且不为根部门 ID + if(StringUtils.isNotBlank(depart.getParentId()) + && !ROOT_TAG.equals(depart.getParentId())) { + // 递归处理该部门的父部门 + this.cycleAllParent(list, depart.getParentId()); + } + } +} diff --git a/sys/system/mapper/SysDictMapper.java b/sys/system/mapper/SysDictMapper.java new file mode 100644 index 0000000..a5c92ab --- /dev/null +++ b/sys/system/mapper/SysDictMapper.java @@ -0,0 +1,40 @@ +/** + * 定义包名,表明该类所属的模块和目录结构,这里是系统模块下的 mapper 包。 + */ +package com.yf.exam.modules.sys.system.mapper; + +/** + * 导入 MyBatis 的 Mapper 注解,用于标识该接口是一个 MyBatis 的映射器接口。 + */ +import org.apache.ibatis.annotations.Mapper; +/** + * 导入 MyBatis 的 Param 注解,用于为方法参数指定名称,在 SQL 语句中可以使用该名称引用参数。 + */ +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 机主信息Mapper,用于与数据库中机主信息相关表进行交互,定义数据库操作方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-08-22 13:46 + */ +// 标识该接口是 MyBatis 的映射器接口,Spring 会自动扫描并将其注册到 MyBatis 中。 +@Mapper +public interface SysDictMapper { + + /** + * 查找数据字典,根据传入的表名、文本、键和值在数据库中查找对应的字典数据。 + * + * @param table 要查询的数据库表名 + * @param text 用于查询的文本条件 + * @param key 用于查询的键条件 + * @param value 用于查询的值条件 + * @return 返回查询到的字典数据,如果未找到则返回 null。 + */ + String findDict(@Param("table") String table, + @Param("text") String text, + @Param("key") String key, + @Param("value") String value); +} diff --git a/sys/system/service/SysDictService.java b/sys/system/service/SysDictService.java new file mode 100644 index 0000000..ae6d0ce --- /dev/null +++ b/sys/system/service/SysDictService.java @@ -0,0 +1,26 @@ +/** + * 定义包名,表明该类所属的模块和目录结构,此包下存放系统相关的服务类 + */ +package com.yf.exam.modules.sys.system.service; + +/** + * 数据字典工具类 + * 该接口定义了与数据字典查找相关的方法,可被不同的实现类实现以提供具体的查找逻辑。 + * @author bool + */ +public interface SysDictService { + + /** + * 查找数据字典 + * 该方法用于根据传入的表名、文本、键和值,在对应的数据字典中查找匹配的结果。 + * @param table 数据字典所在的表名,指定从哪个表中进行查找操作。 + * @param text 查找时匹配的文本信息,可用于筛选符合条件的数据。 + * @param key 数据字典中的键,用于定位特定的数据项。 + * @param value 数据字典中的值,与键配合使用,进一步精确查找。 + * @return 返回查找到的数据字典值,如果未找到则可能返回 null。 + */ + String findDict(String table, + String text, + String key, + String value); +} diff --git a/sys/system/service/impl/SysDictServiceImpl.java b/sys/system/service/impl/SysDictServiceImpl.java new file mode 100644 index 0000000..d7771a4 --- /dev/null +++ b/sys/system/service/impl/SysDictServiceImpl.java @@ -0,0 +1,45 @@ +// 定义包名,指定该类所在的包路径 +package com.yf.exam.modules.sys.system.service.impl; + +// 导入系统字典映射器接口,用于与数据库进行交互 +import com.yf.exam.modules.sys.system.mapper.SysDictMapper; +// 导入系统字典服务接口,定义了系统字典相关的业务方法 +import com.yf.exam.modules.sys.system.service.SysDictService; +// 导入 Spring 框架的自动装配注解,用于依赖注入 +import org.springframework.beans.factory.annotation.Autowired; +// 导入 Spring 框架的服务注解,将该类标记为服务层组件 +import org.springframework.stereotype.Service; + +/** + * SysDictServiceImpl 类实现了 SysDictService 接口, + * 负责处理系统字典相关的业务逻辑,通过调用 SysDictMapper 与数据库进行交互。 + * + * @author bool + */ +// 将该类标记为 Spring 服务组件,使其可以被 Spring 容器管理 +@Service +public class SysDictServiceImpl implements SysDictService { + + /** + * 注入系统字典映射器实例,用于执行数据库操作。 + * 通过 Spring 的自动装配机制,将 SysDictMapper 的实例注入到该类中。 + */ + @Autowired + private SysDictMapper sysDictMapper; + + /** + * 根据传入的表名、文本、键和值,从数据库中查找对应的字典数据。 + * 该方法调用了 SysDictMapper 的 findDict 方法来执行实际的数据库查询操作。 + * + * @param table 要查询的数据库表名 + * @param text 用于查询的文本条件 + * @param key 用于查询的键条件 + * @param value 用于查询的值条件 + * @return 返回从数据库中查询到的字典数据,如果未找到则返回 null + */ + @Override + public String findDict(String table, String text, String key, String value) { + // 调用 SysDictMapper 的 findDict 方法执行数据库查询 + return sysDictMapper.findDict(table, text, key, value); + } +} diff --git a/sys/user/controller/SysRoleController.java b/sys/user/controller/SysRoleController.java new file mode 100644 index 0000000..d2932af --- /dev/null +++ b/sys/user/controller/SysRoleController.java @@ -0,0 +1,106 @@ +// 声明当前类所在的包,表明该类属于系统用户控制器模块 +package com.yf.exam.modules.sys.user.controller; + +// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +// 导入 MyBatis-Plus 的分页元数据接口,用于处理分页查询结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入自定义的 API 统一响应类,用于封装接口返回数据 +import com.yf.exam.core.api.ApiRest; +// 导入自定义的基础控制器类,提供一些通用的控制器方法 +import com.yf.exam.core.api.controller.BaseController; +// 导入自定义的分页请求数据传输对象类,用于封装分页查询请求参数 +import com.yf.exam.core.api.dto.PagingReqDTO; +// 导入自定义的 Bean 映射工具类,用于对象属性的复制 +import com.yf.exam.core.utils.BeanMapper; +// 导入系统角色数据传输对象类,用于在不同层之间传输角色信息 +import com.yf.exam.modules.sys.user.dto.SysRoleDTO; +// 导入系统角色实体类,对应数据库中的角色表 +import com.yf.exam.modules.sys.user.entity.SysRole; +// 导入系统角色服务接口,用于调用角色相关的业务逻辑 +import com.yf.exam.modules.sys.user.service.SysRoleService; +// 导入 Swagger 注解,用于生成 API 文档,标记控制器的标签 +import io.swagger.annotations.Api; +// 导入 Swagger 注解,用于生成 API 文档,标记接口的操作描述 +import io.swagger.annotations.ApiOperation; +// 导入 Shiro 注解,用于权限控制,要求用户具有指定角色才能访问接口 +import org.apache.shiro.authz.annotation.RequiresRoles; +// 导入 Spring 框架的依赖注入注解,用于自动注入依赖的 Bean +import org.springframework.beans.factory.annotation.Autowired; +// 导入 Spring 框架的请求体注解,用于将请求体中的数据绑定到方法参数上 +import org.springframework.web.bind.annotation.RequestBody; +// 导入 Spring 框架的请求映射注解,用于映射请求路径 +import org.springframework.web.bind.annotation.RequestMapping; +// 导入 Spring 框架的请求方法注解,用于指定请求的 HTTP 方法 +import org.springframework.web.bind.annotation.RequestMethod; +// 导入 Spring 框架的控制器注解,标记该类为 RESTful 风格的控制器 +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + *

+ * 管理用户控制器,处理与系统角色相关的接口请求 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 为 Swagger 文档标记该控制器的标签为 "管理用户" +@Api(tags = {"管理用户"}) +// 标记该类为 RESTful 风格的控制器,返回数据直接作为 HTTP 响应体 +@RestController +// 定义该控制器处理的请求路径前缀 +@RequestMapping("/exam/api/sys/role") +public class SysRoleController extends BaseController { + + // 自动注入系统角色服务实例,用于调用角色相关的业务逻辑 + @Autowired + private SysRoleService baseService; + + /** + * 分页查找系统角色信息 + * + * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数 + * @return 封装了分页查询结果的 API 统一响应对象 + */ + // 要求用户具有 "sa" 角色才能访问该接口 + @RequiresRoles("sa") + // 为 Swagger 文档标记该接口的操作描述为 "分页查找" + @ApiOperation(value = "分页查找") + // 映射请求路径为 "/paging",并指定请求方法为 POST + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + // 调用角色服务的分页查询方法,进行分页查询并将结果转换为角色数据传输对象 + IPage page = baseService.paging(reqDTO); + // 调用父类的成功响应方法,返回包含分页结果的响应对象 + return super.success(page); + } + + /** + * 查找系统角色列表,每次最多返回 200 条数据 + * + * @return 封装了角色列表信息的 API 统一响应对象 + */ + // 要求用户具有 "sa" 角色才能访问该接口 + @RequiresRoles("sa") + // 为 Swagger 文档标记该接口的操作描述为 "查找列表" + @ApiOperation(value = "查找列表") + // 映射请求路径为 "/list",并指定请求方法为 POST + @RequestMapping(value = "/list", method = { RequestMethod.POST}) + public ApiRest> list() { + + // 创建一个 MyBatis-Plus 的查询条件构造器,用于构建查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + // 调用角色服务的列表查询方法,根据查询条件获取角色实体列表 + List list = baseService.list(wrapper); + + // 使用 Bean 映射工具类将角色实体列表转换为角色数据传输对象列表 + List dtoList = BeanMapper.mapList(list, SysRoleDTO.class); + + // 调用父类的成功响应方法,返回包含角色列表的响应对象 + return super.success(dtoList); + } +} diff --git a/sys/user/controller/SysUserController.java b/sys/user/controller/SysUserController.java new file mode 100644 index 0000000..7b989ad --- /dev/null +++ b/sys/user/controller/SysUserController.java @@ -0,0 +1,245 @@ +// 声明当前类所在的包,该包属于系统用户控制器模块 +package com.yf.exam.modules.sys.user.controller; + +// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +// 导入 MyBatis-Plus 的分页元数据接口,用于处理分页查询结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入自定义的 API 统一响应类,用于封装接口返回数据 +import com.yf.exam.core.api.ApiRest; +// 导入自定义的基础控制器类,提供一些通用的控制器方法 +import com.yf.exam.core.api.controller.BaseController; +// 导入自定义的基础多个 ID 请求数据传输对象类,用于批量操作时传递 ID 列表 +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.sys.user.dto.SysUserDTO; +// 导入系统用户登录请求数据传输对象类,用于用户登录时传递请求信息 +import com.yf.exam.modules.sys.user.dto.request.SysUserLoginReqDTO; +// 导入系统用户保存请求数据传输对象类,用于保存或修改用户信息时传递请求信息 +import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO; +// 导入系统用户登录响应数据传输对象类,用于用户登录成功后返回响应信息 +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +// 导入系统用户实体类,对应数据库中的用户表 +import com.yf.exam.modules.sys.user.entity.SysUser; +// 导入系统用户服务接口,用于调用用户相关的业务逻辑 +import com.yf.exam.modules.sys.user.service.SysUserService; +// 导入 Swagger 注解,用于生成 API 文档,标记控制器的标签 +import io.swagger.annotations.Api; +// 导入 Swagger 注解,用于生成 API 文档,标记接口的操作描述 +import io.swagger.annotations.ApiOperation; +// 导入 Shiro 注解,用于权限控制,要求用户具有指定角色才能访问接口 +import org.apache.shiro.authz.annotation.RequiresRoles; +// 导入 Spring 框架的依赖注入注解,用于自动注入依赖的 Bean +import org.springframework.beans.factory.annotation.Autowired; +// 导入 Spring 框架的跨域请求注解,允许跨域访问该接口 +import org.springframework.web.bind.annotation.CrossOrigin; +// 导入 Spring 框架的请求体注解,用于将请求体中的数据绑定到方法参数上 +import org.springframework.web.bind.annotation.RequestBody; +// 导入 Spring 框架的请求映射注解,用于映射请求路径 +import org.springframework.web.bind.annotation.RequestMapping; +// 导入 Spring 框架的请求方法注解,用于指定请求的 HTTP 方法 +import org.springframework.web.bind.annotation.RequestMethod; +// 导入 Spring 框架的请求参数注解,用于获取请求参数 +import org.springframework.web.bind.annotation.RequestParam; +// 导入 Spring 框架的控制器注解,标记该类为 RESTful 风格的控制器 +import org.springframework.web.bind.annotation.RestController; + +// 导入 HttpServletRequest 类,用于获取 HTTP 请求信息 +import javax.servlet.http.HttpServletRequest; + +/** + *

+ * 管理用户控制器,处理与系统用户相关的各种请求,如登录、登出、修改资料等操作。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 为 Swagger 文档标记该控制器的标签为 "管理用户" +@Api(tags = {"管理用户"}) +// 标记该类为 RESTful 风格的控制器,返回数据直接作为 HTTP 响应体 +@RestController +// 定义该控制器处理的请求路径前缀 +@RequestMapping("/exam/api/sys/user") +public class SysUserController extends BaseController { + + // 自动注入系统用户服务实例,用于调用用户相关的业务逻辑 + @Autowired + private SysUserService baseService; + + /** + * 用户登录接口,接收用户登录请求信息,调用服务层方法进行登录验证,并返回登录结果。 + * + * @param reqDTO 包含用户登录信息的请求数据传输对象 + * @return 封装了用户登录响应信息的 API 统一响应对象 + */ + @CrossOrigin + @ApiOperation(value = "用户登录") + @RequestMapping(value = "/login", method = {RequestMethod.POST}) + public ApiRest login(@RequestBody SysUserLoginReqDTO reqDTO) { + // 调用服务层的登录方法,传入用户名和密码,获取登录响应信息 + SysUserLoginDTO respDTO = baseService.login(reqDTO.getUsername(), reqDTO.getPassword()); + // 调用父类的成功响应方法,返回包含登录响应信息的响应对象 + return super.success(respDTO); + } + + /** + * 用户登出接口,接收 HTTP 请求,从请求头中获取 token,调用服务层方法进行登出操作,并返回操作结果。 + * + * @param request HTTP 请求对象,用于获取请求头中的 token + * @return 封装了登出操作结果的 API 统一响应对象 + */ + @CrossOrigin + @ApiOperation(value = "用户登录") + @RequestMapping(value = "/logout", method = {RequestMethod.POST}) + public ApiRest logout(HttpServletRequest request) { + // 从请求头中获取 token + String token = request.getHeader("token"); + // 打印当前会话的 token 信息 + System.out.println("+++++当前会话为:"+token); + // 调用服务层的登出方法,传入 token 进行登出操作 + baseService.logout(token); + // 调用父类的成功响应方法,返回操作成功的响应对象 + return super.success(); + } + + /** + * 获取会话信息接口,接收 token 参数,调用服务层方法根据 token 获取用户会话信息,并返回会话信息。 + * + * @param token 用户的 token,用于验证用户身份并获取会话信息 + * @return 封装了用户会话信息的 API 统一响应对象 + */ + @ApiOperation(value = "获取会话") + @RequestMapping(value = "/info", method = {RequestMethod.POST}) + public ApiRest info(@RequestParam("token") String token) { + // 调用服务层的 token 方法,传入 token 获取用户会话信息 + SysUserLoginDTO respDTO = baseService.token(token); + // 调用父类的成功响应方法,返回包含会话信息的响应对象 + return success(respDTO); + } + + /** + * 修改用户资料接口,接收用户资料修改请求信息,调用服务层方法进行用户资料修改,并返回操作结果。 + * + * @param reqDTO 包含用户资料修改信息的请求数据传输对象 + * @return 封装了用户资料修改操作结果的 API 统一响应对象 + */ + @ApiOperation(value = "修改用户资料") + @RequestMapping(value = "/update", method = {RequestMethod.POST}) + public ApiRest update(@RequestBody SysUserDTO reqDTO) { + // 调用服务层的更新方法,传入用户资料修改信息进行用户资料修改 + baseService.update(reqDTO); + // 调用父类的成功响应方法,返回操作成功的响应对象 + return success(); + } + + /** + * 保存或修改系统用户接口,要求用户具有 "sa" 角色,接收用户保存或修改请求信息,调用服务层方法进行用户保存或修改操作,并返回操作结果。 + * + * @param reqDTO 包含用户保存或修改信息的请求数据传输对象 + * @return 封装了用户保存或修改操作结果的 API 统一响应对象 + */ + @RequiresRoles("sa") + @ApiOperation(value = "保存或修改") + @RequestMapping(value = "/save", method = {RequestMethod.POST}) + public ApiRest save(@RequestBody SysUserSaveReqDTO reqDTO) { + // 调用服务层的保存方法,传入用户保存或修改信息进行用户保存或修改操作 + baseService.save(reqDTO); + // 调用父类的成功响应方法,返回操作成功的响应对象 + return success(); + } + + /** + * 批量删除用户接口,要求用户具有 "sa" 角色,接收包含多个用户 ID 的请求信息,调用服务层方法根据 ID 批量删除用户,并返回操作结果。 + * + * @param reqDTO 包含多个用户 ID 的请求数据传输对象,用于指定要删除的用户 + * @return 封装了批量删除用户操作结果的 API 统一响应对象 + */ + @RequiresRoles("sa") + @ApiOperation(value = "批量删除") + @RequestMapping(value = "/delete", method = { RequestMethod.POST}) + public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { + // 根据传入的用户 ID 列表,调用服务层的删除方法批量删除用户 + baseService.removeByIds(reqDTO.getIds()); + // 调用父类的成功响应方法,返回操作成功的响应对象 + return super.success(); + } + + /** + * 分页查找用户接口,要求用户具有 "sa" 角色,接收分页请求信息,调用服务层方法进行分页查询,并返回分页查询结果。 + * + * @param reqDTO 包含分页查询条件和参数的请求数据传输对象 + * @return 封装了分页查询结果的 API 统一响应对象 + */ + @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); + } + + /** + * 修改用户状态接口,要求用户具有 "sa" 角色,接收用户状态修改请求信息,构建查询条件,调用服务层方法修改用户状态,并返回操作结果。 + * + * @param reqDTO 包含用户状态修改信息的请求数据传输对象 + * @return 封装了用户状态修改操作结果的 API 统一响应对象 + */ + @RequiresRoles("sa") + @ApiOperation(value = "修改状态") + @RequestMapping(value = "/state", method = { RequestMethod.POST}) + public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) { + // 创建 MyBatis-Plus 的查询条件构造器,用于构建查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + // 使用 Lambda 表达式构建查询条件,筛选出 ID 在请求 ID 列表中且用户名不为 "admin" 的用户 + wrapper.lambda() + .in(SysUser::getId, reqDTO.getIds()) + .ne(SysUser::getUserName, "admin"); + + // 创建一个新的用户实体对象,用于设置要修改的状态 + SysUser record = new SysUser(); + // 设置用户状态为请求中的状态 + record.setState(reqDTO.getState()); + // 调用服务层的更新方法,根据查询条件更新用户状态 + baseService.update(record, wrapper); + + // 调用父类的成功响应方法,返回操作成功的响应对象 + return super.success(); + } + + /** + * 学员注册接口,接收学员注册请求信息,调用服务层方法进行学员注册,并返回注册结果。 + * + * @param reqDTO 包含学员注册信息的请求数据传输对象 + * @return 封装了学员注册响应信息的 API 统一响应对象 + */ + @ApiOperation(value = "学员注册") + @RequestMapping(value = "/reg", method = {RequestMethod.POST}) + public ApiRest reg(@RequestBody SysUserDTO reqDTO) { + // 调用服务层的注册方法,传入学员注册信息进行学员注册,获取注册响应信息 + SysUserLoginDTO respDTO = baseService.reg(reqDTO); + // 调用父类的成功响应方法,返回包含注册响应信息的响应对象 + return success(respDTO); + } + + /** + * 快速注册接口,接收用户信息,调用服务层方法进行快速注册,如果手机号存在则登录,不存在就注册,并返回操作结果。 + * + * @param reqDTO 包含用户信息的请求数据传输对象 + * @return 封装了快速注册响应信息的 API 统一响应对象 + */ + @ApiOperation(value = "快速注册") + @RequestMapping(value = "/quick-reg", method = {RequestMethod.POST}) + public ApiRest quick(@RequestBody SysUserDTO reqDTO) { + // 调用服务层的快速注册方法,传入用户信息进行快速注册,获取注册或登录响应信息 + SysUserLoginDTO respDTO = baseService.quickReg(reqDTO); + // 调用父类的成功响应方法,返回包含注册或登录响应信息的响应对象 + return success(respDTO); + } +} diff --git a/sys/user/dto/SysRoleDTO.java b/sys/user/dto/SysRoleDTO.java new file mode 100644 index 0000000..7cc31e4 --- /dev/null +++ b/sys/user/dto/SysRoleDTO.java @@ -0,0 +1,45 @@ +// 定义该类所在的包,表明其在项目模块中的位置 +package com.yf.exam.modules.sys.user.dto; + +// 导入 Swagger 注解,用于在生成 API 文档时描述类的信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于在生成 API 文档时描述类属性的信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; + +// 导入 Serializable 接口,使该类的对象可以被序列化和反序列化 +import java.io.Serializable; + +/** + *

+ * 角色请求类,用于在不同层之间传输角色相关的数据,如在控制器与服务层、服务层与数据访问层之间传递数据。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成常用方法 +@Data +// 使用 Swagger 的 ApiModel 注解,为 API 文档描述该类的信息 +@ApiModel(value="角色", description="角色") +public class SysRoleDTO implements Serializable { + + // 序列化版本号,确保序列化和反序列化时类的版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 角色的唯一标识 ID + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供 + */ + @ApiModelProperty(value = "角色ID", required=true) + private String id; + + /** + * 角色的名称 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供 + */ + @ApiModelProperty(value = "角色名称", required=true) + private String roleName; + +} diff --git a/sys/user/dto/SysUserDTO.java b/sys/user/dto/SysUserDTO.java new file mode 100644 index 0000000..8fc9ad0 --- /dev/null +++ b/sys/user/dto/SysUserDTO.java @@ -0,0 +1,108 @@ +/** + * 定义包名,表明该类属于系统用户模块下的数据传输对象包 + */ +package com.yf.exam.modules.sys.user.dto; + +// 导入 Swagger 注解,用于为 API 文档提供类的描述信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于为 API 文档提供类属性的描述信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; + +// 导入 Serializable 接口,使该类的对象可以被序列化 +import java.io.Serializable; +// 导入 Date 类,用于表示日期和时间 +import java.util.Date; + +/** + *

+ * 管理用户请求类,用于在不同层之间传输管理用户的相关信息,如从控制器传递到服务层,或从服务层传递到数据访问层。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法 +@Data +// 使用 Swagger 的 ApiModel 注解,为 API 文档描述该类的信息 +@ApiModel(value="管理用户", description="管理用户") +public class SysUserDTO implements Serializable { + + /** + * 序列化版本号,确保序列化和反序列化时类的版本一致性。 + * 当类的结构发生变化时,可能需要更新该版本号以避免反序列化错误。 + */ + private static final long serialVersionUID = 1L; + + /** + * 管理用户的唯一标识 ID。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 管理用户的用户名,用于系统登录和身份识别。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "用户名", required=true) + private String userName; + + /** + * 管理用户的真实姓名,用于更准确地识别用户身份和展示信息。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "真实姓名", required=true) + private String realName; + + /** + * 管理用户的登录密码,用于系统登录验证。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "密码", required=true) + private String password; + + /** + * 密码盐,用于增强密码的安全性,通常与密码一起进行哈希处理。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "密码盐", required=true) + private String salt; + + /** + * 管理用户所属的角色列表,以字符串形式存储角色 ID,多个 ID 之间可能用特定分隔符分隔。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "角色列表", required=true) + private String roleIds; + + /** + * 管理用户所属部门的 ID,用于关联部门信息。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "部门ID", required=true) + private String departId; + + /** + * 管理用户记录的创建时间,使用 Date 类型表示。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + /** + * 管理用户记录的更新时间,使用 Date 类型表示。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + + /** + * 管理用户的状态,使用整数类型表示不同的状态值,具体状态含义需根据业务逻辑定义。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "状态", required=true) + private Integer state; + +} diff --git a/sys/user/dto/SysUserRoleDTO.java b/sys/user/dto/SysUserRoleDTO.java new file mode 100644 index 0000000..99dd349 --- /dev/null +++ b/sys/user/dto/SysUserRoleDTO.java @@ -0,0 +1,55 @@ +// 定义包名,指定该类所属的模块和目录结构 +package com.yf.exam.modules.sys.user.dto; + +// 导入 Swagger 注解,用于为 API 文档定义模型信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于为 API 文档定义模型属性信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; + +// 导入 Serializable 接口,使该类的对象可以被序列化和反序列化 +import java.io.Serializable; + +/** + *

+ * 用户角色请求类,用于在不同层之间传输用户角色相关的数据,如在控制器与服务层之间传递数据。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法 +@Data +// 使用 Swagger 的 ApiModel 注解,为 API 文档定义该类的信息 +@ApiModel(value="用户角色", description="用户角色") +public class SysUserRoleDTO implements Serializable { + + /** + * 序列化版本号,确保序列化和反序列化时类的版本一致性。 + * 当类的结构发生变化时,可能需要更新该版本号以避免反序列化错误。 + */ + private static final long serialVersionUID = 1L; + + /** + * 用户角色的唯一标识 ID。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 关联的用户的唯一标识 ID。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "用户ID", required=true) + private String userId; + + /** + * 关联的角色的唯一标识 ID。 + * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。 + */ + @ApiModelProperty(value = "角色ID", required=true) + private String roleId; + +} diff --git a/sys/user/dto/request/SysUserLoginReqDTO.java b/sys/user/dto/request/SysUserLoginReqDTO.java new file mode 100644 index 0000000..91e738a --- /dev/null +++ b/sys/user/dto/request/SysUserLoginReqDTO.java @@ -0,0 +1,45 @@ +// 定义当前类所在的包,表明该类属于系统用户模块下的请求数据传输对象包 +package com.yf.exam.modules.sys.user.dto.request; + +// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法 +import lombok.Data; + +// 导入 Serializable 接口,表明该类的对象可以被序列化 +import java.io.Serializable; + +/** + *

+ * 管理员登录请求类,用于封装管理员登录时所需的请求参数 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +@Data +// 为 Swagger 文档提供类的描述信息 +@ApiModel(value="管理员登录请求类", description="管理员登录请求类") +public class SysUserLoginReqDTO implements Serializable { + + // 序列化版本号,用于在反序列化时验证版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 管理员登录使用的用户名 + * 在 API 文档中标记为必需项,表明该参数在请求时必须提供 + */ + @ApiModelProperty(value = "用户名", required=true) + private String username; + + /** + * 管理员登录使用的密码 + * 在 API 文档中标记为必需项,表明该参数在请求时必须提供 + */ + @ApiModelProperty(value = "密码", required=true) + private String password; + +} diff --git a/sys/user/dto/request/SysUserSaveReqDTO.java b/sys/user/dto/request/SysUserSaveReqDTO.java new file mode 100644 index 0000000..5ce334c --- /dev/null +++ b/sys/user/dto/request/SysUserSaveReqDTO.java @@ -0,0 +1,83 @@ +// 声明该类所在的包,明确其在项目模块中的位置 +package com.yf.exam.modules.sys.user.dto.request; + +// 导入 Swagger 注解,用于在生成 API 文档时描述类的信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于在生成 API 文档时描述类属性的信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; + +// 导入 Serializable 接口,使该类的对象可以进行序列化和反序列化操作 +import java.io.Serializable; +// 导入 List 接口,用于存储角色 ID 列表 +import java.util.List; + +/** + *

+ * 管理员保存请求类,用于封装管理员信息保存操作所需的请求参数。 + * 该类作为数据传输对象,在不同层之间传递管理员保存相关的数据。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法 +@Data +// 使用 Swagger 的 ApiModel 注解,为 API 文档描述该类的信息 +@ApiModel(value="管理员保存请求类", description="管理员保存请求类") +public class SysUserSaveReqDTO implements Serializable { + + // 序列化版本号,确保序列化和反序列化时类的版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 管理员的唯一标识 ID + * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 管理员的用户名,用于系统登录和身份识别 + * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供 + */ + @ApiModelProperty(value = "用户名", required=true) + private String userName; + + /** + * 管理员的头像链接,用于在系统中展示管理员的头像 + * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供 + */ + @ApiModelProperty(value = "头像", required=true) + private String avatar; + + /** + * 管理员的真实姓名,用于在系统中进行更准确的身份识别和展示 + * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供 + */ + @ApiModelProperty(value = "真实姓名", required=true) + private String realName; + + /** + * 管理员的登录密码,用于系统登录验证 + * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供 + */ + @ApiModelProperty(value = "密码", required=true) + private String password; + + /** + * 管理员所属部门的 ID,用于关联管理员所在的部门信息 + * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供 + */ + @ApiModelProperty(value = "部门", required=true) + private String departId; + + /** + * 管理员拥有的角色列表,存储角色的 ID 集合 + * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供 + */ + @ApiModelProperty(value = "角色列表", required=true) + private List roles; + +} diff --git a/sys/user/dto/request/SysUserTokenReqDTO.java b/sys/user/dto/request/SysUserTokenReqDTO.java new file mode 100644 index 0000000..e945041 --- /dev/null +++ b/sys/user/dto/request/SysUserTokenReqDTO.java @@ -0,0 +1,38 @@ +// 定义包名,表明该类所属的模块和功能目录 +package com.yf.exam.modules.sys.user.dto.request; + +// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法 +import lombok.Data; + +// 导入 Serializable 接口,使该类的对象可以被序列化和反序列化 +import java.io.Serializable; + +/** + *

+ * 会话检查请求类,用于封装会话检查所需的请求参数 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +@Data +// 为 Swagger 文档提供类的描述信息 +@ApiModel(value="会话检查请求类", description="会话检查请求类") +public class SysUserTokenReqDTO implements Serializable { + + // 序列化版本号,用于在反序列化时验证版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 用于会话检查的令牌 + * 在 API 文档中标记为必需项,表明该参数在请求时必须提供 + */ + @ApiModelProperty(value = "用户名", required=true) + private String token; + +} diff --git a/sys/user/dto/response/SysUserLoginDTO.java b/sys/user/dto/response/SysUserLoginDTO.java new file mode 100644 index 0000000..244086d --- /dev/null +++ b/sys/user/dto/response/SysUserLoginDTO.java @@ -0,0 +1,106 @@ +// 定义包名,表明该类属于系统用户模块下的响应数据传输对象包 +package com.yf.exam.modules.sys.user.dto.response; + +// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息 +import io.swagger.annotations.ApiModel; +// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息 +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法 +import lombok.Data; + +// 导入 Serializable 接口,表明该类的对象可以被序列化 +import java.io.Serializable; +// 导入 Date 类,用于表示日期和时间 +import java.util.Date; +// 导入 List 接口,用于存储角色列表 +import java.util.List; + +/** + *

+ * 管理用户登录响应类,用于封装管理用户登录成功后返回的信息。 + * 该类作为数据传输对象,在不同层之间传递登录响应数据。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +@Data +// 为 Swagger 文档提供类的描述信息 +@ApiModel(value="管理用户登录响应类", description="管理用户登录响应类") +public class SysUserLoginDTO implements Serializable { + + // 序列化版本号,用于在反序列化时验证版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 管理用户的唯一标识 ID + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 管理用户的用户名 + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "用户名", required=true) + private String userName; + + /** + * 管理用户的真实姓名 + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "真实姓名", required=true) + private String realName; + + /** + * 管理用户的角色 ID 列表,以字符串形式存储 + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "角色列表", required=true) + private String roleIds; + + /** + * 管理用户所属部门的 ID + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "部门ID", required=true) + private String departId; + + /** + * 管理用户记录的创建时间 + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + /** + * 管理用户记录的更新时间 + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + + /** + * 管理用户的状态,使用整数表示 + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "状态", required=true) + private Integer state; + + /** + * 管理用户的角色列表,以字符串列表形式存储 + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "角色列表", required=true) + private List roles; + + /** + * 管理用户登录后生成的令牌,用于后续的身份验证 + * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在 + */ + @ApiModelProperty(value = "登录令牌", required=true) + private String token; + +} diff --git a/sys/user/entity/SysRole.java b/sys/user/entity/SysRole.java new file mode 100644 index 0000000..8386f64 --- /dev/null +++ b/sys/user/entity/SysRole.java @@ -0,0 +1,54 @@ +/** + * 定义包名,指定该类所属的模块和实体类所在的目录结构 + */ +package com.yf.exam.modules.sys.user.entity; + +// 导入 MyBatis-Plus 提供的主键生成策略枚举类 +import com.baomidou.mybatisplus.annotation.IdType; +// 导入 MyBatis-Plus 提供的表字段注解,用于指定数据库表字段名 +import com.baomidou.mybatisplus.annotation.TableField; +// 导入 MyBatis-Plus 提供的表主键注解,用于指定数据库表的主键 +import com.baomidou.mybatisplus.annotation.TableId; +// 导入 MyBatis-Plus 提供的表名注解,用于指定实体类对应的数据库表名 +import com.baomidou.mybatisplus.annotation.TableName; +// 导入 MyBatis-Plus 提供的 ActiveRecord 模式基类,方便进行数据库操作 +import com.baomidou.mybatisplus.extension.activerecord.Model; +// 导入 Lombok 提供的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; + +/** + *

+ * 角色实体类,用于映射数据库中的 sys_role 表,封装角色相关的属性。 + * 该类继承自 MyBatis-Plus 的 Model 类,可使用 ActiveRecord 模式进行数据库操作。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法 +@Data +// 指定该实体类对应的数据库表名为 sys_role +@TableName("sys_role") +public class SysRole extends Model { + + /** + * 序列化版本号,用于在反序列化时验证版本一致性。 + * 当实体类的结构发生变化时,可能需要更新该版本号以避免反序列化错误。 + */ + private static final long serialVersionUID = 1L; + + /** + * 角色ID,对应数据库表中的 id 字段,作为表的主键。 + * 使用 MyBatis-Plus 的 TableId 注解指定主键生成策略为 ASSIGN_ID,通常为分布式 ID 生成策略。 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 角色名称,对应数据库表中的 role_name 字段。 + * 使用 MyBatis-Plus 的 TableField 注解明确指定数据库表字段名。 + */ + @TableField("role_name") + private String roleName; + +} diff --git a/sys/user/entity/SysUser.java b/sys/user/entity/SysUser.java new file mode 100644 index 0000000..e6fc698 --- /dev/null +++ b/sys/user/entity/SysUser.java @@ -0,0 +1,103 @@ +// 声明当前类所在的包,明确该类属于系统用户实体类所在的包 +package com.yf.exam.modules.sys.user.entity; + +// 导入 MyBatis-Plus 注解,用于指定主键生成策略 +import com.baomidou.mybatisplus.annotation.IdType; +// 导入 MyBatis-Plus 注解,用于指定数据库表字段名 +import com.baomidou.mybatisplus.annotation.TableField; +// 导入 MyBatis-Plus 注解,用于指定数据库表的主键 +import com.baomidou.mybatisplus.annotation.TableId; +// 导入 MyBatis-Plus 注解,用于指定实体类对应的数据库表名 +import com.baomidou.mybatisplus.annotation.TableName; +// 导入 MyBatis-Plus 扩展的 ActiveRecord 模式基类 +import com.baomidou.mybatisplus.extension.activerecord.Model; +// 导入 Lombok 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; + +// 导入 Date 类,用于表示日期和时间 +import java.util.Date; + +/** + *

+ * 管理用户实体类,用于映射数据库中的 sys_user 表,封装管理用户的相关属性。 + * 该类继承自 MyBatis-Plus 的 Model 类,可使用 ActiveRecord 模式进行数据库操作。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 @Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +@Data +// 指定该实体类对应的数据库表名为 sys_user +@TableName("sys_user") +public class SysUser extends Model { + + // 序列化版本号,用于在反序列化时验证版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 用户的唯一标识 ID + * 使用 MyBatis-Plus 的 @TableId 注解指定该字段为主键, + * value 属性指定数据库表中的字段名,type 属性指定主键生成策略为 ASSIGN_ID + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 用户名,对应数据库表中的 user_name 字段 + * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名 + */ + @TableField("user_name") + private String userName; + + /** + * 真实姓名,对应数据库表中的 real_name 字段 + * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名 + */ + @TableField("real_name") + private String realName; + + /** + * 用户密码,未指定 @TableField 时,默认字段名与属性名相同 + */ + private String password; + + /** + * 密码盐,用于增强密码的安全性,未指定 @TableField 时,默认字段名与属性名相同 + */ + private String salt; + + /** + * 角色列表,以字符串形式存储角色 ID,对应数据库表中的 role_ids 字段 + * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名 + */ + @TableField("role_ids") + private String roleIds; + + /** + * 部门 ID,对应数据库表中的 depart_id 字段 + * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名 + */ + @TableField("depart_id") + private String departId; + + /** + * 用户记录的创建时间,对应数据库表中的 create_time 字段 + * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名 + */ + @TableField("create_time") + private Date createTime; + + /** + * 用户记录的更新时间,对应数据库表中的 update_time 字段 + * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 用户状态,未指定 @TableField 时,默认字段名与属性名相同 + */ + private Integer state; + +} diff --git a/sys/user/entity/SysUserRole.java b/sys/user/entity/SysUserRole.java new file mode 100644 index 0000000..fb33b77 --- /dev/null +++ b/sys/user/entity/SysUserRole.java @@ -0,0 +1,53 @@ +// 声明当前类所在的包,该包属于系统用户模块下的实体类包 +package com.yf.exam.modules.sys.user.entity; + +// 导入 MyBatis-Plus 提供的主键生成策略枚举类 +import com.baomidou.mybatisplus.annotation.IdType; +// 导入 MyBatis-Plus 提供的表字段注解,用于指定数据库表字段名 +import com.baomidou.mybatisplus.annotation.TableField; +// 导入 MyBatis-Plus 提供的表主键注解,用于指定数据库表的主键 +import com.baomidou.mybatisplus.annotation.TableId; +// 导入 MyBatis-Plus 提供的表名注解,用于指定实体类对应的数据库表名 +import com.baomidou.mybatisplus.annotation.TableName; +// 导入 MyBatis-Plus 提供的 ActiveRecord 模式基类,方便进行数据库操作 +import com.baomidou.mybatisplus.extension.activerecord.Model; +// 导入 Lombok 提供的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; + +/** + *

+ * 用户角色实体类,用于映射数据库中的 sys_user_role 表,存储用户与角色的关联信息。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法 +@Data +// 指定该实体类对应的数据库表名为 sys_user_role +@TableName("sys_user_role") +public class SysUserRole extends Model { + + // 序列化版本号,确保序列化和反序列化时类的版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 用户角色关联记录的唯一标识 ID + * 使用 TableId 注解指定该字段为主键,采用 ASSIGN_ID 主键生成策略 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 关联的用户 ID,对应数据库表中的 user_id 字段 + */ + @TableField("user_id") + private String userId; + + /** + * 关联的角色 ID,对应数据库表中的 role_id 字段 + */ + @TableField("role_id") + private String roleId; + +} diff --git a/sys/user/mapper/SysRoleMapper.java b/sys/user/mapper/SysRoleMapper.java new file mode 100644 index 0000000..af66098 --- /dev/null +++ b/sys/user/mapper/SysRoleMapper.java @@ -0,0 +1,32 @@ +/** + * 定义包名,表明该类属于系统用户模块下的映射器包。 + * 包名的组织有助于代码的模块化管理和避免命名冲突。 + */ +package com.yf.exam.modules.sys.user.mapper; + +/** + * 导入 MyBatis-Plus 核心的 BaseMapper 接口, + * 该接口提供了通用的数据库 CRUD 操作方法, + * 可以减少开发者编写重复的 SQL 映射代码。 + */ +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 导入系统角色实体类,该类用于封装角色相关的属性, + * 作为 Mapper 接口操作的对象类型。 + */ +import com.yf.exam.modules.sys.user.entity.SysRole; + +/** + *

+ * 角色Mapper接口,继承自 MyBatis-Plus 的 BaseMapper 接口。 + * 该接口用于定义与系统角色相关的数据库操作方法, + * 借助 BaseMapper 提供的通用方法,可以直接进行角色数据的增删改查操作。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +public interface SysRoleMapper extends BaseMapper { + // 目前该接口仅继承 BaseMapper 的通用方法,未自定义额外的数据库操作方法 +} diff --git a/sys/user/mapper/SysUserMapper.java b/sys/user/mapper/SysUserMapper.java new file mode 100644 index 0000000..7c81b34 --- /dev/null +++ b/sys/user/mapper/SysUserMapper.java @@ -0,0 +1,32 @@ +/** + * 声明该接口所在的包,用于组织代码结构,避免命名冲突。 + * 此包属于系统用户模块下的映射器包。 + */ +package com.yf.exam.modules.sys.user.mapper; + +/** + * 导入 MyBatis-Plus 框架的核心接口 BaseMapper。 + * BaseMapper 提供了一系列通用的数据库 CRUD 操作方法, + * 继承该接口可以减少重复编写基础数据库操作代码的工作量。 + */ +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 导入系统用户实体类 SysUser。 + * 该实体类用于封装系统用户的相关属性, + * 作为当前 Mapper 接口操作的对象类型。 + */ +import com.yf.exam.modules.sys.user.entity.SysUser; + +/** + *

+ * 管理用户Mapper接口,用于与数据库中管理用户相关的数据表进行交互。 + * 继承自 MyBatis-Plus 的 BaseMapper 接口,可直接使用其提供的通用数据库操作方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +public interface SysUserMapper extends BaseMapper { + // 目前该接口仅继承 BaseMapper 的通用方法,未自定义额外的数据库操作方法 +} diff --git a/sys/user/mapper/SysUserRoleMapper.java b/sys/user/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..38f9ac9 --- /dev/null +++ b/sys/user/mapper/SysUserRoleMapper.java @@ -0,0 +1,32 @@ +/** + * 定义包名,表明该类所属的模块和功能目录,这里是系统用户模块下的 mapper 目录。 + */ +package com.yf.exam.modules.sys.user.mapper; + +/** + * 导入 MyBatis-Plus 框架的 BaseMapper 接口, + * 该接口提供了通用的数据库 CRUD 操作方法, + * 继承它可以让我们快速实现对数据库表的基本操作。 + */ +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 导入系统用户角色实体类, + * 该实体类用于封装用户角色的相关属性, + * 作为当前 Mapper 接口操作的对象类型。 + */ +import com.yf.exam.modules.sys.user.entity.SysUserRole; + +/** + *

+ * 用户角色Mapper接口,继承自 MyBatis-Plus 的 BaseMapper 接口。 + * 该接口用于与数据库中用户角色相关的数据表进行交互, + * 借助 BaseMapper 提供的通用方法,可以直接进行用户角色数据的增删改查操作。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +public interface SysUserRoleMapper extends BaseMapper { + // 目前该接口仅继承 BaseMapper 的通用方法,未自定义额外的数据库操作方法 +} diff --git a/sys/user/service/SysRoleService.java b/sys/user/service/SysRoleService.java new file mode 100644 index 0000000..4855f8f --- /dev/null +++ b/sys/user/service/SysRoleService.java @@ -0,0 +1,52 @@ +/** + * 定义包名,表明该接口所属的模块和服务层目录结构。 + * 此包路径用于组织系统用户模块下的服务接口类。 + */ +package com.yf.exam.modules.sys.user.service; + +/** + * 导入 MyBatis-Plus 框架的分页元数据接口, + * 该接口用于表示分页查询的结果,包含总记录数、当前页码等信息。 + */ +import com.baomidou.mybatisplus.core.metadata.IPage; +/** + * 导入 MyBatis-Plus 框架的扩展服务接口, + * 该接口提供了通用的 CRUD 操作方法,可减少重复代码编写。 + */ +import com.baomidou.mybatisplus.extension.service.IService; +/** + * 导入系统角色数据传输对象类, + * 用于在不同层之间传输角色相关的数据,避免直接暴露实体类。 + */ +import com.yf.exam.modules.sys.user.dto.SysRoleDTO; +/** + * 导入系统角色实体类, + * 该类用于映射数据库中的角色表,封装角色的属性信息。 + */ +import com.yf.exam.modules.sys.user.entity.SysRole; +/** + * 导入自定义的分页请求数据传输对象类, + * 用于封装分页查询的请求参数,如当前页码、每页记录数等。 + */ +import com.yf.exam.core.api.dto.PagingReqDTO; + +/** + *

+ * 角色业务类接口,定义了与系统角色相关的业务方法。 + * 继承自 MyBatis-Plus 的 IService 接口,可使用其提供的通用服务方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +public interface SysRoleService extends IService { + + /** + * 分页查询角色数据。 + * 根据传入的分页请求参数,从数据库中查询角色数据并进行分页处理。 + * + * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数。 + * @return 分页查询结果,包含符合条件的角色数据传输对象列表。 + */ + IPage paging(PagingReqDTO reqDTO); +} diff --git a/sys/user/service/SysUserRoleService.java b/sys/user/service/SysUserRoleService.java new file mode 100644 index 0000000..774b663 --- /dev/null +++ b/sys/user/service/SysUserRoleService.java @@ -0,0 +1,94 @@ +/** + * 定义包名,明确该接口所属的模块和服务层目录,用于组织系统用户角色相关的服务类。 + */ +package com.yf.exam.modules.sys.user.service; + +/** + * 导入 MyBatis-Plus 框架的分页元数据接口,用于表示分页查询结果,包含总记录数、当前页码等信息。 + */ +import com.baomidou.mybatisplus.core.metadata.IPage; +/** + * 导入 MyBatis-Plus 框架的扩展服务接口,提供通用的 CRUD 操作方法,减少重复代码编写。 + */ +import com.baomidou.mybatisplus.extension.service.IService; +/** + * 导入系统用户角色数据传输对象类,用于在不同层之间传输用户角色相关的数据,避免直接暴露实体类。 + */ +import com.yf.exam.modules.sys.user.dto.SysUserRoleDTO; +/** + * 导入系统用户角色实体类,用于映射数据库中的用户角色表,封装用户角色的属性信息。 + */ +import com.yf.exam.modules.sys.user.entity.SysUserRole; +/** + * 导入自定义的分页请求数据传输对象类,用于封装分页查询的请求参数,如当前页码、每页记录数等。 + */ +import com.yf.exam.core.api.dto.PagingReqDTO; + +import java.util.List; + +/** + *

+ * 用户角色业务类接口,定义了与系统用户角色相关的业务方法。 + * 继承自 MyBatis-Plus 的 IService 接口,可使用其提供的通用服务方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +public interface SysUserRoleService extends IService { + + /** + * 分页查询用户角色数据。 + * 根据传入的分页请求参数,从数据库中查询用户角色数据并进行分页处理。 + * + * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数。 + * @return 分页查询结果,包含符合条件的用户角色数据传输对象列表。 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 查找指定用户的角色列表。 + * 根据用户 ID 从数据库中查询该用户关联的所有角色 ID。 + * + * @param userId 用户的唯一标识。 + * @return 该用户关联的角色 ID 列表。 + */ + List listRoles(String userId); + + /** + * 保存指定用户的全部角色。 + * 先删除该用户原有的所有角色关联,再添加新的角色关联。 + * + * @param userId 用户的唯一标识。 + * @param ids 要保存的角色 ID 列表。 + * @return 保存操作完成后,返回保存的角色 ID 相关信息(具体格式由实现类定义)。 + */ + String saveRoles(String userId, List ids); + + /** + * 判断指定用户是否为学生角色。 + * 根据用户 ID 检查该用户是否关联了学生角色。 + * + * @param userId 用户的唯一标识。 + * @return 如果用户是学生角色返回 true,否则返回 false。 + */ + boolean isStudent(String userId); + + /** + * 判断指定用户是否为老师角色。 + * 根据用户 ID 检查该用户是否关联了老师角色。 + * + * @param userId 用户的唯一标识。 + * @return 如果用户是老师角色返回 true,否则返回 false。 + */ + boolean isTeacher(String userId); + + /** + * 判断指定用户是否为管理员角色。 + * 根据用户 ID 检查该用户是否关联了管理员角色。 + * + * @param userId 用户的唯一标识。 + * @return 如果用户是管理员角色返回 true,否则返回 false。 + */ + boolean isAdmin(String userId); +} diff --git a/sys/user/service/SysUserService.java b/sys/user/service/SysUserService.java new file mode 100644 index 0000000..c35b999 --- /dev/null +++ b/sys/user/service/SysUserService.java @@ -0,0 +1,99 @@ +// 声明当前接口所在的包,明确该接口属于系统用户服务模块 +package com.yf.exam.modules.sys.user.service; + +// 导入 MyBatis-Plus 分页元数据接口,用于处理分页查询结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入 MyBatis-Plus 扩展服务接口,提供通用的 CRUD 操作方法 +import com.baomidou.mybatisplus.extension.service.IService; +// 导入系统用户数据传输对象类,用于在不同层之间传输用户相关数据 +import com.yf.exam.modules.sys.user.dto.SysUserDTO; +// 导入系统用户保存请求数据传输对象类,用于封装用户保存操作的请求参数 +import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO; +// 导入系统用户登录响应数据传输对象类,用于封装用户登录操作的响应信息 +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +// 导入系统用户实体类,用于映射数据库中的用户表 +import com.yf.exam.modules.sys.user.entity.SysUser; +// 导入自定义的分页请求数据传输对象类,用于封装分页查询的请求参数 +import com.yf.exam.core.api.dto.PagingReqDTO; + +/** + *

+ * 管理用户业务类接口,定义了系统用户相关的业务操作方法。 + * 继承自 MyBatis-Plus 的 IService 接口,可使用其提供的通用服务方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +public interface SysUserService extends IService { + + /** + * 分页查询系统用户数据。 + * 根据传入的分页请求参数,从数据库中查询符合条件的用户数据并进行分页处理。 + * + * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数 + * @return 分页查询结果,包含系统用户数据传输对象列表 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 用户登录方法。 + * 根据传入的用户名和密码,验证用户身份,若验证通过则返回登录信息。 + * + * @param userName 用户名,用于标识用户身份 + * @param password 用户密码,用于验证用户身份 + * @return 系统用户登录响应数据传输对象,包含登录成功后的用户信息和令牌等 + */ + SysUserLoginDTO login(String userName, String password); + + /** + * 根据令牌获取管理会话信息。 + * 验证传入的令牌是否有效,若有效则返回对应的用户登录信息。 + * + * @param token 用户登录后生成的令牌,用于验证用户身份和保持会话 + * @return 系统用户登录响应数据传输对象,包含令牌对应的用户信息 + */ + SysUserLoginDTO token(String token); + + /** + * 用户退出登录方法。 + * 使传入的令牌失效,清除用户的登录会话信息。 + * + * @param token 用户登录后生成的令牌,用于标识用户会话 + */ + void logout(String token); + + /** + * 修改用户资料方法。 + * 根据传入的用户数据传输对象,更新数据库中对应的用户信息。 + * + * @param reqDTO 系统用户数据传输对象,包含要更新的用户信息 + */ + void update(SysUserDTO reqDTO); + + /** + * 保存添加系统用户方法。 + * 根据传入的用户保存请求数据传输对象,将新用户信息保存到数据库中。 + * + * @param reqDTO 系统用户保存请求数据传输对象,包含要添加的用户信息 + */ + void save(SysUserSaveReqDTO reqDTO); + + /** + * 用户注册方法。 + * 根据传入的用户数据传输对象,在数据库中创建新的用户记录,并返回注册后的登录信息。 + * + * @param reqDTO 系统用户数据传输对象,包含要注册的用户信息 + * @return 系统用户登录响应数据传输对象,包含注册成功后的用户信息和令牌等 + */ + SysUserLoginDTO reg(SysUserDTO reqDTO); + + /** + * 快速注册方法。 + * 根据传入的用户数据传输对象,快速创建新的用户记录,并返回注册后的登录信息。 + * + * @param reqDTO 系统用户数据传输对象,包含要快速注册的用户信息 + * @return 系统用户登录响应数据传输对象,包含快速注册成功后的用户信息和令牌等 + */ + SysUserLoginDTO quickReg(SysUserDTO reqDTO); +} diff --git a/sys/user/service/impl/SysRoleServiceImpl.java b/sys/user/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..845939f --- /dev/null +++ b/sys/user/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,62 @@ +// 声明该类所在的包,明确其在项目中的模块和层级位置 +package com.yf.exam.modules.sys.user.service.impl; + +// 导入阿里巴巴的 FastJSON 库,用于 JSON 数据的序列化和反序列化 +import com.alibaba.fastjson.JSON; +// 导入 FastJSON 的 TypeReference 类,用于处理泛型类型的 JSON 反序列化 +import com.alibaba.fastjson.TypeReference; +// 导入 MyBatis-Plus 的查询条件构造器类,用于构建数据库查询条件 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +// 导入 MyBatis-Plus 的分页元数据接口,用于表示分页查询的结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入 MyBatis-Plus 的分页实现类,用于创建分页对象 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 导入 MyBatis-Plus 的服务实现基类,提供了通用的服务层方法实现 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入系统角色数据传输对象类,用于在不同层之间传输角色相关的数据 +import com.yf.exam.modules.sys.user.dto.SysRoleDTO; +// 导入系统角色实体类,用于映射数据库中的角色表 +import com.yf.exam.modules.sys.user.entity.SysRole; +// 导入系统角色数据访问接口,用于与数据库进行角色数据的交互 +import com.yf.exam.modules.sys.user.mapper.SysRoleMapper; +// 导入系统角色服务接口,定义了角色相关的业务方法 +import com.yf.exam.modules.sys.user.service.SysRoleService; +// 导入分页请求数据传输对象类,用于封装分页查询的请求参数 +import com.yf.exam.core.api.dto.PagingReqDTO; +// 导入 Spring 的服务注解,将该类标记为一个服务组件,由 Spring 容器进行管理 +import org.springframework.stereotype.Service; + +/** + *

+ * 系统角色 服务实现类,实现了系统角色相关的业务逻辑。 + * 继承自 MyBatis-Plus 的 ServiceImpl 类,使用 SysRoleMapper 操作 SysRole 实体。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +@Service +public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { + + /** + * 分页查询系统角色信息。 + * + * @param reqDTO 分页请求数据传输对象,包含分页查询的当前页码、每页记录数等信息 + * @return 分页查询结果,包含系统角色数据传输对象列表 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 创建分页对象,根据请求中的当前页码和每页记录数初始化 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + // 创建查询条件构造器,用于构建数据库查询条件,当前未添加具体查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + // 调用父类的 page 方法进行分页查询,获取包含 SysRole 实体的分页结果 + IPage page = this.page(query, wrapper); + // 将包含 SysRole 实体的分页结果转换为 JSON 字符串,再反序列化为包含 SysRoleDTO 的分页结果 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } +} diff --git a/sys/user/service/impl/SysUserRoleServiceImpl.java b/sys/user/service/impl/SysUserRoleServiceImpl.java new file mode 100644 index 0000000..dc67c40 --- /dev/null +++ b/sys/user/service/impl/SysUserRoleServiceImpl.java @@ -0,0 +1,200 @@ +package com.yf.exam.modules.sys.user.service.impl; + +// 导入阿里巴巴的 FastJSON 库,用于 JSON 数据的序列化和反序列化 +import com.alibaba.fastjson.JSON; +// 导入 FastJSON 的 TypeReference 类,用于处理泛型类型的 JSON 反序列化 +import com.alibaba.fastjson.TypeReference; +// 导入 MyBatis-Plus 的查询条件构造器类,用于构建数据库查询条件 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +// 导入 MyBatis-Plus 的分页元数据接口,用于表示分页查询的结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入 MyBatis-Plus 的分页实现类,用于创建分页对象 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 导入 MyBatis-Plus 的服务实现基类,提供了通用的服务层方法实现 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入系统用户角色数据传输对象类,用于在不同层之间传输用户角色相关的数据 +import com.yf.exam.modules.sys.user.dto.SysUserRoleDTO; +// 导入系统用户角色实体类,用于映射数据库中的用户角色表 +import com.yf.exam.modules.sys.user.entity.SysUserRole; +// 导入系统用户角色数据访问接口,用于与数据库进行用户角色数据的交互 +import com.yf.exam.modules.sys.user.mapper.SysUserRoleMapper; +// 导入系统用户角色服务接口,定义了用户角色相关的业务方法 +import com.yf.exam.modules.sys.user.service.SysUserRoleService; +// 导入分页请求数据传输对象类,用于封装分页查询的请求参数 +import com.yf.exam.core.api.dto.PagingReqDTO; +// 导入 Spring 的服务注解,将该类标记为一个服务组件,由 Spring 容器进行管理 +import org.springframework.stereotype.Service; +// 导入 Spring 的集合工具类,用于判断集合是否为空 +import org.springframework.util.CollectionUtils; +// 导入 Spring 的字符串工具类,用于判断字符串是否为空 +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * 用户角色 服务实现类,实现了用户角色相关的业务逻辑。 + * 继承自 MyBatis-Plus 的 ServiceImpl 类,使用 SysUserRoleMapper 操作 SysUserRole 实体。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +@Service +public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService { + + /** + * 分页查询用户角色信息。 + * + * @param reqDTO 分页请求数据传输对象,包含分页查询的当前页码、每页记录数等信息 + * @return 分页查询结果,包含用户角色数据传输对象列表 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 创建分页对象,根据请求中的当前页码和每页记录数初始化 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + // 创建查询条件构造器,用于构建数据库查询条件,当前未添加具体查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + // 调用父类的 page 方法进行分页查询,获取包含 SysUserRole 实体的分页结果 + IPage page = this.page(query, wrapper); + // 将包含 SysUserRole 实体的分页结果转换为 JSON 字符串,再反序列化为包含 SysUserRoleDTO 的分页结果 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + /** + * 根据用户 ID 查询用户的角色列表。 + * + * @param userId 用户的唯一标识 + * @return 用户的角色 ID 列表 + */ + @Override + public List listRoles(String userId) { + + // 创建查询条件构造器,添加用户 ID 作为查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId); + + // 调用父类的 list 方法进行查询,获取符合条件的用户角色实体列表 + List list = this.list(wrapper); + // 初始化一个空的角色 ID 列表 + List roles = new ArrayList<>(); + // 判断查询结果列表是否不为空 + if(!CollectionUtils.isEmpty(list)){ + // 遍历用户角色实体列表,将每个实体的角色 ID 添加到角色 ID 列表中 + for(SysUserRole item: list){ + roles.add(item.getRoleId()); + } + } + + return roles; + } + + /** + * 保存用户的角色信息,先删除用户原有的所有角色,再添加新的角色。 + * + * @param userId 用户的唯一标识 + * @param ids 新的角色 ID 列表 + * @return 保存的角色 ID 以逗号连接的字符串 + */ + @Override + public String saveRoles(String userId, List ids) { + + // 创建查询条件构造器,添加用户 ID 作为查询条件,用于删除用户原有的所有角色 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId); + // 调用父类的 remove 方法删除符合条件的用户角色记录 + this.remove(wrapper); + + // 判断新的角色 ID 列表是否不为空 + if(!CollectionUtils.isEmpty(ids)){ + + // 初始化一个空的用户角色实体列表 + List list = new ArrayList<>(); + // 用于存储以逗号连接的角色 ID 字符串 + String roleIds = null; + + // 遍历新的角色 ID 列表 + for(String item: ids){ + // 创建一个新的用户角色实体对象 + SysUserRole role = new SysUserRole(); + // 设置角色 ID + role.setRoleId(item); + // 设置用户 ID + role.setUserId(userId); + // 将用户角色实体对象添加到列表中 + list.add(role); + // 判断 roleIds 是否为空 + if(StringUtils.isEmpty(roleIds)){ + // 若为空,则直接赋值为当前角色 ID + roleIds = item; + }else{ + // 若不为空,则将当前角色 ID 追加到 roleIds 后面,用逗号分隔 + roleIds+=","+item; + } + } + + // 调用父类的 saveBatch 方法批量保存用户角色实体列表 + this.saveBatch(list); + return roleIds; + } + + return ""; + } + + /** + * 判断用户是否具有学生角色。 + * + * @param userId 用户的唯一标识 + * @return 若用户具有学生角色返回 true,否则返回 false + */ + @Override + public boolean isStudent(String userId) { + + // 创建查询条件构造器,添加用户 ID 和角色 ID(student)作为查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId) + .eq(SysUserRole::getRoleId, "student"); + + // 调用父类的 count 方法统计符合条件的记录数,若大于 0 则表示用户具有学生角色 + return this.count(wrapper) > 0; + } + + /** + * 判断用户是否具有教师角色。 + * + * @param userId 用户的唯一标识 + * @return 若用户具有教师角色返回 true,否则返回 false + */ + @Override + public boolean isTeacher(String userId) { + // 创建查询条件构造器,添加用户 ID 和角色 ID(teacher)作为查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId) + .eq(SysUserRole::getRoleId, "teacher"); + + // 调用父类的 count 方法统计符合条件的记录数,若大于 0 则表示用户具有教师角色 + return this.count(wrapper) > 0; + } + + /** + * 判断用户是否具有管理员角色。 + * + * @param userId 用户的唯一标识 + * @return 若用户具有管理员角色返回 true,否则返回 false + */ + @Override + public boolean isAdmin(String userId) { + // 创建查询条件构造器,添加用户 ID 和角色 ID(sa)作为查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId) + .eq(SysUserRole::getRoleId, "sa"); + + // 调用父类的 count 方法统计符合条件的记录数,若大于 0 则表示用户具有管理员角色 + return this.count(wrapper) > 0; + } +} diff --git a/sys/user/service/impl/SysUserServiceImpl.java b/sys/user/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..6aa7169 --- /dev/null +++ b/sys/user/service/impl/SysUserServiceImpl.java @@ -0,0 +1,383 @@ +// 声明该类所在的包,表明它属于系统用户服务实现模块 +package com.yf.exam.modules.sys.user.service.impl; + +// 导入阿里巴巴 FastJSON 库,用于 JSON 数据的序列化和反序列化 +import com.alibaba.fastjson.JSON; +// 导入 FastJSON 的 TypeReference 类,用于处理泛型类型的 JSON 反序列化 +import com.alibaba.fastjson.TypeReference; +// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +// 导入 MyBatis-Plus 的分页元数据接口,用于表示分页查询结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入 MyBatis-Plus 的 ID 生成工具类,用于生成分布式 ID +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +// 导入 MyBatis-Plus 的分页实现类,用于创建分页对象 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 导入 MyBatis-Plus 的服务实现基类,提供通用的服务层方法实现 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入自定义的 API 错误类,用于封装 API 错误信息 +import com.yf.exam.core.api.ApiError; +// 导入自定义的分页请求数据传输对象类,用于封装分页查询请求参数 +import com.yf.exam.core.api.dto.PagingReqDTO; +// 导入自定义的通用状态枚举类,定义通用的状态常量 +import com.yf.exam.core.enums.CommonState; +// 导入自定义的服务异常类,用于抛出服务层异常 +import com.yf.exam.core.exception.ServiceException; +// 导入自定义的 Bean 映射工具类,用于对象属性的复制 +import com.yf.exam.core.utils.BeanMapper; +// 导入自定义的密码处理工具类,用于密码的加密和验证 +import com.yf.exam.core.utils.passwd.PassHandler; +// 导入自定义的密码信息类,用于封装密码和盐值 +import com.yf.exam.core.utils.passwd.PassInfo; +// 导入自定义的 JWT 工具类,用于生成和验证 JWT 令牌 +import com.yf.exam.ability.shiro.jwt.JwtUtils; +// 导入自定义的系统用户数据传输对象类,用于在不同层之间传输用户数据 +import com.yf.exam.modules.sys.user.dto.SysUserDTO; +// 导入自定义的系统用户保存请求数据传输对象类,用于封装用户保存请求参数 +import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO; +// 导入自定义的系统用户登录响应数据传输对象类,用于封装用户登录响应信息 +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +// 导入自定义的系统用户实体类,用于映射数据库中的用户表 +import com.yf.exam.modules.sys.user.entity.SysUser; +// 导入自定义的系统用户数据访问接口,用于与数据库进行用户数据交互 +import com.yf.exam.modules.sys.user.mapper.SysUserMapper; +// 导入自定义的系统用户角色服务接口,用于处理用户角色相关业务 +import com.yf.exam.modules.sys.user.service.SysUserRoleService; +// 导入自定义的系统用户服务接口,定义用户相关的业务方法 +import com.yf.exam.modules.sys.user.service.SysUserService; +// 导入自定义的用户工具类,提供用户相关的工具方法 +import com.yf.exam.modules.user.UserUtils; +// 导入 Apache Commons Lang3 的字符串工具类,提供字符串操作方法 +import org.apache.commons.lang3.StringUtils; +// 导入 Apache Shiro 的安全工具类,用于进行身份验证和授权操作 +import org.apache.shiro.SecurityUtils; +// 导入 Spring 的自动装配注解,用于自动注入依赖的 Bean +import org.springframework.beans.factory.annotation.Autowired; +// 导入 Spring 的服务注解,将该类标记为服务组件,由 Spring 容器管理 +import org.springframework.stereotype.Service; +// 导入 Spring 的事务管理注解,用于声明事务方法 +import org.springframework.transaction.annotation.Transactional; +// 导入 Spring 的集合工具类,用于判断集合是否为空 +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * 系统用户 服务实现类,实现了系统用户相关的业务逻辑。 + * 继承自 MyBatis-Plus 的 ServiceImpl 类,使用 SysUserMapper 操作 SysUser 实体。 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +@Service +public class SysUserServiceImpl extends ServiceImpl implements SysUserService { + + // 自动注入系统用户角色服务类的实例,用于处理用户角色相关业务 + @Autowired + private SysUserRoleService sysUserRoleService; + + /** + * 分页查询系统用户信息。 + * + * @param reqDTO 分页请求数据传输对象,包含分页信息和查询参数 + * @return 分页查询结果,包含系统用户数据传输对象列表 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 创建分页对象,指定当前页码和每页记录数 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + // 创建查询条件构造器,用于构建数据库查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + // 获取查询参数 + SysUserDTO params = reqDTO.getParams(); + + // 如果查询参数不为空 + if(params!=null){ + // 如果用户名不为空,添加用户名模糊查询条件 + if(!StringUtils.isBlank(params.getUserName())){ + wrapper.lambda().like(SysUser::getUserName, params.getUserName()); + } + + // 如果真实姓名不为空,添加真实姓名模糊查询条件 + if(!StringUtils.isBlank(params.getRealName())){ + wrapper.lambda().like(SysUser::getRealName, params.getRealName()); + } + } + + // 执行分页查询,获取包含系统用户实体的分页结果 + IPage page = this.page(query, wrapper); + // 将包含系统用户实体的分页结果转换为包含系统用户数据传输对象的分页结果 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + /** + * 用户登录方法。 + * + * @param userName 用户名 + * @param password 密码 + * @return 系统用户登录响应数据传输对象,包含登录成功后的用户信息和令牌 + * @throws ServiceException 若用户名不存在、用户被禁用或密码错误,抛出服务异常 + */ + @Override + public SysUserLoginDTO login(String userName, String password) { + + // 创建查询条件构造器,添加用户名等于查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getUserName, userName); + + // 根据查询条件获取一个用户实体 + SysUser user = this.getOne(wrapper, false); + // 如果用户不存在,抛出用户名或密码错误异常 + if(user == null){ + throw new ServiceException(ApiError.ERROR_90010002); + } + + // 如果用户状态为异常(被禁用),抛出用户被禁用异常 + if(user.getState().equals(CommonState.ABNORMAL)){ + throw new ServiceException(ApiError.ERROR_90010005); + } + + // 验证密码是否正确 + boolean check = PassHandler.checkPass(password,user.getSalt(), user.getPassword()); + // 如果密码不正确,抛出用户名或密码错误异常 + if(!check){ + throw new ServiceException(ApiError.ERROR_90010002); + } + + // 为用户设置令牌并返回登录响应信息 + return this.setToken(user); + } + + /** + * 根据令牌验证用户信息并返回登录响应信息。 + * + * @param token 令牌 + * @return 系统用户登录响应数据传输对象,包含验证通过后的用户信息和令牌 + * @throws ServiceException 若令牌验证失败、用户不存在或用户被禁用,抛出服务异常 + */ + @Override + public SysUserLoginDTO token(String token) { + + // 从令牌中获取用户名 + String username = JwtUtils.getUsername(token); + + // 验证令牌是否有效 + boolean check = JwtUtils.verify(token, username); + + // 如果令牌验证失败,抛出用户名或密码错误异常 + if(!check){ + throw new ServiceException(ApiError.ERROR_90010002); + } + + // 创建查询条件构造器,添加用户名等于查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getUserName, username); + + // 根据查询条件获取一个用户实体 + SysUser user = this.getOne(wrapper, false); + // 如果用户不存在,抛出用户不存在异常 + if(user == null){ + throw new ServiceException(ApiError.ERROR_10010002); + } + + // 如果用户状态为异常(被禁用),抛出用户被禁用异常 + if(user.getState().equals(CommonState.ABNORMAL)){ + throw new ServiceException(ApiError.ERROR_90010005); + } + + // 为用户设置令牌并返回登录响应信息 + return this.setToken(user); + } + + /** + * 用户退出登录方法。 + * + * @param token 令牌 + */ + @Override + public void logout(String token) { + + // 仅退出当前会话 + SecurityUtils.getSubject().logout(); + } + + /** + * 更新用户信息,主要处理密码更新。 + * + * @param reqDTO 系统用户数据传输对象,包含要更新的用户信息 + */ + @Override + public void update(SysUserDTO reqDTO) { + + // 获取用户输入的新密码 + String pass = reqDTO.getPassword(); + // 如果新密码不为空 + if(!StringUtils.isBlank(pass)){ + // 生成新的密码信息,包含加密后的密码和盐值 + PassInfo passInfo = PassHandler.buildPassword(pass); + // 根据当前用户 ID 获取用户实体 + SysUser user = this.getById(UserUtils.getUserId()); + // 更新用户密码 + user.setPassword(passInfo.getPassword()); + // 更新用户密码盐值 + user.setSalt(passInfo.getSalt()); + // 更新用户信息到数据库 + this.updateById(user); + } + } + + /** + * 保存或更新用户信息,同时处理用户角色信息。 + * + * @param reqDTO 系统用户保存请求数据传输对象,包含要保存的用户信息和角色列表 + * @throws ServiceException 若角色列表为空,抛出服务异常 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void save(SysUserSaveReqDTO reqDTO) { + + // 获取用户的角色列表 + List roles = reqDTO.getRoles(); + + // 如果角色列表为空,抛出角色不能为空异常 + if(CollectionUtils.isEmpty(roles)){ + throw new ServiceException(ApiError.ERROR_90010003); + } + + // 创建系统用户实体对象 + SysUser user = new SysUser(); + // 将请求数据传输对象的属性复制到用户实体对象中 + BeanMapper.copy(reqDTO, user); + + // 如果用户 ID 为空,说明是新增用户,生成新的用户 ID + if(StringUtils.isBlank(user.getId())){ + user.setId(IdWorker.getIdStr()); + } + + // 如果请求中包含密码,更新用户密码信息 + if(!StringUtils.isBlank(reqDTO.getPassword())){ + // 生成新的密码信息,包含加密后的密码和盐值 + PassInfo pass = PassHandler.buildPassword(reqDTO.getPassword()); + // 更新用户密码 + user.setPassword(pass.getPassword()); + // 更新用户密码盐值 + user.setSalt(pass.getSalt()); + } + + // 保存用户角色信息,并返回角色 ID 字符串 + String roleIds = sysUserRoleService.saveRoles(user.getId(), roles); + // 设置用户的角色 ID 字符串 + user.setRoleIds(roleIds); + // 保存或更新用户信息到数据库 + this.saveOrUpdate(user); + } + + /** + * 用户注册方法。 + * + * @param reqDTO 系统用户数据传输对象,包含要注册的用户信息 + * @return 系统用户登录响应数据传输对象,包含注册成功后的用户信息和令牌 + * @throws ServiceException 若用户名已存在,抛出服务异常 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public SysUserLoginDTO reg(SysUserDTO reqDTO) { + + // 创建查询条件构造器,添加用户名等于查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getUserName, reqDTO.getUserName()); + + // 统计符合条件的用户数量 + int count = this.count(wrapper); + + // 如果用户数量大于 0,说明用户名已存在,抛出用户名已存在异常 + if(count > 0){ + throw new ServiceException(1, "用户名已存在,换一个吧!"); + } + + // 创建系统用户实体对象 + SysUser user = new SysUser(); + // 生成新的用户 ID + user.setId(IdWorker.getIdStr()); + // 设置用户名 + user.setUserName(reqDTO.getUserName()); + // 设置真实姓名 + user.setRealName(reqDTO.getRealName()); + // 生成新的密码信息,包含加密后的密码和盐值 + PassInfo passInfo = PassHandler.buildPassword(reqDTO.getPassword()); + // 设置用户密码 + user.setPassword(passInfo.getPassword()); + // 设置用户密码盐值 + user.setSalt(passInfo.getSalt()); + + // 创建角色列表,默认添加学生角色 + List roles = new ArrayList<>(); + roles.add("student"); + // 保存用户角色信息,并返回角色 ID 字符串 + String roleIds = sysUserRoleService.saveRoles(user.getId(), roles); + // 设置用户的角色 ID 字符串 + user.setRoleIds(roleIds); + // 保存用户信息到数据库 + this.save(user); + + // 为用户设置令牌并返回登录响应信息 + return this.setToken(user); + } + + /** + * 快速注册方法,如果用户已存在则直接返回登录信息,否则进行注册。 + * + * @param reqDTO 系统用户数据传输对象,包含要注册的用户信息 + * @return 系统用户登录响应数据传输对象,包含注册或登录成功后的用户信息和令牌 + */ + @Override + public SysUserLoginDTO quickReg(SysUserDTO reqDTO) { + + // 创建查询条件构造器,添加用户名等于查询条件,并限制查询结果为 1 条 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getUserName, reqDTO.getUserName()); + wrapper.last(" LIMIT 1 "); + // 根据查询条件获取一个用户实体 + SysUser user = this.getOne(wrapper); + // 如果用户存在,为用户设置令牌并返回登录响应信息 + if(user!=null){ + return this.setToken(user); + } + + // 如果用户不存在,进行注册操作 + return this.reg(reqDTO); + } + + /** + * 为用户设置令牌并返回登录响应信息。 + * + * @param user 系统用户实体对象 + * @return 系统用户登录响应数据传输对象,包含用户信息和令牌 + */ + private SysUserLoginDTO setToken(SysUser user){ + + // 创建系统用户登录响应数据传输对象 + SysUserLoginDTO respDTO = new SysUserLoginDTO(); + // 将用户实体对象的属性复制到登录响应数据传输对象中 + BeanMapper.copy(user, respDTO); + + // 生成 JWT 令牌 + String token = JwtUtils.sign(user.getUserName()); + // 设置登录响应数据传输对象的令牌 + respDTO.setToken(token); + + // 获取用户的角色列表 + List roles = sysUserRoleService.listRoles(user.getId()); + // 设置登录响应数据传输对象的角色列表 + respDTO.setRoles(roles); + + return respDTO; + } +} -- 2.34.1 From b6971277385318c4bfd1ee06d6ff99b1f1b68d01 Mon Sep 17 00:00:00 2001 From: yutao <2930275373@qq.com> Date: Mon, 28 Apr 2025 21:34:32 +0800 Subject: [PATCH 3/4] LYT --- .../java/com/yf/exam/ability/Constant.java | 15 - .../yf/exam/ability/job/enums/JobGroup.java | 14 - .../yf/exam/ability/job/enums/JobPrefix.java | 14 - .../exam/ability/job/service/JobService.java | 52 -- .../job/service/impl/JobServiceImpl.java | 153 ------ .../ability/shiro/CNFilterFactoryBean.java | 38 -- .../com/yf/exam/ability/shiro/ShiroRealm.java | 129 ----- .../yf/exam/ability/shiro/aop/JwtFilter.java | 68 --- .../yf/exam/ability/shiro/jwt/JwtToken.java | 45 -- .../yf/exam/ability/shiro/jwt/JwtUtils.java | 95 ---- .../ability/upload/config/UploadConfig.java | 34 -- .../upload/controller/UploadController.java | 58 --- .../exam/ability/upload/dto/UploadReqDTO.java | 25 - .../ability/upload/dto/UploadRespDTO.java | 27 - .../ability/upload/service/UploadService.java | 32 -- .../service/impl/UploadServiceImpl.java | 140 ----- .../exam/ability/upload/utils/FileUtils.java | 159 ------ .../exam/ability/upload/utils/MediaUtils.java | 43 -- .../java/com/yf/exam/aspect/DictAspect.java | 315 ------------ .../exam/aspect/mybatis/QueryInterceptor.java | 129 ----- .../aspect/mybatis/UpdateInterceptor.java | 92 ---- .../com/yf/exam/aspect/utils/InjectUtils.java | 110 ---- .../java/com/yf/exam/config/CorsConfig.java | 50 -- .../com/yf/exam/config/MultipartConfig.java | 28 - .../com/yf/exam/config/MybatisConfig.java | 37 -- .../com/yf/exam/config/ScheduledConfig.java | 93 ---- .../java/com/yf/exam/config/ShiroConfig.java | 153 ------ .../com/yf/exam/config/SwaggerConfig.java | 72 --- .../java/com/yf/exam/core/annon/Dict.java | 32 -- .../java/com/yf/exam/core/api/ApiError.java | 118 ----- .../java/com/yf/exam/core/api/ApiRest.java | 64 --- .../core/api/controller/BaseController.java | 160 ------ .../com/yf/exam/core/api/dto/BaseDTO.java | 15 - .../yf/exam/core/api/dto/BaseIdReqDTO.java | 32 -- .../yf/exam/core/api/dto/BaseIdRespDTO.java | 28 - .../yf/exam/core/api/dto/BaseIdsReqDTO.java | 34 -- .../yf/exam/core/api/dto/BaseStateReqDTO.java | 34 -- .../yf/exam/core/api/dto/PagingReqDTO.java | 61 --- .../yf/exam/core/api/dto/PagingRespDTO.java | 28 - .../yf/exam/core/api/utils/JsonConverter.java | 47 -- .../com/yf/exam/core/enums/CommonState.java | 19 - .../java/com/yf/exam/core/enums/OpenType.java | 18 - .../exam/core/exception/ServiceException.java | 51 -- .../exception/ServiceExceptionHandler.java | 46 -- .../com/yf/exam/core/utils/BeanMapper.java | 59 --- .../com/yf/exam/core/utils/CronUtils.java | 31 -- .../com/yf/exam/core/utils/DateUtils.java | 103 ---- .../java/com/yf/exam/core/utils/IpUtils.java | 65 --- .../com/yf/exam/core/utils/Reflections.java | 324 ------------ .../com/yf/exam/core/utils/SpringUtils.java | 53 -- .../com/yf/exam/core/utils/StringUtils.java | 42 -- .../yf/exam/core/utils/excel/ExportExcel.java | 402 --------------- .../yf/exam/core/utils/excel/ImportExcel.java | 303 ----------- .../utils/excel/annotation/ExcelField.java | 59 --- .../core/utils/excel/fieldtype/ListType.java | 62 --- .../com/yf/exam/core/utils/file/Md5Util.java | 33 -- .../exam/core/utils/passwd/PassHandler.java | 57 --- .../yf/exam/core/utils/passwd/PassInfo.java | 70 --- .../exam/controller/ExamController.java | 151 ------ .../com/yf/exam/modules/exam/dto/ExamDTO.java | 101 ---- .../exam/modules/exam/dto/ExamDepartDTO.java | 33 -- .../yf/exam/modules/exam/dto/ExamRepoDTO.java | 51 -- .../modules/exam/dto/ext/ExamRepoExtDTO.java | 32 -- .../exam/dto/request/ExamSaveReqDTO.java | 32 -- .../exam/dto/response/ExamOnlineRespDTO.java | 22 - .../exam/dto/response/ExamReviewRespDTO.java | 31 -- .../com/yf/exam/modules/exam/entity/Exam.java | 100 ---- .../exam/modules/exam/entity/ExamDepart.java | 42 -- .../yf/exam/modules/exam/entity/ExamRepo.java | 78 --- .../modules/exam/mapper/ExamDepartMapper.java | 15 - .../exam/modules/exam/mapper/ExamMapper.java | 45 -- .../modules/exam/mapper/ExamRepoMapper.java | 26 - .../exam/service/ExamDepartService.java | 32 -- .../modules/exam/service/ExamRepoService.java | 40 -- .../modules/exam/service/ExamService.java | 63 --- .../service/impl/ExamDepartServiceImpl.java | 66 --- .../service/impl/ExamRepoServiceImpl.java | 67 --- .../exam/service/impl/ExamServiceImpl.java | 194 ------- .../paper/controller/PaperController.java | 159 ------ .../yf/exam/modules/paper/dto/PaperDTO.java | 148 ------ .../modules/paper/dto/PaperQuAnswerDTO.java | 79 --- .../yf/exam/modules/paper/dto/PaperQuDTO.java | 93 ---- .../paper/dto/ext/PaperQuAnswerExtDTO.java | 35 -- .../paper/dto/ext/PaperQuDetailDTO.java | 43 -- .../paper/dto/request/PaperAnswerDTO.java | 30 -- .../paper/dto/request/PaperCreateReqDTO.java | 31 -- .../paper/dto/request/PaperListReqDTO.java | 56 -- .../paper/dto/request/PaperQuQueryDTO.java | 30 -- .../paper/dto/response/ExamDetailRespDTO.java | 57 --- .../paper/dto/response/ExamResultRespDTO.java | 25 - .../paper/dto/response/PaperListRespDTO.java | 30 -- .../yf/exam/modules/paper/entity/Paper.java | 125 ----- .../yf/exam/modules/paper/entity/PaperQu.java | 80 --- .../modules/paper/entity/PaperQuAnswer.java | 68 --- .../exam/modules/paper/enums/ExamState.java | 33 -- .../exam/modules/paper/enums/PaperState.java | 33 -- .../exam/modules/paper/job/BreakExamJob.java | 57 --- .../modules/paper/mapper/PaperMapper.java | 39 -- .../paper/mapper/PaperQuAnswerMapper.java | 27 - .../modules/paper/mapper/PaperQuMapper.java | 42 -- .../paper/service/PaperQuAnswerService.java | 44 -- .../modules/paper/service/PaperQuService.java | 70 --- .../modules/paper/service/PaperService.java | 83 --- .../impl/PaperQuAnswerServiceImpl.java | 61 --- .../service/impl/PaperQuServiceImpl.java | 94 ---- .../paper/service/impl/PaperServiceImpl.java | 477 ------------------ user/UserUtils.java | 75 +++ user/book/controller/UserBookController.java | 79 +++ user/book/dto/UserBookDTO.java | 79 +++ user/book/entity/UserBook.java | 80 +++ user/book/mapper/UserBookMapper.java | 18 + user/book/service/UserBookService.java | 41 ++ .../service/impl/UserBookServiceImpl.java | 192 +++++++ user/exam/controller/UserExamController.java | 65 +++ user/exam/dto/UserExamDTO.java | 84 +++ user/exam/dto/request/UserExamReqDTO.java | 42 ++ user/exam/dto/response/UserExamRespDTO.java | 36 ++ user/exam/entity/UserExam.java | 85 ++++ user/exam/mapper/UserExamMapper.java | 37 ++ user/exam/service/UserExamService.java | 50 ++ .../service/impl/UserExamServiceImpl.java | 116 +++++ 121 files changed, 1079 insertions(+), 8040 deletions(-) delete mode 100644 src/main/java/com/yf/exam/ability/Constant.java delete mode 100644 src/main/java/com/yf/exam/ability/job/enums/JobGroup.java delete mode 100644 src/main/java/com/yf/exam/ability/job/enums/JobPrefix.java delete mode 100644 src/main/java/com/yf/exam/ability/job/service/JobService.java delete mode 100644 src/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java delete mode 100644 src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java delete mode 100644 src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java delete mode 100644 src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java delete mode 100644 src/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java delete mode 100644 src/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java delete mode 100644 src/main/java/com/yf/exam/ability/upload/config/UploadConfig.java delete mode 100644 src/main/java/com/yf/exam/ability/upload/controller/UploadController.java delete mode 100644 src/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java delete mode 100644 src/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java delete mode 100644 src/main/java/com/yf/exam/ability/upload/service/UploadService.java delete mode 100644 src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java delete mode 100644 src/main/java/com/yf/exam/ability/upload/utils/FileUtils.java delete mode 100644 src/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java delete mode 100644 src/main/java/com/yf/exam/aspect/DictAspect.java delete mode 100644 src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java delete mode 100644 src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java delete mode 100644 src/main/java/com/yf/exam/aspect/utils/InjectUtils.java delete mode 100644 src/main/java/com/yf/exam/config/CorsConfig.java delete mode 100644 src/main/java/com/yf/exam/config/MultipartConfig.java delete mode 100644 src/main/java/com/yf/exam/config/MybatisConfig.java delete mode 100644 src/main/java/com/yf/exam/config/ScheduledConfig.java delete mode 100644 src/main/java/com/yf/exam/config/ShiroConfig.java delete mode 100644 src/main/java/com/yf/exam/config/SwaggerConfig.java delete mode 100644 src/main/java/com/yf/exam/core/annon/Dict.java delete mode 100644 src/main/java/com/yf/exam/core/api/ApiError.java delete mode 100644 src/main/java/com/yf/exam/core/api/ApiRest.java delete mode 100644 src/main/java/com/yf/exam/core/api/controller/BaseController.java delete mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseDTO.java delete mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java delete mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java delete mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java delete mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java delete mode 100644 src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java delete mode 100644 src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java delete mode 100644 src/main/java/com/yf/exam/core/api/utils/JsonConverter.java delete mode 100644 src/main/java/com/yf/exam/core/enums/CommonState.java delete mode 100644 src/main/java/com/yf/exam/core/enums/OpenType.java delete mode 100644 src/main/java/com/yf/exam/core/exception/ServiceException.java delete mode 100644 src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java delete mode 100644 src/main/java/com/yf/exam/core/utils/BeanMapper.java delete mode 100644 src/main/java/com/yf/exam/core/utils/CronUtils.java delete mode 100644 src/main/java/com/yf/exam/core/utils/DateUtils.java delete mode 100644 src/main/java/com/yf/exam/core/utils/IpUtils.java delete mode 100644 src/main/java/com/yf/exam/core/utils/Reflections.java delete mode 100644 src/main/java/com/yf/exam/core/utils/SpringUtils.java delete mode 100644 src/main/java/com/yf/exam/core/utils/StringUtils.java delete mode 100644 src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java delete mode 100644 src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java delete mode 100644 src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java delete mode 100644 src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java delete mode 100644 src/main/java/com/yf/exam/core/utils/file/Md5Util.java delete mode 100644 src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java delete mode 100644 src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/controller/ExamController.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/entity/Exam.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/service/ExamService.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java delete mode 100644 src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/controller/PaperController.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/entity/Paper.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/enums/ExamState.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/enums/PaperState.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/service/PaperService.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java delete mode 100644 src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java create mode 100644 user/UserUtils.java create mode 100644 user/book/controller/UserBookController.java create mode 100644 user/book/dto/UserBookDTO.java create mode 100644 user/book/entity/UserBook.java create mode 100644 user/book/mapper/UserBookMapper.java create mode 100644 user/book/service/UserBookService.java create mode 100644 user/book/service/impl/UserBookServiceImpl.java create mode 100644 user/exam/controller/UserExamController.java create mode 100644 user/exam/dto/UserExamDTO.java create mode 100644 user/exam/dto/request/UserExamReqDTO.java create mode 100644 user/exam/dto/response/UserExamRespDTO.java create mode 100644 user/exam/entity/UserExam.java create mode 100644 user/exam/mapper/UserExamMapper.java create mode 100644 user/exam/service/UserExamService.java create mode 100644 user/exam/service/impl/UserExamServiceImpl.java diff --git a/src/main/java/com/yf/exam/ability/Constant.java b/src/main/java/com/yf/exam/ability/Constant.java deleted file mode 100644 index 91b40a7..0000000 --- a/src/main/java/com/yf/exam/ability/Constant.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.yf.exam.ability; - - - //通用常量类 - //该类定义了应用程序中使用的通用常量。 - // @author bool - -public class Constant { - - - //文件上传路径 - //该常量定义了文件上传时的默认路径前缀。 - - public static final String FILE_PREFIX = "/upload/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 deleted file mode 100644 index 9b5f078..0000000 --- a/src/main/java/com/yf/exam/ability/job/enums/JobGroup.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.yf.exam.ability.job.enums; - -/** - * 任务分组 - * @author van - */ -public interface JobGroup { - - //定义了一个常量字符串,表示系统任务。 - //这个常量可以用于标识在系统中执行的任务组,通常用于任务调度和管理。 - - String SYSTEM = "system"; -} - 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 deleted file mode 100644 index 404a996..0000000 --- a/src/main/java/com/yf/exam/ability/job/enums/JobPrefix.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.yf.exam.ability.job.enums; - -/** - * 任务前缀 - * @author bool - */ -public interface JobPrefix { - - //定义了一个常量字符串,表示强制交卷的操作前缀。 - //这个常量可以用于标识在系统中强制交卷的任务或操作,通常用于考试系统中的任务管理。 - - String BREAK_EXAM = "break_exam_"; -} - 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 deleted file mode 100644 index c48c118..0000000 --- a/src/main/java/com/yf/exam/ability/job/service/JobService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.yf.exam.ability.job.service; - - - //任务业务类,用于动态处理任务信息 - //@author bool - //@date 2020/11/29 下午2:17 - -public interface JobService { - - - - //任务数据 - - String TASK_DATA = "taskData"; - - - //添加定时任务 - //@param jobClass - //@param jobName - //@param cron - //@param data - - void addCronJob(Class jobClass, String jobName, String cron, String data); - - - //添加立即执行的任务 - //@param jobClass - //@param jobName - //@param data - - void addCronJob(Class jobClass, String jobName, String data); - - - //暂停任务 - //@param jobName - //@param jobGroup - - void pauseJob(String jobName, String jobGroup); - - - //恢复任务 - //@param triggerName - //@param triggerGroup - - void resumeJob(String triggerName, String triggerGroup); - - //删除job - //@param jobName - //@param jobGroup - - void deleteJob(String jobName, String jobGroup); -} 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 deleted file mode 100644 index d8152e2..0000000 --- a/src/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java +++ /dev/null @@ -1,153 +0,0 @@ -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; - -/** - * Quartz定时任务服务实现类 - * @author bool - */ -@Log4j2 -@Service -public class JobServiceImpl implements JobService { - - - //Quartz调度器,用于管理定时任务 - - private Scheduler scheduler; - - - //构造函数,注入SchedulerFactoryBean并初始化scheduler - //@param schedulerFactoryBean Spring的SchedulerFactoryBean实例 - - public JobServiceImpl(@Autowired SchedulerFactoryBean schedulerFactoryBean) { - scheduler = schedulerFactoryBean.getScheduler(); - } - - - //添加一个新的定时任务 - //@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; - // 如果jobName为空,则自动生成一个唯一名称 - if(StringUtils.isEmpty(jobName)){ - 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); - // 构建新的JobDetail实例 - jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build(); - // 使用JobDataMap传递数据给任务 - jobDetail.getJobDataMap().put(TASK_DATA, data); - // 构建Trigger实例 - Trigger trigger = null; - // 如果cron表达式不为空,则使用cron表达式构建Trigger - if(!StringUtils.isEmpty(cron)){ - log.info("+++++表达式执行:"+ JSON.toJSONString(jobDetail)); - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); - trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build(); - } else { - // 如果cron表达式为空,则立即执行任务 - log.info("+++++立即执行:"+ JSON.toJSONString(jobDetail)); - trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).startNow().build(); - } - // 将JobDetail和Trigger添加到调度器中 - scheduler.scheduleJob(jobDetail, trigger); - } catch (Exception e) { - // 打印异常堆栈信息 - e.printStackTrace(); - } - } - - - //添加一个新的立即执行的定时任务 - //@param jobClass 定时任务的类 - //@param jobName 定时任务的名称 - //@param data 传递给任务的数据 - - @Override - public void addCronJob(Class jobClass, String jobName, String data) { - // 调用addCronJob方法,设置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.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.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.jobKey(jobName, jobGroup); - // 使用调度器删除任务 - scheduler.deleteJob(jobKey); - // 记录日志,表示任务已成功删除 - log.info("++++++++++删除任务:{}", jobKey); - } catch (SchedulerException e) { - // 打印调度异常堆栈信息 - e.printStackTrace(); - } - } -} diff --git a/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java b/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java deleted file mode 100644 index 0c9b7a3..0000000 --- a/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java +++ /dev/null @@ -1,38 +0,0 @@ -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 javax.servlet.Filter; -import java.util.Map; - - - //自定义过滤器工厂类,用于处理包含中文字符的URL问题 - //当URL中包含中文字符时,Shiro默认会返回400错误,这是因为InvalidRequestFilter会阻止非ASCII字符的请求。 - //该自定义过滤器重写了createFilterChainManager方法,修改了InvalidRequestFilter的配置,允许非ASCII字符的请求。 - //例如:https://youdomain.com/upload/file/云帆考试系统用户手册.pdf - //@author van - -public class CNFilterFactoryBean extends ShiroFilterFactoryBean { - - //重写createFilterChainManager方法,以允许包含中文字符的URL请求 - //@return 自定义的FilterChainManager - - @Override - protected FilterChainManager createFilterChainManager() { - // 调用父类方法创建FilterChainManager - FilterChainManager manager = super.createFilterChainManager(); - // 获取所有过滤器 - Map filterMap = manager.getFilters(); - // 获取InvalidRequestFilter - Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); - // 检查invalidRequestFilter是否为InvalidRequestFilter实例 - if (invalidRequestFilter instanceof InvalidRequestFilter) { - // 设置InvalidRequestFilter允许非ASCII字符的请求 - ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false); - } - // 返回自定义的FilterChainManager - return manager; - } -} diff --git a/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java b/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java deleted file mode 100644 index df91d62..0000000 --- a/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java +++ /dev/null @@ -1,129 +0,0 @@ -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; - - - //用户登录鉴权和获取用户授权 - //@author bool - -@Component -@Slf4j -public class ShiroRealm extends AuthorizingRealm { - @Autowired - @Lazy - private SysUserService sysUserService; // 注入用户服务,用于处理用户信息相关操作 - - @Autowired - @Lazy - private SysUserRoleService sysUserRoleService; // 注入用户角色服务,用于处理用户角色相关操作 - - - //判断该Realm是否支持指定的AuthenticationToken。 - //仅支持JwtToken类型的token。 - - //@param token 需要验证的AuthenticationToken对象。 - //@return 如果支持该token类型则返回true,否则返回false。 - - @Override - public boolean supports(AuthenticationToken token) { - return token instanceof JwtToken; - } - - - //获取指定用户主体的授权信息。 - //该方法从用户主体中获取用户ID,然后查询与该用户关联的角色信息,并返回授权信息。 - - //@param principals 用户主体集合,包含了用户的权限信息。 - //@return 包含用户角色信息的AuthorizationInfo对象。 - - @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; - } - - - //校验用户的账号密码是否正确。 - //该方法通过获取token并调用checkToken方法验证token的有效性,然后返回认证信息。 - - //@param auth 包含用户认证信息的AuthenticationToken对象。 - //@return 包含认证信息的AuthenticationInfo对象。 - //@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的有效性。 - //该方法从token中提取用户名,并验证token是否有效。如果无效则抛出异常。 - - //@param token 需要验证的token字符串。 - //@return 包含用户信息的SysUserLoginDTO对象。 - //@throws AuthenticationException 如果token无效或已过期则抛出此异常。 - - 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; - } - - - //清除指定用户主体的权限认证缓存。 - //该方法调用父类的clearCache方法来实现缓存清除功能。 - - //@param principals 用户主体集合,包含了用户的权限信息。 - - @Override - public void clearCache(PrincipalCollection principals) { - super.clearCache(principals); - } -} - 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 deleted file mode 100644 index ba02f75..0000000 --- a/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.yf.exam.ability.shiro.aop; - -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; - - - //鉴权登录拦截器,用于处理JWT(JSON Web Token)认证 - //@author bool - -@Slf4j -public class JwtFilter extends BasicHttpAuthenticationFilter { - - - //检查用户是否被允许访问资源 - //@param request 包含用户请求信息的ServletRequest对象 - //@param response 包含用户响应信息的ServletResponse对象 - // @param mappedValue 在Shiro配置中映射的值,通常用于权限检查 - //@return 如果用户成功登录并被允许访问资源,则返回true;否则返回false - - @Override - protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { - try { - // 调用executeLogin方法执行登录认证 - executeLogin(request, response); - // 如果登录成功,返回true表示用户被允许访问 - return true; - } catch (Exception e) { - // 如果发生异常,调用InjectUtils.restError方法写出统一错误信息 - InjectUtils.restError((HttpServletResponse) response); - // 返回false表示用户未被允许访问 - return false; - } - } - - - //执行JWT登录认证 - //@param request 包含用户请求信息的ServletRequest对象 - //@param response 包含用户响应信息的ServletResponse对象 - // @return 如果登录成功,则返回true;否则抛出异常 - //@throws Exception 如果登录过程中发生异常 - - @Override - protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { - HttpServletRequest httpServletRequest = (HttpServletRequest) request; - // 从请求头中获取JWT token - String token = httpServletRequest.getHeader(Constant.TOKEN); - // 创建JwtToken对象 - JwtToken jwtToken = new JwtToken(token); - try { - // 提交给realm进行登录认证,如果认证失败会抛出异常 - getSubject(request, response).login(jwtToken); - // 如果没有抛出异常则代表登录成功,返回true - return true; - } catch (Exception e) { - // 如果发生异常,记录日志 - log.error("JWT登录认证失败: ", e); - // 抛出异常以便在调用处捕获并处理 - throw e; - } - } -} 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 deleted file mode 100644 index 969b715..0000000 --- a/src/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.yf.exam.ability.shiro.jwt; - -import lombok.Data; -import org.apache.shiro.authc.AuthenticationToken; - -/** - * JWT(JSON Web Token)认证令牌,用于Shiro认证过程 - * @author bool - */ -@Data -public class JwtToken implements AuthenticationToken { - - private static final long serialVersionUID = 1L; - - /** - * JWT的字符token,用于认证用户身份 - */ - private String token; - - /** - * 构造函数,初始化JWT token - * @param token JWT的字符token - */ - public JwtToken(String token) { - this.token = token; - } - - /** - * 获取认证主体,这里返回JWT token - * @return JWT token - */ - @Override - public Object getPrincipal() { - return token; - } - - /** - * 获取认证凭证,这里返回JWT token - * @return JWT token - */ - @Override - public Object getCredentials() { - return token; - } -} 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 deleted file mode 100644 index 1ca221f..0000000 --- a/src/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java +++ /dev/null @@ -1,95 +0,0 @@ -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 java.util.Calendar; -import java.util.Date; - -/** - * JWT工具类,用于处理JWT(JSON Web Token)的生成、验证和解析 - * @author bool - */ -public class JwtUtils { - /** - * 有效期24小时(以毫秒为单位) - */ - private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; - - /** - * 校验JWT token是否正确 - * @param token JWT token字符串 - * @param username 用户名 - * @return 如果token有效且用户名匹配,则返回true;否则返回false - */ - public static boolean verify(String token, String username) { - try { - // 根据用户名生成HMAC256算法的密钥 - Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); - // 创建JWT验证器,并指定需要验证的用户名声明 - JWTVerifier verifier = JWT.require(algorithm) - .withClaim("username", username) - .build(); - // 验证token - verifier.verify(token); - return true; - } catch (Exception exception) { - return false; - } - } - - /** - * 从JWT token中解密并获取用户名 - * @param token JWT token字符串 - * @return 如果解析成功,则返回用户名;否则返回null - */ - public static String getUsername(String token) { - try { - // 解码JWT token - DecodedJWT jwt = JWT.decode(token); - // 获取用户名声明 - return jwt.getClaim("username").asString(); - } catch (JWTDecodeException e) { - return null; - } - } - - /** - * 生成JWT token字符串 - * @param username 用户名 - * @return 生成的JWT token字符串 - */ - public static String sign(String username) { - // 计算token的过期时间 - Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); - // 根据用户名生成HMAC256算法的密钥 - Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); - // 创建JWT并附带用户名信息及过期时间 - return JWT.create() - .withClaim("username", username) - .withExpiresAt(date) - .sign(algorithm); - } - - /** - * 根据用户名生成一个新的密钥,用于增强JWT的安全性 - * @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)); - // 对加密串进行MD5哈希 - String secret = Md5Util.md5(sb.toString()); - // 再次对用户名和上一步的MD5哈希结果进行MD5哈希,生成最终的密钥 - return Md5Util.md5(userName + "&" + secret); - } -} 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 deleted file mode 100644 index 00c8207..0000000 --- a/src/main/java/com/yf/exam/ability/upload/config/UploadConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.yf.exam.ability.upload.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - - - //文件上传配置类,用于定义文件上传的相关配置属性。 - //这些属性可以通过application.properties或application.yml文件进行配置。 - - //@author van - -@Data -@Configuration -@ConfigurationProperties(prefix = "conf.upload") -public class UploadConfig { - - //文件上传的访问路径,用户可以通过该路径访问上传的文件。 - //例如:http://example.com/upload/ - - private String url; - - - //文件上传的物理目录路径,指定文件在服务器上的存储位置。 - //例如:/var/www/upload/ - - private String dir; - - - //允许上传的文件后缀名数组,定义了哪些类型的文件可以被上传。 - //例如:{"jpg", "png", "pdf"} - - private String[] allowExtensions; -} 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 deleted file mode 100644 index a54e6fa..0000000 --- a/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.yf.exam.ability.upload.controller; - -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; - - - //本地文件上传下载请求类 - //该类提供文件上传和下载的功能,继承自BaseController。 - //@author bool - -@Log4j2 -@Api(tags = {"文件上传"}) -@RestController -public class UploadController extends BaseController { - - @Autowired - private UploadService uploadService; - - - //文件上传 - //该方法处理文件上传请求,参数通过表单方式提交。 - //@param reqDTO 包含上传文件信息的请求DTO - //@return 包含上传文件结果的响应DTO - - @PostMapping("/common/api/file/upload") - @ApiOperation(value = "文件上传", notes = "此接口较为特殊,参数都通过表单方式提交,而非JSON") - public ApiRest upload(@ModelAttribute UploadReqDTO reqDTO) { - // 上传并返回URL - UploadRespDTO respDTO = uploadService.upload(reqDTO); - return super.success(respDTO); - } - - - //独立文件下载 - //该方法处理文件下载请求。 - //@param request HTTP请求对象 - //@param response HTTP响应对象 - - @GetMapping(Constant.FILE_PREFIX + "**") - @ApiOperation(value = "文件下载", notes = "文件下载") - public void download(HttpServletRequest request, HttpServletResponse response) { - uploadService.download(request, response); - } -} 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 deleted file mode 100644 index 1879fcb..0000000 --- a/src/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java +++ /dev/null @@ -1,25 +0,0 @@ -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; - - - //文件上传请求类。 - //该类用于封装文件上传请求的参数,继承自BaseDTO。 - - // @author bool - // @date 2019-12-26 17:54 - -@Data -@ApiModel(value = "文件上传参数", description = "包含上传文件信息的请求参数") -public class UploadReqDTO extends BaseDTO { - - //上传文件的内容。 - //该字段是必填项,包含了用户需要上传的文件。 - //@ApiModelProperty(value = "上传文件内容", required = true) - - private MultipartFile 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 deleted file mode 100644 index 6aa5903..0000000 --- a/src/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -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; - - - //文件上传响应类。 - //该类用于封装文件上传后的响应信息,继承自BaseDTO。 - - // @author bool - -@Data -@AllArgsConstructor -@NoArgsConstructor -@ApiModel(value = "文件上传响应", description = "包含上传文件后的URL地址的响应信息") -public class UploadRespDTO extends BaseDTO { - - //上传后的完整的URL地址。 - //该字段是必填项,包含了上传文件后可以访问的URL。 - //@ApiModelProperty(value = "上传后的完整的URL地址", required = true) - - private String url; -} 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 deleted file mode 100644 index 3a89174..0000000 --- a/src/main/java/com/yf/exam/ability/upload/service/UploadService.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.yf.exam.ability.upload.service; - -import com.yf.exam.ability.upload.dto.UploadReqDTO; -import com.yf.exam.ability.upload.dto.UploadRespDTO; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - - - //阿里云OSS业务类 - //该接口定义了文件上传和下载的功能,具体实现由其实现类提供。 - //@author bool - //@date 2019-07-12 16:45 - -public interface UploadService { - - - //处理文件上传请求 - //@param reqDTO 文件上传请求对象,包含上传的文件信息 - //@return 文件上传响应对象,包含上传文件后的URL地址 - - UploadRespDTO upload(UploadReqDTO reqDTO); - - - //处理文件下载请求 - //@param request HTTP请求对象,包含下载文件的URI信息 - //@param response HTTP响应对象,用于返回下载的文件内容 - //@throws RuntimeException 如果下载过程中发生错误 - - void download(HttpServletRequest request, HttpServletResponse response); - -} 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 deleted file mode 100644 index 6a750ef..0000000 --- a/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java +++ /dev/null @@ -1,140 +0,0 @@ -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; - - - //文件上传业务类。 - //该类实现了文件上传和下载的功能,继承自UploadService接口。 - - //@author bool - //@date 2019-07-30 21:02 - -@Log4j2 -@Service -public class UploadServiceImpl implements UploadService { - - @Autowired - private UploadConfig conf; - - - //处理文件上传请求。 - - // @param reqDTO 文件上传请求对象,包含上传的文件信息。 - //@return 文件上传响应对象,包含上传文件后的URL地址。 - //@throws ServiceException 如果文件类型不允许上传或上传过程中发生IO异常。 - - @Override - public UploadRespDTO upload(UploadReqDTO reqDTO) { - // 获取上传文件的内容 - MultipartFile file = reqDTO.getFile(); - // 验证文件后缀是否在允许的范围内 - boolean allow = FilenameUtils.isExtension(file.getOriginalFilename(), conf.getAllowExtensions()); - if (!allow) { - throw new ServiceException("文件类型不允许上传!"); - } - // 获取上传文件夹的路径 - String fileDir = conf.getDir(); - // 构造真实物理地址 - String fullPath; - try { - // 处理文件路径 - String filePath = FileUtils.processPath(file); - // 构造文件保存地址 - fullPath = fileDir + filePath; - // 创建文件夹(如果不存在) - FileUtils.checkDir(fullPath); - // 上传文件到指定路径 - FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(fullPath)); - // 生成并返回上传结果 - return this.generateResult(filePath); - } catch (IOException e) { - e.printStackTrace(); - throw new ServiceException("文件上传失败:" + e.getMessage()); - } - } - - - //处理文件下载请求。 - - //@param request HTTP请求对象,包含请求的URI。 - //@param response HTTP响应对象,用于返回文件内容。 - //@throws RuntimeException 如果URL解码过程中发生UnsupportedEncodingException。 - - @Override - public void download(HttpServletRequest request, HttpServletResponse response) { - // 获取真实的文件路径 - String filePath = this.getRealPath(request.getRequestURI()); - // 处理中文文件名解码问题 - try { - filePath = URLDecoder.decode(filePath, "utf-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - // 输出完整路径到控制台(调试使用) - System.out.println("++++完整路径为:" + filePath); - try { - // 将文件内容写入HTTP响应 - FileUtils.writeRange(request, response, filePath); - } catch (IOException e) { - // 如果文件不存在,设置HTTP响应状态为404 - response.setStatus(404); - // 记录错误日志 - log.error("预览文件失败:" + e.getMessage()); - } - } - - - //构造文件上传响应结果。 - - //@param fileName 上传文件的路径。 - //@return 文件上传响应对象,包含上传文件后的完整URL地址。 - - private UploadRespDTO generateResult(String fileName) { - // 获取加速域名 - String domain = conf.getUrl(); - // 构造并返回文件上传响应对象 - return new UploadRespDTO(domain + fileName); - } - - - //获取真实物理文件地址。 - //@param uri 请求的URI,包含文件的相对路径。 - //@return 真实物理文件的完整路径。 - - public String getRealPath(String uri) { - // 定义正则表达式,匹配文件路径 - String regx = Constant.FILE_PREFIX + "(.*)"; - // 查找匹配的文件路径 - Pattern pattern = Pattern.compile(regx); - Matcher m = pattern.matcher(uri); - if (m.find()) { - // 获取匹配的文件路径部分 - String str = m.group(1); - // 构造真实物理文件的完整路径 - return conf.getDir() + str; - } - // 如果未找到匹配路径,返回null - return null; - } -} 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 deleted file mode 100644 index dd92e55..0000000 --- a/src/main/java/com/yf/exam/ability/upload/utils/FileUtils.java +++ /dev/null @@ -1,159 +0,0 @@ -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 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; - - - //文件工具类 - //该类提供了文件上传、下载和处理的相关工具方法。 - //@author bool - -public class FileUtils { - - - //后缀分割符号 - - private static final String SUFFIX_SPLIT = "."; - - - //持以断点的方式输出文件,提供文件在线预览和视频在线播放 - //@param request HTTP请求对象,包含请求头信息如Range - //@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"); - 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 -= len; - } - - 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) { - // 获取原始文件名 - 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(); - } - } -} 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 deleted file mode 100644 index e1ff58f..0000000 --- a/src/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.yf.exam.ability.upload.utils; - -import org.apache.commons.lang3.StringUtils; -import java.util.HashMap; -import java.util.Map; - - - //媒体工具类,用于判断文件的媒体类型 - //该类包含一个媒体类型映射表,并提供获取文件内容类型的方法。 - //@author bool - //@date 2019-11-14 16:21 - -public class MediaUtils { - - - //媒体类型映射表 - //该映射表存储了文件后缀与对应的MIME类型。 - - public static final Map MEDIA_MAP = new HashMap() { - { - // PDF文件 - put(".pdf", "application/pdf"); - // MP4视频文件 - put(".mp4", "video/mp4"); - } - }; - - - //获取文件的MIME类型 - //@param filePath 文件路径 - //@return 文件的MIME类型,如果文件路径无效或无法识别,则返回 "application/octet-stream" - - public static String getContentType(String filePath) { - if (!StringUtils.isBlank(filePath) && filePath.indexOf(".") != -1) { - // 后缀转换成小写 - String suffix = filePath.substring(filePath.lastIndexOf(".")).toLowerCase(); - if (MEDIA_MAP.containsKey(suffix)) { - return MEDIA_MAP.get(suffix); - } - } - return "application/octet-stream"; - } -} diff --git a/src/main/java/com/yf/exam/aspect/DictAspect.java b/src/main/java/com/yf/exam/aspect/DictAspect.java deleted file mode 100644 index cc191e7..0000000 --- a/src/main/java/com/yf/exam/aspect/DictAspect.java +++ /dev/null @@ -1,315 +0,0 @@ -package com.yf.exam.aspect; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.yf.exam.core.annon.Dict; -import com.yf.exam.core.api.ApiRest; -import com.yf.exam.core.utils.Reflections; -import com.yf.exam.modules.sys.system.service.SysDictService; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - - - //数据字典AOP类,处理数据字典值 - - //@author bool - -@Aspect -@Component -@Slf4j -public class DictAspect { - - @Autowired - private SysDictService sysDictService; - - -// 切入Controller执行 -// @param pjp -// @return -// @throws Throwable -// - @Around("execution(public * com.yf.exam..*.*Controller.*(..))") - public Object doAround(ProceedingJoinPoint pjp) throws Throwable { - return this.translate(pjp); - } - -// * -// * 进行翻译并返回,调用前必须实现:BaseDictService -// * -// * @param pjp -// * @return -// * @throws Throwable - public Object translate(ProceedingJoinPoint pjp) throws Throwable { - // 处理字典 - return this.parseAllDictText(pjp.proceed()); - } - -// * -// * 转换全部数据字典 -// * -// * @param result -// - private Object parseAllDictText(Object result) { - - // 非ApiRest类型不处理 - if (result instanceof ApiRest) { - parseFullDictText(result); - } - - return result; - } - - -// * -// * 转换所有类型的数据字典、包含子列表 -// * -// * @param result - private void parseFullDictText(Object result) { - - try { - - Object rest = ((ApiRest) result).getData(); - - // 不处理普通数据类型 - if (rest == null || this.isBaseType(rest.getClass())) { - return; - } - - // 分页的 - if (rest instanceof IPage) { - List items = new ArrayList<>(16); - for (Object record : ((IPage) rest).getRecords()) { - Object item = this.parseObject(record); - items.add(item); - } - ((IPage) rest).setRecords(items); - return; - } - - // 数据列表的 - if (rest instanceof List) { - List items = new ArrayList<>(); - for (Object record : ((List) rest)) { - Object item = this.parseObject(record); - items.add(item); - } - // 重新回写值 - ((ApiRest) result).setData(items); - return; - } - - // 处理单对象 - Object item = this.parseObject(((ApiRest) result).getData()); - ((ApiRest) result).setData(item); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - -// * 处理数据字典值 -// * -// * @param record -// * @return -// - public Object parseObject(Object record) { - - if (record == null) { - return null; - } - - // 不处理普通数据类型 - if (this.isBaseType(record.getClass())) { - return record; - } - - // 转换JSON字符 - String json = JSON.toJSONString(record); - JSONObject item = JSONObject.parseObject(json); - - for (Field field : Reflections.getAllFields(record)) { - - // 如果是List类型 - if (List.class.isAssignableFrom(field.getType())) { - try { - List list = this.processList(field, item.getObject(field.getName(), List.class)); - item.put(field.getName(), list); - continue; - } catch (Exception e) { - e.printStackTrace(); - } - continue; - } - - // 处理普通字段 - 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 - String textValue = this.translateDictValue(code, text, table, key); - if (StringUtils.isEmpty(textValue)) { - textValue = ""; - } - item.put(field.getName() + "_dictText", textValue); - continue; - } - - //日期格式转换 - if ("java.util.Date".equals(field.getType().getName()) && item.get(field.getName()) != null) { - - // 获取注解 - JsonFormat ann = field.getAnnotation(JsonFormat.class); - // 格式化方式 - SimpleDateFormat fmt; - - // 使用注解指定的 - 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; - - } - } - - return item; - } - - -// * 获得类型为List的值 -// * -// * @param field -// * @return - - private List processList(Field field, List list) { - - // 空判断 - if (list == null || list.size() == 0) { - return new ArrayList<>(); - } - - // 获得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){ - return list; - } - } - - // 常规列表无需处理 - if (isBaseType(actualType)) { - return list; - } - - // 返回列表 - List result = new ArrayList<>(16); - - for (int i = 0; i < list.size(); i++) { - // 创建实例-->赋值-->字典处理 - Object data = list.get(i); - try { - data = JSON.parseObject(JSON.toJSONString(data), actualType); - }catch (Exception e){ - // 转换出错不处理 - } - - // 处理后的数据 - Object pds = this.parseObject(data); - result.add(pds); - } - - return result; - } - -// -// * 翻译实现 -// * -// * @param code -// * @param text -// * @param table -// * @param key -// * @return -// - private String translateDictValue(String code, String text, String table, String key) { - if (StringUtils.isEmpty(key)) { - return null; - } - try { - // 翻译值 - String dictText = null; - if (!StringUtils.isEmpty(table)) { - dictText = sysDictService.findDict(table, text, code, key.trim()); - } - - if (!StringUtils.isEmpty(dictText)) { - return dictText; - } - } catch (Exception e) { - e.printStackTrace(); - } - return ""; - } - -// -// * 判断是否基本类型 -// * -// * @param clazz -// * @return -// - private boolean isBaseType(Class clazz) { - - - // 基础数据类型 - if (clazz.equals(java.lang.Integer.class) || - clazz.equals(java.lang.Byte.class) || - clazz.equals(java.lang.Long.class) || - clazz.equals(java.lang.Double.class) || - clazz.equals(java.lang.Float.class) || - clazz.equals(java.lang.Character.class) || - clazz.equals(java.lang.Short.class) || - clazz.equals(java.lang.Boolean.class)) { - return true; - } - - // String类型 - if (clazz.equals(java.lang.String.class)) { - return true; - } - - // 数字 - if (clazz.equals(java.lang.Number.class)) { - return true; - } - - return false; - } - - -} diff --git a/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java b/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java deleted file mode 100644 index 7f7ac38..0000000 --- a/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.yf.exam.aspect.mybatis; - -import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; -import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; -import lombok.extern.log4j.Log4j2; -import net.sf.jsqlparser.parser.CCJSqlParserManager; -import net.sf.jsqlparser.statement.select.PlainSelect; -import net.sf.jsqlparser.statement.select.Select; -import org.apache.commons.lang3.StringUtils; -import org.apache.ibatis.executor.statement.StatementHandler; -import org.apache.ibatis.mapping.MappedStatement; -import org.apache.ibatis.mapping.SqlCommandType; -import org.apache.ibatis.plugin.Interceptor; -import org.apache.ibatis.plugin.Intercepts; -import org.apache.ibatis.plugin.Invocation; -import org.apache.ibatis.plugin.Plugin; -import org.apache.ibatis.plugin.Signature; -import org.apache.ibatis.reflection.DefaultReflectorFactory; -import org.apache.ibatis.reflection.MetaObject; -import org.apache.ibatis.reflection.SystemMetaObject; -import org.apache.shiro.SecurityUtils; -import java.io.StringReader; -import java.sql.Connection; -import java.util.Properties; - - - //查询拦截器,用于拦截处理通用的信息,如用户ID、多租户信息等。 - //特别注意:此处继承了PaginationInterceptor进行分页处理,分页必须在拦截数据后执行,否则容易出现分页不准确,分页计数大于实际数量等问题。 - //@author bool - -@Log4j2 -@Intercepts({ - @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}), -}) -public class QueryInterceptor extends PaginationInterceptor implements Interceptor { - - - //用户ID过滤器标识符 - //该标识符用于在SQL语句中替换为当前登录用户的ID。 - - private static final String USER_FILTER = "{{userId}}"; - - @Override - public Object intercept(Invocation invocation) throws Throwable { - StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); - MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); - MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); - // SQL语句类型 - SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); - // 只过滤查询的SQL语句 - if (SqlCommandType.SELECT == sqlCommandType) { - // 获得原始SQL语句 - String sql = statementHandler.getBoundSql().getSql(); - // 如果SQL语句不包含用户ID过滤器标识符,则不处理 - if (!sql.contains(USER_FILTER)) { - return super.intercept(invocation); - } - // 处理SQL语句 - String outSql = this.parseSql(sql); - // 设置处理后的SQL语句 - metaObject.setValue("delegate.boundSql.sql", outSql); - // 再进行分页处理 - return super.intercept(invocation); - } - // 非查询语句直接执行 - return invocation.proceed(); - } - - @Override - public Object plugin(Object target) { - return Plugin.wrap(target, this); - } - - @Override - public void setProperties(Properties properties) { - // 该方法用于设置插件的属性,当前实现为空 - } - - - //获取当前登录用户 - //return 当前登录用户的SysUserLoginDTO对象,如果未登录则返回null - - private SysUserLoginDTO getLoginUser() { - try { - return SecurityUtils.getSubject().getPrincipal() != null ? (SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal() : null; - } catch (Exception e) { - log.error("获取当前登录用户失败", e); - return null; - } - } - - - //替换用户ID - //@param sql 原始SQL语句 - //@return 替换用户ID后的SQL语句,如果用户未登录则返回null - - private String processUserId(String sql) { - // 当前用户 - SysUserLoginDTO user = this.getLoginUser(); - if (user != null && StringUtils.isNotBlank(user.getId())) { - return sql.replace(USER_FILTER, user.getId()); - } - return null; - } - - - //处理注入用户信息 - //@param src 原始SQL语句 - //@return 处理后的SQL语句,如果处理失败则返回原始SQL语句 - - private String parseSql(String src) { - CCJSqlParserManager parserManager = new CCJSqlParserManager(); - try { - Select select = (Select) parserManager.parse(new StringReader(src)); - PlainSelect selectBody = (PlainSelect) select.getSelectBody(); - // 转换为字符串形式 - String sql = selectBody.toString(); - // 过滤用户ID - sql = this.processUserId(sql); - // 返回处理后的SQL语句 - return sql; - } catch (Exception e) { - log.error("解析SQL语句失败", e); - e.printStackTrace(); - } - // 返回原始SQL语句 - return src; - } -} diff --git a/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java b/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java deleted file mode 100644 index de67d00..0000000 --- a/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.yf.exam.aspect.mybatis; - -import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.ibatis.executor.Executor; -import org.apache.ibatis.mapping.MappedStatement; -import org.apache.ibatis.mapping.SqlCommandType; -import org.apache.ibatis.plugin.Interceptor; -import org.apache.ibatis.plugin.Intercepts; -import org.apache.ibatis.plugin.Invocation; -import org.apache.ibatis.plugin.Plugin; -import org.apache.ibatis.plugin.Signature; - -import java.lang.reflect.Field; -import java.sql.Timestamp; -import java.util.Objects; -import java.util.Properties; - - - //自动给创建时间个更新时间加值 - //@author bool - -@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) -public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor { - - - //创建时间 - - private static final String CREATE_TIME = "createTime"; - - //更新时间 - - private static final String UPDATE_TIME = "updateTime"; - - @Override - public Object intercept(Invocation invocation) throws Throwable { - // 获取MappedStatement对象,该对象包含了SQL执行的相关信息 - MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; - // 获取SQL操作命令类型(如INSERT、UPDATE等) - SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); - // 获取新增或修改的对象参数,该对象对应于SQL语句中的参数 - Object parameter = invocation.getArgs()[1]; - // 获取对象中所有的私有成员变量(对应表字段) - Field[] declaredFields = parameter.getClass().getDeclaredFields(); - // 如果对象有父类,则获取父类的私有成员变量并合并到当前字段列表中 - if (parameter.getClass().getSuperclass() != null) { - Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields(); - declaredFields = ArrayUtils.addAll(declaredFields, superField); - } - String fieldName = null; - // 遍历所有字段,查找createTime和updateTime字段并进行处理 - for (Field field : declaredFields) { - fieldName = field.getName(); - // 如果字段名为createTime且操作类型为INSERT,则设置当前时间戳 - if (Objects.equals(CREATE_TIME, fieldName)) { - if (SqlCommandType.INSERT.equals(sqlCommandType)) { - field.setAccessible(true); - field.set(parameter, new Timestamp(System.currentTimeMillis())); - } - } - // 如果字段名为updateTime且操作类型为INSERT或UPDATE,则设置当前时间戳 - if (Objects.equals(UPDATE_TIME, fieldName)) { - if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { - field.setAccessible(true); - field.set(parameter, new Timestamp(System.currentTimeMillis())); - } - } - } - // 继续执行后续的拦截器链或SQL操作 - return invocation.proceed(); - } - - - @Override - public Object plugin(Object target) { - // 检查目标对象是否是Executor类型 - if (target instanceof Executor) { - // 如果是Executor类型,则使用Plugin.wrap方法包装目标对象,并将其绑定到当前拦截器 - return Plugin.wrap(target, this); - } - // 如果目标对象不是Executor类型,则直接返回原对象 - return target; - } - - - @Override - public void setProperties(Properties properties) { - // 该方法用于设置拦截器的属性 - // 目前实现为空,表示不使用任何属性 - } - -} diff --git a/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java b/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java deleted file mode 100644 index 833d5ab..0000000 --- a/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.yf.exam.aspect.utils; - -import com.alibaba.fastjson.JSON; -import com.yf.exam.core.api.ApiError; -import com.yf.exam.core.api.ApiRest; -import lombok.extern.log4j.Log4j2; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.lang.reflect.Field; - - - //注入工具类 - //该类主要用于处理与注入相关的操作,例如生成错误响应和获取字段信息。 - //@author bool - //@date 2019-07-17 09:32 - -@Log4j2 -@Component -public class InjectUtils { - - - - - //给对象的指定字段赋值。 - //如果字段不存在,则跳过该字段并继续处理下一个字段。 - - //@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) { - // 如果字段不存在,则跳过该字段并继续处理下一个字段 - continue; - } - // 设置字段可访问,以便能够赋值 - field.setAccessible(true); - // 将指定的值赋给字段 - field.set(object, value); - } - } - - - - //获取指定类中字段名对应的字段。 - //如果当前类中不存在该字段,则递归查找其父类中的同名字段。 - - //@param clazz 目标类 - //@param fieldName 字段名 - //@return 如果找到指定字段,则返回该字段对象;否则返回null。 - - private Field getFiled(Class clazz, String fieldName) { - // 打印注入的类的名称,用于调试 - System.out.println("注入的类:" + clazz.toString()); - - try { - // 尝试获取当前类中指定的字段 - return clazz.getDeclaredField(fieldName); - } catch (Exception e) { - // 如果当前类中不存在该字段,则记录错误日志 - log.error(clazz.toString() + ": 不存在字段 " + fieldName + ",尝试查找父类"); - - // 如果当前类有父类,则递归调用本方法查找父类中的同名字段 - if (clazz.getSuperclass() != null) { - return this.getFiled(clazz.getSuperclass(), fieldName); - } - // 如果没有父类或在父类中也未找到该字段,则返回null - return null; - } - } - - - - - //处理REST请求的错误响应,向客户端返回一个固定的错误信息。 - - //@param response HTTP响应对象,用于向客户端发送错误信息。 - //@throws IOException 如果在写入响应或关闭流时发生IO异常,则抛出该异常。 - - public static void restError(HttpServletResponse response) { - try { - // 创建一个包含固定错误信息的ApiRest对象 - ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002); - - // 设置响应的字符编码为UTF-8,以确保正确处理中文字符 - response.setCharacterEncoding("UTF-8"); - - // 设置响应的内容类型为application/json,表示返回的数据格式为JSON - response.setContentType("application/json"); - - // 将ApiRest对象转换为JSON字符串,并写入到HTTP响应中 - response.getWriter().write(JSON.toJSONString(apiRest)); - - // 关闭响应的输出流,释放资源 - response.getWriter().close(); - } catch (IOException e) { - // 捕获并处理可能发生的IO异常 - e.printStackTrace(); - } - } - - -} diff --git a/src/main/java/com/yf/exam/config/CorsConfig.java b/src/main/java/com/yf/exam/config/CorsConfig.java deleted file mode 100644 index 8fdd0dd..0000000 --- a/src/main/java/com/yf/exam/config/CorsConfig.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.yf.exam.config; - -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; - - -// -// * 网关全局设置,允许跨域 -// * @author bool -// * @date 2019-08-13 17:28 -// - -@Configuration -public class CorsConfig { - - @Bean - public FilterRegistrationBean corsFilter() { - // 创建一个基于URL的CORS配置源 - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - // 创建一个新的CORS配置对象 - CorsConfiguration config = new CorsConfiguration(); - - // 允许发送凭据(如cookies) - config.setAllowCredentials(true); - // 允许所有来源的请求 - config.addAllowedOrigin(CorsConfiguration.ALL); - // 允许所有请求头 - config.addAllowedHeader(CorsConfiguration.ALL); - // 允许所有请求方法 - config.addAllowedMethod(CorsConfiguration.ALL); - - // 将CORS配置注册到所有路径 - source.registerCorsConfiguration("/**", config); - - // 创建一个新的FilterRegistrationBean实例,并注册CorsFilter - FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); - // 设置该过滤器的优先级为最高 - bean.setOrder(Ordered.HIGHEST_PRECEDENCE); - - // 返回配置好的FilterRegistrationBean实例 - return bean; - } - - -} diff --git a/src/main/java/com/yf/exam/config/MultipartConfig.java b/src/main/java/com/yf/exam/config/MultipartConfig.java deleted file mode 100644 index fc255e5..0000000 --- a/src/main/java/com/yf/exam/config/MultipartConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.yf.exam.config; - -import org.springframework.boot.web.servlet.MultipartConfigFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.unit.DataSize; - -import javax.servlet.MultipartConfigElement; - - -// * 文件上传配置 -// * @author bool -// * @date 2019-07-29 16:23 -// -@Configuration -public class MultipartConfig { - - @Bean - public MultipartConfigElement multipartConfigElement() { - MultipartConfigFactory factory = new MultipartConfigFactory(); - // 单个数据大小 - factory.setMaxFileSize(DataSize.ofMegabytes(5000L)); - // 总上传数据大小 - factory.setMaxRequestSize(DataSize.ofMegabytes(5000L)); - return factory.createMultipartConfig(); - } - -} diff --git a/src/main/java/com/yf/exam/config/MybatisConfig.java b/src/main/java/com/yf/exam/config/MybatisConfig.java deleted file mode 100644 index 596910d..0000000 --- a/src/main/java/com/yf/exam/config/MybatisConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.yf.exam.config; - -import com.yf.exam.aspect.mybatis.QueryInterceptor; -import com.yf.exam.aspect.mybatis.UpdateInterceptor; -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - - -// * Mybatis过滤器配置 -// * 注意:必须按顺序进行配置,否则容易出现业务异常 -// * @author bool - -@Configuration -@MapperScan("com.yf.exam.modules.**.mapper") -public class MybatisConfig { - - -// * 数据查询过滤器 - - @Bean - public QueryInterceptor queryInterceptor() { - QueryInterceptor query = new QueryInterceptor(); - query.setLimit(-1L); - return query; - } - - -// * 插入数据过滤器 - - @Bean - public UpdateInterceptor updateInterceptor() { - 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 deleted file mode 100644 index ee84590..0000000 --- a/src/main/java/com/yf/exam/config/ScheduledConfig.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.yf.exam.config; - -import lombok.extern.log4j.Log4j2; -import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.AsyncConfigurer; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.SchedulingConfigurer; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.scheduling.config.ScheduledTaskRegistrar; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadPoolExecutor; - - -// * 任务调度配置类,启用定时任务和异步任务的支持。 -// * -// * @author bool - -@Log4j2 -@Configuration -@EnableScheduling -@EnableAsync -public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer { - - -// * 定义定时任务使用的线程池。 -// * -// * @return 配置好的ThreadPoolTaskScheduler实例 - - @Bean(destroyMethod = "shutdown", name = "taskScheduler") - public ThreadPoolTaskScheduler taskScheduler() { - ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - scheduler.setPoolSize(10); // 设置线程池大小为10 - scheduler.setThreadNamePrefix("task-"); // 设置线程名称前缀为"task-" - scheduler.setAwaitTerminationSeconds(600); // 设置等待终止时间为600秒 - scheduler.setWaitForTasksToCompleteOnShutdown(true); // 设置在关闭时等待任务完成 - return scheduler; - } - - -// * 定义异步任务执行使用的线程池。 -// * -// * @return 配置好的ThreadPoolTaskExecutor实例 - - @Bean(name = "asyncExecutor") - public ThreadPoolTaskExecutor asyncExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(10); // 设置核心线程池大小为10 - executor.setQueueCapacity(1000); // 设置队列容量为1000 - executor.setKeepAliveSeconds(600); // 设置线程空闲时间为600秒 - executor.setMaxPoolSize(20); // 设置最大线程池大小为20 - executor.setThreadNamePrefix("taskExecutor-"); // 设置线程名称前缀为"taskExecutor-" - executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 设置拒绝策略为CallerRunsPolicy - executor.initialize(); // 初始化线程池 - return executor; - } - - -// * 配置定时任务注册器,使用自定义的线程池。 -// * -// * @param scheduledTaskRegistrar 定时任务注册器 - - @Override - public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { - ThreadPoolTaskScheduler taskScheduler = taskScheduler(); // 获取自定义的线程池调度器 - scheduledTaskRegistrar.setTaskScheduler(taskScheduler); // 设置定时任务注册器使用的线程池调度器 - } - - -// * 获取异步任务执行器。 -// * -// * @return 自定义的ThreadPoolTaskExecutor实例 - - @Override - public Executor getAsyncExecutor() { - return asyncExecutor(); // 返回自定义的异步任务执行器 - } - - -// * 定义异步任务未捕获异常的处理逻辑。 -// * -// * @return 异步任务未捕获异常的处理程序 - - @Override - public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { - return (throwable, method, objects) -> { - log.error("异步任务执行出现异常, message {}, method {}, params {}", throwable.getMessage(), method, objects); - }; - } -} diff --git a/src/main/java/com/yf/exam/config/ShiroConfig.java b/src/main/java/com/yf/exam/config/ShiroConfig.java deleted file mode 100644 index e014b1e..0000000 --- a/src/main/java/com/yf/exam/config/ShiroConfig.java +++ /dev/null @@ -1,153 +0,0 @@ -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; - - -// * Shiro配置类。 -// * 该类配置了Shiro的安全管理器、过滤器链等组件。 -// * -// * @author bool -// */ -@Slf4j -@Configuration -public class ShiroConfig { - - -// * Filter Chain定义说明: -// * 1、一个URL可以配置多个Filter,使用逗号分隔。 -// * 2、当设置多个过滤器时,全部验证通过,才视为通过。 -// * 3、部分过滤器可指定参数,如perms,roles。 - - -// /** -// * 创建ShiroFilterFactoryBean实例。 -// * -// * @param securityManager 安全管理器。 -// * @return ShiroFilterFactoryBean实例。 -// */ - @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); - - // 其他所有URL使用JWT过滤器 - map.put("/**", "jwt"); - shiroFilterFactoryBean.setFilterChainDefinitionMap(map); - - return shiroFilterFactoryBean; - } - -// /** -// * 创建DefaultWebSecurityManager实例。 -// * -// * @param myRealm 自定义的Shiro Realm。 -// * @return DefaultWebSecurityManager实例。 -// */ - @Bean("securityManager") - public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { - DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); - securityManager.setRealm(myRealm); - - // 禁用session存储 - DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); - DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); - defaultSessionStorageEvaluator.setSessionStorageEnabled(false); - subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); - securityManager.setSubjectDAO(subjectDAO); - - return securityManager; - } - -// /** -// * 添加注解支持,创建DefaultAdvisorAutoProxyCreator实例。 -// * -// * @return DefaultAdvisorAutoProxyCreator实例。 -// */ - @Bean - @DependsOn("lifecycleBeanPostProcessor") - public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { - DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); - defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); - defaultAdvisorAutoProxyCreator.setUsePrefix(true); - defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor"); - return defaultAdvisorAutoProxyCreator; - } - -// /** -// * 创建LifecycleBeanPostProcessor实例。 -// * -// * @return LifecycleBeanPostProcessor实例。 -// */ - @Bean - public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { - return new LifecycleBeanPostProcessor(); - } - -// /** -// * 创建AuthorizationAttributeSourceAdvisor实例。 -// * -// * @param securityManager 安全管理器。 -// * @return AuthorizationAttributeSourceAdvisor实例。 -// */ - @Bean - public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { - AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); - advisor.setSecurityManager(securityManager); - return advisor; - } -} diff --git a/src/main/java/com/yf/exam/config/SwaggerConfig.java b/src/main/java/com/yf/exam/config/SwaggerConfig.java deleted file mode 100644 index 68a399c..0000000 --- a/src/main/java/com/yf/exam/config/SwaggerConfig.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.yf.exam.config; - -import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; -import io.swagger.annotations.ApiOperation; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.ApiKey; -import springfox.documentation.service.Contact; -import springfox.documentation.service.SecurityScheme; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; -import java.util.Collections; - - -// * Swagger配置类,启用Swagger和SwaggerBootstrapUI,配置API文档信息和安全方案。 -// * -// * @author bool -// * @date 2020/8/19 20:53 -// */ -@Configuration -@EnableSwagger2 -@EnableSwaggerBootstrapUI -@ConfigurationProperties(prefix = "swagger") -public class SwaggerConfig { -// -// /** -// * 创建并配置Swagger Docket实例,用于生成考试模块接口的API文档。 -// * -// * @return 配置好的Docket实例 -// */ - @Bean - public Docket examApi() { - return new Docket(DocumentationType.SWAGGER_2) - .apiInfo(apiInfo()) // 设置API信息 - .groupName("考试模块接口") // 设置API分组名称 - .select() - .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 选择带有ApiOperation注解的方法 - .paths(PathSelectors.ant("/exam/api/**")) // 选择路径匹配/exam/api/**的API - .build() - .securitySchemes(Collections.singletonList(securityScheme())); // 添加安全方案 - } - -// /** -// * 构建API信息对象,包含标题、描述、联系人信息和版本号。 -// * -// * @return API信息对象 -// */ - private ApiInfo apiInfo() { - return new ApiInfoBuilder() - .title("考试系统接口") // 设置API文档的标题 - .description("考试系统接口") // 设置API文档的描述 - .contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com")) // 设置联系人信息 - .version("1.0.0") // 设置API文档的版本号 - .build(); - } - -// /** -// * 定义授权头部,用于API的安全验证。 -// * -// * @return 安全方案对象 -// */ - @Bean - SecurityScheme securityScheme() { - return new ApiKey("token", "token", "header"); // 定义一个API Key,名称为"token",位于请求头中 - } -} diff --git a/src/main/java/com/yf/exam/core/annon/Dict.java b/src/main/java/com/yf/exam/core/annon/Dict.java deleted file mode 100644 index fa48f1e..0000000 --- a/src/main/java/com/yf/exam/core/annon/Dict.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.yf.exam.core.annon; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -///** -// * 数据字典注解,用于标识字段需要从数据字典中获取对应的文本信息。 -// * @author bool -// */ -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -public @interface Dict { -// /** -// * 必须指定的数据字典编码,用于唯一标识一个数据字典。 -// * @return 数据字典编码。 -// */ - String dicCode(); - -// /** -// * 可选的数据字典文本,默认为空字符串。 -// * @return 数据字典文本。 -// */ - String dicText() default ""; - -// /** -// * 可选的数据字典表名,默认为空字符串。 -// * @return 数据字典表名。 -// */ - String dictTable() default ""; -} diff --git a/src/main/java/com/yf/exam/core/api/ApiError.java b/src/main/java/com/yf/exam/core/api/ApiError.java deleted file mode 100644 index 8ed8709..0000000 --- a/src/main/java/com/yf/exam/core/api/ApiError.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.yf.exam.core.api; - -import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; -import java.io.Serializable; - -///** -// * 全局错误码定义,用于定义接口的响应数据。 -// * 枚举名称全部使用代码命名,在系统中调用,免去取名难的问题。 -// * @author bool -// * @date 2019-06-14 21:15 -// */ -@NoArgsConstructor -@AllArgsConstructor -public enum ApiError implements Serializable { - //通用错误,接口参数不全 - ERROR_10010001("参数不全或类型错误!"), - -// /** -// * 用户未登录错误 -// */ - ERROR_10010002("您还未登录,请先登录!"), - -// /** -// * 数据不存在错误 -// */ - ERROR_10010003("数据不存在!"), - -// /** -// * 图形验证码错误 -// */ - ERROR_10010012("图形验证码错误!"), - -// /** -// * 短信验证码错误 -// */ - ERROR_10010013("短信验证码错误!"), - -// /** -// * 重复评论错误 -// */ - ERROR_10010014("不允许重复评论!"), - -// /** -// * 考试相关错误,试题被删除 -// */ - ERROR_20010001("试题被删除,无法继续考试!"), - -// /** -// * 考试相关错误,用户有正在进行的考试 -// */ - ERROR_20010002("您有正在进行的考试!"), - -// /** -// * 账号不存在错误 -// */ - ERROR_90010001("账号不存在,请确认!"), - -// /** -// * 账号或密码错误 -// */ - ERROR_90010002("账号或密码错误!"), - -// /** -// * 角色配置错误,至少要包含一个角色 -// */ - ERROR_90010003("至少要包含一个角色!"), - -// /** -// * 管理员账号错误,无法修改 -// */ - ERROR_90010004("管理员账号无法修改!"), - -// /** -// * 账号被禁用错误 -// */ - ERROR_90010005("账号被禁用,请联系管理员!"), - -// /** -// * 活动用户不足错误,无法开启竞拍 -// */ - ERROR_90010006("活动用户不足,无法开启竞拍!"), - -// /** -// * 旧密码错误 -// */ - ERROR_90010007("旧密码不正确,请确认!"), - -// /** -// * 数据不存在错误(重复定义) -// */ - ERROR_60000001("数据不存在!"); - -// /** -// * 错误消息 -// */ - public String msg; - -// /** -// * 生成Markdown格式文档,用于更新文档用的 -// * -// * @param args 命令行参数 -// */ - public static void main(String[] args) { - for (ApiError e : ApiError.values()) { - System.out.println("'"+e.name().replace("ERROR_", "")+"':'"+e.msg+"',"); - } - } - -// /** -// * 获取错误码 -// * -// * @return 错误码 -// */ - public Integer getCode(){ - return Integer.parseInt(this.name().replace("ERROR_", "")); - } -} diff --git a/src/main/java/com/yf/exam/core/api/ApiRest.java b/src/main/java/com/yf/exam/core/api/ApiRest.java deleted file mode 100644 index 358a318..0000000 --- a/src/main/java/com/yf/exam/core/api/ApiRest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.yf.exam.core.api; - - -import com.yf.exam.core.api.ApiError; -import com.yf.exam.core.exception.ServiceException; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import lombok.NoArgsConstructor; - -///** -// * 数据结果返回的封装 -// * @author bool -// * @date 2018/11/20 09:48 -// */ -@Data -@NoArgsConstructor -@ApiModel(value="接口响应", description="接口响应") -public class ApiRest{ - -// /** -// * 响应消息 -// */ - @ApiModelProperty(value = "响应消息") - private String msg; -// /** -// * 响应代码 -// */ - @ApiModelProperty(value = "响应代码,0为成功,1为失败", required = true) - private Integer code; - -// /** -// * 请求或响应body -// */ - @ApiModelProperty(value = "响应内容") - protected T data; - - -// /** -// * 是否成功 -// * @return -// */ - public boolean isSuccess(){ - return code.equals(0); - } - -// /** -// * 构造函数 -// * @param error -// */ - public ApiRest(ServiceException error){ - this.code = error.getCode(); - this.msg = error.getMsg(); - } - -// /** -// * 构造函数 -// * @param error -// */ - public ApiRest(ApiError error){ - this.code = error.getCode(); - this.msg = error.msg; - } -} diff --git a/src/main/java/com/yf/exam/core/api/controller/BaseController.java b/src/main/java/com/yf/exam/core/api/controller/BaseController.java deleted file mode 100644 index 3af2e4d..0000000 --- a/src/main/java/com/yf/exam/core/api/controller/BaseController.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.yf.exam.core.api.controller; - -import com.yf.exam.core.api.ApiError; -import com.yf.exam.core.api.ApiRest; -import com.yf.exam.core.exception.ServiceException; - -///** -// * 基础控制器,提供构建API响应的通用方法。 -// * @author Dav -// */ -public class BaseController { -// /** -// * 成功默认状态码。 -// */ - private static final Integer CODE_SUCCESS = 0; -// /** -// * 成功默认消息。 -// */ - private static final String MSG_SUCCESS = "操作成功!"; -// /** -// * 失败默认状态码。 -// */ - private static final Integer CODE_FAILURE = 1; -// /** -// * 失败默认消息。 -// */ - private static final String MSG_FAILURE = "请求失败!"; - -// /** -// * 构造一个包含状态码、消息和数据的API响应对象。 -// * -// * @param code 表示响应的状态码,通常用于标识请求的成功或失败。 -// * @param message 与状态码相关联的简短消息,描述响应的结果或错误原因。 -// * @param data 响应中包含的业务数据,可以是任何类型的对象。如果数据为null,则不设置响应的数据部分。 -// * @param 泛型类型,表示数据对象的类型。 -// * @return 构建好的ApiRest对象,包含了传入的状态码、消息和数据。 -// */ - protected ApiRest message(Integer code, String message, T data){ - ApiRest response = new ApiRest<>(); - response.setCode(code); - response.setMsg(message); - if(data != null) { - response.setData(data); - } - return response; - } - -// /** -// * 请求成功时返回一个空数据的API响应对象。 -// * -// * @param 数据对象的类型。 -// * @return 包含成功状态码和默认成功消息的ApiRest对象。 -// */ - protected ApiRest success(){ - return message(CODE_SUCCESS, MSG_SUCCESS, null); - } - -// /** -// * 请求成功时返回一个包含自定义消息和数据的API响应对象。 -// * -// * @param message 自定义的成功消息。 -// * @param data 响应中包含的业务数据。 -// * @param 数据对象的类型。 -// * @return 包含成功状态码、自定义消息和数据的ApiRest对象。 -// */ - protected ApiRest success(String message, T data){ - return message(CODE_SUCCESS, message, data); - } - -// /** -// * 请求成功时返回一个仅包含默认消息和数据的API响应对象。 -// * -// * @param data 响应中包含的业务数据。 -// * @param 数据对象的类型。 -// * @return 包含成功状态码、默认成功消息和数据的ApiRest对象。 -// */ - protected ApiRest success(T data){ - return message(CODE_SUCCESS, MSG_SUCCESS, data); - } - -// /** -// * 请求失败时返回一个包含自定义状态码、消息和数据的API响应对象。 -// * -// * @param code 自定义的失败状态码。 -// * @param message 自定义的失败消息。 -// * @param data 响应中包含的业务数据。 -// * @param 数据对象的类型。 -// * @return 包含自定义状态码、消息和数据的ApiRest对象。 -// */ - protected ApiRest failure(Integer code, String message, T data){ - return message(code, message, data); - } - -// /** -// * 请求失败时返回一个包含自定义消息和数据的API响应对象。 -// * -// * @param message 自定义的失败消息。 -// * @param data 响应中包含的业务数据。 -// * @param 数据对象的类型。 -// * @return 包含失败状态码、自定义消息和数据的ApiRest对象。 -// */ - protected ApiRest failure(String message, T data){ - return message(CODE_FAILURE, message, data); - } - -// /** -// * 请求失败时返回一个仅包含自定义消息的API响应对象。 -// * -// * @param message 自定义的失败消息。 -// * @return 包含失败状态码和自定义消息的ApiRest对象。 -// */ - protected ApiRest failure(String message){ - return message(CODE_FAILURE, message, null); - } - -// /** -// * 请求失败时返回一个包含默认失败消息和数据的API响应对象。 -// * -// * @param data 响应中包含的业务数据。 -// * @param 数据对象的类型。 -// * @return 包含失败状态码、默认失败消息和数据的ApiRest对象。 -// */ - protected ApiRest failure(T data){ - return message(CODE_FAILURE, MSG_FAILURE, data); - } - -// /** -// * 请求失败时返回一个仅包含默认失败消息的API响应对象。 -// * -// * @param 数据对象的类型。 -// * @return 包含失败状态码和默认失败消息的ApiRest对象。 -// */ - protected ApiRest failure(){ - return message(CODE_FAILURE, MSG_FAILURE, null); - } - -// /** -// * 请求失败时返回一个包含ApiError中定义的状态码、消息和数据的API响应对象。 -// * -// * @param error 包含错误信息的ApiError对象。 -// * @param data 响应中包含的业务数据。 -// * @param 数据对象的类型。 -// * @return 包含ApiError中定义的状态码、消息和数据的ApiRest对象。 -// */ - protected ApiRest failure(ApiError error, T data){ - return message(error.getCode(), error.msg, data); - } - -// /** -// * 请求失败时返回一个包含ServiceException中定义的状态码和消息的API响应对象。 -// * -// * @param ex 包含异常信息的ServiceException对象。 -// * @param 数据对象的类型。 -// * @return 包含ServiceException中定义的状态码和消息的ApiRest对象。 -// */ - protected ApiRest failure(ServiceException ex){ - ApiRest apiRest = message(ex.getCode(), ex.getMsg(), null); - return apiRest; - } -} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java deleted file mode 100644 index c9e2711..0000000 --- a/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.yf.exam.core.api.dto; - -import lombok.Data; - -import java.io.Serializable; - -///** -// * 请求和响应的基础类,用于处理序列化 -// * @author dav -// * @date 2019/3/16 15:56 -// */ -@Data -public class BaseDTO implements Serializable { - -} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java deleted file mode 100644 index 819aab5..0000000 --- a/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.yf.exam.core.api.dto; - -import com.yf.exam.core.api.dto.BaseDTO; -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -///** -// *

-// * 主键通用请求类,用于根据ID查询 -// *

-// * @author 聪明笨狗 -// * @since 2019-04-20 12:15 -// */ -@Data -@ApiModel(value="主键通用请求类", description="用于根据ID查询的通用请求类") -public class BaseIdReqDTO extends BaseDTO { -// /** -// * 主键ID,用于标识需要查询的实体。 -// * @since 2019-04-20 12:15 -// */ - @ApiModelProperty(value = "主键ID", required=true) - private String id; - -// /** -// * 用户ID,用于标识发起请求的用户。 -// * 该字段在序列化时将被忽略,不返回给客户端。 -// */ - @JsonIgnore - private String userId; -} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java deleted file mode 100644 index a14399a..0000000 --- a/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.yf.exam.core.api.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; - -///** -// *

-// * 主键通用响应类,用于在添加操作后返回内容 -// *

-// * @author 聪明笨狗 -// * @since 2019-04-20 12:15 -// */ -@Data -@ApiModel(value="主键通用响应类", description="在添加操作后返回的通用响应类") -@AllArgsConstructor -@NoArgsConstructor -public class BaseIdRespDTO extends BaseDTO { -// /** -// * 主键ID,用于标识添加的实体。 -// * @since 2019-04-20 12:15 -// */ - @ApiModelProperty(value = "主键ID", required=true) - private String id; -} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java deleted file mode 100644 index f78490e..0000000 --- a/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.yf.exam.core.api.dto; - -import com.yf.exam.core.api.dto.BaseDTO; -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.util.List; - - -// *

-// * 通用ID列表类操作,用于批量删除、修改状态等 -// *

-// * @author bool -// * @date 2019-08-01 19:07 -// */ -@Data -@ApiModel(value="删除参数", description="用于批量删除或修改状态的请求参数") -public class BaseIdsReqDTO extends BaseDTO { -// /** -// * 用户ID,用于标识发起请求的用户。 -// * 该字段在序列化时将被忽略,不返回给客户端。 -// */ - @JsonIgnore - private String userId; - -// /** -// * 要删除的ID列表,用于指定需要批量删除或修改状态的实体。 -// * -// * @since 2019-08-01 19:07 -// */ - @ApiModelProperty(value = "要删除的ID列表", required = true) - private List ids; -} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java deleted file mode 100644 index 5e01ddd..0000000 --- a/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.yf.exam.core.api.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 java.util.List; - -///** -// *

-// * 通用状态请求类,用于批量修改对象的状态 -// *

-// * @author 聪明笨狗 -// * @since 2019-04-20 12:15 -// */ -@Data -@ApiModel(value="通用状态请求类", description="用于批量修改对象状态的请求参数") -@AllArgsConstructor -@NoArgsConstructor -public class BaseStateReqDTO extends BaseDTO { -// /** -// * 要修改对象的ID列表。该字段是必需的。 -// */ - @ApiModelProperty(value = "要修改对象的ID列表", required=true) - private List ids; - -// /** -// * 通用状态,0表示正常,1表示禁用。该字段是必需的。 -// */ - @ApiModelProperty(value = "通用状态,0为正常,1为禁用", required=true) - private Integer state; -} diff --git a/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java b/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java deleted file mode 100644 index abc1eb5..0000000 --- a/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.yf.exam.core.api.dto; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -///** -// * 分页查询类,用于封装分页请求参数 -// * @param 查询参数的类型 -// * @author bool -// */ -@ApiModel(value="分页参数", description="用于分页查询的请求参数") -@Data -public class PagingReqDTO { -// /** -// * 当前页码,必需字段。 -// * 例如:1 表示第一页。 -// */ - @ApiModelProperty(value = "当前页码", required = true, example = "1") - private Integer current; - -// /** -// * 每页数量,必需字段。 -// * 例如:10 表示每页显示10条记录。 -// */ - @ApiModelProperty(value = "每页数量", required = true, example = "10") - private Integer size; - -// /** -// * 查询参数,可以包含具体的查询条件。 -// */ - @ApiModelProperty(value = "查询参数") - private T params; - -// /** -// * 排序字符,用于指定查询结果的排序方式。 -// */ - @ApiModelProperty(value = "排序字符") - private String orderBy; - -// /** -// * 当前用户的ID,用于标识发起请求的用户。 -// * 该字段在序列化时将被忽略,不返回给客户端。 -// */ - @JsonIgnore - @ApiModelProperty(value = "当前用户的ID") - private String userId; - - -// * 将当前分页请求参数转换成MyBatis的简单分页对象。 -// * @return Page 对象,包含当前页码和每页数量 -// */ - public Page toPage() { - Page page = new Page(); - page.setCurrent(this.current); - page.setSize(this.size); - return page; - } -} diff --git a/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java b/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java deleted file mode 100644 index 1773292..0000000 --- a/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.yf.exam.core.api.dto; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; - -///** -// * 分页响应类,继承自MyBatis Plus的Page类,用于封装分页查询结果 -// * @param 分页数据的类型 -// * @author bool -// * @date 2019-07-20 15:17 -// */ -public class PagingRespDTO extends Page { -// /** -// * 获取页面总数量 -// * @return 页面总数量 -// */ - @Override - public long getPages() { - if (this.getSize() == 0L) { - return 0L; - } else { - long pages = this.getTotal() / this.getSize(); - if (this.getTotal() % this.getSize() != 0L) { - ++pages; - } - return pages; - } - } -} diff --git a/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java b/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java deleted file mode 100644 index 3270026..0000000 --- a/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.yf.exam.core.api.utils; - -import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.fastjson.support.config.FastJsonConfig; -import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; - -///** -// * JSON数据转换器,用于转换返回消息的格式 -// * @author dav -// * @date 2018/9/11 19:30 -// */ -public class JsonConverter { - -// /** -// * FastJson消息转换器 -// * @return -// */ - public static HttpMessageConverter fastConverter() { - // 定义一个convert转换消息的对象 - FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); - // 添加FastJson的配置信息 - FastJsonConfig fastJsonConfig = new FastJsonConfig(); - // 默认转换器 - fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat, - SerializerFeature.WriteNullNumberAsZero, - SerializerFeature.MapSortField, - SerializerFeature.WriteNullStringAsEmpty, - SerializerFeature.DisableCircularReferenceDetect, - SerializerFeature.WriteDateUseDateFormat, - SerializerFeature.WriteNullListAsEmpty); - fastJsonConfig.setCharset(Charset.forName("UTF-8")); - // 处理中文乱码问题 - List fastMediaTypes = new ArrayList<>(); - fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); - fastConverter.setSupportedMediaTypes(fastMediaTypes); - // 在convert中添加配置信息 - fastConverter.setFastJsonConfig(fastJsonConfig); - - return fastConverter; - } -} diff --git a/src/main/java/com/yf/exam/core/enums/CommonState.java b/src/main/java/com/yf/exam/core/enums/CommonState.java deleted file mode 100644 index e72a98a..0000000 --- a/src/main/java/com/yf/exam/core/enums/CommonState.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.yf.exam.core.enums; - -///** -// * 通用的状态枚举信息 -// * -// * @author bool -// * @date 2019-09-17 17:57 -// */ -public interface CommonState { - -// /** -// * 普通状态,正常的 -// */ - Integer NORMAL = 0; -// /** -// * 非正常状态,禁用,下架等 -// */ - Integer ABNORMAL = 1; -} diff --git a/src/main/java/com/yf/exam/core/enums/OpenType.java b/src/main/java/com/yf/exam/core/enums/OpenType.java deleted file mode 100644 index 354e722..0000000 --- a/src/main/java/com/yf/exam/core/enums/OpenType.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.yf.exam.core.enums; - -///** -// * 开放方式 -// * @author bool -// */ -public interface OpenType { - -// /** -// * 完全开放 -// */ - Integer OPEN = 1; - -// /** -// * 部门开放 -// */ - Integer DEPT_OPEN = 2; -} diff --git a/src/main/java/com/yf/exam/core/exception/ServiceException.java b/src/main/java/com/yf/exam/core/exception/ServiceException.java deleted file mode 100644 index c92218a..0000000 --- a/src/main/java/com/yf/exam/core/exception/ServiceException.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.yf.exam.core.exception; - -import com.yf.exam.core.api.ApiError; -import com.yf.exam.core.api.ApiRest; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class ServiceException extends RuntimeException{ - -// /** -// * 错误码 -// */ - private Integer code; - -// /** -// * 错误消息 -// */ - private String msg; - -// /** -// * 从结果初始化 -// * @param apiRest -// */ - public ServiceException(ApiRest apiRest){ - this.code = apiRest.getCode(); - this.msg = apiRest.getMsg(); - } - -// /** -// * 从枚举中获取参数 -// * @param apiError -// */ - public ServiceException(ApiError apiError){ - this.code = apiError.getCode(); - this.msg = apiError.msg; - } - - /** - * 异常构造 - * @param msg - */ - public ServiceException(String msg){ - this.code = 1; - this.msg = msg; - } - -} diff --git a/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java b/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java deleted file mode 100644 index 4e7a0a9..0000000 --- a/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.yf.exam.core.exception; - -import com.yf.exam.core.api.ApiRest; -import org.springframework.http.HttpStatus; -import org.springframework.ui.Model; -import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.annotation.*; - -///** -// * 统一异常处理类 -// * @author bool -// * @date 2019-06-21 19:27 -// */ -@RestControllerAdvice -public class ServiceExceptionHandler { - -// /** -// * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器 -// * @param binder -// */ - @InitBinder - public void initWebBinder(WebDataBinder binder){ - - } - -// /** -// * 把值绑定到Model中,使全局@RequestMapping可以获取到该值 -// * @param model -// */ - @ModelAttribute - public void addAttribute(Model model) { - - } - -// /** -// * 捕获ServiceException -// * @param e -// * @return -// */ - @ExceptionHandler({com.yf.exam.core.exception.ServiceException.class}) - @ResponseStatus(HttpStatus.OK) - public ApiRest serviceExceptionHandler(ServiceException e) { - return new ApiRest(e); - } - -} \ No newline at end of file diff --git a/src/main/java/com/yf/exam/core/utils/BeanMapper.java b/src/main/java/com/yf/exam/core/utils/BeanMapper.java deleted file mode 100644 index f7ec09d..0000000 --- a/src/main/java/com/yf/exam/core/utils/BeanMapper.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.yf.exam.core.utils; - -import org.dozer.DozerBeanMapper; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - - -///** -// * 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现: -// * -// * 1. 持有Mapper的单例. -// * 2. 返回值类型转换. -// * 3. 批量转换Collection中的所有对象. -// * 4. 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数. -// * -// */ -public class BeanMapper { - -// /** -// * 持有Dozer单例, 避免重复创建DozerMapper消耗资源. -// */ - private static DozerBeanMapper dozerBeanMapper = new DozerBeanMapper(); - -// /** -// * 基于Dozer转换对象的类型. -// */ - public static T map(Object source, Class destinationClass) { - return dozerBeanMapper.map(source, destinationClass); - } - -// /** -// * 基于Dozer转换Collection中对象的类型. -// */ - public static List mapList(Iterable sourceList, Class destinationClass) { - List destinationList = new ArrayList(); - for (Object sourceObject : sourceList) { - T destinationObject = dozerBeanMapper.map(sourceObject, destinationClass); - destinationList.add(destinationObject); - } - return destinationList; - } - -// /** -// * 基于Dozer将对象A的值拷贝到对象B中. -// */ - public static void copy(Object source, Object destinationObject) { - if(source!=null) { - dozerBeanMapper.map(source, destinationObject); - } - } - - public static List mapList(Collection source, Function mapper) { - return source.stream().map(mapper).collect(Collectors.toList()); - } -} \ No newline at end of file diff --git a/src/main/java/com/yf/exam/core/utils/CronUtils.java b/src/main/java/com/yf/exam/core/utils/CronUtils.java deleted file mode 100644 index 22266f9..0000000 --- a/src/main/java/com/yf/exam/core/utils/CronUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.yf.exam.core.utils; - -import java.text.SimpleDateFormat; -import java.util.Date; - -///** -// * 时间转换quartz表达式 -// * @author bool -// * @date 2020/11/29 下午3:00 -// */ -public class CronUtils { - -// /** -// * 格式化数据 -// */ - private static final String DATE_FORMAT = "ss mm HH dd MM ? yyyy"; - -// /** -// * 准确的时间点到表达式 -// * @param date -// * @return -// */ - public static String dateToCron(final Date date){ - SimpleDateFormat fmt = new SimpleDateFormat(DATE_FORMAT); - String formatTimeStr = ""; - if (date != null) { - formatTimeStr = fmt.format(date); - } - return formatTimeStr; - } -} diff --git a/src/main/java/com/yf/exam/core/utils/DateUtils.java b/src/main/java/com/yf/exam/core/utils/DateUtils.java deleted file mode 100644 index 8b6f03e..0000000 --- a/src/main/java/com/yf/exam/core/utils/DateUtils.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.yf.exam.core.utils; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; - -///** -// * 日期处理工具类 -// * ClassName: DateUtils
-// * date: 2018年12月13日 下午6:34:02
-// * -// * @author Bool -// * @version -// */ -public class DateUtils { - -// /** -// * -// * calcExpDays:计算某个日期与当前日期相差的天数,如果计算的日期大于现在时间,将返回负数;否则返回正数
-// * @author Bool -// * @param userCreateTime -// * @return -// * @since JDK 1.6 -// */ - public static int calcExpDays(Date userCreateTime){ - - Calendar start = Calendar.getInstance(); - start.setTime(userCreateTime); - - Calendar now = Calendar.getInstance(); - now.setTime(new Date()); - - long l = now.getTimeInMillis() - start.getTimeInMillis(); - int days = new Long(l / (1000 * 60 * 60 * 24)).intValue(); - return days; - } - - -// /** -// * -// * dateNow:获取当前时间的字符串格式,根据传入的格式化来展示.
-// * @author Bool -// * @param format 日期格式化 -// * @return -// */ - public static String dateNow(String format) { - SimpleDateFormat fmt = new SimpleDateFormat(format); - Calendar c = new GregorianCalendar(); - return fmt.format(c.getTime()); - } - -// /** -// * formatDate:格式化日期,返回指定的格式
-// * @author Bool -// * @param time -// * @param format -// * @return -// */ - public static String formatDate(Date time, String format) { - SimpleDateFormat fmt = new SimpleDateFormat(format); - return fmt.format(time.getTime()); - } - - - -// /** -// * parseDate:将字符串转换成日期,使用:yyyy-MM-dd HH:mm:ss 来格式化 -// * @author Bool -// * @param date -// * @return -// */ - public static Date parseDate(String date) { - return parseDate(date, "yyyy-MM-dd HH:mm:ss"); - } - - -// /** -// * -// * parseDate:将字符串转换成日期,使用指定格式化来格式化 -// * @author Bool -// * @param date -// * @param pattern -// * @return -// */ - public static Date parseDate(String date, String pattern) { - - if (pattern==null) { - pattern = "yyyy-MM-dd HH:mm:ss"; - } - - SimpleDateFormat fmt = new SimpleDateFormat(pattern); - - try { - - return fmt.parse(date); - } catch (Exception ex) { - ex.printStackTrace(); - } - return null; - - } -} diff --git a/src/main/java/com/yf/exam/core/utils/IpUtils.java b/src/main/java/com/yf/exam/core/utils/IpUtils.java deleted file mode 100644 index 87270fd..0000000 --- a/src/main/java/com/yf/exam/core/utils/IpUtils.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.yf.exam.core.utils; - - -import javax.servlet.http.HttpServletRequest; - -///** -// * IP获取工具类,用户获取网络请求过来的真实IP -// * ClassName: IpUtils
-// * date: 2018年2月13日 下午7:27:52
-// * -// * @author Bool -// * @version -// */ -public class IpUtils { - - -// /** -// * -// * getClientIp:通过请求获取客户端的真实IP地址 -// * @author Bool -// * @param request -// * @return -// */ - public static String extractClientIp(HttpServletRequest request) { - - String ip = null; - - //X-Forwarded-For:Squid 服务代理 - String ipAddresses = request.getHeader("X-Forwarded-For"); - - if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { - //Proxy-Client-IP:apache 服务代理 - ipAddresses = request.getHeader("Proxy-Client-IP"); - } - - if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { - //WL-Proxy-Client-IP:weblogic 服务代理 - ipAddresses = request.getHeader("WL-Proxy-Client-IP"); - } - - if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { - //HTTP_CLIENT_IP:有些代理服务器 - ipAddresses = request.getHeader("HTTP_CLIENT_IP"); - } - - if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { - //X-Real-IP:nginx服务代理 - ipAddresses = request.getHeader("X-Real-IP"); - } - - //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP - if (ipAddresses != null && ipAddresses.length() != 0) { - ip = ipAddresses.split(",")[0]; - } - - //还是不能获取到,最后再通过request.getRemoteAddr();获取 - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { - ip = request.getRemoteAddr(); - } - - return ip; - } - - -} diff --git a/src/main/java/com/yf/exam/core/utils/Reflections.java b/src/main/java/com/yf/exam/core/utils/Reflections.java deleted file mode 100644 index a15c18c..0000000 --- a/src/main/java/com/yf/exam/core/utils/Reflections.java +++ /dev/null @@ -1,324 +0,0 @@ -/** - * Copyright (c) 2005-2012 springside.org.cn - */ -package com.yf.exam.core.utils; - -import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.springframework.util.Assert; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -///** -// * 反射工具类. -// * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. -// * @author calvin -// * @version 2016-01-15 -// */ -@Log4j2 -public class Reflections { - - private static final String SETTER_PREFIX = "set"; - - private static final String GETTER_PREFIX = "get"; - - private static final String CGLIB_CLASS_SEPARATOR = "$$"; - - -// /** -// * 获取类的所有属性,包括父类 -// * -// * @param object -// * @return -// */ - public static Field[] getAllFields(Object object) { - Class clazz = object.getClass(); - List fieldList = new ArrayList<>(); - while (clazz != null) { - fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); - clazz = clazz.getSuperclass(); - } - Field[] fields = new Field[fieldList.size()]; - fieldList.toArray(fields); - return fields; - } - - -// /** -// * 调用Getter方法. -// * 支持多级,如:对象名.对象名.方法 -// */ - public static Object invokeGetter(Object obj, String propertyName) { - Object object = obj; - for (String name : StringUtils.split(propertyName, ".")){ - String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); - object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); - } - return object; - } - -// /** -// * 调用Setter方法, 仅匹配方法名。 -// * 支持多级,如:对象名.对象名.方法 -// */ - public static void invokeSetter(Object obj, String propertyName, Object value) { - Object object = obj; - String[] names = StringUtils.split(propertyName, "."); - for (int i=0; i[] parameterTypes, - final Object[] args) { - Method method = getAccessibleMethod(obj, methodName, parameterTypes); - if (method == null) { - throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); - } - - try { - return method.invoke(obj, args); - } catch (Exception e) { - throw convertReflectionExceptionToUnchecked(e); - } - } - -// /** -// * 直接调用对象方法, 无视private/protected修饰符, -// * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. -// * 只匹配函数名,如果有多个同名函数调用第一个。 -// */ - public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) { - Method method = getAccessibleMethodByName(obj, methodName); - if (method == null) { - throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); - } - - try { - return method.invoke(obj, args); - } catch (Exception e) { - throw convertReflectionExceptionToUnchecked(e); - } - } - -// /** -// * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. -// * -// * 如向上转型到Object仍无法找到, 返回null. -// */ - public static Field getAccessibleField(final Object obj, final String fieldName) { - Validate.notNull(obj, "object can't be null"); - Validate.notBlank(fieldName, "fieldName can't be blank"); - for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { - try { - Field field = superClass.getDeclaredField(fieldName); - makeAccessible(field); - return field; - } catch (NoSuchFieldException e) {//NOSONAR - // Field不在当前类定义,继续向上转型 - continue;// new add - } - } - return null; - } - -// /** -// * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. -// * 如向上转型到Object仍无法找到, 返回null. -// * 匹配函数名+参数类型。 -// * -// * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) -// */ - public static Method getAccessibleMethod(final Object obj, final String methodName, - final Class... parameterTypes) { - Validate.notNull(obj, "object can't be null"); - Validate.notBlank(methodName, "methodName can't be blank"); - - for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { - try { - Method method = searchType.getDeclaredMethod(methodName, parameterTypes); - makeAccessible(method); - return method; - } catch (NoSuchMethodException e) { - // Method不在当前类定义,继续向上转型 - continue;// new add - } - } - return null; - } - -// /** -// * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. -// * 如向上转型到Object仍无法找到, 返回null. -// * 只匹配函数名。 -// * -// * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) -// */ - public static Method getAccessibleMethodByName(final Object obj, final String methodName) { - Validate.notNull(obj, "object can't be null"); - Validate.notBlank(methodName, "methodName can't be blank"); - - for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { - Method[] methods = searchType.getDeclaredMethods(); - for (Method method : methods) { - if (method.getName().equals(methodName)) { - makeAccessible(method); - return method; - } - } - } - return null; - } - -// /** -// * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 -// */ - public static void makeAccessible(Method method) { - if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) - && !method.isAccessible()) { - method.setAccessible(true); - } - } - -// /** -// * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 -// */ - public static void makeAccessible(Field field) { - if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier - .isFinal(field.getModifiers())) && !field.isAccessible()) { - field.setAccessible(true); - } - } - -// /** -// * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 -// * 如无法找到, 返回Object.class. -// * eg. -// * public UserDao extends HibernateDao -// * -// * @param clazz The class to introspect -// * @return the first generic declaration, or Object.class if cannot be determined -// */ - @SuppressWarnings("unchecked") - public static Class getClassGenricType(final Class clazz) { - return getClassGenricType(clazz, 0); - } - -// /** -// * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. -// * 如无法找到, 返回Object.class. -// * -// * 如public UserDao extends HibernateDao -// * -// * @param clazz clazz The class to introspect -// * @param index the Index of the generic ddeclaration,start from 0. -// * @return the index generic declaration, or Object.class if cannot be determined -// */ - public static Class getClassGenricType(final Class clazz, final int index) { - - Type genType = clazz.getGenericSuperclass(); - - if (!(genType instanceof ParameterizedType)) { - log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType"); - return Object.class; - } - - Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); - - if (index >= params.length || index < 0) { - log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " - + params.length); - return Object.class; - } - if (!(params[index] instanceof Class)) { - log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); - return Object.class; - } - - return (Class) params[index]; - } - - public static Class getUserClass(Object instance) { - Assert.notNull(instance, "Instance must not be null"); - Class clazz = instance.getClass(); - if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { - Class superClass = clazz.getSuperclass(); - if (superClass != null && !Object.class.equals(superClass)) { - return superClass; - } - } - return clazz; - - } - -// /** -// * 将反射时的checked exception转换为unchecked exception. -// */ - public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) { - if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException - || e instanceof NoSuchMethodException) { - return new IllegalArgumentException(e); - } else if (e instanceof InvocationTargetException) { - return new RuntimeException(((InvocationTargetException) e).getTargetException()); - } else if (e instanceof RuntimeException) { - return (RuntimeException) e; - } - return new RuntimeException("Unexpected Checked Exception.", e); - } -} diff --git a/src/main/java/com/yf/exam/core/utils/SpringUtils.java b/src/main/java/com/yf/exam/core/utils/SpringUtils.java deleted file mode 100644 index 41c6dda..0000000 --- a/src/main/java/com/yf/exam/core/utils/SpringUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.yf.exam.core.utils; - -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.stereotype.Component; - -///** -// * Spring获取工具类,用于在非Spring管理的类中获取Spring管理的Bean。 -// * -// * @author bool -// * @date 2019-12-09 15:55 -// */ -@Component -public class SpringUtils implements ApplicationContextAware { - -// /** -// * 静态变量,用于存储Spring的ApplicationContext对象。 -// */ - private static ApplicationContext applicationContext; - -// /** -// * 实现ApplicationContextAware接口的方法,用于设置ApplicationContext。 -// * -// * @param context Spring的ApplicationContext对象 -// * @throws BeansException 如果发生Bean相关的异常 -// */ - @Override - public void setApplicationContext(ApplicationContext context) throws BeansException { - applicationContext = context; - } - -// /** -// * 根据Bean的类型获取Spring管理的Bean。 -// * -// * @param tClass Bean的类型 -// * @return 指定类型的Bean实例 -// */ - public static T getBean(Class tClass) { - return applicationContext.getBean(tClass); - } - -// /** -// * 根据Bean的名称和类型获取Spring管理的Bean。 -// * -// * @param name Bean的名称 -// * @param type Bean的类型 -// * @return 指定名称和类型的Bean实例 -// */ - public static T getBean(String name, Class type) { - return applicationContext.getBean(name, type); - } -} diff --git a/src/main/java/com/yf/exam/core/utils/StringUtils.java b/src/main/java/com/yf/exam/core/utils/StringUtils.java deleted file mode 100644 index 03affe9..0000000 --- a/src/main/java/com/yf/exam/core/utils/StringUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.yf.exam.core.utils; - -import java.util.Map; -// -///** -// * 字符串常用工具类,提供一些字符串处理的静态方法。 -// * -// * @author bool -// * @date 2019-05-15 11:40 -// */ -public class StringUtils { - -// /** -// * 判断给定的字符串是否为空或空白。 -// * 空白字符串是指长度为0的字符串或仅包含空白字符的字符串。 -// * -// * @param str 要判断的字符串 -// * @return 如果字符串为空或空白,返回true;否则返回false -// */ - public static boolean isBlank(String str) { - return str == null || "".equals(str); - } - -// /** -// * 将给定的Map对象转换成一个XML格式的字符串。 -// * 格式为value...。 -// * -// * @param params 要转换的Map对象,键和值都是字符串类型 -// * @return 转换成的XML格式字符串 -// */ - public static String mapToXml(Map params) { - StringBuffer sb = new StringBuffer(""); - for (String key : params.keySet()) { - sb.append("<") - .append(key).append(">") - .append(params.get(key)) - .append(""); - } - sb.append(""); - return sb.toString(); - } -} diff --git a/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java b/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java deleted file mode 100644 index 14a3b64..0000000 --- a/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java +++ /dev/null @@ -1,402 +0,0 @@ -/** - * Copyright © 2015-2020 JeePlus All rights reserved. - */ -package com.yf.exam.core.utils.excel; - -import com.google.common.collect.Lists; -import com.yf.exam.core.utils.Reflections; -import com.yf.exam.core.utils.excel.annotation.ExcelField; -import org.apache.commons.lang3.StringUtils; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.Comment; -import org.apache.poi.ss.usermodel.DataFormat; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.IndexedColors; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.ss.util.CellRangeAddress; -import org.apache.poi.xssf.streaming.SXSSFWorkbook; -import org.apache.poi.xssf.usermodel.XSSFClientAnchor; -import org.apache.poi.xssf.usermodel.XSSFRichTextString; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.net.URLEncoder; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -///** -// * 导出Excel文件(导出“XLSX”格式,支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion) -// * @author jeeplus -// * @version 2016-04-21 -// */ -public class ExportExcel { - - private static Logger log = LoggerFactory.getLogger(ExportExcel.class); - -// /** -// * 工作薄对象 -// */ - private SXSSFWorkbook wb; - -// /** -// * 工作表对象 -// */ - private Sheet sheet; - -// /** -// * 样式列表 -// */ - private Map styles; - -// /** -// * 当前行号 -// */ - private int rownum; - -// /** -// * 注解列表(Object[]{ ExcelField, Field/Method }) -// */ - List annotationList = Lists.newArrayList(); - -// /** -// * 构造函数 -// * @param title 表格标题,传“空值”,表示无标题 -// * @param cls 实体对象,通过annotation.ExportField获取标题 -// */ - public ExportExcel(String title, Class cls){ - this(title, cls, 1); - } - -// /** -// * 构造函数 -// * @param title 表格标题,传“空值”,表示无标题 -// * @param cls 实体对象,通过annotation.ExportField获取标题 -// * @param type 导出类型(1:导出数据;2:导出模板) -// * @param groups 导入分组 -// */ - public ExportExcel(String title, Class cls, int type, int... groups){ - // Get annotation field - Field[] fs = cls.getDeclaredFields(); - for (Field f : fs){ - ExcelField ef = f.getAnnotation(ExcelField.class); - if (ef != null && (ef.type()==0 || ef.type()==type)){ - if (groups!=null && groups.length>0){ - boolean inGroup = false; - for (int g : groups){ - if (inGroup){ - break; - } - for (int efg : ef.groups()){ - if (g == efg){ - inGroup = true; - annotationList.add(new Object[]{ef, f}); - break; - } - } - } - }else{ - annotationList.add(new Object[]{ef, f}); - } - } - } - // Get annotation method - Method[] ms = cls.getDeclaredMethods(); - for (Method m : ms){ - ExcelField ef = m.getAnnotation(ExcelField.class); - if (ef != null && (ef.type()==0 || ef.type()==type)){ - if (groups!=null && groups.length>0){ - boolean inGroup = false; - for (int g : groups){ - if (inGroup){ - break; - } - for (int efg : ef.groups()){ - if (g == efg){ - inGroup = true; - annotationList.add(new Object[]{ef, m}); - break; - } - } - } - }else{ - annotationList.add(new Object[]{ef, m}); - } - } - } - // Field sorting - Collections.sort(annotationList, new Comparator() { - @Override - public int compare(Object[] o1, Object[] o2) { - return new Integer(((ExcelField)o1[0]).sort()).compareTo( - new Integer(((ExcelField)o2[0]).sort())); - } - }); - // Initialize - List headerList = Lists.newArrayList(); - for (Object[] os : annotationList){ - String t = ((ExcelField)os[0]).title(); - // 如果是导出,则去掉注释 - if (type==1){ - String[] ss = StringUtils.split(t, "**", 2); - if (ss.length==2){ - t = ss[0]; - } - } - headerList.add(t); - } - initialize(title, headerList); - } -// /** -// * 初始化函数 -// * @param title 表格标题,传“空值”,表示无标题 -// * @param headerList 表头列表 -// */ - private void initialize(String title, List headerList) { - this.wb = new SXSSFWorkbook(500); - this.sheet = wb.createSheet("Export"); - this.styles = createStyles(wb); - // Create title - if (StringUtils.isNotBlank(title)){ - Row titleRow = sheet.createRow(rownum++); - titleRow.setHeightInPoints(30); - Cell titleCell = titleRow.createCell(0); - titleCell.setCellStyle(styles.get("title")); - titleCell.setCellValue(title); - sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), - titleRow.getRowNum(), titleRow.getRowNum(), headerList.size()-1)); - } - // Create header - if (headerList == null){ - throw new RuntimeException("headerList not null!"); - } - Row headerRow = sheet.createRow(rownum++); - headerRow.setHeightInPoints(16); - for (int i = 0; i < headerList.size(); i++) { - Cell cell = headerRow.createCell(i); - cell.setCellStyle(styles.get("header")); - String[] ss = StringUtils.split(headerList.get(i), "**", 2); - if (ss.length==2){ - cell.setCellValue(ss[0]); - Comment comment = this.sheet.createDrawingPatriarch().createCellComment( - new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6)); - comment.setString(new XSSFRichTextString(ss[1])); - cell.setCellComment(comment); - }else{ - cell.setCellValue(headerList.get(i)); - } - sheet.autoSizeColumn(i); - } - for (int i = 0; i < headerList.size(); i++) { - int colWidth = sheet.getColumnWidth(i)*2; - sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth); - } - log.debug("Initialize success."); - } - - /** - * 创建表格样式 - * @param wb 工作薄对象 - * @return 样式列表 - */ - private Map createStyles(Workbook wb) { - Map styles = new HashMap<>(16); - - CellStyle style = wb.createCellStyle(); - style.setAlignment(CellStyle.ALIGN_CENTER); - style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); - Font titleFont = wb.createFont(); - titleFont.setFontName("Arial"); - titleFont.setFontHeightInPoints((short) 16); - titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD); - style.setFont(titleFont); - styles.put("title", style); - - style = wb.createCellStyle(); - style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); - style.setBorderRight(CellStyle.BORDER_THIN); - style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderLeft(CellStyle.BORDER_THIN); - style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderTop(CellStyle.BORDER_THIN); - style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderBottom(CellStyle.BORDER_THIN); - style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - Font dataFont = wb.createFont(); - dataFont.setFontName("Arial"); - dataFont.setFontHeightInPoints((short) 10); - style.setFont(dataFont); - styles.put("data", style); - - style = wb.createCellStyle(); - style.cloneStyleFrom(styles.get("data")); - style.setAlignment(CellStyle.ALIGN_LEFT); - styles.put("data1", style); - - style = wb.createCellStyle(); - style.cloneStyleFrom(styles.get("data")); - style.setAlignment(CellStyle.ALIGN_CENTER); - styles.put("data2", style); - - style = wb.createCellStyle(); - style.cloneStyleFrom(styles.get("data")); - style.setAlignment(CellStyle.ALIGN_RIGHT); - styles.put("data3", style); - - style = wb.createCellStyle(); - style.cloneStyleFrom(styles.get("data")); -// style.setWrapText(true); - style.setAlignment(CellStyle.ALIGN_CENTER); - style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setFillPattern(CellStyle.SOLID_FOREGROUND); - Font headerFont = wb.createFont(); - headerFont.setFontName("Arial"); - headerFont.setFontHeightInPoints((short) 10); - headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD); - headerFont.setColor(IndexedColors.WHITE.getIndex()); - style.setFont(headerFont); - styles.put("header", style); - - return styles; - } - -// /** -// * 添加一行 -// * @return 行对象 -// */ - public Row addRow(){ - return sheet.createRow(rownum++); - } - - -// /** -// * 添加一个单元格 -// * @param row 添加的行 -// * @param column 添加列号 -// * @param val 添加值 -// * @return 单元格对象 -// */ - public Cell addCell(Row row, int column, Object val){ - return this.addCell(row, column, val, 0, Class.class); - } - -// /** -// * 添加一个单元格 -// * @param row 添加的行 -// * @param column 添加列号 -// * @param val 添加值 -// * @param align 对齐方式(1:靠左;2:居中;3:靠右) -// * @return 单元格对象 -// */ - public Cell addCell(Row row, int column, Object val, int align, Class fieldType){ - Cell cell = row.createCell(column); - CellStyle style = styles.get("data"+(align>=1&&align<=3?align:"")); - try { - if (val == null){ - cell.setCellValue(""); - } else if (val instanceof String) { - cell.setCellValue((String) val); - } else if (val instanceof Integer) { - cell.setCellValue((Integer) val); - } else if (val instanceof Long) { - cell.setCellValue((Long) val); - } else if (val instanceof Double) { - cell.setCellValue((Double) val); - } else if (val instanceof Float) { - cell.setCellValue((Float) val); - } else if (val instanceof Date) { - DataFormat format = wb.createDataFormat(); - style.setDataFormat(format.getFormat("yyyy-MM-dd")); - cell.setCellValue((Date) val); - } else { - if (fieldType != Class.class){ - cell.setCellValue((String)fieldType.getMethod("setValue", Object.class).invoke(null, val)); - }else{ - cell.setCellValue((String)Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(), - "fieldtype."+val.getClass().getSimpleName()+"Type")).getMethod("setValue", Object.class).invoke(null, val)); - } - } - } catch (Exception ex) { - log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString()); - cell.setCellValue(val.toString()); - } - cell.setCellStyle(style); - return cell; - } - -// /** -// * 添加数据(通过annotation.ExportField添加数据) -// * @return list 数据列表 -// */ - public ExportExcel setDataList(List list){ - for (E e : list){ - int colunm = 0; - Row row = this.addRow(); - StringBuilder sb = new StringBuilder(); - for (Object[] os : annotationList){ - ExcelField ef = (ExcelField)os[0]; - Object val = null; - try{ - if (StringUtils.isNotBlank(ef.value())){ - val = Reflections.invokeGetter(e, ef.value()); - }else{ - if (os[1] instanceof Field){ - val = Reflections.invokeGetter(e, ((Field)os[1]).getName()); - }else if (os[1] instanceof Method){ - val = Reflections.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {}); - } - } - }catch(Exception ex) { - log.info(ex.toString()); - val = ""; - } - this.addCell(row, colunm++, val, ef.align(), ef.fieldType()); - sb.append(val + ", "); - } - log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString()); - } - return this; - } - -// /** -// * 输出数据流 -// * @param os 输出数据流 -// */ - public ExportExcel write(OutputStream os) throws IOException{ - wb.write(os); - return this; - } - -// /** -// * 输出到客户端 -// * @param fileName 输出文件名 -// */ - public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{ - response.reset(); - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setContentType("application/octet-stream; charset=utf-8"); - response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName, "utf-8")); - write(response.getOutputStream()); - return this; - } - -// /** -// * 清理临时文件 -// */ - public ExportExcel dispose(){ - wb.dispose(); - return this; - } - -} diff --git a/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java b/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java deleted file mode 100644 index d4c1fa4..0000000 --- a/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java +++ /dev/null @@ -1,303 +0,0 @@ -/** - * Copyright © 2015-2020 JeePlus All rights reserved. - */ -package com.yf.exam.core.utils.excel; - -import com.google.common.collect.Lists; -import com.yf.exam.core.utils.Reflections; -import com.yf.exam.core.utils.excel.annotation.ExcelField; -import org.apache.commons.lang3.StringUtils; -import org.apache.poi.hssf.usermodel.HSSFDateUtil; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.openxml4j.exceptions.InvalidFormatException; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.text.NumberFormat; -import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; - -///** -// * 导入Excel文件(支持“XLS”和“XLSX”格式) -// * @author jeeplus -// * @version 2016-03-10 -// */ -public class ImportExcel { - - private static Logger log = LoggerFactory.getLogger(ImportExcel.class); - -// /** -// * 工作薄对象 -// */ - private Workbook wb; - -// /** -// * 工作表对象 -// */ - private Sheet sheet; -// -// /** -// * 标题行号 -// */ - private int headerNum; - - - -// /** -// * 构造函数 -// * @param multipartFile 导入文件对象 -// * @param headerNum 标题行号,数据行号=标题行号+1 -// * @param sheetIndex 工作表编号 -// * @throws InvalidFormatException -// * @throws IOException -// */ - public ImportExcel(MultipartFile multipartFile, int headerNum, int sheetIndex) - throws InvalidFormatException, IOException { - this(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), headerNum, sheetIndex); - } - -// /** -// * 构造函数 -// * @param is 导入文件对象 -// * @param headerNum 标题行号,数据行号=标题行号+1 -// * @param sheetIndex 工作表编号 -// * @throws InvalidFormatException -// * @throws IOException -// */ - public ImportExcel(String fileName, InputStream is, int headerNum, int sheetIndex) - throws IOException { - if (StringUtils.isBlank(fileName)){ - throw new RuntimeException("导入文档为空!"); - }else if(fileName.toLowerCase().endsWith("xls")){ - this.wb = new HSSFWorkbook(is); - }else if(fileName.toLowerCase().endsWith("xlsx")){ - this.wb = new XSSFWorkbook(is); - }else{ - throw new RuntimeException("文档格式不正确!"); - } - if (this.wb.getNumberOfSheets() List getDataList(Class cls, int... groups) throws InstantiationException, IllegalAccessException{ - List annotationList = Lists.newArrayList(); - // Get annotation field - Field[] fs = cls.getDeclaredFields(); - for (Field f : fs){ - ExcelField ef = f.getAnnotation(ExcelField.class); - if (ef != null && (ef.type()==0 || ef.type()==2)){ - if (groups!=null && groups.length>0){ - boolean inGroup = false; - for (int g : groups){ - if (inGroup){ - break; - } - for (int efg : ef.groups()){ - if (g == efg){ - inGroup = true; - annotationList.add(new Object[]{ef, f}); - break; - } - } - } - }else{ - annotationList.add(new Object[]{ef, f}); - } - } - } - // Get annotation method - Method[] ms = cls.getDeclaredMethods(); - for (Method m : ms){ - ExcelField ef = m.getAnnotation(ExcelField.class); - if (ef != null && (ef.type()==0 || ef.type()==2)){ - if (groups!=null && groups.length>0){ - boolean inGroup = false; - for (int g : groups){ - if (inGroup){ - break; - } - for (int efg : ef.groups()){ - if (g == efg){ - inGroup = true; - annotationList.add(new Object[]{ef, m}); - break; - } - } - } - }else{ - annotationList.add(new Object[]{ef, m}); - } - } - } - // Field sorting - Collections.sort(annotationList, new Comparator() { - @Override - public int compare(Object[] o1, Object[] o2) { - return new Integer(((ExcelField)o1[0]).sort()).compareTo( - new Integer(((ExcelField)o2[0]).sort())); - } - }); - // Get excel data - List dataList = Lists.newArrayList(); - for (int i = this.getDataRowNum(); i < this.getLastDataRowNum(); i++) { - E e = (E)cls.newInstance(); - int column = 0; - Row row = this.getRow(i); - StringBuilder sb = new StringBuilder(); - for (Object[] os : annotationList){ - Object val = this.getCellValue(row, column++); - if (val != null){ - ExcelField ef = (ExcelField)os[0]; - // Get param type and type cast - Class valType = Class.class; - if (os[1] instanceof Field){ - valType = ((Field)os[1]).getType(); - }else if (os[1] instanceof Method){ - Method method = ((Method)os[1]); - if ("get".equals(method.getName().substring(0, 3))){ - valType = method.getReturnType(); - }else if("set".equals(method.getName().substring(0, 3))){ - valType = ((Method)os[1]).getParameterTypes()[0]; - } - } - //log.debug("Import value type: ["+i+","+column+"] " + valType); - try { - //如果导入的java对象,需要在这里自己进行变换。 - if (valType == String.class){ - String s = String.valueOf(val.toString()); - if(StringUtils.endsWith(s, ".0")){ - val = StringUtils.substringBefore(s, ".0"); - }else{ - val = String.valueOf(val.toString()); - } - }else if (valType == Integer.class){ - val = Double.valueOf(val.toString()).intValue(); - }else if (valType == Long.class){ - val = Double.valueOf(val.toString()).longValue(); - }else if (valType == Double.class){ - val = Double.valueOf(val.toString()); - }else if (valType == Float.class){ - val = Float.valueOf(val.toString()); - }else if (valType == Date.class){ - SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); - val=sdf.parse(val.toString()); - }else{ - if (ef.fieldType() != Class.class){ - val = ef.fieldType().getMethod("getValue", String.class).invoke(null, val.toString()); - }else{ - val = Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(), - "fieldtype."+valType.getSimpleName()+"Type")).getMethod("getValue", String.class).invoke(null, val.toString()); - } - } - } catch (Exception ex) { - log.info("Get cell value ["+i+","+column+"] error: " + ex.toString()); - val = null; - } - // set entity value - if (os[1] instanceof Field){ - Reflections.invokeSetter(e, ((Field)os[1]).getName(), val); - }else if (os[1] instanceof Method){ - String mthodName = ((Method)os[1]).getName(); - if ("get".equals(mthodName.substring(0, 3))){ - mthodName = "set"+StringUtils.substringAfter(mthodName, "get"); - } - Reflections.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val}); - } - } - sb.append(val+", "); - } - dataList.add(e); - log.debug("Read success: ["+i+"] "+sb.toString()); - } - return dataList; - } - -} diff --git a/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java b/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java deleted file mode 100644 index 1670f76..0000000 --- a/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright © 2015-2020 JeePlus All rights reserved. - */ -package com.yf.exam.core.utils.excel.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -///** -// * Excel注解定义 -// * @author jeeplus -// * @version 2016-03-10 -// */ -@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface ExcelField { - -// /** -// * 导出字段名(默认调用当前字段的“get”方法,如指定导出字段为对象,请填写“对象名.对象属性”,例:“area.name”、“office.name”) -// */ - String value() default ""; - -// /** -// * 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效) -// */ - String title(); - -// /** -// * 字段类型(0:导出导入;1:仅导出;2:仅导入) -// */ - int type() default 0; - -// /** -// * 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右) -// */ - int align() default 0; - -// /** -// * 导出字段字段排序(升序) -// */ - int sort() default 0; - -// /** -// * 如果是字典类型,请设置字典的type值 -// */ - String dictType() default ""; - -// /** -// * 反射类型 -// */ - Class fieldType() default Class.class; - -// /** -// * 字段归属组(根据分组导出导入) -// */ - int[] groups() default {}; -} diff --git a/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java b/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java deleted file mode 100644 index f7b4500..0000000 --- a/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright © 2015-2020 JeePlus All rights reserved. - */ -package com.yf.exam.core.utils.excel.fieldtype; - -import com.google.common.collect.Lists; -import com.yf.exam.core.utils.StringUtils; -import java.util.List; - -///** -// * 字段类型转换工具类,用于处理Excel导入导出时的List类型字段转换。 -// * -// * @author jeeplus -// * @version 2016-5-29 -// */ -public class ListType { - -// /** -// * 从字符串中获取对象值(导入)。 -// * 该方法将输入的字符串按逗号分割,并将每个分割后的子字符串添加到List中。 -// * -// * @param val 输入的字符串,格式为“item1,item2,item3,...” -// * @return 包含分割后子字符串的List对象 -// */ - public static Object getValue(String val) { - List list = Lists.newArrayList(); - if (!StringUtils.isBlank(val)) { - for (String s : val.split(",")) { - list.add(s); - } - } - return list; - } - -// /** -// * 将对象值设置为字符串(导出)。 -// * 该方法将输入的List对象中的每个元素按逗号拼接成一个字符串。 -// * -// * @param val 输入的List对象 -// * @return 拼接后的字符串,格式为“item1,item2,item3,...” -// */ - public static String setValue(Object val) { - if (val != null) { - List list = (List) val; - StringBuffer sb = null; - for (String item : list) { - if (StringUtils.isBlank(item)) { - continue; - } - if (sb == null) { - sb = new StringBuffer(item); - } else { - sb.append(",").append(item); - } - } - if (sb != null) { - return sb.toString().replace("[]", ""); - } - } - return ""; - } -} diff --git a/src/main/java/com/yf/exam/core/utils/file/Md5Util.java b/src/main/java/com/yf/exam/core/utils/file/Md5Util.java deleted file mode 100644 index 94f7a73..0000000 --- a/src/main/java/com/yf/exam/core/utils/file/Md5Util.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.yf.exam.core.utils.file; - -import java.security.MessageDigest; - -///** -// * MD5工具类,用于生成字符串的MD5哈希值。 -// * -// * @author Bool -// * @version 2018年1月13日 下午6:54:53 -// */ -public class Md5Util { - -// /** -// * 生成给定字符串的简单MD5哈希值。 -// * 该方法将输入字符串编码为UTF-8字节数组,然后使用MD5算法进行哈希计算,最后将哈希结果转换为十六进制字符串。 -// * -// * @param str 输入的字符串 -// * @return 字符串的MD5哈希值,如果发生异常则返回null -// */ - public static String md5(String str) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] array = md.digest(str.getBytes("UTF-8")); - StringBuilder sb = new StringBuilder(); - for (byte item : array) { - sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); - } - return sb.toString(); - } catch (Exception e) { - return null; - } - } -} diff --git a/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java b/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java deleted file mode 100644 index cc65066..0000000 --- a/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.yf.exam.core.utils.passwd; - - -import com.yf.exam.core.utils.file.Md5Util; -import org.apache.commons.lang3.RandomStringUtils; - -///** -// * 通用的密码处理类,用于生成密码和校验密码 -// * ClassName: PassGenerator
-// * date: 2017年12月13日 下午7:13:03
-// * -// * @author Bool -// * @version -// */ -public class PassHandler { - -// /** -// * checkPass:校验密码是否一致 -// * @author Bool -// * @param inputPass 用户传入的密码 -// * @param salt 数据库保存的密码随机码 -// * @param pass 数据库保存的密码MD5 -// * @return -// */ - public static boolean checkPass(String inputPass , String salt , String pass){ - String pwdMd5 = Md5Util.md5(inputPass); - return Md5Util.md5(pwdMd5 + salt).equals(pass); - } - - -// /** -// * -// * buildPassword:用于用户注册时产生一个密码 -// * @author Bool -// * @param inputPass 输入的密码 -// * @return PassInfo 返回一个密码对象,记得保存 -// */ - public static PassInfo buildPassword(String inputPass) { - - //产生一个6位数的随机码 - String salt = RandomStringUtils.randomAlphabetic(6); - //加密后的密码 - String encryptPassword = Md5Util.md5(Md5Util.md5(inputPass)+salt); - //返回对象 - return new PassInfo(salt,encryptPassword); - } - - - public static void main(String[] args) { - - PassInfo info = buildPassword("190601"); - - System.out.println(info.getPassword()); - System.out.println(info.getSalt()); - } - -} diff --git a/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java b/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java deleted file mode 100644 index f0ff99f..0000000 --- a/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.yf.exam.core.utils.passwd; - -///** -// * 密码实体类,用于存储和管理密码及其随机盐值。 -// * -// * @author Bool -// * @version 2018年2月13日 下午7:13:50 -// */ -public class PassInfo { - -// /** -// * 密码的随机盐值。 -// * 用于增强密码的安全性。 -// */ - private String salt; - -// /** -// * 经过MD5哈希处理后的密码。 -// * 存储最终用于验证的密码字符串。 -// */ - private String password; - -// /** -// * 构造方法,用于初始化密码实体对象。 -// * -// * @param salt 密码的随机盐值 -// * @param password 经过MD5哈希处理后的密码 -// */ - public PassInfo(String salt, String password) { - super(); - this.salt = salt; - this.password = password; - } - -// /** -// * 获取密码的随机盐值。 -// * -// * @return 密码的随机盐值 -// */ - public String getSalt() { - return salt; - } - -// /** -// * 设置密码的随机盐值。 -// * -// * @param salt 密码的随机盐值 -// */ - public void setSalt(String salt) { - this.salt = salt; - } - -// /** -// * 获取经过MD5哈希处理后的密码。 -// * -// * @return 经过MD5哈希处理后的密码 -// */ - public String getPassword() { - return password; - } - -// /** -// * 设置经过MD5哈希处理后的密码。 -// * -// * @param password 经过MD5哈希处理后的密码 -// */ - public void setPassword(String password) { - this.password = password; - } -} 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 deleted file mode 100644 index cbc57f8..0000000 --- a/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java +++ /dev/null @@ -1,151 +0,0 @@ -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; - -///** -//*

-//* 考试控制器 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-07-25 16:18 -//*/ -@Api(tags={"考试"}) -@RestController -@RequestMapping("/exam/api/exam/exam") -public class ExamController extends BaseController { - - @Autowired - 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 -// */ - @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 -// * @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 -// * @return -// */ - @RequiresRoles("sa") - @ApiOperation(value = "查找详情") - @RequestMapping(value = "/state", method = { RequestMethod.POST}) - 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()); - - baseService.update(exam, wrapper); - return super.success(); - } - - -// /** -// * 分页查找 -// * @param reqDTO -// * @return -// */ - @ApiOperation(value = "考试视角") - @RequestMapping(value = "/online-paging", method = { RequestMethod.POST}) - 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 -// */ - @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); - } - - -} 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 deleted file mode 100644 index c83a9e5..0000000 --- a/src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.yf.exam.modules.exam.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 java.io.Serializable; -import java.util.Date; - -///** -//*

-//* 考试数据传输类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-07-25 16:18 -//*/ -@Data -@ApiModel(value="考试", description="考试") -public class ExamDTO implements Serializable { - - - private static final long serialVersionUID = 1L; - - - @ApiModelProperty(value = "ID", required=true) - private String id; - - @ApiModelProperty(value = "考试名称", required=true) - private String title; - - @ApiModelProperty(value = "考试描述", required=true) - private String content; - - @ApiModelProperty(value = "1公开2部门3定员", required=true) - private Integer openType; - - @ApiModelProperty(value = "考试状态", required=true) - private Integer state; - - @ApiModelProperty(value = "是否限时", required=true) - private Boolean timeLimit; - - @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") - @DateTimeFormat(pattern = "yyyy-MM-dd") - @ApiModelProperty(value = "开始时间", required=true) - private Date startTime; - - @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; - - - - - /** - * 是否结束 - * @return - */ - 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() > startTime.getTime() - && System.currentTimeMillis() < endTime.getTime() - && !ExamState.DISABLED.equals(this.state)){ - return ExamState.ENABLE; - } - - } - - return this.state; - } -} 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 deleted file mode 100644 index fb95681..0000000 --- a/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.yf.exam.modules.exam.dto; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -import java.io.Serializable; - -///** -//*

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

-//* -//* @author 聪明笨狗 -//* @since 2020-09-03 17:24 -//*/ -@Data -@ApiModel(value="考试部门", description="考试部门") -public class ExamDepartDTO 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 departId; - -} 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 deleted file mode 100644 index 4e3905f..0000000 --- a/src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.yf.exam.modules.exam.dto; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -import java.io.Serializable; - -///** -//*

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

-//* -//* @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; - -} 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 deleted file mode 100644 index 3c0e961..0000000 --- a/src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.yf.exam.modules.exam.dto.ext; - -import com.yf.exam.modules.exam.dto.ExamRepoDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -///** -//*

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

-//* -//* @author 聪明笨狗 -//* @since 2020-09-05 11:14 -//*/ -@Data -@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类") -public class ExamRepoExtDTO extends ExamRepoDTO { - - private static final long serialVersionUID = 1L; - - - @ApiModelProperty(value = "单选题总量", required=true) - private Integer totalRadio; - - @ApiModelProperty(value = "多选题总量", required=true) - private Integer totalMulti; - - @ApiModelProperty(value = "判断题总量", required=true) - 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 deleted file mode 100644 index f7ac8da..0000000 --- a/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.yf.exam.modules.exam.dto.request; - -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 java.util.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; - -} 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 deleted file mode 100644 index 1243faa..0000000 --- a/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.yf.exam.modules.exam.dto.response; - -import com.yf.exam.modules.exam.dto.ExamDTO; -import io.swagger.annotations.ApiModel; -import lombok.Data; - - -//*

-//* 考试分页响应类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-07-25 16:18 -//*/ -@Data -@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类") -public class ExamOnlineRespDTO extends ExamDTO { - - private static final long serialVersionUID = 1L; - - -} 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 deleted file mode 100644 index 9da1b8b..0000000 --- a/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.yf.exam.modules.exam.dto.response; - -import com.yf.exam.modules.exam.dto.ExamDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -///** -//*

-//* 考试分页响应类 -//*

-//* -//* @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; - - - -} 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 deleted file mode 100644 index 1fbbba7..0000000 --- a/src/main/java/com/yf/exam/modules/exam/entity/Exam.java +++ /dev/null @@ -1,100 +0,0 @@ -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; - -///** -//*

-//* 考试实体类 -//*

-//* -//* @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; - -} 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 deleted file mode 100644 index 9c3af6b..0000000 --- a/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java +++ /dev/null @@ -1,42 +0,0 @@ -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; - -///** -//*

-//* 考试部门实体类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-09-03 17:24 -//*/ -@Data -@TableName("el_exam_depart") -public class ExamDepart extends Model { - - private static final long serialVersionUID = 1L; - -// /** -// * ID -// */ - @TableId(value = "id", type = IdType.ASSIGN_ID) - private String id; - -// /** -// * 考试ID -// */ - @TableField("exam_id") - private String examId; - -// /** -// * 部门ID -// */ - @TableField("depart_id") - private String departId; - -} 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 deleted file mode 100644 index faffa1a..0000000 --- a/src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java +++ /dev/null @@ -1,78 +0,0 @@ -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; - -///** -//*

-//* 考试题库实体类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-09-05 11:14 -//*/ -@Data -@TableName("el_exam_repo") -public class ExamRepo extends Model { - - private static final long serialVersionUID = 1L; - -// /** -// * ID -// */ - @TableId(value = "id", type = IdType.ASSIGN_ID) - private String id; - -// /** -// * 考试ID -// */ - @TableField("exam_id") - private String examId; - -// /** -// * 题库ID -// */ - @TableField("repo_id") - private String repoId; - -// /** -// * 单选题数量 -// */ - @TableField("radio_count") - private Integer radioCount; - -// /** -// * 单选题分数 -// */ - @TableField("radio_score") - private Integer radioScore; - -// /** -// * 多选题数量 -// */ - @TableField("multi_count") - private Integer multiCount; - -// /** -// * 多选题分数 -// */ - @TableField("multi_score") - private Integer multiScore; - -// /** -// * 判断题数量 -// */ - @TableField("judge_count") - private Integer judgeCount; - -// /** -// * 判断题分数 -// */ - @TableField("judge_score") - private Integer judgeScore; - -} 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 deleted file mode 100644 index 7bbc393..0000000 --- a/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.yf.exam.modules.exam.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 { - -} 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 deleted file mode 100644 index e5b1c99..0000000 --- a/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.yf.exam.modules.exam.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; - -///** -//*

-//* 考试Mapper -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-07-25 16:18 -//*/ -public interface ExamMapper extends BaseMapper { - -// /** -// * 查找分页内容 -// * @param page -// * @param query -// * @return -// */ - IPage paging(Page page, @Param("query") ExamDTO query); - -// /** -// * 查找分页内容 -// * @param page -// * @param query -// * @return -// */ - IPage reviewPaging(Page page, @Param("query") ExamDTO query); - -// /** -// * 在线考试分页响应类-考生视角 -// * @param page -// * @param query -// * @return -// */ - IPage online(Page page, @Param("query") ExamDTO query); -} 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 deleted file mode 100644 index 88a8f92..0000000 --- a/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.yf.exam.modules.exam.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 java.util.List; - -///** -//*

-//* 考试题库Mapper -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-09-05 11:14 -//*/ -public interface ExamRepoMapper extends BaseMapper { - -// /** -// * 查找考试题库列表 -// * @param examId -// * @return -// */ - List listByExam(@Param("examId") String examId); -} 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 deleted file mode 100644 index 4499374..0000000 --- a/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.yf.exam.modules.exam.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import com.yf.exam.modules.exam.entity.ExamDepart; - -import java.util.List; - -///** -//*

-//* 考试部门业务类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-09-03 17:24 -//*/ -public interface ExamDepartService extends IService { - -// /** -// * 保存全部 -// * @param examId -// * @param departs -// */ - void saveAll(String examId, List departs); - - -// /** -// * 根据考试查找对应的部门 -// * @param examId -// * @return -// */ - List listByExam(String examId); -} 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 deleted file mode 100644 index 7a8420d..0000000 --- a/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java +++ /dev/null @@ -1,40 +0,0 @@ -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 java.util.List; - -///** -//*

-//* 考试题库业务类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-09-05 11:14 -//*/ -public interface ExamRepoService extends IService { - - -// /** -// * 保存全部 -// * @param examId -// * @param list -// */ - void saveAll(String examId, List list); -// -// /** -// * 查找考试题库列表 -// * @param examId -// * @return -// */ - List listByExam(String examId); - -// /** -// * 清理脏数据 -// * @param examId -// */ - void clear(String examId); - -} 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 deleted file mode 100644 index d619d5a..0000000 --- a/src/main/java/com/yf/exam/modules/exam/service/ExamService.java +++ /dev/null @@ -1,63 +0,0 @@ -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; - -///** -//*

-//* 考试业务类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-07-25 16:18 -//*/ -public interface ExamService extends IService { - -// /** -// * 保存考试信息 -// * @param reqDTO -// */ - void save(ExamSaveReqDTO reqDTO); - -// /** -// * 查找考试详情 -// * @param id -// * @return -// */ - ExamSaveReqDTO findDetail(String id); - -// /** -// * 查找考试详情--简要信息 -// * @param id -// * @return -// */ - ExamDTO findById(String id); - -// /** -// * 分页查询数据 -// * @param reqDTO -// * @return -// */ - IPage paging(PagingReqDTO reqDTO); - - -// /** -// * 在线考试分页响应类-考生视角 -// * @param reqDTO -// * @return -// */ - IPage onlinePaging(PagingReqDTO reqDTO); - - -// * 待阅试卷列表 -// * @param reqDTO -// * @return -// */ - IPage reviewPaging(PagingReqDTO reqDTO); -} 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 deleted file mode 100644 index 0f4b20f..0000000 --- a/src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -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 java.util.ArrayList; -import java.util.List; - -///** -//*

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

-//* -//* @author 聪明笨狗 -//* @since 2020-09-03 17:24 -//*/ -@Service -public class ExamDepartServiceImpl extends ServiceImpl implements ExamDepartService { - - @Override - public void saveAll(String examId, List departs) { - - // 先删除 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(ExamDepart::getExamId, examId); - this.remove(wrapper); - - // 再增加 - if(CollectionUtils.isEmpty(departs)){ - throw new ServiceException(1, "请至少选择选择一个部门!!"); - } - List list = new ArrayList<>(); - - for(String id: departs){ - ExamDepart depart = new ExamDepart(); - depart.setDepartId(id); - depart.setExamId(examId); - list.add(depart); - } - - 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()); - } - } - - return ids; - - } -} 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 deleted file mode 100644 index 84b81c9..0000000 --- a/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java +++ /dev/null @@ -1,67 +0,0 @@ -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; - -///** -//*

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

-//* -//* @author 聪明笨狗 -//* @since 2020-09-05 11:14 -//*/ -@Service -public class ExamRepoServiceImpl extends ServiceImpl implements ExamRepoService { - - - @Transactional(rollbackFor = Exception.class) - @Override - public void saveAll(String examId, List list) { - - // 先删除 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(ExamRepo::getExamId, examId); - this.remove(wrapper); - - // 再增加 - 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()); - } - - this.saveBatch(repos); - } - - @Override - public List listByExam(String examId) { - return baseMapper.listByExam(examId); - } - - @Override - public void clear(String examId) { - - // 先删除 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(ExamRepo::getExamId, examId); - this.remove(wrapper); - } - - -} 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 deleted file mode 100644 index 3fac3c3..0000000 --- a/src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java +++ /dev/null @@ -1,194 +0,0 @@ -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; - -///** -//*

-//* 考试业务实现类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-07-25 16:18 -//*/ -@Service -public class ExamServiceImpl extends ServiceImpl implements ExamService { - - - @Autowired - private ExamRepoService examRepoService; - - @Autowired - private ExamDepartService examDepartService; - - @Override - public void save(ExamSaveReqDTO reqDTO) { - - // ID - String id = reqDTO.getId(); - - if(StringUtils.isBlank(id)){ - id = IdWorker.getIdStr(); - } - - //复制参数 - Exam entity = new Exam(); - - // 计算分值 - this.calcScore(reqDTO); - - - // 复制基本数据 - BeanMapper.copy(reqDTO, entity); - entity.setId(id); - - // 修复状态 - if (reqDTO.getTimeLimit()!=null - && !reqDTO.getTimeLimit() - && reqDTO.getState()!=null - && reqDTO.getState() == 2) { - entity.setState(0); - } else { - entity.setState(reqDTO.getState()); - } - - // 题库组卷 - try { - examRepoService.saveAll(id, reqDTO.getRepoList()); - }catch (DuplicateKeyException e){ - throw new ServiceException(1, "不能选择重复的题库!"); - } - - - // 开放的部门 - if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){ - examDepartService.saveAll(id, reqDTO.getDepartIds()); - } - - this.saveOrUpdate(entity); - - } - - @Override - public ExamSaveReqDTO findDetail(String id) { - ExamSaveReqDTO respDTO = new ExamSaveReqDTO(); - Exam exam = this.getById(id); - BeanMapper.copy(exam, respDTO); - - // 考试部门 - List departIds = examDepartService.listByExam(id); - respDTO.setDepartIds(departIds); - - // 题库 - List repos = examRepoService.listByExam(id); - respDTO.setRepoList(repos); - - return respDTO; - } - - @Override - public ExamDTO findById(String id) { - ExamDTO respDTO = new ExamDTO(); - Exam exam = this.getById(id); - BeanMapper.copy(exam, respDTO); - return respDTO; - } - - @Override - public IPage paging(PagingReqDTO reqDTO) { - - //创建分页对象 - Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); - - //转换结果 - IPage pageData = baseMapper.paging(page, reqDTO.getParams()); - return pageData; - } - - @Override - public IPage onlinePaging(PagingReqDTO reqDTO) { - - - // 创建分页对象 - Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); - - // 查找分页 - IPage pageData = baseMapper.online(page, reqDTO.getParams()); - - return pageData; - } - - @Override - public IPage reviewPaging(PagingReqDTO reqDTO) { - // 创建分页对象 - Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); - - // 查找分页 - IPage pageData = baseMapper.reviewPaging(page, reqDTO.getParams()); - - return pageData; - } - - -// /** -// * 计算分值 -// * @param 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(); - } - 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(); - } - } - - - - reqDTO.setTotalScore(objScore); - } - -} diff --git a/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java b/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java deleted file mode 100644 index 4df0c66..0000000 --- a/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.yf.exam.modules.paper.controller; - -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.BaseIdRespDTO; -import com.yf.exam.core.api.dto.BaseIdsReqDTO; -import com.yf.exam.core.api.dto.PagingReqDTO; -import com.yf.exam.core.utils.BeanMapper; -import com.yf.exam.modules.paper.dto.PaperDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; -import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; -import com.yf.exam.modules.paper.dto.request.PaperCreateReqDTO; -import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; -import com.yf.exam.modules.paper.dto.request.PaperQuQueryDTO; -import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; -import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; -import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; -import com.yf.exam.modules.paper.entity.Paper; -import com.yf.exam.modules.paper.service.PaperService; -import com.yf.exam.modules.user.UserUtils; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import org.apache.shiro.authz.annotation.RequiresRoles; -import org.springframework.beans.BeanUtils; -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; - -///** -//*

-//* 试卷控制器 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -@Api(tags={"试卷"}) -@RestController -@RequestMapping("/exam/api/paper/paper") -public class PaperController extends BaseController { - - @Autowired - private PaperService baseService; - -// /** -// * 分页查找 -// * @param reqDTO -// * @return -// */ - @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 -// */ - @ApiOperation(value = "创建试卷") - @RequestMapping(value = "/create-paper", method = { RequestMethod.POST}) - public ApiRest save(@RequestBody PaperCreateReqDTO reqDTO) { - //复制参数 - String paperId = baseService.createPaper(UserUtils.getUserId(), reqDTO.getExamId()); - return super.success(new BaseIdRespDTO(paperId)); - } - -// /** -// * 批量删除 -// * @param reqDTO -// * @return -// */ - @ApiOperation(value = "试卷详情") - @RequestMapping(value = "/paper-detail", method = { RequestMethod.POST}) - public ApiRest paperDetail(@RequestBody BaseIdReqDTO reqDTO) { - //根据ID删除 - ExamDetailRespDTO respDTO = baseService.paperDetail(reqDTO.getId()); - return super.success(respDTO); - } - -// /** -// * 批量删除 -// * @param reqDTO -// * @return -// */ - @ApiOperation(value = "试题详情") - @RequestMapping(value = "/qu-detail", method = { RequestMethod.POST}) - public ApiRest quDetail(@RequestBody PaperQuQueryDTO reqDTO) { - //根据ID删除 - PaperQuDetailDTO respDTO = baseService.findQuDetail(reqDTO.getPaperId(), reqDTO.getQuId()); - return super.success(respDTO); - } - -// /** -// * 填充答案 -// * @param reqDTO -// * @return -// */ - @ApiOperation(value = "填充答案") - @RequestMapping(value = "/fill-answer", method = { RequestMethod.POST}) - public ApiRest fillAnswer(@RequestBody PaperAnswerDTO reqDTO) { - //根据ID删除 - baseService.fillAnswer(reqDTO); - return super.success(); - } - - -// /** -// * 交卷操作 -// * @param reqDTO -// * @return -// */ - @ApiOperation(value = "交卷操作") - @RequestMapping(value = "/hand-exam", method = { RequestMethod.POST}) - public ApiRest handleExam(@RequestBody BaseIdReqDTO reqDTO) { - //根据ID删除 - baseService.handExam(reqDTO.getId()); - return super.success(); - } - - -// /** -// * 批量删除 -// * @param reqDTO -// * @return -// */ - @ApiOperation(value = "试卷详情") - @RequestMapping(value = "/paper-result", method = { RequestMethod.POST}) - public ApiRest paperResult(@RequestBody BaseIdReqDTO reqDTO) { - //根据ID删除 - ExamResultRespDTO respDTO = baseService.paperResult(reqDTO.getId()); - return super.success(respDTO); - } - - -///** -// * 检测用户当前是否有进行中的考试 -// * 该方法会调用基础服务中的checkProcess方法来获取用户的考试状态 -// * @return ApiRest 包含用户进行中考试信息的ApiRest对象,如果没有进行中的考试,则PaperDTO为null -// */ -@ApiOperation(value = "检测进行中的考试") -@RequestMapping(value = "/check-process", method = { RequestMethod.POST}) -public ApiRest checkProcess() { - // 调用基础服务的checkProcess方法,传入当前用户ID,获取用户进行中的考试信息 - PaperDTO dto = baseService.checkProcess(UserUtils.getUserId()); - // 返回一个包含用户进行中考试信息的成功响应 - return super.success(dto); -} - -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java deleted file mode 100644 index 42dcde3..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.yf.exam.modules.paper.dto; - -import com.yf.exam.core.annon.Dict; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.io.Serializable; -import java.util.Date; - -///** -// * 试卷请求类 -// * 该类用于表示试卷的详细信息 -// * -// * @author 聪明笨狗 -// * @since 2020-05-25 17:31 -// */ -@Data -@ApiModel(value = "试卷", description = "用于表示试卷详细信息的DTO类") -public class PaperDTO implements Serializable { -// /** -// * 序列化版本UID -// * 用于序列化和反序列化操作,保持版本一致性 -// */ - private static final long serialVersionUID = 1L; - -// /** -// * 试卷ID -// * 该字段是必填的,表示试卷的唯一标识符 -// */ - @ApiModelProperty(value = "试卷ID", required = true) - private String id; - -// /** -// * 用户ID -// * 该字段是必填的,表示试卷所属用户的唯一标识符 -// * 使用Dict注解关联sys_user表,根据id获取real_name -// */ - @Dict(dictTable = "sys_user", dicText = "real_name", dicCode = "id") - @ApiModelProperty(value = "用户ID", required = true) - private String userId; - -// /** -// * 部门ID -// * 该字段是必填的,表示试卷所属部门的唯一标识符 -// * 使用Dict注解关联sys_depart表,根据id获取dept_name -// */ - @Dict(dictTable = "sys_depart", dicText = "dept_name", dicCode = "id") - @ApiModelProperty(value = "部门ID", required = true) - private String departId; - -// /** -// * 规则ID -// * 该字段是必填的,表示试卷的考试规则的唯一标识符 -// */ - @ApiModelProperty(value = "规则ID", required = true) - private String examId; - -// /** -// * 考试标题 -// * 该字段是必填的,表示试卷的标题 -// */ - @ApiModelProperty(value = "考试标题", required = true) - private String title; - -// /** -// * 考试时长 -// * 该字段是必填的,表示试卷的考试总时长(分钟) -// */ - @ApiModelProperty(value = "考试时长", required = true) - private Integer totalTime; - -// /** -// * 用户时长 -// * 该字段是必填的,表示用户实际完成考试所用的时间(分钟) -// */ - @ApiModelProperty(value = "用户时长", required = true) - private Integer userTime; - -// /** -// * 试卷总分 -// * 该字段是必填的,表示试卷的总分值 -// */ - @ApiModelProperty(value = "试卷总分", required = true) - private Integer totalScore; - -// /** -// * 及格分 -// * 该字段是必填的,表示试卷的及格分数线 -// */ - @ApiModelProperty(value = "及格分", required = true) - private Integer qualifyScore; - -// /** -// * 客观分 -// * 该字段是必填的,表示试卷中客观题的总分值 -// */ - @ApiModelProperty(value = "客观分", required = true) - private Integer objScore; - -// /** -// * 主观分 -// * 该字段是必填的,表示试卷中主观题的总分值 -// */ - @ApiModelProperty(value = "主观分", required = true) - private Integer subjScore; - -// /** -// * 用户得分 -// * 该字段是必填的,表示用户在试卷中的实际得分 -// */ - @ApiModelProperty(value = "用户得分", required = true) - private Integer userScore; - -// /** -// * 是否包含简答题 -// * 该字段是必填的,表示试卷中是否包含简答题 -// */ - @ApiModelProperty(value = "是否包含简答题", required = true) - private Boolean hasSaq; - -// /** -// * 试卷状态 -// * 该字段是必填的,表示试卷的状态 -// */ - @ApiModelProperty(value = "试卷状态", required = true) - private Integer state; - -// /** -// * 创建时间 -// * 该字段是必填的,表示试卷的创建时间 -// */ - @ApiModelProperty(value = "创建时间", required = true) - private Date createTime; - -// /** -// * 更新时间 -// * 该字段是必填的,表示试卷的最后更新时间 -// */ - @ApiModelProperty(value = "更新时间", required = true) - private Date updateTime; - -// /** -// * 截止时间 -// * 该字段表示试卷的截止提交时间 -// */ - @ApiModelProperty(value = "截止时间") - private Date limitTime; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java deleted file mode 100644 index 9d2b0c5..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.yf.exam.modules.paper.dto; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.io.Serializable; - -///** -// * 试卷考题备选答案请求类 -// * 该类用于表示试卷考题的备选答案信息 -// * -// * @author 聪明笨狗 -// * @since 2020-05-25 17:31 -// */ -@Data -@ApiModel(value = "试卷考题备选答案", description = "用于表示试卷考题备选答案的DTO类") -public class PaperQuAnswerDTO implements Serializable { -// /** -// * 序列化版本UID -// * 用于序列化和反序列化操作,保持版本一致性 -// */ - private static final long serialVersionUID = 1L; - -// /** -// * 自增ID -// * 该字段是必填的,表示备选答案的唯一标识符 -// */ - @ApiModelProperty(value = "自增ID", required = true) - private String id; - -// /** -// * 试卷ID -// * 该字段是必填的,表示该备选答案所属试卷的唯一标识符 -// */ - @ApiModelProperty(value = "试卷ID", required = true) - private String paperId; - -// /** -// * 回答项ID -// * 该字段是必填的,表示该备选答案项的唯一标识符 -// */ - @ApiModelProperty(value = "回答项ID", required = true) - private String answerId; - -// /** -// * 题目ID -// * 该字段是必填的,表示该备选答案所属题目的唯一标识符 -// */ - @ApiModelProperty(value = "题目ID", required = true) - private String quId; - -// /** -// * 是否正确项 -// * 该字段是必填的,表示该备选答案是否为正确答案 -// */ - @ApiModelProperty(value = "是否正确项", required = true) - private Boolean isRight; - -// /** -// * 是否选中 -// * 该字段是必填的,表示该备选答案是否被用户选中 -// */ - @ApiModelProperty(value = "是否选中", required = true) - private Boolean checked; - -// /** -// * 排序 -// * 该字段是必填的,表示该备选答案在选项列表中的排序顺序 -// */ - @ApiModelProperty(value = "排序", required = true) - private Integer sort; - -// /** -// * 选项标签 -// * 该字段是必填的,表示该备选答案的选项标签(例如:A, B, C) -// */ - @ApiModelProperty(value = "选项标签", required = true) - private String abc; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java deleted file mode 100644 index 4c9f3b4..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.yf.exam.modules.paper.dto; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.io.Serializable; - -///** -// * 试卷考题请求类 -// * 该类用于表示试卷中考题的详细信息 -// * -// * @author 聪明笨狗 -// * @since 2020-05-25 17:31 -// */ -@Data -@ApiModel(value = "试卷考题", description = "用于表示试卷中考题详细信息的DTO类") -public class PaperQuDTO implements Serializable { -// /** -// * 序列化版本UID -// * 用于序列化和反序列化操作,保持版本一致性 -// */ - private static final long serialVersionUID = 1L; - -// /** -// * ID -// * 该字段是必填的,表示考题的唯一标识符 -// */ - @ApiModelProperty(value = "ID", required = true) - private String id; - -// /** -// * 试卷ID -// * 该字段是必填的,表示该考题所属试卷的唯一标识符 -// */ - @ApiModelProperty(value = "试卷ID", required = true) - private String paperId; - -// /** -// * 题目ID -// * 该字段是必填的,表示该考题本身的唯一标识符 -// */ - @ApiModelProperty(value = "题目ID", required = true) - private String quId; - -// /** -// * 题目类型 -// * 该字段是必填的,表示该考题的类型(例如:单选题、多选题、简答题等) -// */ - @ApiModelProperty(value = "题目类型", required = true) - private Integer quType; - -// /** -// * 是否已答 -// * 该字段是必填的,表示该考题是否已经被用户作答 -// */ - @ApiModelProperty(value = "是否已答", required = true) - private Boolean answered; - -// /** -// * 主观答案 -// * 该字段是必填的,表示用户对于主观题的回答内容 -// */ - @ApiModelProperty(value = "主观答案", required = true) - private String answer; - -// /** -// * 问题排序 -// * 该字段是必填的,表示该考题在试卷中的排序顺序 -// */ - @ApiModelProperty(value = "问题排序", required = true) - private Integer sort; - -// /** -// * 单题分分值 -// * 该字段是必填的,表示该考题的分值 -// */ - @ApiModelProperty(value = "单题分分值", required = true) - private Integer score; -// -// /** -// * 实际得分(主观题) -// * 该字段是必填的,表示用户在主观题上实际获得的得分 -// */ - @ApiModelProperty(value = "实际得分(主观题)", required = true) - private Integer actualScore; - -// /** -// * 是否答对 -// * 该字段是必填的,表示用户对于该考题的回答是否正确 -// */ - @ApiModelProperty(value = "是否答对", required = true) - private Boolean isRight; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java deleted file mode 100644 index 70af226..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.yf.exam.modules.paper.dto.ext; - -import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -///** -// *

-// * 扩展的试卷考题备选答案请求类 -// * 该类继承自PaperQuAnswerDTO,增加了试题图片和答案内容的字段 -// *

-// * -// * @author 聪明笨狗 -// * @since 2020-05-25 17:31 -// */ -@Data -@ApiModel(value = "试卷考题备选答案", description = "扩展的试卷考题备选答案请求类,包含试题图片和答案内容") -public class PaperQuAnswerExtDTO extends PaperQuAnswerDTO { - private static final long serialVersionUID = 1L; - -// /** -// * 试题图片的URL或路径 -// * 该字段是必填的,表示试题对应的图片 -// */ - @ApiModelProperty(value = "试题图片", required = true) - private String image; - -// /** -// * 答案内容 -// * 该字段是必填的,表示试题的答案内容 -// */ - @ApiModelProperty(value = "答案内容", required = true) - private String content; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java deleted file mode 100644 index ab7797a..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.yf.exam.modules.paper.dto.ext; - -import com.yf.exam.modules.paper.dto.PaperQuDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.util.List; - -///** -// *

-// * 扩展的试卷考题请求类 -// * 该类继承自PaperQuDTO,增加了图片、题目内容和答案内容列表的字段 -// *

-// * -// * @author 聪明笨狗 -// * @since 2020-05-25 17:31 -// */ -@Data -@ApiModel(value = "试卷题目详情类", description = "扩展的试卷题目详情类,包含图片、题目内容和答案内容列表") -public class PaperQuDetailDTO extends PaperQuDTO { - private static final long serialVersionUID = 1L; - -// /** -// * 题目的图片URL或路径 -// * 该字段是必填的,表示题目的图片 -// */ - @ApiModelProperty(value = "图片", required = true) - private String image; - -// /** -// * 题目的内容 -// * 该字段是必填的,表示题目的详细内容 -// */ - @ApiModelProperty(value = "题目内容", required = true) - private String content; - -// /** -// * 答案内容列表 -// * 该字段是必填的,表示题目的所有答案内容 -// */ - @ApiModelProperty(value = "答案内容", required = true) - List answerList; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java deleted file mode 100644 index 91e735f..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.yf.exam.modules.paper.dto.request; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.util.List; - -///** -// * 查找试卷题目详情请求类 -// * 该类继承自PaperQuQueryDTO,并添加了回答列表和主观答案的字段 -// * -// * @author bool -// */ -@Data -@ApiModel(value = "查找试卷题目详情请求类", description = "用于请求查找试卷题目详情的DTO类") -public class PaperAnswerDTO extends PaperQuQueryDTO { -// /** -// * 回答列表 -// * 该字段是必填的,包含用户对每个题目的回答 -// */ - @ApiModelProperty(value = "回答列表", required = true) - private List answers; - -// /** -// * 主观答案 -// * 该字段是必填的,包含用户对主观题目的回答 -// */ - @ApiModelProperty(value = "主观答案", required = true) - private String answer; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java deleted file mode 100644 index b723c54..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.yf.exam.modules.paper.dto.request; - -import com.yf.exam.core.api.dto.BaseDTO; -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -///** -// * 试卷创建请求类 -// * 该类继承自BaseDTO,用于创建试卷的请求参数 -// * -// * @author bool -// */ -@Data -@ApiModel(value = "试卷创建请求类", description = "用于请求创建试卷的DTO类") -public class PaperCreateReqDTO extends BaseDTO { -// /** -// * 用户ID -// * 该字段在JSON序列化时被忽略,用于内部处理 -// */ - @JsonIgnore - private String userId; - -// /** -// * 考试ID -// * 该字段是必填的,表示创建试卷所属的考试ID -// */ - @ApiModelProperty(value = "考试ID", required = true) - private String examId; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java deleted file mode 100644 index 940fd17..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.yf.exam.modules.paper.dto.request; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.io.Serializable; - -///** -// *

-// * 试卷列表请求类 -// * 该类用于请求试卷列表的参数 -// *

-// * -// * @author 聪明笨狗 -// * @since 2020-05-25 17:31 -// */ -@Data -@ApiModel(value = "试卷列表请求类", description = "用于请求试卷列表的DTO类") -public class PaperListReqDTO implements Serializable { - private static final long serialVersionUID = 1L; - -// /** -// * 用户ID -// * 该字段是必填的,表示请求试卷列表的用户ID -// */ - @ApiModelProperty(value = "用户ID", required = true) - private String userId; - -// /** -// * 部门ID -// * 该字段是必填的,表示请求试卷列表的部门ID -// */ - @ApiModelProperty(value = "部门ID", required = true) - private String departId; - -// /** -// * 规则ID -// * 该字段是必填的,表示请求试卷列表的规则ID(可能是考试ID) -// */ - @ApiModelProperty(value = "规则ID", required = true) - private String examId; - -// /** -// * 用户昵称 -// * 该字段是必填的,表示请求试卷列表的用户昵称 -// */ - @ApiModelProperty(value = "用户昵称", required = true) - private String realName; - -// /** -// * 试卷状态 -// * 该字段是必填的,表示请求试卷列表的试卷状态 -// */ - @ApiModelProperty(value = "试卷状态", required = true) - private Integer state; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java deleted file mode 100644 index ed94b54..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.yf.exam.modules.paper.dto.request; - -import com.yf.exam.core.api.dto.BaseDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -///** -// * 查找试卷题目详情请求类 -// * 该类继承自BaseDTO,用于请求查找试卷中某个题目的详情信息 -// * -// * @author bool -// */ -@Data -@ApiModel(value = "查找试卷题目详情请求类", description = "用于请求查找试卷中某个题目的详情信息的DTO类") -public class PaperQuQueryDTO extends BaseDTO { -// /** -// * 试卷ID -// * 该字段是必填的,表示要查找题目的试卷ID -// */ - @ApiModelProperty(value = "试卷ID", required = true) - private String paperId; - -// /** -// * 题目ID -// * 该字段是必填的,表示要查找的题目ID -// */ - @ApiModelProperty(value = "题目ID", required = true) - private String quId; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java deleted file mode 100644 index 5f351f3..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.yf.exam.modules.paper.dto.response; - -import com.yf.exam.modules.paper.dto.PaperDTO; -import com.yf.exam.modules.paper.dto.PaperQuDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.util.Calendar; -import java.util.List; - -///** -// * 考试详情响应类 -// * 该类继承自PaperDTO,用于返回考试的详细信息 -// * -// * @author 聪明笨狗 -// */ -@Data -@ApiModel(value = "考试详情", description = "用于返回考试详细信息的DTO类") -public class ExamDetailRespDTO extends PaperDTO { -// /** -// * 单选题列表 -// * 该字段是必填的,表示考试中的单选题列表 -// */ - @ApiModelProperty(value = "单选题列表", required = true) - private List radioList; - -// /** -// * 多选题列表 -// * 该字段是必填的,表示考试中的多选题列表 -// */ - @ApiModelProperty(value = "多选题列表", required = true) - private List multiList; - -// /** -// * 判断题列表 -// * 该字段是必填的,表示考试中的判断题列表 -// */ - @ApiModelProperty(value = "判断题列表", required = true) - private List judgeList; - -// /** -// * 获取剩余结束秒数 -// * 该方法计算并返回考试剩余的结束时间(秒) -// * -// * @return 剩余的秒数 -// */ - @ApiModelProperty(value = "剩余结束秒数", required = true) - public Long getLeftSeconds() { - // 创建日历实例并设置为当前考试的创建时间 - Calendar cl = Calendar.getInstance(); - cl.setTime(this.getCreateTime()); - // 添加考试的总时间(以分钟为单位) - cl.add(Calendar.MINUTE, getTotalTime()); - // 计算剩余时间的秒数 - return (cl.getTimeInMillis() - System.currentTimeMillis()) / 1000; - } -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java deleted file mode 100644 index d0d27c5..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.yf.exam.modules.paper.dto.response; - -import com.yf.exam.modules.paper.dto.PaperDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.util.List; - -///** -// * 考试结果展示响应类 -// * 该类继承自PaperDTO,用于返回考试结果的详细信息 -// * -// * @author 聪明笨狗 -// */ -@Data -@ApiModel(value = "考试结果展示响应类", description = "用于返回考试结果详细信息的DTO类") -public class ExamResultRespDTO extends PaperDTO { -// /** -// * 问题列表 -// * 该字段是必填的,表示考试中的问题列表详细信息 -// */ - @ApiModelProperty(value = "问题列表", required = true) - private List quList; -} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java deleted file mode 100644 index 930339d..0000000 --- a/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.yf.exam.modules.paper.dto.response; - -import com.yf.exam.modules.paper.dto.PaperDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -///** -// * 试卷列表响应类 -// * 该类继承自PaperDTO,用于返回试卷列表的详细信息 -// * -// * @author 聪明笨狗 -// * @since 2020-05-25 17:31 -// */ -@Data -@ApiModel(value = "试卷列表响应类", description = "用于返回试卷列表详细信息的DTO类") -public class PaperListRespDTO extends PaperDTO { -// /** -// * 序列化版本UID -// * 用于序列化和反序列化操作,保持版本一致性 -// */ - private static final long serialVersionUID = 1L; -// -// /** -// * 人员姓名 -// * 该字段是必填的,表示试卷对应的人员姓名 -// */ - @ApiModelProperty(value = "人员姓名", required = true) - private String realName; -} diff --git a/src/main/java/com/yf/exam/modules/paper/entity/Paper.java b/src/main/java/com/yf/exam/modules/paper/entity/Paper.java deleted file mode 100644 index 852295b..0000000 --- a/src/main/java/com/yf/exam/modules/paper/entity/Paper.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.yf.exam.modules.paper.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; - -///** -//*

-//* 试卷实体类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 17:31 -//*/ -@Data -@TableName("el_paper") -public class Paper extends Model { - - private static final long serialVersionUID = 1L; -// -// /** -// * 试卷ID -// */ - @TableId(value = "id", type = IdType.ASSIGN_ID) - private String id; -// -// /** -// * 用户ID -// */ - @TableField("user_id") - private String userId; - -// /** -// * 部门ID -// */ - @TableField("depart_id") - private String departId; - -// /** -// * 规则ID -// */ - @TableField("exam_id") - private String examId; - -// /** -// * 考试标题 -// */ - private String title; -// -// /** -// * 考试时长 -// */ - @TableField("total_time") - private Integer totalTime; - -// /** -// * 用户时长 -// */ - @TableField("user_time") - private Integer userTime; - -// /** -// * 试卷总分 -// */ - @TableField("total_score") - private Integer totalScore; -// -// /** -// * 及格分 -// */ - @TableField("qualify_score") - private Integer qualifyScore; - -// /** -// * 客观分 -// */ - @TableField("obj_score") - private Integer objScore; - -// /** -// * 主观分 -// */ - @TableField("subj_score") - private Integer subjScore; - -// /** -// * 用户得分 -// */ - @TableField("user_score") - private Integer userScore; - -// /** -// * 是否包含简答题 -// */ - @TableField("has_saq") - private Boolean hasSaq; - -// /** -// * 试卷状态 -// */ - private Integer state; - -// /** -// * 创建时间 -// */ - @TableField("create_time") - private Date createTime; -// -// /** -// * 更新时间 -// */ - @TableField("update_time") - private Date updateTime; - -// /** -// * 截止时间 -// */ - @TableField("limit_time") - private Date limitTime; -} diff --git a/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java b/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java deleted file mode 100644 index 4b83b1b..0000000 --- a/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.yf.exam.modules.paper.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; -// -///** -//*

-//* 试卷考题实体类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 17:31 -//*/ -@Data -@TableName("el_paper_qu") -public class PaperQu extends Model { - - private static final long serialVersionUID = 1L; -// -// /** -// * ID -// */ - @TableId(value = "id", type = IdType.ASSIGN_ID) - private String id; -// -// /** -// * 试卷ID -// */ - @TableField("paper_id") - private String paperId; - -// /** -// * 题目ID -// */ - @TableField("qu_id") - private String quId; - -// /** -// * 题目类型 -// */ - @TableField("qu_type") - private Integer quType; - -// /** -// * 是否已答 -// */ - private Boolean answered; - -// /** -// * 主观答案 -// */ - private String answer; -// -// /** -// * 问题排序 -// */ - private Integer sort; -// -// /** -// * 单题分分值 -// */ - private Integer score; - -// /** -// * 实际得分(主观题) -// */ - @TableField("actual_score") - private Integer actualScore; -// -// /** -// * 是否答对 -// */ - @TableField("is_right") - private Boolean isRight; - -} diff --git a/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java b/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java deleted file mode 100644 index f119da8..0000000 --- a/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.yf.exam.modules.paper.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; - -///** -//*

-//* 试卷考题备选答案实体类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 17:31 -//*/ -@Data -@TableName("el_paper_qu_answer") -public class PaperQuAnswer extends Model { - - -// /** -// * 自增ID -// */ - @TableId(value = "id", type = IdType.ASSIGN_ID) - private String id; - -// /** -// * 试卷ID -// */ - @TableField("paper_id") - private String paperId; - -// /** -// * 回答项ID -// */ - @TableField("answer_id") - private String answerId; - -// /** -// * 题目ID -// */ - @TableField("qu_id") - private String quId; - -// /** -// * 是否正确项 -// */ - @TableField("is_right") - private Boolean isRight; -// -// /** -// * 是否选中 -// */ - private Boolean checked; - -// /** -// * 排序 -// */ - private Integer sort; - -// /** -// * 选项标签 -// */ - private String abc; - -} diff --git a/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java b/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java deleted file mode 100644 index c85db62..0000000 --- a/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.yf.exam.modules.paper.enums; - - -///** -// * 考试状态 -// * @author bool -// * @date 2019-10-30 13:11 -// */ -public interface ExamState { - - -// /** -// * 考试中 -// */ - Integer ENABLE = 0; -// -// /** -// * 待阅卷 -// */ - Integer DISABLED = 1; - -// /** -// * 已完成 -// */ - Integer READY_START = 2; -// -// /** -// * 已结束 -// */ - Integer OVERDUE = 3; - - -} diff --git a/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java b/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java deleted file mode 100644 index 17f83d5..0000000 --- a/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.yf.exam.modules.paper.enums; - - -///** -// * 试卷状态 -// * @author bool -// * @date 2019-10-30 13:11 -// */ -public interface PaperState { - -// -// /** -// * 考试中 -// */ - Integer ING = 0; -// -// /** -// * 待阅卷 -// */ - Integer WAIT_OPT = 1; -// -// /** -// * 已完成 -// */ - Integer FINISHED = 2; - -// /** -// * 弃考 -// */ - Integer BREAK = 3; - - -} diff --git a/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java b/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java deleted file mode 100644 index 075d664..0000000 --- a/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.yf.exam.modules.paper.job; - -import com.yf.exam.ability.job.service.JobService; -import com.yf.exam.modules.paper.service.PaperService; -import lombok.extern.log4j.Log4j2; -import org.quartz.Job; -import org.quartz.JobDetail; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -///** -// * 超时自动交卷任务 -// * 该类实现了一个Quartz Job,用于在考试时间到期时自动提交试卷 -// * -// * @author bool -// */ -@Log4j2 -@Component -public class BreakExamJob implements Job { -// /** -// * 自动装配PaperService -// * 用于处理试卷相关的业务逻辑 -// */ - @Autowired - private PaperService paperService; - -// /** -// * 执行任务的方法 -// * 该方法会在Quartz调度器调用时执行,处理到期的交卷操作 -// * -// * @param jobExecutionContext 任务执行上下文 -// * @throws JobExecutionException 如果任务执行过程中发生异常,将抛出此异常 -// */ - @Override - public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { - // 获取任务详情 - JobDetail detail = jobExecutionContext.getJobDetail(); - - // 获取任务名称和组 - String name = detail.getKey().getName(); - String group = detail.getKey().getGroup(); - - // 从任务数据映射中获取任务数据 - String data = String.valueOf(detail.getJobDataMap().get(JobService.TASK_DATA)); - - // 记录日志信息 - log.info("++++++++++定时任务:处理到期的交卷"); - log.info("++++++++++jobName:{}", name); - log.info("++++++++++jobGroup:{}", group); - log.info("++++++++++taskData:{}", data); - - // 调用PaperService的handExam方法,强制交卷 - paperService.handExam(data); - } -} diff --git a/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java b/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java deleted file mode 100644 index f2bb96c..0000000 --- a/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.yf.exam.modules.paper.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.paper.dto.PaperDTO; -import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; -import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; -import com.yf.exam.modules.paper.entity.Paper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -///** -//*

-//* 试卷Mapper -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -public interface PaperMapper extends BaseMapper { - -// /** -// * 查找试卷分页 -// * @param page -// * @param query -// * @return -// */ - IPage paging(Page page, @Param("query") PaperListReqDTO query); - - -// /** -// * 试卷列表响应类 -// * @param query -// * @return -// */ - List list(@Param("query") PaperDTO query); -} diff --git a/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java b/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java deleted file mode 100644 index ba41dfa..0000000 --- a/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.yf.exam.modules.paper.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; -import com.yf.exam.modules.paper.entity.PaperQuAnswer; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -///** -//*

-//* 试卷考题备选答案Mapper -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -public interface PaperQuAnswerMapper extends BaseMapper { - -// /** -// * 查找试卷试题答案列表 -// * @param paperId -// * @param quId -// * @return -// */ - List list(@Param("paperId") String paperId, @Param("quId") String quId); -} diff --git a/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java b/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java deleted file mode 100644 index 43b3c4f..0000000 --- a/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.yf.exam.modules.paper.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; -import com.yf.exam.modules.paper.entity.PaperQu; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -///** -//*

-//* 试卷考题Mapper -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -public interface PaperQuMapper extends BaseMapper { - -// /** -// * 统计客观分 -// * @param paperId -// * @return -// */ - int sumObjective(@Param("paperId") String paperId); -// -// /** -// * 统计主观分 -// * @param paperId -// * @return -// */ - int sumSubjective(@Param("paperId") String paperId); -// -// /** -// * 找出全部试题列表 -// * @param paperId -// * @return -// */ - List listByPaper(@Param("paperId") String paperId); -} - - diff --git a/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java b/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java deleted file mode 100644 index 01aeb09..0000000 --- a/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.yf.exam.modules.paper.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.paper.dto.PaperQuAnswerDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; -import com.yf.exam.modules.paper.entity.PaperQuAnswer; - -import java.util.List; - -///** -//*

-//* 试卷考题备选答案业务类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -public interface PaperQuAnswerService extends IService { - -// /** -// * 分页查询数据 -// * @param reqDTO -// * @return -// */ - IPage paging(PagingReqDTO reqDTO); - -// /** -// * 查找试卷试题答案列表 -// * @param paperId -// * @param quId -// * @return -// */ - List listForExam(String paperId, String quId); - -// /** -// * 查找答案列表,用来填充 -// * @param paperId -// * @param quId -// * @return -// */ - List listForFill(String paperId, String quId); -} diff --git a/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java b/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java deleted file mode 100644 index 6fa4f58..0000000 --- a/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.yf.exam.modules.paper.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.paper.dto.PaperQuDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; -import com.yf.exam.modules.paper.entity.PaperQu; - -import java.util.List; - -///** -//*

-//* 试卷考题业务类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -public interface PaperQuService extends IService { - -// /** -// * 分页查询数据 -// * @param reqDTO -// * @return -// */ - IPage paging(PagingReqDTO reqDTO); - -// /** -// * 根据试卷找出题目列表 -// * @param paperId -// * @return -// */ - List listByPaper(String paperId); - -// /** -// * 查找详情 -// * @param paperId -// * @param quId -// * @return -// */ - PaperQu findByKey(String paperId, String quId); -// -// /** -// * 根据组合索引更新 -// * @param qu -// */ - void updateByKey(PaperQu qu); - -// /** -// * 统计客观分 -// * @param paperId -// * @return -// */ - int sumObjective(String paperId); -// -// /** -// * 统计主观分 -// * @param paperId -// * @return -// */ - int sumSubjective(String paperId); - -// /** -// * 找出全部试题列表 -// * @param paperId -// * @return -// */ - List listForPaperResult(String paperId); -} diff --git a/src/main/java/com/yf/exam/modules/paper/service/PaperService.java b/src/main/java/com/yf/exam/modules/paper/service/PaperService.java deleted file mode 100644 index 2555044..0000000 --- a/src/main/java/com/yf/exam/modules/paper/service/PaperService.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.yf.exam.modules.paper.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.paper.dto.PaperDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; -import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; -import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; -import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; -import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; -import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; -import com.yf.exam.modules.paper.entity.Paper; - -///** -//*

-//* 试卷业务类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -public interface PaperService extends IService { - -// /** -// * 创建试卷 -// * @param userId -// * @param examId -// * @return -// */ - String createPaper(String userId, String examId); - - -// /** -// * 查找详情 -// * @param paperId -// * @return -// */ - ExamDetailRespDTO paperDetail(String paperId); - -// /** -// * 考试结果 -// * @param paperId -// * @return -// */ - ExamResultRespDTO paperResult(String paperId); - -// /** -// * 查找题目详情 -// * @param paperId -// * @param quId -// * @return -// */ - PaperQuDetailDTO findQuDetail(String paperId, String quId); - -// /** -// * 填充答案 -// * @param reqDTO -// */ - void fillAnswer(PaperAnswerDTO reqDTO); - -// /** -// * 交卷操作 -// * @param paperId -// * @return -// */ - void handExam(String paperId); - -// /** -// * 试卷列表响应类 -// * @param reqDTO -// * @return -// */ - IPage paging(PagingReqDTO reqDTO); - -// /** -// * 检测是否有进行中的考试 -// * @param userId -// * @return -// */ - PaperDTO checkProcess(String userId); - -} diff --git a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java deleted file mode 100644 index 86e02c7..0000000 --- a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.yf.exam.modules.paper.service.impl; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.TypeReference; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -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.modules.paper.dto.PaperQuAnswerDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; -import com.yf.exam.modules.paper.entity.PaperQuAnswer; -import com.yf.exam.modules.paper.mapper.PaperQuAnswerMapper; -import com.yf.exam.modules.paper.service.PaperQuAnswerService; -import org.springframework.stereotype.Service; - -import java.util.List; - -///** -//*

-//* 语言设置 服务实现类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -@Service -public class PaperQuAnswerServiceImpl extends ServiceImpl implements PaperQuAnswerService { - - @Override - public IPage paging(PagingReqDTO reqDTO) { - - //创建分页对象 - IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); - - //查询条件 - QueryWrapper wrapper = new QueryWrapper<>(); - - //获得数据 - IPage page = this.page(query, wrapper); - //转换结果 - IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); - return pageData; - } - - @Override - public List listForExam(String paperId, String quId) { - return baseMapper.list(paperId, quId); - } - - @Override - public List listForFill(String paperId, String quId) { - //查询条件 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda() - .eq(PaperQuAnswer::getPaperId, paperId) - .eq(PaperQuAnswer::getQuId, quId); - - return this.list(wrapper); - } -} diff --git a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java deleted file mode 100644 index 72ecab2..0000000 --- a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.yf.exam.modules.paper.service.impl; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.TypeReference; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -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.utils.BeanMapper; -import com.yf.exam.modules.paper.dto.PaperQuDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; -import com.yf.exam.modules.paper.entity.PaperQu; -import com.yf.exam.modules.paper.mapper.PaperQuMapper; -import com.yf.exam.modules.paper.service.PaperQuService; -import org.springframework.stereotype.Service; - -import java.util.List; - -///** -//*

-//* 语言设置 服务实现类 -//*

-//* -//* @author 聪明笨狗 -//* @since 2020-05-25 16:33 -//*/ -@Service -public class PaperQuServiceImpl extends ServiceImpl implements PaperQuService { - - @Override - public IPage paging(PagingReqDTO reqDTO) { - - //创建分页对象 - IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); - - //查询条件 - QueryWrapper wrapper = new QueryWrapper<>(); - - //获得数据 - IPage page = this.page(query, wrapper); - //转换结果 - IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); - return pageData; - } - - @Override - public List listByPaper(String paperId) { - - //查询条件 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(PaperQu::getPaperId, paperId) - .orderByAsc(PaperQu::getSort); - - List list = this.list(wrapper); - return BeanMapper.mapList(list, PaperQuDTO.class); - } - - @Override - public PaperQu findByKey(String paperId, String quId) { - //查询条件 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(PaperQu::getPaperId, paperId) - .eq(PaperQu::getQuId, quId); - - return this.getOne(wrapper, false); - } - - @Override - public void updateByKey(PaperQu qu) { - - //查询条件 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(PaperQu::getPaperId, qu.getPaperId()) - .eq(PaperQu::getQuId, qu.getQuId()); - - this.update(qu, wrapper); - } - - @Override - public int sumObjective(String paperId) { - return baseMapper.sumObjective(paperId); - } - - @Override - public int sumSubjective(String paperId) { - return baseMapper.sumSubjective(paperId); - } - - @Override - public List listForPaperResult(String paperId) { - return baseMapper.listByPaper(paperId); - } -} diff --git a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java deleted file mode 100644 index 670d74b..0000000 --- a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java +++ /dev/null @@ -1,477 +0,0 @@ -package com.yf.exam.modules.paper.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.core.toolkit.IdWorker; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.yf.exam.ability.job.enums.JobGroup; -import com.yf.exam.ability.job.enums.JobPrefix; -import com.yf.exam.ability.job.service.JobService; -import com.yf.exam.core.api.ApiError; -import com.yf.exam.core.api.dto.PagingReqDTO; -import com.yf.exam.core.exception.ServiceException; -import com.yf.exam.core.utils.BeanMapper; -import com.yf.exam.core.utils.CronUtils; -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.service.ExamRepoService; -import com.yf.exam.modules.exam.service.ExamService; -import com.yf.exam.modules.paper.dto.PaperDTO; -import com.yf.exam.modules.paper.dto.PaperQuDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; -import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; -import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; -import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; -import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; -import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; -import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; -import com.yf.exam.modules.paper.entity.Paper; -import com.yf.exam.modules.paper.entity.PaperQu; -import com.yf.exam.modules.paper.entity.PaperQuAnswer; -import com.yf.exam.modules.paper.enums.ExamState; -import com.yf.exam.modules.paper.enums.PaperState; -import com.yf.exam.modules.paper.job.BreakExamJob; -import com.yf.exam.modules.paper.mapper.PaperMapper; -import com.yf.exam.modules.paper.service.PaperQuAnswerService; -import com.yf.exam.modules.paper.service.PaperQuService; -import com.yf.exam.modules.paper.service.PaperService; -import com.yf.exam.modules.qu.entity.Qu; -import com.yf.exam.modules.qu.entity.QuAnswer; -import com.yf.exam.modules.qu.enums.QuType; -import com.yf.exam.modules.qu.service.QuAnswerService; -import com.yf.exam.modules.qu.service.QuService; -import com.yf.exam.modules.sys.user.entity.SysUser; -import com.yf.exam.modules.sys.user.service.SysUserService; -import com.yf.exam.modules.user.book.service.UserBookService; -import com.yf.exam.modules.user.exam.service.UserExamService; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.CollectionUtils; - -import java.util.*; - -///** -// * 试卷服务实现类 -// * 该类实现了PaperService接口,用于处理试卷相关的业务逻辑 -// * -// * @author 聪明笨狗 -// * @since 2020-05-25 16:33 -// */ -@Service -public class PaperServiceImpl extends ServiceImpl implements PaperService { - @Autowired - private SysUserService sysUserService; - @Autowired - private ExamService examService; - @Autowired - private QuService quService; - @Autowired - private QuAnswerService quAnswerService; - @Autowired - private PaperService paperService; - @Autowired - private PaperQuService paperQuService; - @Autowired - private PaperQuAnswerService paperQuAnswerService; - @Autowired - private UserBookService userBookService; - @Autowired - private ExamRepoService examRepoService; - @Autowired - private UserExamService userExamService; - @Autowired - private JobService jobService; - -// /** -// * 展示的选项,ABC这样 -// */ - private static List ABC = Arrays.asList(new String[]{ - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", - "Y", "Z" - }); - - @Transactional(rollbackFor = Exception.class) - @Override - public String createPaper(String userId, String examId) { - // 校验是否有正在考试的试卷 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda() - .eq(Paper::getUserId, userId) - .eq(Paper::getState, PaperState.ING); - int exists = this.count(wrapper); - if (exists > 0) { - throw new ServiceException(ApiError.ERROR_20010002); - } - - // 查找考试 - ExamDTO exam = examService.findById(examId); - if (exam == null) { - throw new ServiceException(1, "考试不存在!"); - } - if (!ExamState.ENABLE.equals(exam.getState())) { - throw new ServiceException(1, "考试状态不正确!"); - } - - // 考试题目列表 - List quList = this.generateByRepo(examId); - if (CollectionUtils.isEmpty(quList)) { - throw new ServiceException(1, "规则不正确,无对应的考题!"); - } - - // 保存试卷内容 - Paper paper = this.savePaper(userId, exam, quList); - - // 强制交卷任务 - String jobName = JobPrefix.BREAK_EXAM + paper.getId(); - jobService.addCronJob(BreakExamJob.class, jobName, CronUtils.dateToCron(paper.getLimitTime()), paper.getId()); - - return paper.getId(); - } - - @Override - public ExamDetailRespDTO paperDetail(String paperId) { - ExamDetailRespDTO respDTO = new ExamDetailRespDTO(); - // 试题基本信息 - Paper paper = paperService.getById(paperId); - BeanMapper.copy(paper, respDTO); - - // 查找题目列表 - List list = paperQuService.listByPaper(paperId); - List radioList = new ArrayList<>(); - List multiList = new ArrayList<>(); - List judgeList = new ArrayList<>(); - - for (PaperQuDTO item : list) { - if (QuType.RADIO.equals(item.getQuType())) { - radioList.add(item); - } - if (QuType.MULTI.equals(item.getQuType())) { - multiList.add(item); - } - if (QuType.JUDGE.equals(item.getQuType())) { - judgeList.add(item); - } - } - - respDTO.setRadioList(radioList); - respDTO.setMultiList(multiList); - respDTO.setJudgeList(judgeList); - - return respDTO; - } - - @Override - public ExamResultRespDTO paperResult(String paperId) { - ExamResultRespDTO respDTO = new ExamResultRespDTO(); - // 试题基本信息 - Paper paper = paperService.getById(paperId); - BeanMapper.copy(paper, respDTO); - - List quList = paperQuService.listForPaperResult(paperId); - respDTO.setQuList(quList); - - return respDTO; - } - - @Override - public PaperQuDetailDTO findQuDetail(String paperId, String quId) { - PaperQuDetailDTO respDTO = new PaperQuDetailDTO(); - // 问题 - Qu qu = quService.getById(quId); - // 基本信息 - PaperQu paperQu = paperQuService.findByKey(paperId, quId); - BeanMapper.copy(paperQu, respDTO); - - respDTO.setContent(qu.getContent()); - respDTO.setImage(qu.getImage()); - - // 答案列表 - List list = paperQuAnswerService.listForExam(paperId, quId); - respDTO.setAnswerList(list); - - return respDTO; - } - -// /** -// * 题库组题方式产生题目列表 -// * @param examId -// * @return -// */ - private List generateByRepo(String examId) { - // 查找规则指定的题库 - List list = examRepoService.listByExam(examId); - // 最终的题目列表 - List quList = new ArrayList<>(); - // 排除ID,避免题目重复 - List excludes = new ArrayList<>(); - excludes.add("none"); - - if (!CollectionUtils.isEmpty(list)) { - for (ExamRepoExtDTO item : list) { - // 单选题 - if (item.getRadioCount() > 0) { - List radioList = quService.listByRandom(item.getRepoId(), QuType.RADIO, excludes, item.getRadioCount()); - for (Qu qu : radioList) { - PaperQu paperQu = this.processPaperQu(item, qu); - quList.add(paperQu); - excludes.add(qu.getId()); - } - } - // 多选题 - if (item.getMultiCount() > 0) { - List multiList = quService.listByRandom(item.getRepoId(), QuType.MULTI, excludes, item.getMultiCount()); - for (Qu qu : multiList) { - PaperQu paperQu = this.processPaperQu(item, qu); - quList.add(paperQu); - excludes.add(qu.getId()); - } - } - // 判断题 - if (item.getJudgeCount() > 0) { - List judgeList = quService.listByRandom(item.getRepoId(), QuType.JUDGE, excludes, item.getJudgeCount()); - for (Qu qu : judgeList) { - PaperQu paperQu = this.processPaperQu(item, qu); - quList.add(paperQu); - excludes.add(qu.getId()); - } - } - } - } - return quList; - } - -// /** -// * 填充试题题目信息 -// * @param repo -// * @param qu -// * @return -// */ - private PaperQu processPaperQu(ExamRepoDTO repo, Qu qu) { - // 保存试题信息 - PaperQu paperQu = new PaperQu(); - paperQu.setQuId(qu.getId()); - paperQu.setAnswered(false); - paperQu.setIsRight(false); - paperQu.setQuType(qu.getQuType()); - - if (QuType.RADIO.equals(qu.getQuType())) { - paperQu.setScore(repo.getRadioScore()); - paperQu.setActualScore(repo.getRadioScore()); - } - if (QuType.MULTI.equals(qu.getQuType())) { - paperQu.setScore(repo.getMultiScore()); - paperQu.setActualScore(repo.getMultiScore()); - } - if (QuType.JUDGE.equals(qu.getQuType())) { - paperQu.setScore(repo.getJudgeScore()); - paperQu.setActualScore(repo.getJudgeScore()); - } - - return paperQu; - } - -// /** -// * 保存试卷 -// * @param userId -// * @param exam -// * @param quList -// * @return -// */ - private Paper savePaper(String userId, ExamDTO exam, List quList) { - // 查找用户 - SysUser user = sysUserService.getById(userId); - - // 保存试卷基本信息 - Paper paper = new Paper(); - paper.setDepartId(user.getDepartId()); - paper.setExamId(exam.getId()); - paper.setTitle(exam.getTitle()); - paper.setTotalScore(exam.getTotalScore()); - paper.setTotalTime(exam.getTotalTime()); - paper.setUserScore(0); - paper.setUserId(userId); - paper.setCreateTime(new Date()); - paper.setUpdateTime(new Date()); - paper.setQualifyScore(exam.getQualifyScore()); - paper.setState(PaperState.ING); - paper.setHasSaq(false); - - // 截止时间 - Calendar cl = Calendar.getInstance(); - cl.setTimeInMillis(System.currentTimeMillis()); - cl.add(Calendar.MINUTE, exam.getTotalTime()); - paper.setLimitTime(cl.getTime()); - - paperService.save(paper); - - if (!CollectionUtils.isEmpty(quList)) { - this.savePaperQu(paper.getId(), quList); - } - - return paper; - } - -// /** -// * 保存试卷试题列表 -// * @param paperId -// * @param quList -// */ - private void savePaperQu(String paperId, List quList) { - List batchQuList = new ArrayList<>(); - List batchAnswerList = new ArrayList<>(); - int sort = 0; - - for (PaperQu item : quList) { - item.setPaperId(paperId); - item.setSort(sort); - item.setId(IdWorker.getIdStr()); - - // 回答列表 - List answerList = quAnswerService.listAnswerByRandom(item.getQuId()); - if (!CollectionUtils.isEmpty(answerList)) { - int ii = 0; - for (QuAnswer answer : answerList) { - PaperQuAnswer paperQuAnswer = new PaperQuAnswer(); - paperQuAnswer.setId(UUID.randomUUID().toString()); - paperQuAnswer.setPaperId(paperId); - paperQuAnswer.setQuId(answer.getQuId()); - paperQuAnswer.setAnswerId(answer.getId()); - paperQuAnswer.setChecked(false); - paperQuAnswer.setSort(ii); - paperQuAnswer.setAbc(ABC.get(ii)); - paperQuAnswer.setIsRight(answer.getIsRight()); - ii++; - batchAnswerList.add(paperQuAnswer); - } - } - batchQuList.add(item); - sort++; - } - - // 添加问题 - paperQuService.saveBatch(batchQuList); - - // 批量添加问题答案 - paperQuAnswerService.saveBatch(batchAnswerList); - } - - @Transactional(rollbackFor = Exception.class) - @Override - public void fillAnswer(PaperAnswerDTO reqDTO) { - // 未作答 - if (CollectionUtils.isEmpty(reqDTO.getAnswers()) && StringUtils.isBlank(reqDTO.getAnswer())) { - return; - } - - // 查找答案列表 - List list = paperQuAnswerService.listForFill(reqDTO.getPaperId(), reqDTO.getQuId()); - // 是否正确 - boolean right = true; - - // 更新正确答案 - for (PaperQuAnswer item : list) { - if (reqDTO.getAnswers().contains(item.getId())) { - item.setChecked(true); - } else { - item.setChecked(false); - } - - // 有一个对不上就是错的 - if (item.getIsRight() != null && !item.getIsRight().equals(item.getChecked())) { - right = false; - } - - paperQuAnswerService.updateById(item); - } - - // 修改为已回答 - PaperQu qu = new PaperQu(); - qu.setQuId(reqDTO.getQuId()); - qu.setPaperId(reqDTO.getPaperId()); - qu.setIsRight(right); - qu.setAnswer(reqDTO.getAnswer()); - qu.setAnswered(true); - - paperQuService.updateByKey(qu); - } - - @Transactional(rollbackFor = Exception.class) - @Override - public void handExam(String paperId) { - // 获取试卷信息 - Paper paper = paperService.getById(paperId); - - // 如果不是正常的,抛出异常 - if (!PaperState.ING.equals(paper.getState())) { - throw new ServiceException(1, "试卷状态不正确!"); - } - - // 客观分 - int objScore = paperQuService.sumObjective(paperId); - paper.setObjScore(objScore); - paper.setUserScore(objScore); - - // 主观分,因为要阅卷,所以给0 - paper.setSubjScore(0); - - // 待阅卷 - if (paper.getHasSaq()) { - paper.setState(PaperState.WAIT_OPT); - } else { - // 同步保存考试成绩 - userExamService.joinResult(paper.getUserId(), paper.getExamId(), objScore, objScore >= paper.getQualifyScore()); - paper.setState(PaperState.FINISHED); - } - - paper.setUpdateTime(new Date()); - - // 计算考试时长 - Calendar cl = Calendar.getInstance(); - cl.setTimeInMillis(System.currentTimeMillis()); - int userTime = (int) ((System.currentTimeMillis() - paper.getCreateTime().getTime()) / 1000 / 60); - if (userTime == 0) { - userTime = 1; - } - paper.setUserTime(userTime); - - // 更新试卷 - paperService.updateById(paper); - - // 终止定时任务 - String name = JobPrefix.BREAK_EXAM + paperId; - jobService.deleteJob(name, JobGroup.SYSTEM); - - // 把打错的问题加入错题本 - List list = paperQuService.listByPaper(paperId); - for (PaperQuDTO qu : list) { - // 主观题和对的都不加入错题库 - if (qu.getIsRight()) { - continue; - } - - // 加入错题本 - new Thread(() -> userBookService.addBook(paper.getExamId(), qu.getQuId())).run(); - } - } - - @Override - public IPage paging(PagingReqDTO reqDTO) { - return baseMapper.paging(reqDTO.toPage(), reqDTO.getParams()); - } - - @Override - public PaperDTO checkProcess(String userId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda() - .eq(Paper::getUserId, userId) - .eq(Paper::getState, PaperState.ING); - Paper paper = this.getOne(wrapper, false); - if (paper != null) { - return BeanMapper.map(paper, PaperDTO.class); - } - return null; - } -} diff --git a/user/UserUtils.java b/user/UserUtils.java new file mode 100644 index 0000000..7341166 --- /dev/null +++ b/user/UserUtils.java @@ -0,0 +1,75 @@ +package com.yf.exam.modules.user; + +// 导入自定义的 API 错误信息类 +import com.yf.exam.core.api.ApiError; +// 导入自定义的服务异常类 +import com.yf.exam.core.exception.ServiceException; +// 导入系统用户登录信息响应数据传输对象 +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +// 导入 Shiro 安全工具类 +import org.apache.shiro.SecurityUtils; + +/** + * 用户静态工具类,提供获取当前登录用户信息的静态方法。 + * @author bool + */ +public class UserUtils { + + /** + * 获取当前登录用户的ID。 + * + * @param throwable 一个布尔值,指示在获取用户 ID 失败时是否抛出异常。 + * 若为 true,则在失败时抛出 ServiceException 异常; + * 若为 false,则在失败时返回 null。 + * @return 当前登录用户的 ID,如果获取失败且 throwable 为 false,则返回 null。 + */ + public static String getUserId(boolean throwable){ + try { + // 从 Shiro 的 Subject 中获取当前用户的主身份信息,并转换为 SysUserLoginDTO 对象,然后获取用户 ID + return ((SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal()).getId(); + }catch (Exception e){ + if(throwable){ + // 若 throwable 为 true,获取用户 ID 失败时抛出服务异常 + throw new ServiceException(ApiError.ERROR_10010002); + } + // 若 throwable 为 false,获取用户 ID 失败时返回 null + return null; + } + } + + /** + * 判断当前登录用户是否为管理员。 + * + * @param throwable 一个布尔值,指示在获取用户信息失败时是否抛出异常。 + * 若为 true,则在失败时抛出 ServiceException 异常; + * 若为 false,则在失败时返回 false。 + * @return 若当前登录用户的角色列表包含 "sa",则返回 true;否则返回 false。 + * 若获取用户信息失败且 throwable 为 false,也返回 false。 + */ + public static boolean isAdmin(boolean throwable){ + try { + // 从 Shiro 的 Subject 中获取当前用户的主身份信息,并转换为 SysUserLoginDTO 对象 + SysUserLoginDTO dto = ((SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal()); + // 判断用户的角色列表是否包含 "sa" + return dto.getRoles().contains("sa"); + }catch (Exception e){ + if(throwable){ + // 若 throwable 为 true,获取用户信息失败时抛出服务异常 + throw new ServiceException(ApiError.ERROR_10010002); + } + } + + // 若获取用户信息失败且 throwable 为 false,返回 false + return false; + } + + /** + * 获取当前登录用户的 ID,默认在获取失败时会抛出异常。 + * 该方法调用了 getUserId(boolean throwable) 方法,并将 throwable 参数设置为 true。 + * + * @return 当前登录用户的 ID,如果获取失败则抛出 ServiceException 异常。 + */ + public static String getUserId(){ + return getUserId(true); + } +} diff --git a/user/book/controller/UserBookController.java b/user/book/controller/UserBookController.java new file mode 100644 index 0000000..9ff16d0 --- /dev/null +++ b/user/book/controller/UserBookController.java @@ -0,0 +1,79 @@ +package com.yf.exam.modules.user.book.controller; + +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.BaseIdRespDTO; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.user.book.dto.UserBookDTO; +import com.yf.exam.modules.user.book.service.UserBookService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +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; + +/** + *

+ * 错题本控制器,处理与错题本相关的HTTP请求 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-27 17:56 + */ +@Api(tags={"错题本"}) +@RestController +@RequestMapping("/exam/api/user/wrong-book") +public class UserBookController extends BaseController { + + /** + * 注入错题本服务类,用于处理错题本相关的业务逻辑 + */ + @Autowired + private UserBookService baseService; + + /** + * 批量删除错题本记录 + * @param reqDTO 包含要删除记录ID列表的请求对象 + * @return 操作结果的统一响应对象 + */ + @ApiOperation(value = "批量删除") + @RequestMapping(value = "/delete", method = { RequestMethod.POST}) + public ApiRest delete(@RequestBody BaseIdsReqDTO reqDTO) { + // 根据传入的ID列表删除对应的错题本记录 + baseService.removeByIds(reqDTO.getIds()); + // 返回操作成功的响应 + return super.success(); + } + + /** + * 分页查找错题本记录 + * @param reqDTO 包含分页信息和查询条件的请求对象 + * @return 包含分页结果的统一响应对象 + */ + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + // 调用服务层方法进行分页查询,并将结果转换为UserBookDTO对象 + IPage page = baseService.paging(reqDTO); + // 返回包含分页结果的成功响应 + return super.success(page); + } + + /** + * 查找下一个错题记录,每次最多返回200条数据 + * @param reqDTO 包含考试ID和当前题目ID的请求对象 + * @return 包含下一个题目ID的统一响应对象 + */ + @ApiOperation(value = "查找列表") + @RequestMapping(value = "/next", method = { RequestMethod.POST}) + public ApiRest nextQu(@RequestBody UserBookDTO reqDTO) { + // 调用服务层方法查找下一个错题的题目ID + String quId = baseService.findNext(reqDTO.getExamId(), reqDTO.getQuId()); + // 将下一个题目ID封装到响应对象中并返回成功响应 + return super.success(new BaseIdRespDTO(quId)); + } +} diff --git a/user/book/dto/UserBookDTO.java b/user/book/dto/UserBookDTO.java new file mode 100644 index 0000000..96e4cad --- /dev/null +++ b/user/book/dto/UserBookDTO.java @@ -0,0 +1,79 @@ +package com.yf.exam.modules.user.book.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * 错题本请求类,用于在不同层之间传输错题本相关的数据。 + * 该类包含了错题本记录的基本信息,如考试ID、用户ID、题目ID等。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-27 17:56 + */ +@Data +@ApiModel(value="错题本", description="错题本") +public class UserBookDTO implements Serializable { + + // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 错题本记录的唯一标识ID + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 关联的考试ID,表明该错题属于哪次考试 + */ + @ApiModelProperty(value = "考试ID", required=true) + private String examId; + + /** + * 关联的用户ID,表明该错题属于哪个用户 + */ + @ApiModelProperty(value = "用户ID", required=true) + private String userId; + + /** + * 关联的题目ID,表明具体是哪道题目做错了 + */ + @ApiModelProperty(value = "题目ID", required=true) + private String quId; + + /** + * 该错题加入错题本的时间 + */ + @ApiModelProperty(value = "加入时间", required=true) + private Date createTime; + + /** + * 该错题最近一次出错的时间 + */ + @ApiModelProperty(value = "最近错误时间", required=true) + private Date updateTime; + + /** + * 该错题累计出错的次数 + */ + @ApiModelProperty(value = "错误时间", required=true) + private Integer wrongCount; + + /** + * 错题的题目标题,用于快速识别题目内容 + */ + @ApiModelProperty(value = "题目标题", required=true) + private String title; + + /** + * 错题在错题本中的排序序号 + */ + @ApiModelProperty(value = "错题序号", required=true) + private Integer sort; +} diff --git a/user/book/entity/UserBook.java b/user/book/entity/UserBook.java new file mode 100644 index 0000000..a36fb6a --- /dev/null +++ b/user/book/entity/UserBook.java @@ -0,0 +1,80 @@ +package com.yf.exam.modules.user.book.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; + +/** + *

+ * 错题本实体类,对应数据库表 `el_user_book`,用于存储用户错题相关信息。 + * 该类继承自 MyBatis-Plus 的 `Model` 类,可使用其提供的 ActiveRecord 功能。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-27 17:56 + */ +@Data +@TableName("el_user_book") +public class UserBook extends Model { + + // 序列化版本号,确保序列化和反序列化过程中类版本的一致性 + private static final long serialVersionUID = 1L; + + /** + * 错题本记录的唯一标识 ID,在插入数据时由系统自动分配。 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 关联的考试 ID,表明该错题所属的考试。 + */ + @TableField("exam_id") + private String examId; + + /** + * 关联的用户 ID,表明该错题所属的用户。 + */ + @TableField("user_id") + private String userId; + + /** + * 关联的题目 ID,表明具体是哪道题目被做错。 + */ + @TableField("qu_id") + private String quId; + + /** + * 错题被加入错题本的时间,记录首次出错时间。 + */ + @TableField("create_time") + private Date createTime; + + /** + * 该错题最近一次出错的时间,反映错题的最新错误情况。 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 该错题累计出错的次数,用于统计错题的出错频率。 + */ + @TableField("wrong_count") + private Integer wrongCount; + + /** + * 错题的题目标题,方便用户快速识别错题内容。 + */ + private String title; + + /** + * 错题在错题本中的排序序号,用于对错题进行排序展示。 + */ + private Integer sort; + +} diff --git a/user/book/mapper/UserBookMapper.java b/user/book/mapper/UserBookMapper.java new file mode 100644 index 0000000..68af834 --- /dev/null +++ b/user/book/mapper/UserBookMapper.java @@ -0,0 +1,18 @@ +package com.yf.exam.modules.user.book.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.user.book.entity.UserBook; + +/** + *

+ * 错题本Mapper接口,用于与数据库中错题本相关表进行数据交互。 + * 该接口继承自MyBatis-Plus的BaseMapper接口,可直接使用BaseMapper提供的基础CRUD操作方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-27 17:56 + */ +public interface UserBookMapper extends BaseMapper { + // 若需要自定义数据库操作方法,可在此处添加接口方法声明 + // 对应的SQL语句可以在XML文件中实现,或者使用@Select、@Insert等注解 +} diff --git a/user/book/service/UserBookService.java b/user/book/service/UserBookService.java new file mode 100644 index 0000000..b4c0f74 --- /dev/null +++ b/user/book/service/UserBookService.java @@ -0,0 +1,41 @@ +package com.yf.exam.modules.user.book.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.user.book.dto.UserBookDTO; +import com.yf.exam.modules.user.book.entity.UserBook; + +/** + *

+ * 错题本业务类接口,定义了错题本相关的业务操作方法。 + * 该接口继承自 MyBatis-Plus 的 IService 接口,可使用其提供的基础服务方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-27 17:56 + */ +public interface UserBookService extends IService { + + /** + * 分页查询错题本数据 + * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserBookDTO + * @return 包含分页结果的 UserBookDTO 对象集合,封装在 IPage 中 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 将指定题目加入错题本 + * @param examId 关联的考试 ID,标识该错题所属的考试 + * @param quId 关联的题目 ID,标识具体是哪道题目 + */ + void addBook(String examId, String quId); + + /** + * 查找当前错题的下一个错题 + * @param examId 关联的考试 ID,限定查找范围为该考试内的错题 + * @param quId 当前错题的题目 ID,基于此查找下一个错题 + * @return 下一个错题的题目 ID,如果不存在则可能返回 null + */ + String findNext(String examId, String quId); +} diff --git a/user/book/service/impl/UserBookServiceImpl.java b/user/book/service/impl/UserBookServiceImpl.java new file mode 100644 index 0000000..38d8b48 --- /dev/null +++ b/user/book/service/impl/UserBookServiceImpl.java @@ -0,0 +1,192 @@ +package com.yf.exam.modules.user.book.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +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.modules.qu.entity.Qu; +import com.yf.exam.modules.qu.service.QuService; +import com.yf.exam.modules.user.UserUtils; +import com.yf.exam.modules.user.book.dto.UserBookDTO; +import com.yf.exam.modules.user.book.entity.UserBook; +import com.yf.exam.modules.user.book.mapper.UserBookMapper; +import com.yf.exam.modules.user.book.service.UserBookService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +/** + *

+ * 错题本服务实现类,实现了错题本相关的业务逻辑。 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-27 17:56 + */ +@Service +public class UserBookServiceImpl extends ServiceImpl implements UserBookService { + + /** + * 注入题目服务类,用于获取题目相关信息 + */ + @Autowired + private QuService quService; + + /** + * 分页查询用户错题本记录 + * @param reqDTO 包含分页信息和查询条件的请求对象 + * @return 包含分页结果的错题本记录 DTO 对象 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 创建分页对象,指定当前页码和每页显示数量 + Page query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + // 构建查询条件包装器 + QueryWrapper wrapper = new QueryWrapper<>(); + // 只查询当前用户的错题记录 + wrapper.lambda().eq(UserBook::getUserId, UserUtils.getUserId(true)); + + // 获取查询参数 + UserBookDTO params = reqDTO.getParams(); + if (params != null) { + // 如果标题参数不为空,添加模糊查询条件 + if (!StringUtils.isEmpty(params.getTitle())) { + wrapper.lambda().like(UserBook::getTitle, params.getTitle()); + } + + // 如果考试 ID 参数不为空,添加精确查询条件 + if (!StringUtils.isEmpty(params.getExamId())) { + wrapper.lambda().eq(UserBook::getExamId, params.getExamId()); + } + } + + // 执行分页查询,获取错题本实体分页数据 + IPage page = this.page(query, wrapper); + // 将实体分页数据转换为 DTO 分页数据 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + /** + * 将错题添加到用户错题本中 + * @param examId 考试 ID + * @param quId 题目 ID + */ + @Override + public void addBook(String examId, String quId) { + + // 构建查询条件,查找该用户在本次考试中该题目的错题记录 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(UserBook::getUserId, UserUtils.getUserId()) + .eq(UserBook::getExamId, examId) + .eq(UserBook::getQuId, quId); + + // 查找已有的错题信息 + UserBook book = this.getOne(wrapper, false); + + // 获取题目信息 + Qu qu = quService.getById(quId); + + if (book == null) { + // 如果该错题记录不存在,则创建新的错题记录 + book = new UserBook(); + book.setExamId(examId); + book.setUserId(UserUtils.getUserId()); + book.setTitle(qu.getContent()); + book.setQuId(quId); + book.setWrongCount(1); + // 获取当前考试中用户错题的最大排序值,并加 1 作为新记录的排序值 + Integer maxSort = this.findMaxSort(examId, UserUtils.getUserId()); + book.setSort(maxSort + 1); + + // 保存新的错题记录 + this.save(book); + } else { + // 如果该错题记录已存在,错误次数加 1 + book.setWrongCount(book.getWrongCount() + 1); + // 更新错题记录 + this.updateById(book); + } + } + + /** + * 查找当前错题的下一个错题的题目 ID + * @param examId 考试 ID + * @param quId 当前错题的题目 ID + * @return 下一个错题的题目 ID,如果不存在则返回 null + */ + @Override + public String findNext(String examId, String quId) { + + // 初始化排序值为一个较大值 + Integer sort = 999999; + + if (!StringUtils.isEmpty(quId)) { + // 构建查询条件,查找当前错题记录 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(UserBook::getUserId, UserUtils.getUserId()) + .eq(UserBook::getExamId, examId) + .eq(UserBook::getQuId, quId); + // 按排序值降序排序 + wrapper.last(" ORDER BY `sort` DESC"); + + // 获取当前错题记录 + UserBook last = this.getOne(wrapper, false); + if (last != null) { + // 如果找到当前错题记录,获取其排序值 + sort = last.getSort(); + } + } + + // 构建查询条件,查找排序值小于当前错题的下一个错题记录 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(UserBook::getUserId, UserUtils.getUserId()) + .eq(UserBook::getExamId, examId) + .lt(UserBook::getSort, sort); + // 按排序值降序排序 + wrapper.last(" ORDER BY `sort` DESC"); + + // 获取下一个错题记录 + UserBook next = this.getOne(wrapper, false); + if (next != null) { + // 如果找到下一个错题记录,返回其题目 ID + return next.getQuId(); + } + + return null; + } + + /** + * 查找指定考试中用户错题的最大排序值 + * @param examId 考试 ID + * @param userId 用户 ID + * @return 最大排序值,如果没有记录则返回 0 + */ + private Integer findMaxSort(String examId, String userId) { + + // 构建查询条件,查找指定考试中用户的错题记录 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(UserBook::getExamId, examId) + .eq(UserBook::getUserId, userId); + // 按排序值降序排序 + wrapper.last(" ORDER BY `sort` DESC"); + + // 获取排序值最大的错题记录 + UserBook book = this.getOne(wrapper, false); + if (book == null) { + // 如果没有记录,返回 0 + return 0; + } + // 返回最大排序值 + return book.getSort(); + } +} diff --git a/user/exam/controller/UserExamController.java b/user/exam/controller/UserExamController.java new file mode 100644 index 0000000..ae03687 --- /dev/null +++ b/user/exam/controller/UserExamController.java @@ -0,0 +1,65 @@ +package com.yf.exam.modules.user.exam.controller; + +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.PagingReqDTO; +import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; +import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; +import com.yf.exam.modules.user.exam.service.UserExamService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +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; + +/** + *

+ * 考试记录控制器,处理与用户考试记录相关的 HTTP 请求。 + * 该控制器提供了分页查询考试记录的接口。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-21 15:13 + */ +@Api(tags={"考试记录"}) +@RestController +@RequestMapping("/exam/api/user/exam") +public class UserExamController extends BaseController { + + /** + * 注入用户考试记录服务类,用于处理考试记录相关的业务逻辑。 + */ + @Autowired + private UserExamService baseService; + + /** + * 分页查找考试记录 + * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserExamReqDTO + * @return 包含分页结果的统一响应对象,分页结果为 UserExamRespDTO 类型 + */ + @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 包含分页信息和查询条件的请求对象,泛型为 UserExamReqDTO + * @return 包含分页结果的统一响应对象,分页结果为 UserExamRespDTO 类型 + */ + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/my-paging", method = { RequestMethod.POST}) + public ApiRest> myPaging(@RequestBody PagingReqDTO reqDTO) { + // 调用服务层的分页查询方法,获取当前用户分页后的考试记录 + IPage page = baseService.myPaging(reqDTO); + // 返回包含分页结果的成功响应 + return super.success(page); + } +} diff --git a/user/exam/dto/UserExamDTO.java b/user/exam/dto/UserExamDTO.java new file mode 100644 index 0000000..e6d915d --- /dev/null +++ b/user/exam/dto/UserExamDTO.java @@ -0,0 +1,84 @@ +// 声明该类所在的包,此包用于存放用户考试相关的数据传输对象 +package com.yf.exam.modules.user.exam.dto; + +// 导入自定义的字典注解,用于处理数据字典映射 +import com.yf.exam.core.annon.Dict; +// 导入 Swagger 相关注解,用于生成 API 文档 +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; +// 导入日期类,用于表示时间相关的属性 +import java.util.Date; +// 导入序列化接口,使该类的对象可以被序列化和反序列化 +import java.io.Serializable; + +/** + *

+ * 考试记录数据传输类,用于在不同层(如控制层、服务层、数据访问层)之间传输考试记录相关的数据。 + * 该类封装了考试记录的基本信息,如用户ID、考试ID、考试次数、最高分数等。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-21 15:13 + */ +// 使用 Lombok 的 Data 注解,简化代码,自动生成常用方法 +@Data +// 使用 Swagger 的 ApiModel 注解,为 API 文档提供该类的描述信息 +@ApiModel(value="考试记录", description="考试记录") +public class UserExamDTO implements Serializable { + + // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性,避免版本不一致导致的错误 + private static final long serialVersionUID = 1L; + + /** + * 考试记录的唯一标识ID,用于在数据库中唯一确定一条考试记录。 + */ + private String id; + + /** + * 参加考试的用户ID,该字段为必填项,用于关联参加考试的用户。 + */ + @ApiModelProperty(value = "用户ID", required=true) + private String userId; + + /** + * 关联的考试ID,该字段为必填项。 + * @Dict 注解用于从 `el_exam` 表中根据 `id` 字段获取对应的 `title` 作为字典文本, + * 方便在前端展示考试的标题信息。 + */ + @Dict(dictTable = "el_exam", dicText = "title", dicCode = "id") + @ApiModelProperty(value = "考试ID", required=true) + private String examId; + + /** + * 用户参加该考试的次数,该字段为必填项,用于统计用户参加特定考试的频率。 + */ + @ApiModelProperty(value = "考试次数", required=true) + private Integer tryCount; + + /** + * 用户参加该考试获得的最高分数,该字段为必填项,用于记录用户在该考试中的最佳成绩。 + */ + @ApiModelProperty(value = "最高分数", required=true) + private Integer maxScore; + + /** + * 用户是否通过该考试,该字段为必填项,用于判断用户在考试中的最终结果。 + */ + @ApiModelProperty(value = "是否通过", required=true) + private Boolean passed; + + /** + * 考试记录的创建时间,记录该考试记录首次被创建的时间。 + */ + @ApiModelProperty(value = "创建时间") + private Date createTime; + + /** + * 考试记录的更新时间,记录该考试记录最后一次被修改的时间。 + */ + @ApiModelProperty(value = "更新时间") + private Date updateTime; + +} diff --git a/user/exam/dto/request/UserExamReqDTO.java b/user/exam/dto/request/UserExamReqDTO.java new file mode 100644 index 0000000..594eeb7 --- /dev/null +++ b/user/exam/dto/request/UserExamReqDTO.java @@ -0,0 +1,42 @@ +package com.yf.exam.modules.user.exam.dto.request; + +// 导入 UserExamDTO 类,作为当前类的父类 +import com.yf.exam.modules.user.exam.dto.UserExamDTO; +// 导入 Swagger 注解,用于 API 文档生成 +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法 +import lombok.Data; + +/** + *

+ * 考试记录数据传输类,用于封装考试记录相关的请求数据。 + * 该类继承自 UserExamDTO,可复用其属性,同时添加了额外的请求所需属性。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-21 15:13 + */ +// 使用 Lombok 的 Data 注解,自动生成常用方法 +@Data +// 使用 Swagger 的 ApiModel 注解,为 API 文档提供模型描述 +@ApiModel(value="考试记录", description="考试记录") +public class UserExamReqDTO extends UserExamDTO { + + // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 考试名称,用于标识具体的考试,在请求中为必填项。 + */ + // 使用 Swagger 的 ApiModelProperty 注解,为 API 文档描述该属性 + @ApiModelProperty(value = "考试名称", required=true) + private String title; + + /** + * 参考人员的真实姓名,用于关联参加考试的人员,在请求中为必填项。 + */ + // 使用 Swagger 的 ApiModelProperty 注解,为 API 文档描述该属性 + @ApiModelProperty(value = "人员名称", required=true) + private String realName; +} diff --git a/user/exam/dto/response/UserExamRespDTO.java b/user/exam/dto/response/UserExamRespDTO.java new file mode 100644 index 0000000..982a836 --- /dev/null +++ b/user/exam/dto/response/UserExamRespDTO.java @@ -0,0 +1,36 @@ +package com.yf.exam.modules.user.exam.dto.response; + +import com.yf.exam.modules.user.exam.dto.UserExamDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 考试记录响应数据传输类,用于封装返回给前端的考试记录相关信息。 + * 该类继承自 UserExamDTO,复用其属性,同时添加了额外的响应所需属性。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-21 15:13 + */ +@Data +@ApiModel(value="考试记录", description="考试记录") +public class UserExamRespDTO extends UserExamDTO { + + // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 考试名称,标识具体的考试,该信息在响应中为必填项。 + */ + @ApiModelProperty(value = "考试名称", required=true) + private String title; + + /** + * 参考人员的真实姓名,用于关联参加考试的人员,该信息在响应中为必填项。 + */ + @ApiModelProperty(value = "人员名称", required=true) + private String realName; + +} diff --git a/user/exam/entity/UserExam.java b/user/exam/entity/UserExam.java new file mode 100644 index 0000000..b1104ae --- /dev/null +++ b/user/exam/entity/UserExam.java @@ -0,0 +1,85 @@ +package com.yf.exam.modules.user.exam.entity; + +// 导入 MyBatis-Plus 主键类型注解,用于指定主键生成策略 +import com.baomidou.mybatisplus.annotation.IdType; +// 导入 MyBatis-Plus 表字段注解,用于指定实体类属性与数据库表字段的映射关系 +import com.baomidou.mybatisplus.annotation.TableField; +// 导入 MyBatis-Plus 表主键注解,用于指定实体类的主键属性 +import com.baomidou.mybatisplus.annotation.TableId; +// 导入 MyBatis-Plus 表名注解,用于指定实体类对应的数据库表名 +import com.baomidou.mybatisplus.annotation.TableName; +// 导入 MyBatis-Plus 活动记录模型类,实体类继承该类可使用活动记录模式操作数据库 +import com.baomidou.mybatisplus.extension.activerecord.Model; +// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法 +import lombok.Data; +// 导入日期类,用于表示时间相关的属性 +import java.util.Date; + +/** + *

+ * 考试记录实体类,对应数据库中的 `el_user_exam` 表,用于封装考试记录的相关信息。 + * 该类继承自 MyBatis-Plus 的 Model 类,可使用活动记录模式进行数据库操作。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-21 15:13 + */ +// 使用 Lombok 的 Data 注解,简化代码,自动生成常用方法 +@Data +// 使用 MyBatis-Plus 的 TableName 注解,指定该实体类对应的数据库表名为 `el_user_exam` +@TableName("el_user_exam") +public class UserExam extends Model { + + // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性 + private static final long serialVersionUID = 1L; + + /** + * 考试记录的唯一标识 ID,对应数据库表中的 `id` 字段。 + * 使用 MyBatis-Plus 的 TableId 注解指定主键,采用 ASSIGN_ID 策略自动生成 ID。 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 用户 ID,对应数据库表中的 `user_id` 字段,用于关联参加考试的用户。 + */ + @TableField("user_id") + private String userId; + + /** + * 考试 ID,对应数据库表中的 `exam_id` 字段,用于关联具体的考试。 + */ + @TableField("exam_id") + private String examId; + + /** + * 考试次数,对应数据库表中的 `try_count` 字段,记录用户参加该考试的次数。 + */ + @TableField("try_count") + private Integer tryCount; + + /** + * 最高分数,对应数据库表中的 `max_score` 字段,记录用户参加该考试获得的最高分数。 + */ + @TableField("max_score") + private Integer maxScore; + + /** + * 是否通过,对应数据库表中未显式指定字段名(默认按属性名映射), + * 记录用户是否通过该考试。 + */ + private Boolean passed; + + /** + * 创建时间,对应数据库表中的 `create_time` 字段,记录该考试记录的创建时间。 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间,对应数据库表中的 `update_time` 字段,记录该考试记录的更新时间。 + */ + @TableField("update_time") + private Date updateTime; + +} diff --git a/user/exam/mapper/UserExamMapper.java b/user/exam/mapper/UserExamMapper.java new file mode 100644 index 0000000..24aa5c5 --- /dev/null +++ b/user/exam/mapper/UserExamMapper.java @@ -0,0 +1,37 @@ +package com.yf.exam.modules.user.exam.mapper; + +// 导入 MyBatis-Plus 基础 Mapper 接口,提供基本的数据库 CRUD 操作 +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +// 导入 MyBatis-Plus 分页查询结果接口,用于封装分页查询结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入 MyBatis-Plus 分页对象,用于设置分页查询的参数 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 导入考试记录请求数据传输对象,用于封装查询条件 +import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; +// 导入考试记录响应数据传输对象,用于封装查询结果 +import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; +// 导入考试记录实体类,对应数据库中的考试记录表 +import com.yf.exam.modules.user.exam.entity.UserExam; +// 导入 MyBatis 参数注解,用于在 SQL 语句中引用方法参数 +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 考试记录Mapper接口,用于定义与考试记录相关的数据库操作方法。 + * 该接口继承自 MyBatis-Plus 的 BaseMapper 接口,可使用其提供的基础数据库操作方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-21 15:13 + */ +public interface UserExamMapper extends BaseMapper { + + /** + * 分页查询我的考试记录 + * @param page 分页对象,包含当前页码、每页显示数量等分页信息 + * @param query 考试记录请求数据传输对象,包含查询条件,如考试名称、参考人员姓名等 + * @return 包含分页结果的考试记录响应数据传输对象集合,封装在 IPage 中 + */ + IPage paging(Page page, @Param("query") UserExamReqDTO query); + +} diff --git a/user/exam/service/UserExamService.java b/user/exam/service/UserExamService.java new file mode 100644 index 0000000..4c80bf4 --- /dev/null +++ b/user/exam/service/UserExamService.java @@ -0,0 +1,50 @@ +package com.yf.exam.modules.user.exam.service; + +// 导入 MyBatis-Plus 分页元数据接口,用于封装分页查询结果 +import com.baomidou.mybatisplus.core.metadata.IPage; +// 导入 MyBatis-Plus 扩展服务接口,提供基础的 CRUD 操作 +import com.baomidou.mybatisplus.extension.service.IService; +// 导入自定义的分页请求数据传输对象,用于封装分页查询的请求参数 +import com.yf.exam.core.api.dto.PagingReqDTO; +// 导入考试记录请求数据传输对象,用于封装考试记录查询的具体条件 +import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; +// 导入考试记录响应数据传输对象,用于封装考试记录查询的返回结果 +import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; +// 导入考试记录实体类,对应数据库中的考试记录数据 +import com.yf.exam.modules.user.exam.entity.UserExam; + +/** + *

+ * 考试记录业务类,定义了与考试记录相关的业务操作接口。 + * 该接口继承自 MyBatis-Plus 的 IService 接口,可使用其提供的基础服务方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-21 15:13 + */ +public interface UserExamService extends IService { + + /** + * 分页查询考试记录数据 + * @param reqDTO 包含分页信息和考试记录查询条件的请求对象,泛型为 UserExamReqDTO + * @return 包含分页结果的考试记录响应对象集合,封装在 IPage 中 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 分页查询当前用户的考试记录数据 + * @param reqDTO 包含分页信息和考试记录查询条件的请求对象,泛型为 UserExamReqDTO + * @return 包含分页结果的考试记录响应对象集合,封装在 IPage 中 + */ + IPage myPaging(PagingReqDTO reqDTO); + + /** + * 考试完成后,将用户的考试成绩信息加入到考试记录中 + * 如果该用户针对此考试已有记录,则更新相关信息;若不存在,则创建新记录。 + * @param userId 参加考试的用户 ID + * @param examId 考试的 ID + * @param score 用户本次考试获得的分数 + * @param passed 用户是否通过本次考试 + */ + void joinResult(String userId, String examId, Integer score, boolean passed); +} diff --git a/user/exam/service/impl/UserExamServiceImpl.java b/user/exam/service/impl/UserExamServiceImpl.java new file mode 100644 index 0000000..1d0062a --- /dev/null +++ b/user/exam/service/impl/UserExamServiceImpl.java @@ -0,0 +1,116 @@ +package com.yf.exam.modules.user.exam.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.user.UserUtils; +import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; +import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; +import com.yf.exam.modules.user.exam.entity.UserExam; +import com.yf.exam.modules.user.exam.mapper.UserExamMapper; +import com.yf.exam.modules.user.exam.service.UserExamService; +import org.springframework.stereotype.Service; + +import java.util.Date; + +/** + *

+ * 考试记录业务实现类,实现了 UserExamService 接口,处理考试记录相关的业务逻辑。 + * 继承自 MyBatis-Plus 的 ServiceImpl 类,可使用其提供的基础服务方法。 + *

+ * + * @author 聪明笨狗 + * @since 2020-09-21 15:13 + */ +@Service +public class UserExamServiceImpl extends ServiceImpl implements UserExamService { + + /** + * 分页查询考试记录 + * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserExamReqDTO + * @return 包含分页结果的 UserExamRespDTO 对象集合,封装在 IPage 中 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + // 调用 Mapper 层的分页查询方法,将请求的分页信息和查询参数传入 + // 并将查询结果转换为包含 UserExamRespDTO 的分页数据 + IPage pageData = baseMapper.paging(reqDTO.toPage(), reqDTO.getParams()); + return pageData; + } + + /** + * 分页查询当前用户的考试记录 + * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserExamReqDTO + * @return 包含分页结果的 UserExamRespDTO 对象集合,封装在 IPage 中 + */ + @Override + public IPage myPaging(PagingReqDTO reqDTO) { + // 获取请求中的查询参数 + UserExamReqDTO params = reqDTO.getParams(); + + // 若查询参数为空,则创建一个新的 UserExamReqDTO 对象 + if(params == null){ + params = new UserExamReqDTO(); + } + + // 设置当前用户的 ID 到查询参数中 + params.setUserId(UserUtils.getUserId()); + + // 调用 Mapper 层的分页查询方法,将请求的分页信息和更新后的查询参数传入 + // 并将查询结果转换为包含 UserExamRespDTO 的分页数据 + IPage pageData = baseMapper.paging(reqDTO.toPage(), params); + return pageData; + } + + /** + * 记录用户考试结果,更新或创建考试记录 + * @param userId 用户 ID + * @param examId 考试 ID + * @param score 用户本次考试得分 + * @param passed 用户是否通过本次考试 + */ + @Override + public void joinResult(String userId, String examId, Integer score, boolean passed) { + // 构建查询条件,根据用户 ID 和考试 ID 查询考试记录 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(UserExam::getUserId, userId) + .eq(UserExam::getExamId, examId); + + // 根据查询条件获取一条考试记录 + UserExam record = this.getOne(wrapper, false); + if(record == null){ + // 若记录不存在,则创建一条新的考试记录 + record = new UserExam(); + // 设置记录创建时间 + record.setCreateTime(new Date()); + // 设置记录更新时间 + record.setUpdateTime(new Date()); + // 设置用户 ID + record.setUserId(userId); + // 设置考试 ID + record.setExamId(examId); + // 设置最高分数为本次考试得分 + record.setMaxScore(score); + // 设置是否通过考试 + record.setPassed(passed); + // 保存新的考试记录 + this.save(record); + return; + } + + // 修复低分数不加入统计问题,增加考试次数 + record.setTryCount(record.getTryCount() + 1); + // 更新记录更新时间 + record.setUpdateTime(new Date()); + + // 若本次考试得分高于之前的最高分数,则更新最高分数和是否通过考试状态 + if(record.getMaxScore() < score){ + record.setMaxScore(score); + record.setPassed(passed); + } + + // 根据记录 ID 更新考试记录 + this.updateById(record); + } +} -- 2.34.1 From 2aa2e771b17d299132b92202af00fd4e3653e6d0 Mon Sep 17 00:00:00 2001 From: yutao <2930275373@qq.com> Date: Mon, 28 Apr 2025 22:06:43 +0800 Subject: [PATCH 4/4] LYT --- mapper/exam/ExamDepartMapper.xml | 30 ++ mapper/exam/ExamMapper.xml | 123 +++++ mapper/exam/ExamRepoMapper.xml | 60 +++ mapper/paper/PaperMapper.xml | 102 ++++ mapper/paper/PaperQuAnswerMapper.xml | 75 +++ mapper/paper/PaperQuMapper.xml | 94 ++++ mapper/qu/QuAnswerMapper.xml | 38 ++ mapper/qu/QuMapper.xml | 158 ++++++ mapper/qu/QuRepoMapper.xml | 38 ++ mapper/repo/RepoMapper.xml | 76 +++ mapper/sys/depart/SysDepartMapper.xml | 58 +++ mapper/sys/system/SysDictMapper.xml | 16 + mapper/sys/user/SysRoleMapper.xml | 34 ++ mapper/sys/user/SysUserMapper.xml | 48 ++ mapper/sys/user/SysUserRoleMapper.xml | 33 ++ mapper/user/UserBookMapper.xml | 46 ++ mapper/user/UserExamMapper.xml | 101 ++++ .../java/com/yf/exam/ability/Constant.java | 15 + .../yf/exam/ability/job/enums/JobGroup.java | 14 + .../yf/exam/ability/job/enums/JobPrefix.java | 14 + .../exam/ability/job/service/JobService.java | 52 ++ .../job/service/impl/JobServiceImpl.java | 153 ++++++ .../ability/shiro/CNFilterFactoryBean.java | 38 ++ .../com/yf/exam/ability/shiro/ShiroRealm.java | 129 +++++ .../yf/exam/ability/shiro/aop/JwtFilter.java | 68 +++ .../yf/exam/ability/shiro/jwt/JwtToken.java | 45 ++ .../yf/exam/ability/shiro/jwt/JwtUtils.java | 95 ++++ .../ability/upload/config/UploadConfig.java | 34 ++ .../upload/controller/UploadController.java | 58 +++ .../exam/ability/upload/dto/UploadReqDTO.java | 25 + .../ability/upload/dto/UploadRespDTO.java | 27 + .../ability/upload/service/UploadService.java | 32 ++ .../service/impl/UploadServiceImpl.java | 140 +++++ .../exam/ability/upload/utils/FileUtils.java | 159 ++++++ .../exam/ability/upload/utils/MediaUtils.java | 43 ++ .../java/com/yf/exam/aspect/DictAspect.java | 315 ++++++++++++ .../exam/aspect/mybatis/QueryInterceptor.java | 129 +++++ .../aspect/mybatis/UpdateInterceptor.java | 92 ++++ .../com/yf/exam/aspect/utils/InjectUtils.java | 110 ++++ .../java/com/yf/exam/config/CorsConfig.java | 50 ++ .../com/yf/exam/config/MultipartConfig.java | 28 + .../com/yf/exam/config/MybatisConfig.java | 37 ++ .../com/yf/exam/config/ScheduledConfig.java | 93 ++++ .../java/com/yf/exam/config/ShiroConfig.java | 153 ++++++ .../com/yf/exam/config/SwaggerConfig.java | 72 +++ .../java/com/yf/exam/core/annon/Dict.java | 32 ++ .../java/com/yf/exam/core/api/ApiError.java | 118 +++++ .../java/com/yf/exam/core/api/ApiRest.java | 64 +++ .../core/api/controller/BaseController.java | 160 ++++++ .../com/yf/exam/core/api/dto/BaseDTO.java | 15 + .../yf/exam/core/api/dto/BaseIdReqDTO.java | 32 ++ .../yf/exam/core/api/dto/BaseIdRespDTO.java | 28 + .../yf/exam/core/api/dto/BaseIdsReqDTO.java | 34 ++ .../yf/exam/core/api/dto/BaseStateReqDTO.java | 34 ++ .../yf/exam/core/api/dto/PagingReqDTO.java | 61 +++ .../yf/exam/core/api/dto/PagingRespDTO.java | 28 + .../yf/exam/core/api/utils/JsonConverter.java | 47 ++ .../com/yf/exam/core/enums/CommonState.java | 19 + .../java/com/yf/exam/core/enums/OpenType.java | 18 + .../exam/core/exception/ServiceException.java | 51 ++ .../exception/ServiceExceptionHandler.java | 46 ++ .../com/yf/exam/core/utils/BeanMapper.java | 59 +++ .../com/yf/exam/core/utils/CronUtils.java | 31 ++ .../com/yf/exam/core/utils/DateUtils.java | 103 ++++ .../java/com/yf/exam/core/utils/IpUtils.java | 65 +++ .../com/yf/exam/core/utils/Reflections.java | 324 ++++++++++++ .../com/yf/exam/core/utils/SpringUtils.java | 53 ++ .../com/yf/exam/core/utils/StringUtils.java | 42 ++ .../yf/exam/core/utils/excel/ExportExcel.java | 402 +++++++++++++++ .../yf/exam/core/utils/excel/ImportExcel.java | 303 +++++++++++ .../utils/excel/annotation/ExcelField.java | 59 +++ .../core/utils/excel/fieldtype/ListType.java | 62 +++ .../com/yf/exam/core/utils/file/Md5Util.java | 33 ++ .../exam/core/utils/passwd/PassHandler.java | 57 +++ .../yf/exam/core/utils/passwd/PassInfo.java | 70 +++ .../exam/controller/ExamController.java | 151 ++++++ .../com/yf/exam/modules/exam/dto/ExamDTO.java | 101 ++++ .../exam/modules/exam/dto/ExamDepartDTO.java | 33 ++ .../yf/exam/modules/exam/dto/ExamRepoDTO.java | 51 ++ .../modules/exam/dto/ext/ExamRepoExtDTO.java | 32 ++ .../exam/dto/request/ExamSaveReqDTO.java | 32 ++ .../exam/dto/response/ExamOnlineRespDTO.java | 22 + .../exam/dto/response/ExamReviewRespDTO.java | 31 ++ .../com/yf/exam/modules/exam/entity/Exam.java | 100 ++++ .../exam/modules/exam/entity/ExamDepart.java | 42 ++ .../yf/exam/modules/exam/entity/ExamRepo.java | 78 +++ .../modules/exam/mapper/ExamDepartMapper.java | 15 + .../exam/modules/exam/mapper/ExamMapper.java | 45 ++ .../modules/exam/mapper/ExamRepoMapper.java | 26 + .../exam/service/ExamDepartService.java | 32 ++ .../modules/exam/service/ExamRepoService.java | 40 ++ .../modules/exam/service/ExamService.java | 63 +++ .../service/impl/ExamDepartServiceImpl.java | 66 +++ .../service/impl/ExamRepoServiceImpl.java | 67 +++ .../exam/service/impl/ExamServiceImpl.java | 194 +++++++ .../paper/controller/PaperController.java | 159 ++++++ .../yf/exam/modules/paper/dto/PaperDTO.java | 148 ++++++ .../modules/paper/dto/PaperQuAnswerDTO.java | 79 +++ .../yf/exam/modules/paper/dto/PaperQuDTO.java | 93 ++++ .../paper/dto/ext/PaperQuAnswerExtDTO.java | 35 ++ .../paper/dto/ext/PaperQuDetailDTO.java | 43 ++ .../paper/dto/request/PaperAnswerDTO.java | 30 ++ .../paper/dto/request/PaperCreateReqDTO.java | 31 ++ .../paper/dto/request/PaperListReqDTO.java | 56 ++ .../paper/dto/request/PaperQuQueryDTO.java | 30 ++ .../paper/dto/response/ExamDetailRespDTO.java | 57 +++ .../paper/dto/response/ExamResultRespDTO.java | 25 + .../paper/dto/response/PaperListRespDTO.java | 30 ++ .../yf/exam/modules/paper/entity/Paper.java | 125 +++++ .../yf/exam/modules/paper/entity/PaperQu.java | 80 +++ .../modules/paper/entity/PaperQuAnswer.java | 68 +++ .../exam/modules/paper/enums/ExamState.java | 33 ++ .../exam/modules/paper/enums/PaperState.java | 33 ++ .../exam/modules/paper/job/BreakExamJob.java | 57 +++ .../modules/paper/mapper/PaperMapper.java | 39 ++ .../paper/mapper/PaperQuAnswerMapper.java | 27 + .../modules/paper/mapper/PaperQuMapper.java | 42 ++ .../paper/service/PaperQuAnswerService.java | 44 ++ .../modules/paper/service/PaperQuService.java | 70 +++ .../modules/paper/service/PaperService.java | 83 +++ .../impl/PaperQuAnswerServiceImpl.java | 61 +++ .../service/impl/PaperQuServiceImpl.java | 94 ++++ .../paper/service/impl/PaperServiceImpl.java | 477 ++++++++++++++++++ 123 files changed, 9170 insertions(+) create mode 100644 mapper/exam/ExamDepartMapper.xml create mode 100644 mapper/exam/ExamMapper.xml create mode 100644 mapper/exam/ExamRepoMapper.xml create mode 100644 mapper/paper/PaperMapper.xml create mode 100644 mapper/paper/PaperQuAnswerMapper.xml create mode 100644 mapper/paper/PaperQuMapper.xml create mode 100644 mapper/qu/QuAnswerMapper.xml create mode 100644 mapper/qu/QuMapper.xml create mode 100644 mapper/qu/QuRepoMapper.xml create mode 100644 mapper/repo/RepoMapper.xml create mode 100644 mapper/sys/depart/SysDepartMapper.xml create mode 100644 mapper/sys/system/SysDictMapper.xml create mode 100644 mapper/sys/user/SysRoleMapper.xml create mode 100644 mapper/sys/user/SysUserMapper.xml create mode 100644 mapper/sys/user/SysUserRoleMapper.xml create mode 100644 mapper/user/UserBookMapper.xml create mode 100644 mapper/user/UserExamMapper.xml create mode 100644 src/main/java/com/yf/exam/ability/Constant.java create mode 100644 src/main/java/com/yf/exam/ability/job/enums/JobGroup.java create mode 100644 src/main/java/com/yf/exam/ability/job/enums/JobPrefix.java create mode 100644 src/main/java/com/yf/exam/ability/job/service/JobService.java create mode 100644 src/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java create mode 100644 src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java create mode 100644 src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java create mode 100644 src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java create mode 100644 src/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java create mode 100644 src/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java create mode 100644 src/main/java/com/yf/exam/ability/upload/config/UploadConfig.java create mode 100644 src/main/java/com/yf/exam/ability/upload/controller/UploadController.java create mode 100644 src/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java create mode 100644 src/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java create mode 100644 src/main/java/com/yf/exam/ability/upload/service/UploadService.java create mode 100644 src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java create mode 100644 src/main/java/com/yf/exam/ability/upload/utils/FileUtils.java create mode 100644 src/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java create mode 100644 src/main/java/com/yf/exam/aspect/DictAspect.java create mode 100644 src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java create mode 100644 src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java create mode 100644 src/main/java/com/yf/exam/aspect/utils/InjectUtils.java create mode 100644 src/main/java/com/yf/exam/config/CorsConfig.java create mode 100644 src/main/java/com/yf/exam/config/MultipartConfig.java create mode 100644 src/main/java/com/yf/exam/config/MybatisConfig.java create mode 100644 src/main/java/com/yf/exam/config/ScheduledConfig.java create mode 100644 src/main/java/com/yf/exam/config/ShiroConfig.java create mode 100644 src/main/java/com/yf/exam/config/SwaggerConfig.java create mode 100644 src/main/java/com/yf/exam/core/annon/Dict.java create mode 100644 src/main/java/com/yf/exam/core/api/ApiError.java create mode 100644 src/main/java/com/yf/exam/core/api/ApiRest.java create mode 100644 src/main/java/com/yf/exam/core/api/controller/BaseController.java create mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseDTO.java create mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java create mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java create mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java create mode 100644 src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java create mode 100644 src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java create mode 100644 src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java create mode 100644 src/main/java/com/yf/exam/core/api/utils/JsonConverter.java create mode 100644 src/main/java/com/yf/exam/core/enums/CommonState.java create mode 100644 src/main/java/com/yf/exam/core/enums/OpenType.java create mode 100644 src/main/java/com/yf/exam/core/exception/ServiceException.java create mode 100644 src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java create mode 100644 src/main/java/com/yf/exam/core/utils/BeanMapper.java create mode 100644 src/main/java/com/yf/exam/core/utils/CronUtils.java create mode 100644 src/main/java/com/yf/exam/core/utils/DateUtils.java create mode 100644 src/main/java/com/yf/exam/core/utils/IpUtils.java create mode 100644 src/main/java/com/yf/exam/core/utils/Reflections.java create mode 100644 src/main/java/com/yf/exam/core/utils/SpringUtils.java create mode 100644 src/main/java/com/yf/exam/core/utils/StringUtils.java create mode 100644 src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java create mode 100644 src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java create mode 100644 src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java create mode 100644 src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java create mode 100644 src/main/java/com/yf/exam/core/utils/file/Md5Util.java create mode 100644 src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java create mode 100644 src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java create mode 100644 src/main/java/com/yf/exam/modules/exam/controller/ExamController.java create mode 100644 src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java create mode 100644 src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java create mode 100644 src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java create mode 100644 src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java create mode 100644 src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java create mode 100644 src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java create mode 100644 src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java create mode 100644 src/main/java/com/yf/exam/modules/exam/entity/Exam.java create mode 100644 src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java create mode 100644 src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java create mode 100644 src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java create mode 100644 src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java create mode 100644 src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java create mode 100644 src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java create mode 100644 src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java create mode 100644 src/main/java/com/yf/exam/modules/exam/service/ExamService.java create mode 100644 src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java create mode 100644 src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java create mode 100644 src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java create mode 100644 src/main/java/com/yf/exam/modules/paper/controller/PaperController.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java create mode 100644 src/main/java/com/yf/exam/modules/paper/entity/Paper.java create mode 100644 src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java create mode 100644 src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java create mode 100644 src/main/java/com/yf/exam/modules/paper/enums/ExamState.java create mode 100644 src/main/java/com/yf/exam/modules/paper/enums/PaperState.java create mode 100644 src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java create mode 100644 src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java create mode 100644 src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java create mode 100644 src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java create mode 100644 src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java create mode 100644 src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java create mode 100644 src/main/java/com/yf/exam/modules/paper/service/PaperService.java create mode 100644 src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java create mode 100644 src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java create mode 100644 src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java diff --git a/mapper/exam/ExamDepartMapper.xml b/mapper/exam/ExamDepartMapper.xml new file mode 100644 index 0000000..d4fa71f --- /dev/null +++ b/mapper/exam/ExamDepartMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`exam_id`,`depart_id` + + + diff --git a/mapper/exam/ExamMapper.xml b/mapper/exam/ExamMapper.xml new file mode 100644 index 0000000..e9e0edf --- /dev/null +++ b/mapper/exam/ExamMapper.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`title`,`content`,`open_type`,`join_type`,`level`,`state`,`time_limit`,`start_time`,`end_time`,`create_time`,`update_time`,`total_score`,`total_time`,`qualify_score` + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mapper/exam/ExamRepoMapper.xml b/mapper/exam/ExamRepoMapper.xml new file mode 100644 index 0000000..1058901 --- /dev/null +++ b/mapper/exam/ExamRepoMapper.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`exam_id`,`repo_id`,`radio_count`,`radio_score`,`multi_count`,`multi_score`,`judge_count`,`judge_score` + + + + + + + + + + + + + + + + diff --git a/mapper/paper/PaperMapper.xml b/mapper/paper/PaperMapper.xml new file mode 100644 index 0000000..a09169d --- /dev/null +++ b/mapper/paper/PaperMapper.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`user_id`,`depart_id`,`exam_id`,`title`,`total_time`,`user_time`,`total_score`,`qualify_score`,`obj_score`,`subj_score`,`user_score`,`has_saq`,`state`,`create_time`,`update_time`,`limit_time` + + + + + + + + + + + + diff --git a/mapper/paper/PaperQuAnswerMapper.xml b/mapper/paper/PaperQuAnswerMapper.xml new file mode 100644 index 0000000..87e2e64 --- /dev/null +++ b/mapper/paper/PaperQuAnswerMapper.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`paper_id`,`answer_id`,`qu_id`,`is_right`,`checked`,`sort`,`abc` + + + + + + + + + + + + + + + + + + + + + diff --git a/mapper/paper/PaperQuMapper.xml b/mapper/paper/PaperQuMapper.xml new file mode 100644 index 0000000..d3a45f5 --- /dev/null +++ b/mapper/paper/PaperQuMapper.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`paper_id`,`qu_id`,`qu_type`,`answered`,`answer`,`sort`,`score`,`actual_score`,`is_right` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mapper/qu/QuAnswerMapper.xml b/mapper/qu/QuAnswerMapper.xml new file mode 100644 index 0000000..6c81bb4 --- /dev/null +++ b/mapper/qu/QuAnswerMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + `id`,`qu_id`,`is_right`,`image`,`content`,`analysis` + + + diff --git a/mapper/qu/QuMapper.xml b/mapper/qu/QuMapper.xml new file mode 100644 index 0000000..cbb2321 --- /dev/null +++ b/mapper/qu/QuMapper.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`qu_type`,`level`,`image`,`content`,`create_time`,`update_time`,`remark`,`analysis` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AND q.qu_type = #{query.quType} + + + + + AND po.repo_id IN + + #{repoId} + + + + + AND q.content LIKE CONCAT('%',#{query.content},'%') + + + + + AND q.id NOT IN + + + #{quId} + + + + + + + + + + + + + diff --git a/mapper/qu/QuRepoMapper.xml b/mapper/qu/QuRepoMapper.xml new file mode 100644 index 0000000..e883c6f --- /dev/null +++ b/mapper/qu/QuRepoMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + `id`,`qu_id`,`repo_id`,`qu_type`,`sort` + + + + diff --git a/mapper/repo/RepoMapper.xml b/mapper/repo/RepoMapper.xml new file mode 100644 index 0000000..1d87759 --- /dev/null +++ b/mapper/repo/RepoMapper.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`code`,`title`,`radio_count`,`multi_count`,`judge_count`,`remark`,`create_time`,`update_time` + + + + + + + + + + + + + + + + diff --git a/mapper/sys/depart/SysDepartMapper.xml b/mapper/sys/depart/SysDepartMapper.xml new file mode 100644 index 0000000..38992f6 --- /dev/null +++ b/mapper/sys/depart/SysDepartMapper.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`dept_type`,`parent_id`,`dept_name`,`dept_code`,`sort` + + + + + + + + + + + + + + + + + diff --git a/mapper/sys/system/SysDictMapper.xml b/mapper/sys/system/SysDictMapper.xml new file mode 100644 index 0000000..59996cb --- /dev/null +++ b/mapper/sys/system/SysDictMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/mapper/sys/user/SysRoleMapper.xml b/mapper/sys/user/SysRoleMapper.xml new file mode 100644 index 0000000..5547d55 --- /dev/null +++ b/mapper/sys/user/SysRoleMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + `id`,`role_name` + + + diff --git a/mapper/sys/user/SysUserMapper.xml b/mapper/sys/user/SysUserMapper.xml new file mode 100644 index 0000000..a9965db --- /dev/null +++ b/mapper/sys/user/SysUserMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 定义需要查询的列,使用反引号避免与 SQL 关键字冲突 + `id`,`user_name`,`real_name`,`password`,`salt`,`role_ids`,`depart_id`,`create_time`,`update_time`,`state` + + + diff --git a/mapper/sys/user/SysUserRoleMapper.xml b/mapper/sys/user/SysUserRoleMapper.xml new file mode 100644 index 0000000..6a6790d --- /dev/null +++ b/mapper/sys/user/SysUserRoleMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + `id`,`user_id`,`role_id` + + + diff --git a/mapper/user/UserBookMapper.xml b/mapper/user/UserBookMapper.xml new file mode 100644 index 0000000..943aedb --- /dev/null +++ b/mapper/user/UserBookMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 定义需要查询的列,使用反引号避免与 SQL 关键字冲突 + `id`,`exam_id`,`user_id`,`qu_id`,`create_time`,`update_time`,`wrong_count`,`title`,`sort` + + + diff --git a/mapper/user/UserExamMapper.xml b/mapper/user/UserExamMapper.xml new file mode 100644 index 0000000..51943ae --- /dev/null +++ b/mapper/user/UserExamMapper.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`user_id`,`exam_id`,`try_count`,`max_score`,`passed`,`create_time`,`update_time` + + + + + + + + + + + + + diff --git a/src/main/java/com/yf/exam/ability/Constant.java b/src/main/java/com/yf/exam/ability/Constant.java new file mode 100644 index 0000000..91b40a7 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/Constant.java @@ -0,0 +1,15 @@ +package com.yf.exam.ability; + + + //通用常量类 + //该类定义了应用程序中使用的通用常量。 + // @author bool + +public class Constant { + + + //文件上传路径 + //该常量定义了文件上传时的默认路径前缀。 + + public static final String FILE_PREFIX = "/upload/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 new file mode 100644 index 0000000..9b5f078 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/job/enums/JobGroup.java @@ -0,0 +1,14 @@ +package com.yf.exam.ability.job.enums; + +/** + * 任务分组 + * @author van + */ +public interface JobGroup { + + //定义了一个常量字符串,表示系统任务。 + //这个常量可以用于标识在系统中执行的任务组,通常用于任务调度和管理。 + + String SYSTEM = "system"; +} + 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 new file mode 100644 index 0000000..404a996 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/job/enums/JobPrefix.java @@ -0,0 +1,14 @@ +package com.yf.exam.ability.job.enums; + +/** + * 任务前缀 + * @author bool + */ +public interface JobPrefix { + + //定义了一个常量字符串,表示强制交卷的操作前缀。 + //这个常量可以用于标识在系统中强制交卷的任务或操作,通常用于考试系统中的任务管理。 + + String BREAK_EXAM = "break_exam_"; +} + 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 new file mode 100644 index 0000000..c48c118 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/job/service/JobService.java @@ -0,0 +1,52 @@ +package com.yf.exam.ability.job.service; + + + //任务业务类,用于动态处理任务信息 + //@author bool + //@date 2020/11/29 下午2:17 + +public interface JobService { + + + + //任务数据 + + String TASK_DATA = "taskData"; + + + //添加定时任务 + //@param jobClass + //@param jobName + //@param cron + //@param data + + void addCronJob(Class jobClass, String jobName, String cron, String data); + + + //添加立即执行的任务 + //@param jobClass + //@param jobName + //@param data + + void addCronJob(Class jobClass, String jobName, String data); + + + //暂停任务 + //@param jobName + //@param jobGroup + + void pauseJob(String jobName, String jobGroup); + + + //恢复任务 + //@param triggerName + //@param triggerGroup + + void resumeJob(String triggerName, String triggerGroup); + + //删除job + //@param jobName + //@param jobGroup + + void deleteJob(String jobName, String jobGroup); +} 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 new file mode 100644 index 0000000..d8152e2 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java @@ -0,0 +1,153 @@ +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; + +/** + * Quartz定时任务服务实现类 + * @author bool + */ +@Log4j2 +@Service +public class JobServiceImpl implements JobService { + + + //Quartz调度器,用于管理定时任务 + + private Scheduler scheduler; + + + //构造函数,注入SchedulerFactoryBean并初始化scheduler + //@param schedulerFactoryBean Spring的SchedulerFactoryBean实例 + + public JobServiceImpl(@Autowired SchedulerFactoryBean schedulerFactoryBean) { + scheduler = schedulerFactoryBean.getScheduler(); + } + + + //添加一个新的定时任务 + //@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; + // 如果jobName为空,则自动生成一个唯一名称 + if(StringUtils.isEmpty(jobName)){ + 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); + // 构建新的JobDetail实例 + jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build(); + // 使用JobDataMap传递数据给任务 + jobDetail.getJobDataMap().put(TASK_DATA, data); + // 构建Trigger实例 + Trigger trigger = null; + // 如果cron表达式不为空,则使用cron表达式构建Trigger + if(!StringUtils.isEmpty(cron)){ + log.info("+++++表达式执行:"+ JSON.toJSONString(jobDetail)); + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); + trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build(); + } else { + // 如果cron表达式为空,则立即执行任务 + log.info("+++++立即执行:"+ JSON.toJSONString(jobDetail)); + trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).startNow().build(); + } + // 将JobDetail和Trigger添加到调度器中 + scheduler.scheduleJob(jobDetail, trigger); + } catch (Exception e) { + // 打印异常堆栈信息 + e.printStackTrace(); + } + } + + + //添加一个新的立即执行的定时任务 + //@param jobClass 定时任务的类 + //@param jobName 定时任务的名称 + //@param data 传递给任务的数据 + + @Override + public void addCronJob(Class jobClass, String jobName, String data) { + // 调用addCronJob方法,设置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.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.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.jobKey(jobName, jobGroup); + // 使用调度器删除任务 + scheduler.deleteJob(jobKey); + // 记录日志,表示任务已成功删除 + log.info("++++++++++删除任务:{}", jobKey); + } catch (SchedulerException e) { + // 打印调度异常堆栈信息 + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java b/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java new file mode 100644 index 0000000..0c9b7a3 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java @@ -0,0 +1,38 @@ +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 javax.servlet.Filter; +import java.util.Map; + + + //自定义过滤器工厂类,用于处理包含中文字符的URL问题 + //当URL中包含中文字符时,Shiro默认会返回400错误,这是因为InvalidRequestFilter会阻止非ASCII字符的请求。 + //该自定义过滤器重写了createFilterChainManager方法,修改了InvalidRequestFilter的配置,允许非ASCII字符的请求。 + //例如:https://youdomain.com/upload/file/云帆考试系统用户手册.pdf + //@author van + +public class CNFilterFactoryBean extends ShiroFilterFactoryBean { + + //重写createFilterChainManager方法,以允许包含中文字符的URL请求 + //@return 自定义的FilterChainManager + + @Override + protected FilterChainManager createFilterChainManager() { + // 调用父类方法创建FilterChainManager + FilterChainManager manager = super.createFilterChainManager(); + // 获取所有过滤器 + Map filterMap = manager.getFilters(); + // 获取InvalidRequestFilter + Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); + // 检查invalidRequestFilter是否为InvalidRequestFilter实例 + if (invalidRequestFilter instanceof InvalidRequestFilter) { + // 设置InvalidRequestFilter允许非ASCII字符的请求 + ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false); + } + // 返回自定义的FilterChainManager + return manager; + } +} diff --git a/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java b/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java new file mode 100644 index 0000000..df91d62 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java @@ -0,0 +1,129 @@ +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; + + + //用户登录鉴权和获取用户授权 + //@author bool + +@Component +@Slf4j +public class ShiroRealm extends AuthorizingRealm { + @Autowired + @Lazy + private SysUserService sysUserService; // 注入用户服务,用于处理用户信息相关操作 + + @Autowired + @Lazy + private SysUserRoleService sysUserRoleService; // 注入用户角色服务,用于处理用户角色相关操作 + + + //判断该Realm是否支持指定的AuthenticationToken。 + //仅支持JwtToken类型的token。 + + //@param token 需要验证的AuthenticationToken对象。 + //@return 如果支持该token类型则返回true,否则返回false。 + + @Override + public boolean supports(AuthenticationToken token) { + return token instanceof JwtToken; + } + + + //获取指定用户主体的授权信息。 + //该方法从用户主体中获取用户ID,然后查询与该用户关联的角色信息,并返回授权信息。 + + //@param principals 用户主体集合,包含了用户的权限信息。 + //@return 包含用户角色信息的AuthorizationInfo对象。 + + @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; + } + + + //校验用户的账号密码是否正确。 + //该方法通过获取token并调用checkToken方法验证token的有效性,然后返回认证信息。 + + //@param auth 包含用户认证信息的AuthenticationToken对象。 + //@return 包含认证信息的AuthenticationInfo对象。 + //@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的有效性。 + //该方法从token中提取用户名,并验证token是否有效。如果无效则抛出异常。 + + //@param token 需要验证的token字符串。 + //@return 包含用户信息的SysUserLoginDTO对象。 + //@throws AuthenticationException 如果token无效或已过期则抛出此异常。 + + 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; + } + + + //清除指定用户主体的权限认证缓存。 + //该方法调用父类的clearCache方法来实现缓存清除功能。 + + //@param principals 用户主体集合,包含了用户的权限信息。 + + @Override + public void clearCache(PrincipalCollection principals) { + super.clearCache(principals); + } +} + 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 new file mode 100644 index 0000000..ba02f75 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java @@ -0,0 +1,68 @@ +package com.yf.exam.ability.shiro.aop; + +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; + + + //鉴权登录拦截器,用于处理JWT(JSON Web Token)认证 + //@author bool + +@Slf4j +public class JwtFilter extends BasicHttpAuthenticationFilter { + + + //检查用户是否被允许访问资源 + //@param request 包含用户请求信息的ServletRequest对象 + //@param response 包含用户响应信息的ServletResponse对象 + // @param mappedValue 在Shiro配置中映射的值,通常用于权限检查 + //@return 如果用户成功登录并被允许访问资源,则返回true;否则返回false + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + try { + // 调用executeLogin方法执行登录认证 + executeLogin(request, response); + // 如果登录成功,返回true表示用户被允许访问 + return true; + } catch (Exception e) { + // 如果发生异常,调用InjectUtils.restError方法写出统一错误信息 + InjectUtils.restError((HttpServletResponse) response); + // 返回false表示用户未被允许访问 + return false; + } + } + + + //执行JWT登录认证 + //@param request 包含用户请求信息的ServletRequest对象 + //@param response 包含用户响应信息的ServletResponse对象 + // @return 如果登录成功,则返回true;否则抛出异常 + //@throws Exception 如果登录过程中发生异常 + + @Override + protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + // 从请求头中获取JWT token + String token = httpServletRequest.getHeader(Constant.TOKEN); + // 创建JwtToken对象 + JwtToken jwtToken = new JwtToken(token); + try { + // 提交给realm进行登录认证,如果认证失败会抛出异常 + getSubject(request, response).login(jwtToken); + // 如果没有抛出异常则代表登录成功,返回true + return true; + } catch (Exception e) { + // 如果发生异常,记录日志 + log.error("JWT登录认证失败: ", e); + // 抛出异常以便在调用处捕获并处理 + throw e; + } + } +} 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 new file mode 100644 index 0000000..969b715 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java @@ -0,0 +1,45 @@ +package com.yf.exam.ability.shiro.jwt; + +import lombok.Data; +import org.apache.shiro.authc.AuthenticationToken; + +/** + * JWT(JSON Web Token)认证令牌,用于Shiro认证过程 + * @author bool + */ +@Data +public class JwtToken implements AuthenticationToken { + + private static final long serialVersionUID = 1L; + + /** + * JWT的字符token,用于认证用户身份 + */ + private String token; + + /** + * 构造函数,初始化JWT token + * @param token JWT的字符token + */ + public JwtToken(String token) { + this.token = token; + } + + /** + * 获取认证主体,这里返回JWT token + * @return JWT token + */ + @Override + public Object getPrincipal() { + return token; + } + + /** + * 获取认证凭证,这里返回JWT token + * @return JWT token + */ + @Override + public Object getCredentials() { + return token; + } +} 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 new file mode 100644 index 0000000..1ca221f --- /dev/null +++ b/src/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java @@ -0,0 +1,95 @@ +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 java.util.Calendar; +import java.util.Date; + +/** + * JWT工具类,用于处理JWT(JSON Web Token)的生成、验证和解析 + * @author bool + */ +public class JwtUtils { + /** + * 有效期24小时(以毫秒为单位) + */ + private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; + + /** + * 校验JWT token是否正确 + * @param token JWT token字符串 + * @param username 用户名 + * @return 如果token有效且用户名匹配,则返回true;否则返回false + */ + public static boolean verify(String token, String username) { + try { + // 根据用户名生成HMAC256算法的密钥 + Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); + // 创建JWT验证器,并指定需要验证的用户名声明 + JWTVerifier verifier = JWT.require(algorithm) + .withClaim("username", username) + .build(); + // 验证token + verifier.verify(token); + return true; + } catch (Exception exception) { + return false; + } + } + + /** + * 从JWT token中解密并获取用户名 + * @param token JWT token字符串 + * @return 如果解析成功,则返回用户名;否则返回null + */ + public static String getUsername(String token) { + try { + // 解码JWT token + DecodedJWT jwt = JWT.decode(token); + // 获取用户名声明 + return jwt.getClaim("username").asString(); + } catch (JWTDecodeException e) { + return null; + } + } + + /** + * 生成JWT token字符串 + * @param username 用户名 + * @return 生成的JWT token字符串 + */ + public static String sign(String username) { + // 计算token的过期时间 + Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); + // 根据用户名生成HMAC256算法的密钥 + Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); + // 创建JWT并附带用户名信息及过期时间 + return JWT.create() + .withClaim("username", username) + .withExpiresAt(date) + .sign(algorithm); + } + + /** + * 根据用户名生成一个新的密钥,用于增强JWT的安全性 + * @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)); + // 对加密串进行MD5哈希 + String secret = Md5Util.md5(sb.toString()); + // 再次对用户名和上一步的MD5哈希结果进行MD5哈希,生成最终的密钥 + return Md5Util.md5(userName + "&" + secret); + } +} 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 new file mode 100644 index 0000000..00c8207 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/upload/config/UploadConfig.java @@ -0,0 +1,34 @@ +package com.yf.exam.ability.upload.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + + + //文件上传配置类,用于定义文件上传的相关配置属性。 + //这些属性可以通过application.properties或application.yml文件进行配置。 + + //@author van + +@Data +@Configuration +@ConfigurationProperties(prefix = "conf.upload") +public class UploadConfig { + + //文件上传的访问路径,用户可以通过该路径访问上传的文件。 + //例如:http://example.com/upload/ + + private String url; + + + //文件上传的物理目录路径,指定文件在服务器上的存储位置。 + //例如:/var/www/upload/ + + private String dir; + + + //允许上传的文件后缀名数组,定义了哪些类型的文件可以被上传。 + //例如:{"jpg", "png", "pdf"} + + private String[] allowExtensions; +} 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 new file mode 100644 index 0000000..a54e6fa --- /dev/null +++ b/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java @@ -0,0 +1,58 @@ +package com.yf.exam.ability.upload.controller; + +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; + + + //本地文件上传下载请求类 + //该类提供文件上传和下载的功能,继承自BaseController。 + //@author bool + +@Log4j2 +@Api(tags = {"文件上传"}) +@RestController +public class UploadController extends BaseController { + + @Autowired + private UploadService uploadService; + + + //文件上传 + //该方法处理文件上传请求,参数通过表单方式提交。 + //@param reqDTO 包含上传文件信息的请求DTO + //@return 包含上传文件结果的响应DTO + + @PostMapping("/common/api/file/upload") + @ApiOperation(value = "文件上传", notes = "此接口较为特殊,参数都通过表单方式提交,而非JSON") + public ApiRest upload(@ModelAttribute UploadReqDTO reqDTO) { + // 上传并返回URL + UploadRespDTO respDTO = uploadService.upload(reqDTO); + return super.success(respDTO); + } + + + //独立文件下载 + //该方法处理文件下载请求。 + //@param request HTTP请求对象 + //@param response HTTP响应对象 + + @GetMapping(Constant.FILE_PREFIX + "**") + @ApiOperation(value = "文件下载", notes = "文件下载") + public void download(HttpServletRequest request, HttpServletResponse response) { + uploadService.download(request, response); + } +} 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 new file mode 100644 index 0000000..1879fcb --- /dev/null +++ b/src/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java @@ -0,0 +1,25 @@ +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; + + + //文件上传请求类。 + //该类用于封装文件上传请求的参数,继承自BaseDTO。 + + // @author bool + // @date 2019-12-26 17:54 + +@Data +@ApiModel(value = "文件上传参数", description = "包含上传文件信息的请求参数") +public class UploadReqDTO extends BaseDTO { + + //上传文件的内容。 + //该字段是必填项,包含了用户需要上传的文件。 + //@ApiModelProperty(value = "上传文件内容", required = true) + + private MultipartFile 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 new file mode 100644 index 0000000..6aa5903 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java @@ -0,0 +1,27 @@ +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; + + + //文件上传响应类。 + //该类用于封装文件上传后的响应信息,继承自BaseDTO。 + + // @author bool + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ApiModel(value = "文件上传响应", description = "包含上传文件后的URL地址的响应信息") +public class UploadRespDTO extends BaseDTO { + + //上传后的完整的URL地址。 + //该字段是必填项,包含了上传文件后可以访问的URL。 + //@ApiModelProperty(value = "上传后的完整的URL地址", required = true) + + private String url; +} 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 new file mode 100644 index 0000000..3a89174 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/upload/service/UploadService.java @@ -0,0 +1,32 @@ +package com.yf.exam.ability.upload.service; + +import com.yf.exam.ability.upload.dto.UploadReqDTO; +import com.yf.exam.ability.upload.dto.UploadRespDTO; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + + //阿里云OSS业务类 + //该接口定义了文件上传和下载的功能,具体实现由其实现类提供。 + //@author bool + //@date 2019-07-12 16:45 + +public interface UploadService { + + + //处理文件上传请求 + //@param reqDTO 文件上传请求对象,包含上传的文件信息 + //@return 文件上传响应对象,包含上传文件后的URL地址 + + UploadRespDTO upload(UploadReqDTO reqDTO); + + + //处理文件下载请求 + //@param request HTTP请求对象,包含下载文件的URI信息 + //@param response HTTP响应对象,用于返回下载的文件内容 + //@throws RuntimeException 如果下载过程中发生错误 + + void download(HttpServletRequest request, HttpServletResponse response); + +} 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 new file mode 100644 index 0000000..6a750ef --- /dev/null +++ b/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java @@ -0,0 +1,140 @@ +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; + + + //文件上传业务类。 + //该类实现了文件上传和下载的功能,继承自UploadService接口。 + + //@author bool + //@date 2019-07-30 21:02 + +@Log4j2 +@Service +public class UploadServiceImpl implements UploadService { + + @Autowired + private UploadConfig conf; + + + //处理文件上传请求。 + + // @param reqDTO 文件上传请求对象,包含上传的文件信息。 + //@return 文件上传响应对象,包含上传文件后的URL地址。 + //@throws ServiceException 如果文件类型不允许上传或上传过程中发生IO异常。 + + @Override + public UploadRespDTO upload(UploadReqDTO reqDTO) { + // 获取上传文件的内容 + MultipartFile file = reqDTO.getFile(); + // 验证文件后缀是否在允许的范围内 + boolean allow = FilenameUtils.isExtension(file.getOriginalFilename(), conf.getAllowExtensions()); + if (!allow) { + throw new ServiceException("文件类型不允许上传!"); + } + // 获取上传文件夹的路径 + String fileDir = conf.getDir(); + // 构造真实物理地址 + String fullPath; + try { + // 处理文件路径 + String filePath = FileUtils.processPath(file); + // 构造文件保存地址 + fullPath = fileDir + filePath; + // 创建文件夹(如果不存在) + FileUtils.checkDir(fullPath); + // 上传文件到指定路径 + FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(fullPath)); + // 生成并返回上传结果 + return this.generateResult(filePath); + } catch (IOException e) { + e.printStackTrace(); + throw new ServiceException("文件上传失败:" + e.getMessage()); + } + } + + + //处理文件下载请求。 + + //@param request HTTP请求对象,包含请求的URI。 + //@param response HTTP响应对象,用于返回文件内容。 + //@throws RuntimeException 如果URL解码过程中发生UnsupportedEncodingException。 + + @Override + public void download(HttpServletRequest request, HttpServletResponse response) { + // 获取真实的文件路径 + String filePath = this.getRealPath(request.getRequestURI()); + // 处理中文文件名解码问题 + try { + filePath = URLDecoder.decode(filePath, "utf-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + // 输出完整路径到控制台(调试使用) + System.out.println("++++完整路径为:" + filePath); + try { + // 将文件内容写入HTTP响应 + FileUtils.writeRange(request, response, filePath); + } catch (IOException e) { + // 如果文件不存在,设置HTTP响应状态为404 + response.setStatus(404); + // 记录错误日志 + log.error("预览文件失败:" + e.getMessage()); + } + } + + + //构造文件上传响应结果。 + + //@param fileName 上传文件的路径。 + //@return 文件上传响应对象,包含上传文件后的完整URL地址。 + + private UploadRespDTO generateResult(String fileName) { + // 获取加速域名 + String domain = conf.getUrl(); + // 构造并返回文件上传响应对象 + return new UploadRespDTO(domain + fileName); + } + + + //获取真实物理文件地址。 + //@param uri 请求的URI,包含文件的相对路径。 + //@return 真实物理文件的完整路径。 + + public String getRealPath(String uri) { + // 定义正则表达式,匹配文件路径 + String regx = Constant.FILE_PREFIX + "(.*)"; + // 查找匹配的文件路径 + Pattern pattern = Pattern.compile(regx); + Matcher m = pattern.matcher(uri); + if (m.find()) { + // 获取匹配的文件路径部分 + String str = m.group(1); + // 构造真实物理文件的完整路径 + return conf.getDir() + str; + } + // 如果未找到匹配路径,返回null + return null; + } +} 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 new file mode 100644 index 0000000..dd92e55 --- /dev/null +++ b/src/main/java/com/yf/exam/ability/upload/utils/FileUtils.java @@ -0,0 +1,159 @@ +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 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; + + + //文件工具类 + //该类提供了文件上传、下载和处理的相关工具方法。 + //@author bool + +public class FileUtils { + + + //后缀分割符号 + + private static final String SUFFIX_SPLIT = "."; + + + //持以断点的方式输出文件,提供文件在线预览和视频在线播放 + //@param request HTTP请求对象,包含请求头信息如Range + //@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"); + 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 -= len; + } + + 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) { + // 获取原始文件名 + 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(); + } + } +} 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 new file mode 100644 index 0000000..e1ff58f --- /dev/null +++ b/src/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java @@ -0,0 +1,43 @@ +package com.yf.exam.ability.upload.utils; + +import org.apache.commons.lang3.StringUtils; +import java.util.HashMap; +import java.util.Map; + + + //媒体工具类,用于判断文件的媒体类型 + //该类包含一个媒体类型映射表,并提供获取文件内容类型的方法。 + //@author bool + //@date 2019-11-14 16:21 + +public class MediaUtils { + + + //媒体类型映射表 + //该映射表存储了文件后缀与对应的MIME类型。 + + public static final Map MEDIA_MAP = new HashMap() { + { + // PDF文件 + put(".pdf", "application/pdf"); + // MP4视频文件 + put(".mp4", "video/mp4"); + } + }; + + + //获取文件的MIME类型 + //@param filePath 文件路径 + //@return 文件的MIME类型,如果文件路径无效或无法识别,则返回 "application/octet-stream" + + public static String getContentType(String filePath) { + if (!StringUtils.isBlank(filePath) && filePath.indexOf(".") != -1) { + // 后缀转换成小写 + String suffix = filePath.substring(filePath.lastIndexOf(".")).toLowerCase(); + if (MEDIA_MAP.containsKey(suffix)) { + return MEDIA_MAP.get(suffix); + } + } + return "application/octet-stream"; + } +} diff --git a/src/main/java/com/yf/exam/aspect/DictAspect.java b/src/main/java/com/yf/exam/aspect/DictAspect.java new file mode 100644 index 0000000..cc191e7 --- /dev/null +++ b/src/main/java/com/yf/exam/aspect/DictAspect.java @@ -0,0 +1,315 @@ +package com.yf.exam.aspect; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.yf.exam.core.annon.Dict; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.utils.Reflections; +import com.yf.exam.modules.sys.system.service.SysDictService; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + + + //数据字典AOP类,处理数据字典值 + + //@author bool + +@Aspect +@Component +@Slf4j +public class DictAspect { + + @Autowired + private SysDictService sysDictService; + + +// 切入Controller执行 +// @param pjp +// @return +// @throws Throwable +// + @Around("execution(public * com.yf.exam..*.*Controller.*(..))") + public Object doAround(ProceedingJoinPoint pjp) throws Throwable { + return this.translate(pjp); + } + +// * +// * 进行翻译并返回,调用前必须实现:BaseDictService +// * +// * @param pjp +// * @return +// * @throws Throwable + public Object translate(ProceedingJoinPoint pjp) throws Throwable { + // 处理字典 + return this.parseAllDictText(pjp.proceed()); + } + +// * +// * 转换全部数据字典 +// * +// * @param result +// + private Object parseAllDictText(Object result) { + + // 非ApiRest类型不处理 + if (result instanceof ApiRest) { + parseFullDictText(result); + } + + return result; + } + + +// * +// * 转换所有类型的数据字典、包含子列表 +// * +// * @param result + private void parseFullDictText(Object result) { + + try { + + Object rest = ((ApiRest) result).getData(); + + // 不处理普通数据类型 + if (rest == null || this.isBaseType(rest.getClass())) { + return; + } + + // 分页的 + if (rest instanceof IPage) { + List items = new ArrayList<>(16); + for (Object record : ((IPage) rest).getRecords()) { + Object item = this.parseObject(record); + items.add(item); + } + ((IPage) rest).setRecords(items); + return; + } + + // 数据列表的 + if (rest instanceof List) { + List items = new ArrayList<>(); + for (Object record : ((List) rest)) { + Object item = this.parseObject(record); + items.add(item); + } + // 重新回写值 + ((ApiRest) result).setData(items); + return; + } + + // 处理单对象 + Object item = this.parseObject(((ApiRest) result).getData()); + ((ApiRest) result).setData(item); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + +// * 处理数据字典值 +// * +// * @param record +// * @return +// + public Object parseObject(Object record) { + + if (record == null) { + return null; + } + + // 不处理普通数据类型 + if (this.isBaseType(record.getClass())) { + return record; + } + + // 转换JSON字符 + String json = JSON.toJSONString(record); + JSONObject item = JSONObject.parseObject(json); + + for (Field field : Reflections.getAllFields(record)) { + + // 如果是List类型 + if (List.class.isAssignableFrom(field.getType())) { + try { + List list = this.processList(field, item.getObject(field.getName(), List.class)); + item.put(field.getName(), list); + continue; + } catch (Exception e) { + e.printStackTrace(); + } + continue; + } + + // 处理普通字段 + 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 + String textValue = this.translateDictValue(code, text, table, key); + if (StringUtils.isEmpty(textValue)) { + textValue = ""; + } + item.put(field.getName() + "_dictText", textValue); + continue; + } + + //日期格式转换 + if ("java.util.Date".equals(field.getType().getName()) && item.get(field.getName()) != null) { + + // 获取注解 + JsonFormat ann = field.getAnnotation(JsonFormat.class); + // 格式化方式 + SimpleDateFormat fmt; + + // 使用注解指定的 + 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; + + } + } + + return item; + } + + +// * 获得类型为List的值 +// * +// * @param field +// * @return + + private List processList(Field field, List list) { + + // 空判断 + if (list == null || list.size() == 0) { + return new ArrayList<>(); + } + + // 获得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){ + return list; + } + } + + // 常规列表无需处理 + if (isBaseType(actualType)) { + return list; + } + + // 返回列表 + List result = new ArrayList<>(16); + + for (int i = 0; i < list.size(); i++) { + // 创建实例-->赋值-->字典处理 + Object data = list.get(i); + try { + data = JSON.parseObject(JSON.toJSONString(data), actualType); + }catch (Exception e){ + // 转换出错不处理 + } + + // 处理后的数据 + Object pds = this.parseObject(data); + result.add(pds); + } + + return result; + } + +// +// * 翻译实现 +// * +// * @param code +// * @param text +// * @param table +// * @param key +// * @return +// + private String translateDictValue(String code, String text, String table, String key) { + if (StringUtils.isEmpty(key)) { + return null; + } + try { + // 翻译值 + String dictText = null; + if (!StringUtils.isEmpty(table)) { + dictText = sysDictService.findDict(table, text, code, key.trim()); + } + + if (!StringUtils.isEmpty(dictText)) { + return dictText; + } + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + +// +// * 判断是否基本类型 +// * +// * @param clazz +// * @return +// + private boolean isBaseType(Class clazz) { + + + // 基础数据类型 + if (clazz.equals(java.lang.Integer.class) || + clazz.equals(java.lang.Byte.class) || + clazz.equals(java.lang.Long.class) || + clazz.equals(java.lang.Double.class) || + clazz.equals(java.lang.Float.class) || + clazz.equals(java.lang.Character.class) || + clazz.equals(java.lang.Short.class) || + clazz.equals(java.lang.Boolean.class)) { + return true; + } + + // String类型 + if (clazz.equals(java.lang.String.class)) { + return true; + } + + // 数字 + if (clazz.equals(java.lang.Number.class)) { + return true; + } + + return false; + } + + +} diff --git a/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java b/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java new file mode 100644 index 0000000..7f7ac38 --- /dev/null +++ b/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java @@ -0,0 +1,129 @@ +package com.yf.exam.aspect.mybatis; + +import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +import lombok.extern.log4j.Log4j2; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Plugin; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.reflection.DefaultReflectorFactory; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.apache.shiro.SecurityUtils; +import java.io.StringReader; +import java.sql.Connection; +import java.util.Properties; + + + //查询拦截器,用于拦截处理通用的信息,如用户ID、多租户信息等。 + //特别注意:此处继承了PaginationInterceptor进行分页处理,分页必须在拦截数据后执行,否则容易出现分页不准确,分页计数大于实际数量等问题。 + //@author bool + +@Log4j2 +@Intercepts({ + @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}), +}) +public class QueryInterceptor extends PaginationInterceptor implements Interceptor { + + + //用户ID过滤器标识符 + //该标识符用于在SQL语句中替换为当前登录用户的ID。 + + private static final String USER_FILTER = "{{userId}}"; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); + MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); + MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); + // SQL语句类型 + SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); + // 只过滤查询的SQL语句 + if (SqlCommandType.SELECT == sqlCommandType) { + // 获得原始SQL语句 + String sql = statementHandler.getBoundSql().getSql(); + // 如果SQL语句不包含用户ID过滤器标识符,则不处理 + if (!sql.contains(USER_FILTER)) { + return super.intercept(invocation); + } + // 处理SQL语句 + String outSql = this.parseSql(sql); + // 设置处理后的SQL语句 + metaObject.setValue("delegate.boundSql.sql", outSql); + // 再进行分页处理 + return super.intercept(invocation); + } + // 非查询语句直接执行 + return invocation.proceed(); + } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + // 该方法用于设置插件的属性,当前实现为空 + } + + + //获取当前登录用户 + //return 当前登录用户的SysUserLoginDTO对象,如果未登录则返回null + + private SysUserLoginDTO getLoginUser() { + try { + return SecurityUtils.getSubject().getPrincipal() != null ? (SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal() : null; + } catch (Exception e) { + log.error("获取当前登录用户失败", e); + return null; + } + } + + + //替换用户ID + //@param sql 原始SQL语句 + //@return 替换用户ID后的SQL语句,如果用户未登录则返回null + + private String processUserId(String sql) { + // 当前用户 + SysUserLoginDTO user = this.getLoginUser(); + if (user != null && StringUtils.isNotBlank(user.getId())) { + return sql.replace(USER_FILTER, user.getId()); + } + return null; + } + + + //处理注入用户信息 + //@param src 原始SQL语句 + //@return 处理后的SQL语句,如果处理失败则返回原始SQL语句 + + private String parseSql(String src) { + CCJSqlParserManager parserManager = new CCJSqlParserManager(); + try { + Select select = (Select) parserManager.parse(new StringReader(src)); + PlainSelect selectBody = (PlainSelect) select.getSelectBody(); + // 转换为字符串形式 + String sql = selectBody.toString(); + // 过滤用户ID + sql = this.processUserId(sql); + // 返回处理后的SQL语句 + return sql; + } catch (Exception e) { + log.error("解析SQL语句失败", e); + e.printStackTrace(); + } + // 返回原始SQL语句 + return src; + } +} diff --git a/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java b/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java new file mode 100644 index 0000000..de67d00 --- /dev/null +++ b/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java @@ -0,0 +1,92 @@ +package com.yf.exam.aspect.mybatis; + +import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Plugin; +import org.apache.ibatis.plugin.Signature; + +import java.lang.reflect.Field; +import java.sql.Timestamp; +import java.util.Objects; +import java.util.Properties; + + + //自动给创建时间个更新时间加值 + //@author bool + +@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) +public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor { + + + //创建时间 + + private static final String CREATE_TIME = "createTime"; + + //更新时间 + + private static final String UPDATE_TIME = "updateTime"; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + // 获取MappedStatement对象,该对象包含了SQL执行的相关信息 + MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; + // 获取SQL操作命令类型(如INSERT、UPDATE等) + SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); + // 获取新增或修改的对象参数,该对象对应于SQL语句中的参数 + Object parameter = invocation.getArgs()[1]; + // 获取对象中所有的私有成员变量(对应表字段) + Field[] declaredFields = parameter.getClass().getDeclaredFields(); + // 如果对象有父类,则获取父类的私有成员变量并合并到当前字段列表中 + if (parameter.getClass().getSuperclass() != null) { + Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields(); + declaredFields = ArrayUtils.addAll(declaredFields, superField); + } + String fieldName = null; + // 遍历所有字段,查找createTime和updateTime字段并进行处理 + for (Field field : declaredFields) { + fieldName = field.getName(); + // 如果字段名为createTime且操作类型为INSERT,则设置当前时间戳 + if (Objects.equals(CREATE_TIME, fieldName)) { + if (SqlCommandType.INSERT.equals(sqlCommandType)) { + field.setAccessible(true); + field.set(parameter, new Timestamp(System.currentTimeMillis())); + } + } + // 如果字段名为updateTime且操作类型为INSERT或UPDATE,则设置当前时间戳 + if (Objects.equals(UPDATE_TIME, fieldName)) { + if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { + field.setAccessible(true); + field.set(parameter, new Timestamp(System.currentTimeMillis())); + } + } + } + // 继续执行后续的拦截器链或SQL操作 + return invocation.proceed(); + } + + + @Override + public Object plugin(Object target) { + // 检查目标对象是否是Executor类型 + if (target instanceof Executor) { + // 如果是Executor类型,则使用Plugin.wrap方法包装目标对象,并将其绑定到当前拦截器 + return Plugin.wrap(target, this); + } + // 如果目标对象不是Executor类型,则直接返回原对象 + return target; + } + + + @Override + public void setProperties(Properties properties) { + // 该方法用于设置拦截器的属性 + // 目前实现为空,表示不使用任何属性 + } + +} diff --git a/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java b/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java new file mode 100644 index 0000000..833d5ab --- /dev/null +++ b/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java @@ -0,0 +1,110 @@ +package com.yf.exam.aspect.utils; + +import com.alibaba.fastjson.JSON; +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.ApiRest; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; + + + //注入工具类 + //该类主要用于处理与注入相关的操作,例如生成错误响应和获取字段信息。 + //@author bool + //@date 2019-07-17 09:32 + +@Log4j2 +@Component +public class InjectUtils { + + + + + //给对象的指定字段赋值。 + //如果字段不存在,则跳过该字段并继续处理下一个字段。 + + //@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) { + // 如果字段不存在,则跳过该字段并继续处理下一个字段 + continue; + } + // 设置字段可访问,以便能够赋值 + field.setAccessible(true); + // 将指定的值赋给字段 + field.set(object, value); + } + } + + + + //获取指定类中字段名对应的字段。 + //如果当前类中不存在该字段,则递归查找其父类中的同名字段。 + + //@param clazz 目标类 + //@param fieldName 字段名 + //@return 如果找到指定字段,则返回该字段对象;否则返回null。 + + private Field getFiled(Class clazz, String fieldName) { + // 打印注入的类的名称,用于调试 + System.out.println("注入的类:" + clazz.toString()); + + try { + // 尝试获取当前类中指定的字段 + return clazz.getDeclaredField(fieldName); + } catch (Exception e) { + // 如果当前类中不存在该字段,则记录错误日志 + log.error(clazz.toString() + ": 不存在字段 " + fieldName + ",尝试查找父类"); + + // 如果当前类有父类,则递归调用本方法查找父类中的同名字段 + if (clazz.getSuperclass() != null) { + return this.getFiled(clazz.getSuperclass(), fieldName); + } + // 如果没有父类或在父类中也未找到该字段,则返回null + return null; + } + } + + + + + //处理REST请求的错误响应,向客户端返回一个固定的错误信息。 + + //@param response HTTP响应对象,用于向客户端发送错误信息。 + //@throws IOException 如果在写入响应或关闭流时发生IO异常,则抛出该异常。 + + public static void restError(HttpServletResponse response) { + try { + // 创建一个包含固定错误信息的ApiRest对象 + ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002); + + // 设置响应的字符编码为UTF-8,以确保正确处理中文字符 + response.setCharacterEncoding("UTF-8"); + + // 设置响应的内容类型为application/json,表示返回的数据格式为JSON + response.setContentType("application/json"); + + // 将ApiRest对象转换为JSON字符串,并写入到HTTP响应中 + response.getWriter().write(JSON.toJSONString(apiRest)); + + // 关闭响应的输出流,释放资源 + response.getWriter().close(); + } catch (IOException e) { + // 捕获并处理可能发生的IO异常 + e.printStackTrace(); + } + } + + +} diff --git a/src/main/java/com/yf/exam/config/CorsConfig.java b/src/main/java/com/yf/exam/config/CorsConfig.java new file mode 100644 index 0000000..8fdd0dd --- /dev/null +++ b/src/main/java/com/yf/exam/config/CorsConfig.java @@ -0,0 +1,50 @@ +package com.yf.exam.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + + +// +// * 网关全局设置,允许跨域 +// * @author bool +// * @date 2019-08-13 17:28 +// + +@Configuration +public class CorsConfig { + + @Bean + public FilterRegistrationBean corsFilter() { + // 创建一个基于URL的CORS配置源 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + // 创建一个新的CORS配置对象 + CorsConfiguration config = new CorsConfiguration(); + + // 允许发送凭据(如cookies) + config.setAllowCredentials(true); + // 允许所有来源的请求 + config.addAllowedOrigin(CorsConfiguration.ALL); + // 允许所有请求头 + config.addAllowedHeader(CorsConfiguration.ALL); + // 允许所有请求方法 + config.addAllowedMethod(CorsConfiguration.ALL); + + // 将CORS配置注册到所有路径 + source.registerCorsConfiguration("/**", config); + + // 创建一个新的FilterRegistrationBean实例,并注册CorsFilter + FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); + // 设置该过滤器的优先级为最高 + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + + // 返回配置好的FilterRegistrationBean实例 + return bean; + } + + +} diff --git a/src/main/java/com/yf/exam/config/MultipartConfig.java b/src/main/java/com/yf/exam/config/MultipartConfig.java new file mode 100644 index 0000000..fc255e5 --- /dev/null +++ b/src/main/java/com/yf/exam/config/MultipartConfig.java @@ -0,0 +1,28 @@ +package com.yf.exam.config; + +import org.springframework.boot.web.servlet.MultipartConfigFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; + +import javax.servlet.MultipartConfigElement; + + +// * 文件上传配置 +// * @author bool +// * @date 2019-07-29 16:23 +// +@Configuration +public class MultipartConfig { + + @Bean + public MultipartConfigElement multipartConfigElement() { + MultipartConfigFactory factory = new MultipartConfigFactory(); + // 单个数据大小 + factory.setMaxFileSize(DataSize.ofMegabytes(5000L)); + // 总上传数据大小 + factory.setMaxRequestSize(DataSize.ofMegabytes(5000L)); + return factory.createMultipartConfig(); + } + +} diff --git a/src/main/java/com/yf/exam/config/MybatisConfig.java b/src/main/java/com/yf/exam/config/MybatisConfig.java new file mode 100644 index 0000000..596910d --- /dev/null +++ b/src/main/java/com/yf/exam/config/MybatisConfig.java @@ -0,0 +1,37 @@ +package com.yf.exam.config; + +import com.yf.exam.aspect.mybatis.QueryInterceptor; +import com.yf.exam.aspect.mybatis.UpdateInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +// * Mybatis过滤器配置 +// * 注意:必须按顺序进行配置,否则容易出现业务异常 +// * @author bool + +@Configuration +@MapperScan("com.yf.exam.modules.**.mapper") +public class MybatisConfig { + + +// * 数据查询过滤器 + + @Bean + public QueryInterceptor queryInterceptor() { + QueryInterceptor query = new QueryInterceptor(); + query.setLimit(-1L); + return query; + } + + +// * 插入数据过滤器 + + @Bean + public UpdateInterceptor updateInterceptor() { + 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 new file mode 100644 index 0000000..ee84590 --- /dev/null +++ b/src/main/java/com/yf/exam/config/ScheduledConfig.java @@ -0,0 +1,93 @@ +package com.yf.exam.config; + +import lombok.extern.log4j.Log4j2; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + + +// * 任务调度配置类,启用定时任务和异步任务的支持。 +// * +// * @author bool + +@Log4j2 +@Configuration +@EnableScheduling +@EnableAsync +public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer { + + +// * 定义定时任务使用的线程池。 +// * +// * @return 配置好的ThreadPoolTaskScheduler实例 + + @Bean(destroyMethod = "shutdown", name = "taskScheduler") + public ThreadPoolTaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(10); // 设置线程池大小为10 + scheduler.setThreadNamePrefix("task-"); // 设置线程名称前缀为"task-" + scheduler.setAwaitTerminationSeconds(600); // 设置等待终止时间为600秒 + scheduler.setWaitForTasksToCompleteOnShutdown(true); // 设置在关闭时等待任务完成 + return scheduler; + } + + +// * 定义异步任务执行使用的线程池。 +// * +// * @return 配置好的ThreadPoolTaskExecutor实例 + + @Bean(name = "asyncExecutor") + public ThreadPoolTaskExecutor asyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); // 设置核心线程池大小为10 + executor.setQueueCapacity(1000); // 设置队列容量为1000 + executor.setKeepAliveSeconds(600); // 设置线程空闲时间为600秒 + executor.setMaxPoolSize(20); // 设置最大线程池大小为20 + executor.setThreadNamePrefix("taskExecutor-"); // 设置线程名称前缀为"taskExecutor-" + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 设置拒绝策略为CallerRunsPolicy + executor.initialize(); // 初始化线程池 + return executor; + } + + +// * 配置定时任务注册器,使用自定义的线程池。 +// * +// * @param scheduledTaskRegistrar 定时任务注册器 + + @Override + public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { + ThreadPoolTaskScheduler taskScheduler = taskScheduler(); // 获取自定义的线程池调度器 + scheduledTaskRegistrar.setTaskScheduler(taskScheduler); // 设置定时任务注册器使用的线程池调度器 + } + + +// * 获取异步任务执行器。 +// * +// * @return 自定义的ThreadPoolTaskExecutor实例 + + @Override + public Executor getAsyncExecutor() { + return asyncExecutor(); // 返回自定义的异步任务执行器 + } + + +// * 定义异步任务未捕获异常的处理逻辑。 +// * +// * @return 异步任务未捕获异常的处理程序 + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return (throwable, method, objects) -> { + log.error("异步任务执行出现异常, message {}, method {}, params {}", throwable.getMessage(), method, objects); + }; + } +} diff --git a/src/main/java/com/yf/exam/config/ShiroConfig.java b/src/main/java/com/yf/exam/config/ShiroConfig.java new file mode 100644 index 0000000..e014b1e --- /dev/null +++ b/src/main/java/com/yf/exam/config/ShiroConfig.java @@ -0,0 +1,153 @@ +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; + + +// * Shiro配置类。 +// * 该类配置了Shiro的安全管理器、过滤器链等组件。 +// * +// * @author bool +// */ +@Slf4j +@Configuration +public class ShiroConfig { + + +// * Filter Chain定义说明: +// * 1、一个URL可以配置多个Filter,使用逗号分隔。 +// * 2、当设置多个过滤器时,全部验证通过,才视为通过。 +// * 3、部分过滤器可指定参数,如perms,roles。 + + +// /** +// * 创建ShiroFilterFactoryBean实例。 +// * +// * @param securityManager 安全管理器。 +// * @return ShiroFilterFactoryBean实例。 +// */ + @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); + + // 其他所有URL使用JWT过滤器 + map.put("/**", "jwt"); + shiroFilterFactoryBean.setFilterChainDefinitionMap(map); + + return shiroFilterFactoryBean; + } + +// /** +// * 创建DefaultWebSecurityManager实例。 +// * +// * @param myRealm 自定义的Shiro Realm。 +// * @return DefaultWebSecurityManager实例。 +// */ + @Bean("securityManager") + public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(myRealm); + + // 禁用session存储 + DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); + DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); + defaultSessionStorageEvaluator.setSessionStorageEnabled(false); + subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); + securityManager.setSubjectDAO(subjectDAO); + + return securityManager; + } + +// /** +// * 添加注解支持,创建DefaultAdvisorAutoProxyCreator实例。 +// * +// * @return DefaultAdvisorAutoProxyCreator实例。 +// */ + @Bean + @DependsOn("lifecycleBeanPostProcessor") + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); + defaultAdvisorAutoProxyCreator.setUsePrefix(true); + defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor"); + return defaultAdvisorAutoProxyCreator; + } + +// /** +// * 创建LifecycleBeanPostProcessor实例。 +// * +// * @return LifecycleBeanPostProcessor实例。 +// */ + @Bean + public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + +// /** +// * 创建AuthorizationAttributeSourceAdvisor实例。 +// * +// * @param securityManager 安全管理器。 +// * @return AuthorizationAttributeSourceAdvisor实例。 +// */ + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); + advisor.setSecurityManager(securityManager); + return advisor; + } +} diff --git a/src/main/java/com/yf/exam/config/SwaggerConfig.java b/src/main/java/com/yf/exam/config/SwaggerConfig.java new file mode 100644 index 0000000..68a399c --- /dev/null +++ b/src/main/java/com/yf/exam/config/SwaggerConfig.java @@ -0,0 +1,72 @@ +package com.yf.exam.config; + +import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; +import io.swagger.annotations.ApiOperation; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; +import java.util.Collections; + + +// * Swagger配置类,启用Swagger和SwaggerBootstrapUI,配置API文档信息和安全方案。 +// * +// * @author bool +// * @date 2020/8/19 20:53 +// */ +@Configuration +@EnableSwagger2 +@EnableSwaggerBootstrapUI +@ConfigurationProperties(prefix = "swagger") +public class SwaggerConfig { +// +// /** +// * 创建并配置Swagger Docket实例,用于生成考试模块接口的API文档。 +// * +// * @return 配置好的Docket实例 +// */ + @Bean + public Docket examApi() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) // 设置API信息 + .groupName("考试模块接口") // 设置API分组名称 + .select() + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 选择带有ApiOperation注解的方法 + .paths(PathSelectors.ant("/exam/api/**")) // 选择路径匹配/exam/api/**的API + .build() + .securitySchemes(Collections.singletonList(securityScheme())); // 添加安全方案 + } + +// /** +// * 构建API信息对象,包含标题、描述、联系人信息和版本号。 +// * +// * @return API信息对象 +// */ + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("考试系统接口") // 设置API文档的标题 + .description("考试系统接口") // 设置API文档的描述 + .contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com")) // 设置联系人信息 + .version("1.0.0") // 设置API文档的版本号 + .build(); + } + +// /** +// * 定义授权头部,用于API的安全验证。 +// * +// * @return 安全方案对象 +// */ + @Bean + SecurityScheme securityScheme() { + return new ApiKey("token", "token", "header"); // 定义一个API Key,名称为"token",位于请求头中 + } +} diff --git a/src/main/java/com/yf/exam/core/annon/Dict.java b/src/main/java/com/yf/exam/core/annon/Dict.java new file mode 100644 index 0000000..fa48f1e --- /dev/null +++ b/src/main/java/com/yf/exam/core/annon/Dict.java @@ -0,0 +1,32 @@ +package com.yf.exam.core.annon; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +///** +// * 数据字典注解,用于标识字段需要从数据字典中获取对应的文本信息。 +// * @author bool +// */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Dict { +// /** +// * 必须指定的数据字典编码,用于唯一标识一个数据字典。 +// * @return 数据字典编码。 +// */ + String dicCode(); + +// /** +// * 可选的数据字典文本,默认为空字符串。 +// * @return 数据字典文本。 +// */ + String dicText() default ""; + +// /** +// * 可选的数据字典表名,默认为空字符串。 +// * @return 数据字典表名。 +// */ + String dictTable() default ""; +} diff --git a/src/main/java/com/yf/exam/core/api/ApiError.java b/src/main/java/com/yf/exam/core/api/ApiError.java new file mode 100644 index 0000000..8ed8709 --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/ApiError.java @@ -0,0 +1,118 @@ +package com.yf.exam.core.api; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import java.io.Serializable; + +///** +// * 全局错误码定义,用于定义接口的响应数据。 +// * 枚举名称全部使用代码命名,在系统中调用,免去取名难的问题。 +// * @author bool +// * @date 2019-06-14 21:15 +// */ +@NoArgsConstructor +@AllArgsConstructor +public enum ApiError implements Serializable { + //通用错误,接口参数不全 + ERROR_10010001("参数不全或类型错误!"), + +// /** +// * 用户未登录错误 +// */ + ERROR_10010002("您还未登录,请先登录!"), + +// /** +// * 数据不存在错误 +// */ + ERROR_10010003("数据不存在!"), + +// /** +// * 图形验证码错误 +// */ + ERROR_10010012("图形验证码错误!"), + +// /** +// * 短信验证码错误 +// */ + ERROR_10010013("短信验证码错误!"), + +// /** +// * 重复评论错误 +// */ + ERROR_10010014("不允许重复评论!"), + +// /** +// * 考试相关错误,试题被删除 +// */ + ERROR_20010001("试题被删除,无法继续考试!"), + +// /** +// * 考试相关错误,用户有正在进行的考试 +// */ + ERROR_20010002("您有正在进行的考试!"), + +// /** +// * 账号不存在错误 +// */ + ERROR_90010001("账号不存在,请确认!"), + +// /** +// * 账号或密码错误 +// */ + ERROR_90010002("账号或密码错误!"), + +// /** +// * 角色配置错误,至少要包含一个角色 +// */ + ERROR_90010003("至少要包含一个角色!"), + +// /** +// * 管理员账号错误,无法修改 +// */ + ERROR_90010004("管理员账号无法修改!"), + +// /** +// * 账号被禁用错误 +// */ + ERROR_90010005("账号被禁用,请联系管理员!"), + +// /** +// * 活动用户不足错误,无法开启竞拍 +// */ + ERROR_90010006("活动用户不足,无法开启竞拍!"), + +// /** +// * 旧密码错误 +// */ + ERROR_90010007("旧密码不正确,请确认!"), + +// /** +// * 数据不存在错误(重复定义) +// */ + ERROR_60000001("数据不存在!"); + +// /** +// * 错误消息 +// */ + public String msg; + +// /** +// * 生成Markdown格式文档,用于更新文档用的 +// * +// * @param args 命令行参数 +// */ + public static void main(String[] args) { + for (ApiError e : ApiError.values()) { + System.out.println("'"+e.name().replace("ERROR_", "")+"':'"+e.msg+"',"); + } + } + +// /** +// * 获取错误码 +// * +// * @return 错误码 +// */ + public Integer getCode(){ + return Integer.parseInt(this.name().replace("ERROR_", "")); + } +} diff --git a/src/main/java/com/yf/exam/core/api/ApiRest.java b/src/main/java/com/yf/exam/core/api/ApiRest.java new file mode 100644 index 0000000..358a318 --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/ApiRest.java @@ -0,0 +1,64 @@ +package com.yf.exam.core.api; + + +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.exception.ServiceException; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +///** +// * 数据结果返回的封装 +// * @author bool +// * @date 2018/11/20 09:48 +// */ +@Data +@NoArgsConstructor +@ApiModel(value="接口响应", description="接口响应") +public class ApiRest{ + +// /** +// * 响应消息 +// */ + @ApiModelProperty(value = "响应消息") + private String msg; +// /** +// * 响应代码 +// */ + @ApiModelProperty(value = "响应代码,0为成功,1为失败", required = true) + private Integer code; + +// /** +// * 请求或响应body +// */ + @ApiModelProperty(value = "响应内容") + protected T data; + + +// /** +// * 是否成功 +// * @return +// */ + public boolean isSuccess(){ + return code.equals(0); + } + +// /** +// * 构造函数 +// * @param error +// */ + public ApiRest(ServiceException error){ + this.code = error.getCode(); + this.msg = error.getMsg(); + } + +// /** +// * 构造函数 +// * @param error +// */ + public ApiRest(ApiError error){ + this.code = error.getCode(); + this.msg = error.msg; + } +} diff --git a/src/main/java/com/yf/exam/core/api/controller/BaseController.java b/src/main/java/com/yf/exam/core/api/controller/BaseController.java new file mode 100644 index 0000000..3af2e4d --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/controller/BaseController.java @@ -0,0 +1,160 @@ +package com.yf.exam.core.api.controller; + +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.exception.ServiceException; + +///** +// * 基础控制器,提供构建API响应的通用方法。 +// * @author Dav +// */ +public class BaseController { +// /** +// * 成功默认状态码。 +// */ + private static final Integer CODE_SUCCESS = 0; +// /** +// * 成功默认消息。 +// */ + private static final String MSG_SUCCESS = "操作成功!"; +// /** +// * 失败默认状态码。 +// */ + private static final Integer CODE_FAILURE = 1; +// /** +// * 失败默认消息。 +// */ + private static final String MSG_FAILURE = "请求失败!"; + +// /** +// * 构造一个包含状态码、消息和数据的API响应对象。 +// * +// * @param code 表示响应的状态码,通常用于标识请求的成功或失败。 +// * @param message 与状态码相关联的简短消息,描述响应的结果或错误原因。 +// * @param data 响应中包含的业务数据,可以是任何类型的对象。如果数据为null,则不设置响应的数据部分。 +// * @param 泛型类型,表示数据对象的类型。 +// * @return 构建好的ApiRest对象,包含了传入的状态码、消息和数据。 +// */ + protected ApiRest message(Integer code, String message, T data){ + ApiRest response = new ApiRest<>(); + response.setCode(code); + response.setMsg(message); + if(data != null) { + response.setData(data); + } + return response; + } + +// /** +// * 请求成功时返回一个空数据的API响应对象。 +// * +// * @param 数据对象的类型。 +// * @return 包含成功状态码和默认成功消息的ApiRest对象。 +// */ + protected ApiRest success(){ + return message(CODE_SUCCESS, MSG_SUCCESS, null); + } + +// /** +// * 请求成功时返回一个包含自定义消息和数据的API响应对象。 +// * +// * @param message 自定义的成功消息。 +// * @param data 响应中包含的业务数据。 +// * @param 数据对象的类型。 +// * @return 包含成功状态码、自定义消息和数据的ApiRest对象。 +// */ + protected ApiRest success(String message, T data){ + return message(CODE_SUCCESS, message, data); + } + +// /** +// * 请求成功时返回一个仅包含默认消息和数据的API响应对象。 +// * +// * @param data 响应中包含的业务数据。 +// * @param 数据对象的类型。 +// * @return 包含成功状态码、默认成功消息和数据的ApiRest对象。 +// */ + protected ApiRest success(T data){ + return message(CODE_SUCCESS, MSG_SUCCESS, data); + } + +// /** +// * 请求失败时返回一个包含自定义状态码、消息和数据的API响应对象。 +// * +// * @param code 自定义的失败状态码。 +// * @param message 自定义的失败消息。 +// * @param data 响应中包含的业务数据。 +// * @param 数据对象的类型。 +// * @return 包含自定义状态码、消息和数据的ApiRest对象。 +// */ + protected ApiRest failure(Integer code, String message, T data){ + return message(code, message, data); + } + +// /** +// * 请求失败时返回一个包含自定义消息和数据的API响应对象。 +// * +// * @param message 自定义的失败消息。 +// * @param data 响应中包含的业务数据。 +// * @param 数据对象的类型。 +// * @return 包含失败状态码、自定义消息和数据的ApiRest对象。 +// */ + protected ApiRest failure(String message, T data){ + return message(CODE_FAILURE, message, data); + } + +// /** +// * 请求失败时返回一个仅包含自定义消息的API响应对象。 +// * +// * @param message 自定义的失败消息。 +// * @return 包含失败状态码和自定义消息的ApiRest对象。 +// */ + protected ApiRest failure(String message){ + return message(CODE_FAILURE, message, null); + } + +// /** +// * 请求失败时返回一个包含默认失败消息和数据的API响应对象。 +// * +// * @param data 响应中包含的业务数据。 +// * @param 数据对象的类型。 +// * @return 包含失败状态码、默认失败消息和数据的ApiRest对象。 +// */ + protected ApiRest failure(T data){ + return message(CODE_FAILURE, MSG_FAILURE, data); + } + +// /** +// * 请求失败时返回一个仅包含默认失败消息的API响应对象。 +// * +// * @param 数据对象的类型。 +// * @return 包含失败状态码和默认失败消息的ApiRest对象。 +// */ + protected ApiRest failure(){ + return message(CODE_FAILURE, MSG_FAILURE, null); + } + +// /** +// * 请求失败时返回一个包含ApiError中定义的状态码、消息和数据的API响应对象。 +// * +// * @param error 包含错误信息的ApiError对象。 +// * @param data 响应中包含的业务数据。 +// * @param 数据对象的类型。 +// * @return 包含ApiError中定义的状态码、消息和数据的ApiRest对象。 +// */ + protected ApiRest failure(ApiError error, T data){ + return message(error.getCode(), error.msg, data); + } + +// /** +// * 请求失败时返回一个包含ServiceException中定义的状态码和消息的API响应对象。 +// * +// * @param ex 包含异常信息的ServiceException对象。 +// * @param 数据对象的类型。 +// * @return 包含ServiceException中定义的状态码和消息的ApiRest对象。 +// */ + protected ApiRest failure(ServiceException ex){ + ApiRest apiRest = message(ex.getCode(), ex.getMsg(), null); + return apiRest; + } +} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java new file mode 100644 index 0000000..c9e2711 --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java @@ -0,0 +1,15 @@ +package com.yf.exam.core.api.dto; + +import lombok.Data; + +import java.io.Serializable; + +///** +// * 请求和响应的基础类,用于处理序列化 +// * @author dav +// * @date 2019/3/16 15:56 +// */ +@Data +public class BaseDTO implements Serializable { + +} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java new file mode 100644 index 0000000..819aab5 --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java @@ -0,0 +1,32 @@ +package com.yf.exam.core.api.dto; + +import com.yf.exam.core.api.dto.BaseDTO; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +///** +// *

+// * 主键通用请求类,用于根据ID查询 +// *

+// * @author 聪明笨狗 +// * @since 2019-04-20 12:15 +// */ +@Data +@ApiModel(value="主键通用请求类", description="用于根据ID查询的通用请求类") +public class BaseIdReqDTO extends BaseDTO { +// /** +// * 主键ID,用于标识需要查询的实体。 +// * @since 2019-04-20 12:15 +// */ + @ApiModelProperty(value = "主键ID", required=true) + private String id; + +// /** +// * 用户ID,用于标识发起请求的用户。 +// * 该字段在序列化时将被忽略,不返回给客户端。 +// */ + @JsonIgnore + private String userId; +} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java new file mode 100644 index 0000000..a14399a --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java @@ -0,0 +1,28 @@ +package com.yf.exam.core.api.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; + +///** +// *

+// * 主键通用响应类,用于在添加操作后返回内容 +// *

+// * @author 聪明笨狗 +// * @since 2019-04-20 12:15 +// */ +@Data +@ApiModel(value="主键通用响应类", description="在添加操作后返回的通用响应类") +@AllArgsConstructor +@NoArgsConstructor +public class BaseIdRespDTO extends BaseDTO { +// /** +// * 主键ID,用于标识添加的实体。 +// * @since 2019-04-20 12:15 +// */ + @ApiModelProperty(value = "主键ID", required=true) + private String id; +} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java new file mode 100644 index 0000000..f78490e --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java @@ -0,0 +1,34 @@ +package com.yf.exam.core.api.dto; + +import com.yf.exam.core.api.dto.BaseDTO; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.util.List; + + +// *

+// * 通用ID列表类操作,用于批量删除、修改状态等 +// *

+// * @author bool +// * @date 2019-08-01 19:07 +// */ +@Data +@ApiModel(value="删除参数", description="用于批量删除或修改状态的请求参数") +public class BaseIdsReqDTO extends BaseDTO { +// /** +// * 用户ID,用于标识发起请求的用户。 +// * 该字段在序列化时将被忽略,不返回给客户端。 +// */ + @JsonIgnore + private String userId; + +// /** +// * 要删除的ID列表,用于指定需要批量删除或修改状态的实体。 +// * +// * @since 2019-08-01 19:07 +// */ + @ApiModelProperty(value = "要删除的ID列表", required = true) + private List ids; +} diff --git a/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java b/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java new file mode 100644 index 0000000..5e01ddd --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java @@ -0,0 +1,34 @@ +package com.yf.exam.core.api.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 java.util.List; + +///** +// *

+// * 通用状态请求类,用于批量修改对象的状态 +// *

+// * @author 聪明笨狗 +// * @since 2019-04-20 12:15 +// */ +@Data +@ApiModel(value="通用状态请求类", description="用于批量修改对象状态的请求参数") +@AllArgsConstructor +@NoArgsConstructor +public class BaseStateReqDTO extends BaseDTO { +// /** +// * 要修改对象的ID列表。该字段是必需的。 +// */ + @ApiModelProperty(value = "要修改对象的ID列表", required=true) + private List ids; + +// /** +// * 通用状态,0表示正常,1表示禁用。该字段是必需的。 +// */ + @ApiModelProperty(value = "通用状态,0为正常,1为禁用", required=true) + private Integer state; +} diff --git a/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java b/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java new file mode 100644 index 0000000..abc1eb5 --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java @@ -0,0 +1,61 @@ +package com.yf.exam.core.api.dto; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +///** +// * 分页查询类,用于封装分页请求参数 +// * @param 查询参数的类型 +// * @author bool +// */ +@ApiModel(value="分页参数", description="用于分页查询的请求参数") +@Data +public class PagingReqDTO { +// /** +// * 当前页码,必需字段。 +// * 例如:1 表示第一页。 +// */ + @ApiModelProperty(value = "当前页码", required = true, example = "1") + private Integer current; + +// /** +// * 每页数量,必需字段。 +// * 例如:10 表示每页显示10条记录。 +// */ + @ApiModelProperty(value = "每页数量", required = true, example = "10") + private Integer size; + +// /** +// * 查询参数,可以包含具体的查询条件。 +// */ + @ApiModelProperty(value = "查询参数") + private T params; + +// /** +// * 排序字符,用于指定查询结果的排序方式。 +// */ + @ApiModelProperty(value = "排序字符") + private String orderBy; + +// /** +// * 当前用户的ID,用于标识发起请求的用户。 +// * 该字段在序列化时将被忽略,不返回给客户端。 +// */ + @JsonIgnore + @ApiModelProperty(value = "当前用户的ID") + private String userId; + + +// * 将当前分页请求参数转换成MyBatis的简单分页对象。 +// * @return Page 对象,包含当前页码和每页数量 +// */ + public Page toPage() { + Page page = new Page(); + page.setCurrent(this.current); + page.setSize(this.size); + return page; + } +} diff --git a/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java b/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java new file mode 100644 index 0000000..1773292 --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java @@ -0,0 +1,28 @@ +package com.yf.exam.core.api.dto; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +///** +// * 分页响应类,继承自MyBatis Plus的Page类,用于封装分页查询结果 +// * @param 分页数据的类型 +// * @author bool +// * @date 2019-07-20 15:17 +// */ +public class PagingRespDTO extends Page { +// /** +// * 获取页面总数量 +// * @return 页面总数量 +// */ + @Override + public long getPages() { + if (this.getSize() == 0L) { + return 0L; + } else { + long pages = this.getTotal() / this.getSize(); + if (this.getTotal() % this.getSize() != 0L) { + ++pages; + } + return pages; + } + } +} diff --git a/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java b/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java new file mode 100644 index 0000000..3270026 --- /dev/null +++ b/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java @@ -0,0 +1,47 @@ +package com.yf.exam.core.api.utils; + +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +///** +// * JSON数据转换器,用于转换返回消息的格式 +// * @author dav +// * @date 2018/9/11 19:30 +// */ +public class JsonConverter { + +// /** +// * FastJson消息转换器 +// * @return +// */ + public static HttpMessageConverter fastConverter() { + // 定义一个convert转换消息的对象 + FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); + // 添加FastJson的配置信息 + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + // 默认转换器 + fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat, + SerializerFeature.WriteNullNumberAsZero, + SerializerFeature.MapSortField, + SerializerFeature.WriteNullStringAsEmpty, + SerializerFeature.DisableCircularReferenceDetect, + SerializerFeature.WriteDateUseDateFormat, + SerializerFeature.WriteNullListAsEmpty); + fastJsonConfig.setCharset(Charset.forName("UTF-8")); + // 处理中文乱码问题 + List fastMediaTypes = new ArrayList<>(); + fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); + fastConverter.setSupportedMediaTypes(fastMediaTypes); + // 在convert中添加配置信息 + fastConverter.setFastJsonConfig(fastJsonConfig); + + return fastConverter; + } +} diff --git a/src/main/java/com/yf/exam/core/enums/CommonState.java b/src/main/java/com/yf/exam/core/enums/CommonState.java new file mode 100644 index 0000000..e72a98a --- /dev/null +++ b/src/main/java/com/yf/exam/core/enums/CommonState.java @@ -0,0 +1,19 @@ +package com.yf.exam.core.enums; + +///** +// * 通用的状态枚举信息 +// * +// * @author bool +// * @date 2019-09-17 17:57 +// */ +public interface CommonState { + +// /** +// * 普通状态,正常的 +// */ + Integer NORMAL = 0; +// /** +// * 非正常状态,禁用,下架等 +// */ + Integer ABNORMAL = 1; +} diff --git a/src/main/java/com/yf/exam/core/enums/OpenType.java b/src/main/java/com/yf/exam/core/enums/OpenType.java new file mode 100644 index 0000000..354e722 --- /dev/null +++ b/src/main/java/com/yf/exam/core/enums/OpenType.java @@ -0,0 +1,18 @@ +package com.yf.exam.core.enums; + +///** +// * 开放方式 +// * @author bool +// */ +public interface OpenType { + +// /** +// * 完全开放 +// */ + Integer OPEN = 1; + +// /** +// * 部门开放 +// */ + Integer DEPT_OPEN = 2; +} diff --git a/src/main/java/com/yf/exam/core/exception/ServiceException.java b/src/main/java/com/yf/exam/core/exception/ServiceException.java new file mode 100644 index 0000000..c92218a --- /dev/null +++ b/src/main/java/com/yf/exam/core/exception/ServiceException.java @@ -0,0 +1,51 @@ +package com.yf.exam.core.exception; + +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.ApiRest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ServiceException extends RuntimeException{ + +// /** +// * 错误码 +// */ + private Integer code; + +// /** +// * 错误消息 +// */ + private String msg; + +// /** +// * 从结果初始化 +// * @param apiRest +// */ + public ServiceException(ApiRest apiRest){ + this.code = apiRest.getCode(); + this.msg = apiRest.getMsg(); + } + +// /** +// * 从枚举中获取参数 +// * @param apiError +// */ + public ServiceException(ApiError apiError){ + this.code = apiError.getCode(); + this.msg = apiError.msg; + } + + /** + * 异常构造 + * @param msg + */ + public ServiceException(String msg){ + this.code = 1; + this.msg = msg; + } + +} diff --git a/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java b/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java new file mode 100644 index 0000000..4e7a0a9 --- /dev/null +++ b/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java @@ -0,0 +1,46 @@ +package com.yf.exam.core.exception; + +import com.yf.exam.core.api.ApiRest; +import org.springframework.http.HttpStatus; +import org.springframework.ui.Model; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.*; + +///** +// * 统一异常处理类 +// * @author bool +// * @date 2019-06-21 19:27 +// */ +@RestControllerAdvice +public class ServiceExceptionHandler { + +// /** +// * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器 +// * @param binder +// */ + @InitBinder + public void initWebBinder(WebDataBinder binder){ + + } + +// /** +// * 把值绑定到Model中,使全局@RequestMapping可以获取到该值 +// * @param model +// */ + @ModelAttribute + public void addAttribute(Model model) { + + } + +// /** +// * 捕获ServiceException +// * @param e +// * @return +// */ + @ExceptionHandler({com.yf.exam.core.exception.ServiceException.class}) + @ResponseStatus(HttpStatus.OK) + public ApiRest serviceExceptionHandler(ServiceException e) { + return new ApiRest(e); + } + +} \ No newline at end of file diff --git a/src/main/java/com/yf/exam/core/utils/BeanMapper.java b/src/main/java/com/yf/exam/core/utils/BeanMapper.java new file mode 100644 index 0000000..f7ec09d --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/BeanMapper.java @@ -0,0 +1,59 @@ +package com.yf.exam.core.utils; + +import org.dozer.DozerBeanMapper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + + +///** +// * 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现: +// * +// * 1. 持有Mapper的单例. +// * 2. 返回值类型转换. +// * 3. 批量转换Collection中的所有对象. +// * 4. 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数. +// * +// */ +public class BeanMapper { + +// /** +// * 持有Dozer单例, 避免重复创建DozerMapper消耗资源. +// */ + private static DozerBeanMapper dozerBeanMapper = new DozerBeanMapper(); + +// /** +// * 基于Dozer转换对象的类型. +// */ + public static T map(Object source, Class destinationClass) { + return dozerBeanMapper.map(source, destinationClass); + } + +// /** +// * 基于Dozer转换Collection中对象的类型. +// */ + public static List mapList(Iterable sourceList, Class destinationClass) { + List destinationList = new ArrayList(); + for (Object sourceObject : sourceList) { + T destinationObject = dozerBeanMapper.map(sourceObject, destinationClass); + destinationList.add(destinationObject); + } + return destinationList; + } + +// /** +// * 基于Dozer将对象A的值拷贝到对象B中. +// */ + public static void copy(Object source, Object destinationObject) { + if(source!=null) { + dozerBeanMapper.map(source, destinationObject); + } + } + + public static List mapList(Collection source, Function mapper) { + return source.stream().map(mapper).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/yf/exam/core/utils/CronUtils.java b/src/main/java/com/yf/exam/core/utils/CronUtils.java new file mode 100644 index 0000000..22266f9 --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/CronUtils.java @@ -0,0 +1,31 @@ +package com.yf.exam.core.utils; + +import java.text.SimpleDateFormat; +import java.util.Date; + +///** +// * 时间转换quartz表达式 +// * @author bool +// * @date 2020/11/29 下午3:00 +// */ +public class CronUtils { + +// /** +// * 格式化数据 +// */ + private static final String DATE_FORMAT = "ss mm HH dd MM ? yyyy"; + +// /** +// * 准确的时间点到表达式 +// * @param date +// * @return +// */ + public static String dateToCron(final Date date){ + SimpleDateFormat fmt = new SimpleDateFormat(DATE_FORMAT); + String formatTimeStr = ""; + if (date != null) { + formatTimeStr = fmt.format(date); + } + return formatTimeStr; + } +} diff --git a/src/main/java/com/yf/exam/core/utils/DateUtils.java b/src/main/java/com/yf/exam/core/utils/DateUtils.java new file mode 100644 index 0000000..8b6f03e --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/DateUtils.java @@ -0,0 +1,103 @@ +package com.yf.exam.core.utils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +///** +// * 日期处理工具类 +// * ClassName: DateUtils
+// * date: 2018年12月13日 下午6:34:02
+// * +// * @author Bool +// * @version +// */ +public class DateUtils { + +// /** +// * +// * calcExpDays:计算某个日期与当前日期相差的天数,如果计算的日期大于现在时间,将返回负数;否则返回正数
+// * @author Bool +// * @param userCreateTime +// * @return +// * @since JDK 1.6 +// */ + public static int calcExpDays(Date userCreateTime){ + + Calendar start = Calendar.getInstance(); + start.setTime(userCreateTime); + + Calendar now = Calendar.getInstance(); + now.setTime(new Date()); + + long l = now.getTimeInMillis() - start.getTimeInMillis(); + int days = new Long(l / (1000 * 60 * 60 * 24)).intValue(); + return days; + } + + +// /** +// * +// * dateNow:获取当前时间的字符串格式,根据传入的格式化来展示.
+// * @author Bool +// * @param format 日期格式化 +// * @return +// */ + public static String dateNow(String format) { + SimpleDateFormat fmt = new SimpleDateFormat(format); + Calendar c = new GregorianCalendar(); + return fmt.format(c.getTime()); + } + +// /** +// * formatDate:格式化日期,返回指定的格式
+// * @author Bool +// * @param time +// * @param format +// * @return +// */ + public static String formatDate(Date time, String format) { + SimpleDateFormat fmt = new SimpleDateFormat(format); + return fmt.format(time.getTime()); + } + + + +// /** +// * parseDate:将字符串转换成日期,使用:yyyy-MM-dd HH:mm:ss 来格式化 +// * @author Bool +// * @param date +// * @return +// */ + public static Date parseDate(String date) { + return parseDate(date, "yyyy-MM-dd HH:mm:ss"); + } + + +// /** +// * +// * parseDate:将字符串转换成日期,使用指定格式化来格式化 +// * @author Bool +// * @param date +// * @param pattern +// * @return +// */ + public static Date parseDate(String date, String pattern) { + + if (pattern==null) { + pattern = "yyyy-MM-dd HH:mm:ss"; + } + + SimpleDateFormat fmt = new SimpleDateFormat(pattern); + + try { + + return fmt.parse(date); + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + + } +} diff --git a/src/main/java/com/yf/exam/core/utils/IpUtils.java b/src/main/java/com/yf/exam/core/utils/IpUtils.java new file mode 100644 index 0000000..87270fd --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/IpUtils.java @@ -0,0 +1,65 @@ +package com.yf.exam.core.utils; + + +import javax.servlet.http.HttpServletRequest; + +///** +// * IP获取工具类,用户获取网络请求过来的真实IP +// * ClassName: IpUtils
+// * date: 2018年2月13日 下午7:27:52
+// * +// * @author Bool +// * @version +// */ +public class IpUtils { + + +// /** +// * +// * getClientIp:通过请求获取客户端的真实IP地址 +// * @author Bool +// * @param request +// * @return +// */ + public static String extractClientIp(HttpServletRequest request) { + + String ip = null; + + //X-Forwarded-For:Squid 服务代理 + String ipAddresses = request.getHeader("X-Forwarded-For"); + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //Proxy-Client-IP:apache 服务代理 + ipAddresses = request.getHeader("Proxy-Client-IP"); + } + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //WL-Proxy-Client-IP:weblogic 服务代理 + ipAddresses = request.getHeader("WL-Proxy-Client-IP"); + } + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //HTTP_CLIENT_IP:有些代理服务器 + ipAddresses = request.getHeader("HTTP_CLIENT_IP"); + } + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //X-Real-IP:nginx服务代理 + ipAddresses = request.getHeader("X-Real-IP"); + } + + //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP + if (ipAddresses != null && ipAddresses.length() != 0) { + ip = ipAddresses.split(",")[0]; + } + + //还是不能获取到,最后再通过request.getRemoteAddr();获取 + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + ip = request.getRemoteAddr(); + } + + return ip; + } + + +} diff --git a/src/main/java/com/yf/exam/core/utils/Reflections.java b/src/main/java/com/yf/exam/core/utils/Reflections.java new file mode 100644 index 0000000..a15c18c --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/Reflections.java @@ -0,0 +1,324 @@ +/** + * Copyright (c) 2005-2012 springside.org.cn + */ +package com.yf.exam.core.utils; + +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.springframework.util.Assert; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +///** +// * 反射工具类. +// * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. +// * @author calvin +// * @version 2016-01-15 +// */ +@Log4j2 +public class Reflections { + + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + +// /** +// * 获取类的所有属性,包括父类 +// * +// * @param object +// * @return +// */ + public static Field[] getAllFields(Object object) { + Class clazz = object.getClass(); + List fieldList = new ArrayList<>(); + while (clazz != null) { + fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); + clazz = clazz.getSuperclass(); + } + Field[] fields = new Field[fieldList.size()]; + fieldList.toArray(fields); + return fields; + } + + +// /** +// * 调用Getter方法. +// * 支持多级,如:对象名.对象名.方法 +// */ + public static Object invokeGetter(Object obj, String propertyName) { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")){ + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return object; + } + +// /** +// * 调用Setter方法, 仅匹配方法名。 +// * 支持多级,如:对象名.对象名.方法 +// */ + public static void invokeSetter(Object obj, String propertyName, Object value) { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i=0; i[] parameterTypes, + final Object[] args) { + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) { + throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); + } + + try { + return method.invoke(obj, args); + } catch (Exception e) { + throw convertReflectionExceptionToUnchecked(e); + } + } + +// /** +// * 直接调用对象方法, 无视private/protected修饰符, +// * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. +// * 只匹配函数名,如果有多个同名函数调用第一个。 +// */ + public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) { + Method method = getAccessibleMethodByName(obj, methodName); + if (method == null) { + throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); + } + + try { + return method.invoke(obj, args); + } catch (Exception e) { + throw convertReflectionExceptionToUnchecked(e); + } + } + +// /** +// * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. +// * +// * 如向上转型到Object仍无法找到, 返回null. +// */ + public static Field getAccessibleField(final Object obj, final String fieldName) { + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { + try { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } catch (NoSuchFieldException e) {//NOSONAR + // Field不在当前类定义,继续向上转型 + continue;// new add + } + } + return null; + } + +// /** +// * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. +// * 如向上转型到Object仍无法找到, 返回null. +// * 匹配函数名+参数类型。 +// * +// * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) +// */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) { + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(methodName, "methodName can't be blank"); + + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { + try { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } catch (NoSuchMethodException e) { + // Method不在当前类定义,继续向上转型 + continue;// new add + } + } + return null; + } + +// /** +// * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. +// * 如向上转型到Object仍无法找到, 返回null. +// * 只匹配函数名。 +// * +// * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) +// */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName) { + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(methodName, "methodName can't be blank"); + + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals(methodName)) { + makeAccessible(method); + return method; + } + } + } + return null; + } + +// /** +// * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 +// */ + public static void makeAccessible(Method method) { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) { + method.setAccessible(true); + } + } + +// /** +// * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 +// */ + public static void makeAccessible(Field field) { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier + .isFinal(field.getModifiers())) && !field.isAccessible()) { + field.setAccessible(true); + } + } + +// /** +// * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 +// * 如无法找到, 返回Object.class. +// * eg. +// * public UserDao extends HibernateDao +// * +// * @param clazz The class to introspect +// * @return the first generic declaration, or Object.class if cannot be determined +// */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) { + return getClassGenricType(clazz, 0); + } + +// /** +// * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. +// * 如无法找到, 返回Object.class. +// * +// * 如public UserDao extends HibernateDao +// * +// * @param clazz clazz The class to introspect +// * @param index the Index of the generic ddeclaration,start from 0. +// * @return the index generic declaration, or Object.class if cannot be determined +// */ + public static Class getClassGenricType(final Class clazz, final int index) { + + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) { + log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) { + log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) { + log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) { + Assert.notNull(instance, "Instance must not be null"); + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) { + return superClass; + } + } + return clazz; + + } + +// /** +// * 将反射时的checked exception转换为unchecked exception. +// */ + public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) { + return new IllegalArgumentException(e); + } else if (e instanceof InvocationTargetException) { + return new RuntimeException(((InvocationTargetException) e).getTargetException()); + } else if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + return new RuntimeException("Unexpected Checked Exception.", e); + } +} diff --git a/src/main/java/com/yf/exam/core/utils/SpringUtils.java b/src/main/java/com/yf/exam/core/utils/SpringUtils.java new file mode 100644 index 0000000..41c6dda --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/SpringUtils.java @@ -0,0 +1,53 @@ +package com.yf.exam.core.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +///** +// * Spring获取工具类,用于在非Spring管理的类中获取Spring管理的Bean。 +// * +// * @author bool +// * @date 2019-12-09 15:55 +// */ +@Component +public class SpringUtils implements ApplicationContextAware { + +// /** +// * 静态变量,用于存储Spring的ApplicationContext对象。 +// */ + private static ApplicationContext applicationContext; + +// /** +// * 实现ApplicationContextAware接口的方法,用于设置ApplicationContext。 +// * +// * @param context Spring的ApplicationContext对象 +// * @throws BeansException 如果发生Bean相关的异常 +// */ + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + applicationContext = context; + } + +// /** +// * 根据Bean的类型获取Spring管理的Bean。 +// * +// * @param tClass Bean的类型 +// * @return 指定类型的Bean实例 +// */ + public static T getBean(Class tClass) { + return applicationContext.getBean(tClass); + } + +// /** +// * 根据Bean的名称和类型获取Spring管理的Bean。 +// * +// * @param name Bean的名称 +// * @param type Bean的类型 +// * @return 指定名称和类型的Bean实例 +// */ + public static T getBean(String name, Class type) { + return applicationContext.getBean(name, type); + } +} diff --git a/src/main/java/com/yf/exam/core/utils/StringUtils.java b/src/main/java/com/yf/exam/core/utils/StringUtils.java new file mode 100644 index 0000000..03affe9 --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/StringUtils.java @@ -0,0 +1,42 @@ +package com.yf.exam.core.utils; + +import java.util.Map; +// +///** +// * 字符串常用工具类,提供一些字符串处理的静态方法。 +// * +// * @author bool +// * @date 2019-05-15 11:40 +// */ +public class StringUtils { + +// /** +// * 判断给定的字符串是否为空或空白。 +// * 空白字符串是指长度为0的字符串或仅包含空白字符的字符串。 +// * +// * @param str 要判断的字符串 +// * @return 如果字符串为空或空白,返回true;否则返回false +// */ + public static boolean isBlank(String str) { + return str == null || "".equals(str); + } + +// /** +// * 将给定的Map对象转换成一个XML格式的字符串。 +// * 格式为value...。 +// * +// * @param params 要转换的Map对象,键和值都是字符串类型 +// * @return 转换成的XML格式字符串 +// */ + public static String mapToXml(Map params) { + StringBuffer sb = new StringBuffer(""); + for (String key : params.keySet()) { + sb.append("<") + .append(key).append(">") + .append(params.get(key)) + .append(""); + } + sb.append(""); + return sb.toString(); + } +} diff --git a/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java b/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java new file mode 100644 index 0000000..14a3b64 --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java @@ -0,0 +1,402 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.yf.exam.core.utils.excel; + +import com.google.common.collect.Lists; +import com.yf.exam.core.utils.Reflections; +import com.yf.exam.core.utils.excel.annotation.ExcelField; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +///** +// * 导出Excel文件(导出“XLSX”格式,支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion) +// * @author jeeplus +// * @version 2016-04-21 +// */ +public class ExportExcel { + + private static Logger log = LoggerFactory.getLogger(ExportExcel.class); + +// /** +// * 工作薄对象 +// */ + private SXSSFWorkbook wb; + +// /** +// * 工作表对象 +// */ + private Sheet sheet; + +// /** +// * 样式列表 +// */ + private Map styles; + +// /** +// * 当前行号 +// */ + private int rownum; + +// /** +// * 注解列表(Object[]{ ExcelField, Field/Method }) +// */ + List annotationList = Lists.newArrayList(); + +// /** +// * 构造函数 +// * @param title 表格标题,传“空值”,表示无标题 +// * @param cls 实体对象,通过annotation.ExportField获取标题 +// */ + public ExportExcel(String title, Class cls){ + this(title, cls, 1); + } + +// /** +// * 构造函数 +// * @param title 表格标题,传“空值”,表示无标题 +// * @param cls 实体对象,通过annotation.ExportField获取标题 +// * @param type 导出类型(1:导出数据;2:导出模板) +// * @param groups 导入分组 +// */ + public ExportExcel(String title, Class cls, int type, int... groups){ + // Get annotation field + Field[] fs = cls.getDeclaredFields(); + for (Field f : fs){ + ExcelField ef = f.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==type)){ + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, f}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, f}); + } + } + } + // Get annotation method + Method[] ms = cls.getDeclaredMethods(); + for (Method m : ms){ + ExcelField ef = m.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==type)){ + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, m}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, m}); + } + } + } + // Field sorting + Collections.sort(annotationList, new Comparator() { + @Override + public int compare(Object[] o1, Object[] o2) { + return new Integer(((ExcelField)o1[0]).sort()).compareTo( + new Integer(((ExcelField)o2[0]).sort())); + } + }); + // Initialize + List headerList = Lists.newArrayList(); + for (Object[] os : annotationList){ + String t = ((ExcelField)os[0]).title(); + // 如果是导出,则去掉注释 + if (type==1){ + String[] ss = StringUtils.split(t, "**", 2); + if (ss.length==2){ + t = ss[0]; + } + } + headerList.add(t); + } + initialize(title, headerList); + } +// /** +// * 初始化函数 +// * @param title 表格标题,传“空值”,表示无标题 +// * @param headerList 表头列表 +// */ + private void initialize(String title, List headerList) { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet("Export"); + this.styles = createStyles(wb); + // Create title + if (StringUtils.isNotBlank(title)){ + Row titleRow = sheet.createRow(rownum++); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), + titleRow.getRowNum(), titleRow.getRowNum(), headerList.size()-1)); + } + // Create header + if (headerList == null){ + throw new RuntimeException("headerList not null!"); + } + Row headerRow = sheet.createRow(rownum++); + headerRow.setHeightInPoints(16); + for (int i = 0; i < headerList.size(); i++) { + Cell cell = headerRow.createCell(i); + cell.setCellStyle(styles.get("header")); + String[] ss = StringUtils.split(headerList.get(i), "**", 2); + if (ss.length==2){ + cell.setCellValue(ss[0]); + Comment comment = this.sheet.createDrawingPatriarch().createCellComment( + new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6)); + comment.setString(new XSSFRichTextString(ss[1])); + cell.setCellComment(comment); + }else{ + cell.setCellValue(headerList.get(i)); + } + sheet.autoSizeColumn(i); + } + for (int i = 0; i < headerList.size(); i++) { + int colWidth = sheet.getColumnWidth(i)*2; + sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth); + } + log.debug("Initialize success."); + } + + /** + * 创建表格样式 + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) { + Map styles = new HashMap<>(16); + + CellStyle style = wb.createCellStyle(); + style.setAlignment(CellStyle.ALIGN_CENTER); + style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD); + style.setFont(titleFont); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + style.setBorderRight(CellStyle.BORDER_THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(CellStyle.BORDER_THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(CellStyle.BORDER_THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(CellStyle.BORDER_THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_LEFT); + styles.put("data1", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_CENTER); + styles.put("data2", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_RIGHT); + styles.put("data3", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); +// style.setWrapText(true); + style.setAlignment(CellStyle.ALIGN_CENTER); + style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(CellStyle.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD); + headerFont.setColor(IndexedColors.WHITE.getIndex()); + style.setFont(headerFont); + styles.put("header", style); + + return styles; + } + +// /** +// * 添加一行 +// * @return 行对象 +// */ + public Row addRow(){ + return sheet.createRow(rownum++); + } + + +// /** +// * 添加一个单元格 +// * @param row 添加的行 +// * @param column 添加列号 +// * @param val 添加值 +// * @return 单元格对象 +// */ + public Cell addCell(Row row, int column, Object val){ + return this.addCell(row, column, val, 0, Class.class); + } + +// /** +// * 添加一个单元格 +// * @param row 添加的行 +// * @param column 添加列号 +// * @param val 添加值 +// * @param align 对齐方式(1:靠左;2:居中;3:靠右) +// * @return 单元格对象 +// */ + public Cell addCell(Row row, int column, Object val, int align, Class fieldType){ + Cell cell = row.createCell(column); + CellStyle style = styles.get("data"+(align>=1&&align<=3?align:"")); + try { + if (val == null){ + cell.setCellValue(""); + } else if (val instanceof String) { + cell.setCellValue((String) val); + } else if (val instanceof Integer) { + cell.setCellValue((Integer) val); + } else if (val instanceof Long) { + cell.setCellValue((Long) val); + } else if (val instanceof Double) { + cell.setCellValue((Double) val); + } else if (val instanceof Float) { + cell.setCellValue((Float) val); + } else if (val instanceof Date) { + DataFormat format = wb.createDataFormat(); + style.setDataFormat(format.getFormat("yyyy-MM-dd")); + cell.setCellValue((Date) val); + } else { + if (fieldType != Class.class){ + cell.setCellValue((String)fieldType.getMethod("setValue", Object.class).invoke(null, val)); + }else{ + cell.setCellValue((String)Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(), + "fieldtype."+val.getClass().getSimpleName()+"Type")).getMethod("setValue", Object.class).invoke(null, val)); + } + } + } catch (Exception ex) { + log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString()); + cell.setCellValue(val.toString()); + } + cell.setCellStyle(style); + return cell; + } + +// /** +// * 添加数据(通过annotation.ExportField添加数据) +// * @return list 数据列表 +// */ + public ExportExcel setDataList(List list){ + for (E e : list){ + int colunm = 0; + Row row = this.addRow(); + StringBuilder sb = new StringBuilder(); + for (Object[] os : annotationList){ + ExcelField ef = (ExcelField)os[0]; + Object val = null; + try{ + if (StringUtils.isNotBlank(ef.value())){ + val = Reflections.invokeGetter(e, ef.value()); + }else{ + if (os[1] instanceof Field){ + val = Reflections.invokeGetter(e, ((Field)os[1]).getName()); + }else if (os[1] instanceof Method){ + val = Reflections.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {}); + } + } + }catch(Exception ex) { + log.info(ex.toString()); + val = ""; + } + this.addCell(row, colunm++, val, ef.align(), ef.fieldType()); + sb.append(val + ", "); + } + log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString()); + } + return this; + } + +// /** +// * 输出数据流 +// * @param os 输出数据流 +// */ + public ExportExcel write(OutputStream os) throws IOException{ + wb.write(os); + return this; + } + +// /** +// * 输出到客户端 +// * @param fileName 输出文件名 +// */ + public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{ + response.reset(); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setContentType("application/octet-stream; charset=utf-8"); + response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName, "utf-8")); + write(response.getOutputStream()); + return this; + } + +// /** +// * 清理临时文件 +// */ + public ExportExcel dispose(){ + wb.dispose(); + return this; + } + +} diff --git a/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java b/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java new file mode 100644 index 0000000..d4c1fa4 --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java @@ -0,0 +1,303 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.yf.exam.core.utils.excel; + +import com.google.common.collect.Lists; +import com.yf.exam.core.utils.Reflections; +import com.yf.exam.core.utils.excel.annotation.ExcelField; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.hssf.usermodel.HSSFDateUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +///** +// * 导入Excel文件(支持“XLS”和“XLSX”格式) +// * @author jeeplus +// * @version 2016-03-10 +// */ +public class ImportExcel { + + private static Logger log = LoggerFactory.getLogger(ImportExcel.class); + +// /** +// * 工作薄对象 +// */ + private Workbook wb; + +// /** +// * 工作表对象 +// */ + private Sheet sheet; +// +// /** +// * 标题行号 +// */ + private int headerNum; + + + +// /** +// * 构造函数 +// * @param multipartFile 导入文件对象 +// * @param headerNum 标题行号,数据行号=标题行号+1 +// * @param sheetIndex 工作表编号 +// * @throws InvalidFormatException +// * @throws IOException +// */ + public ImportExcel(MultipartFile multipartFile, int headerNum, int sheetIndex) + throws InvalidFormatException, IOException { + this(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), headerNum, sheetIndex); + } + +// /** +// * 构造函数 +// * @param is 导入文件对象 +// * @param headerNum 标题行号,数据行号=标题行号+1 +// * @param sheetIndex 工作表编号 +// * @throws InvalidFormatException +// * @throws IOException +// */ + public ImportExcel(String fileName, InputStream is, int headerNum, int sheetIndex) + throws IOException { + if (StringUtils.isBlank(fileName)){ + throw new RuntimeException("导入文档为空!"); + }else if(fileName.toLowerCase().endsWith("xls")){ + this.wb = new HSSFWorkbook(is); + }else if(fileName.toLowerCase().endsWith("xlsx")){ + this.wb = new XSSFWorkbook(is); + }else{ + throw new RuntimeException("文档格式不正确!"); + } + if (this.wb.getNumberOfSheets() List getDataList(Class cls, int... groups) throws InstantiationException, IllegalAccessException{ + List annotationList = Lists.newArrayList(); + // Get annotation field + Field[] fs = cls.getDeclaredFields(); + for (Field f : fs){ + ExcelField ef = f.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==2)){ + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, f}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, f}); + } + } + } + // Get annotation method + Method[] ms = cls.getDeclaredMethods(); + for (Method m : ms){ + ExcelField ef = m.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==2)){ + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, m}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, m}); + } + } + } + // Field sorting + Collections.sort(annotationList, new Comparator() { + @Override + public int compare(Object[] o1, Object[] o2) { + return new Integer(((ExcelField)o1[0]).sort()).compareTo( + new Integer(((ExcelField)o2[0]).sort())); + } + }); + // Get excel data + List dataList = Lists.newArrayList(); + for (int i = this.getDataRowNum(); i < this.getLastDataRowNum(); i++) { + E e = (E)cls.newInstance(); + int column = 0; + Row row = this.getRow(i); + StringBuilder sb = new StringBuilder(); + for (Object[] os : annotationList){ + Object val = this.getCellValue(row, column++); + if (val != null){ + ExcelField ef = (ExcelField)os[0]; + // Get param type and type cast + Class valType = Class.class; + if (os[1] instanceof Field){ + valType = ((Field)os[1]).getType(); + }else if (os[1] instanceof Method){ + Method method = ((Method)os[1]); + if ("get".equals(method.getName().substring(0, 3))){ + valType = method.getReturnType(); + }else if("set".equals(method.getName().substring(0, 3))){ + valType = ((Method)os[1]).getParameterTypes()[0]; + } + } + //log.debug("Import value type: ["+i+","+column+"] " + valType); + try { + //如果导入的java对象,需要在这里自己进行变换。 + if (valType == String.class){ + String s = String.valueOf(val.toString()); + if(StringUtils.endsWith(s, ".0")){ + val = StringUtils.substringBefore(s, ".0"); + }else{ + val = String.valueOf(val.toString()); + } + }else if (valType == Integer.class){ + val = Double.valueOf(val.toString()).intValue(); + }else if (valType == Long.class){ + val = Double.valueOf(val.toString()).longValue(); + }else if (valType == Double.class){ + val = Double.valueOf(val.toString()); + }else if (valType == Float.class){ + val = Float.valueOf(val.toString()); + }else if (valType == Date.class){ + SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + val=sdf.parse(val.toString()); + }else{ + if (ef.fieldType() != Class.class){ + val = ef.fieldType().getMethod("getValue", String.class).invoke(null, val.toString()); + }else{ + val = Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(), + "fieldtype."+valType.getSimpleName()+"Type")).getMethod("getValue", String.class).invoke(null, val.toString()); + } + } + } catch (Exception ex) { + log.info("Get cell value ["+i+","+column+"] error: " + ex.toString()); + val = null; + } + // set entity value + if (os[1] instanceof Field){ + Reflections.invokeSetter(e, ((Field)os[1]).getName(), val); + }else if (os[1] instanceof Method){ + String mthodName = ((Method)os[1]).getName(); + if ("get".equals(mthodName.substring(0, 3))){ + mthodName = "set"+StringUtils.substringAfter(mthodName, "get"); + } + Reflections.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val}); + } + } + sb.append(val+", "); + } + dataList.add(e); + log.debug("Read success: ["+i+"] "+sb.toString()); + } + return dataList; + } + +} diff --git a/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java b/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java new file mode 100644 index 0000000..1670f76 --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.yf.exam.core.utils.excel.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +///** +// * Excel注解定义 +// * @author jeeplus +// * @version 2016-03-10 +// */ +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcelField { + +// /** +// * 导出字段名(默认调用当前字段的“get”方法,如指定导出字段为对象,请填写“对象名.对象属性”,例:“area.name”、“office.name”) +// */ + String value() default ""; + +// /** +// * 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效) +// */ + String title(); + +// /** +// * 字段类型(0:导出导入;1:仅导出;2:仅导入) +// */ + int type() default 0; + +// /** +// * 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右) +// */ + int align() default 0; + +// /** +// * 导出字段字段排序(升序) +// */ + int sort() default 0; + +// /** +// * 如果是字典类型,请设置字典的type值 +// */ + String dictType() default ""; + +// /** +// * 反射类型 +// */ + Class fieldType() default Class.class; + +// /** +// * 字段归属组(根据分组导出导入) +// */ + int[] groups() default {}; +} diff --git a/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java b/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java new file mode 100644 index 0000000..f7b4500 --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.yf.exam.core.utils.excel.fieldtype; + +import com.google.common.collect.Lists; +import com.yf.exam.core.utils.StringUtils; +import java.util.List; + +///** +// * 字段类型转换工具类,用于处理Excel导入导出时的List类型字段转换。 +// * +// * @author jeeplus +// * @version 2016-5-29 +// */ +public class ListType { + +// /** +// * 从字符串中获取对象值(导入)。 +// * 该方法将输入的字符串按逗号分割,并将每个分割后的子字符串添加到List中。 +// * +// * @param val 输入的字符串,格式为“item1,item2,item3,...” +// * @return 包含分割后子字符串的List对象 +// */ + public static Object getValue(String val) { + List list = Lists.newArrayList(); + if (!StringUtils.isBlank(val)) { + for (String s : val.split(",")) { + list.add(s); + } + } + return list; + } + +// /** +// * 将对象值设置为字符串(导出)。 +// * 该方法将输入的List对象中的每个元素按逗号拼接成一个字符串。 +// * +// * @param val 输入的List对象 +// * @return 拼接后的字符串,格式为“item1,item2,item3,...” +// */ + public static String setValue(Object val) { + if (val != null) { + List list = (List) val; + StringBuffer sb = null; + for (String item : list) { + if (StringUtils.isBlank(item)) { + continue; + } + if (sb == null) { + sb = new StringBuffer(item); + } else { + sb.append(",").append(item); + } + } + if (sb != null) { + return sb.toString().replace("[]", ""); + } + } + return ""; + } +} diff --git a/src/main/java/com/yf/exam/core/utils/file/Md5Util.java b/src/main/java/com/yf/exam/core/utils/file/Md5Util.java new file mode 100644 index 0000000..94f7a73 --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/file/Md5Util.java @@ -0,0 +1,33 @@ +package com.yf.exam.core.utils.file; + +import java.security.MessageDigest; + +///** +// * MD5工具类,用于生成字符串的MD5哈希值。 +// * +// * @author Bool +// * @version 2018年1月13日 下午6:54:53 +// */ +public class Md5Util { + +// /** +// * 生成给定字符串的简单MD5哈希值。 +// * 该方法将输入字符串编码为UTF-8字节数组,然后使用MD5算法进行哈希计算,最后将哈希结果转换为十六进制字符串。 +// * +// * @param str 输入的字符串 +// * @return 字符串的MD5哈希值,如果发生异常则返回null +// */ + public static String md5(String str) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] array = md.digest(str.getBytes("UTF-8")); + StringBuilder sb = new StringBuilder(); + for (byte item : array) { + sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); + } + return sb.toString(); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java b/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java new file mode 100644 index 0000000..cc65066 --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java @@ -0,0 +1,57 @@ +package com.yf.exam.core.utils.passwd; + + +import com.yf.exam.core.utils.file.Md5Util; +import org.apache.commons.lang3.RandomStringUtils; + +///** +// * 通用的密码处理类,用于生成密码和校验密码 +// * ClassName: PassGenerator
+// * date: 2017年12月13日 下午7:13:03
+// * +// * @author Bool +// * @version +// */ +public class PassHandler { + +// /** +// * checkPass:校验密码是否一致 +// * @author Bool +// * @param inputPass 用户传入的密码 +// * @param salt 数据库保存的密码随机码 +// * @param pass 数据库保存的密码MD5 +// * @return +// */ + public static boolean checkPass(String inputPass , String salt , String pass){ + String pwdMd5 = Md5Util.md5(inputPass); + return Md5Util.md5(pwdMd5 + salt).equals(pass); + } + + +// /** +// * +// * buildPassword:用于用户注册时产生一个密码 +// * @author Bool +// * @param inputPass 输入的密码 +// * @return PassInfo 返回一个密码对象,记得保存 +// */ + public static PassInfo buildPassword(String inputPass) { + + //产生一个6位数的随机码 + String salt = RandomStringUtils.randomAlphabetic(6); + //加密后的密码 + String encryptPassword = Md5Util.md5(Md5Util.md5(inputPass)+salt); + //返回对象 + return new PassInfo(salt,encryptPassword); + } + + + public static void main(String[] args) { + + PassInfo info = buildPassword("190601"); + + System.out.println(info.getPassword()); + System.out.println(info.getSalt()); + } + +} diff --git a/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java b/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java new file mode 100644 index 0000000..f0ff99f --- /dev/null +++ b/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java @@ -0,0 +1,70 @@ +package com.yf.exam.core.utils.passwd; + +///** +// * 密码实体类,用于存储和管理密码及其随机盐值。 +// * +// * @author Bool +// * @version 2018年2月13日 下午7:13:50 +// */ +public class PassInfo { + +// /** +// * 密码的随机盐值。 +// * 用于增强密码的安全性。 +// */ + private String salt; + +// /** +// * 经过MD5哈希处理后的密码。 +// * 存储最终用于验证的密码字符串。 +// */ + private String password; + +// /** +// * 构造方法,用于初始化密码实体对象。 +// * +// * @param salt 密码的随机盐值 +// * @param password 经过MD5哈希处理后的密码 +// */ + public PassInfo(String salt, String password) { + super(); + this.salt = salt; + this.password = password; + } + +// /** +// * 获取密码的随机盐值。 +// * +// * @return 密码的随机盐值 +// */ + public String getSalt() { + return salt; + } + +// /** +// * 设置密码的随机盐值。 +// * +// * @param salt 密码的随机盐值 +// */ + public void setSalt(String salt) { + this.salt = salt; + } + +// /** +// * 获取经过MD5哈希处理后的密码。 +// * +// * @return 经过MD5哈希处理后的密码 +// */ + public String getPassword() { + return password; + } + +// /** +// * 设置经过MD5哈希处理后的密码。 +// * +// * @param password 经过MD5哈希处理后的密码 +// */ + public void setPassword(String password) { + this.password = password; + } +} 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 new file mode 100644 index 0000000..cbc57f8 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java @@ -0,0 +1,151 @@ +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; + +///** +//*

+//* 考试控制器 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-07-25 16:18 +//*/ +@Api(tags={"考试"}) +@RestController +@RequestMapping("/exam/api/exam/exam") +public class ExamController extends BaseController { + + @Autowired + 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 +// */ + @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 +// * @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 +// * @return +// */ + @RequiresRoles("sa") + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/state", method = { RequestMethod.POST}) + 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()); + + baseService.update(exam, wrapper); + return super.success(); + } + + +// /** +// * 分页查找 +// * @param reqDTO +// * @return +// */ + @ApiOperation(value = "考试视角") + @RequestMapping(value = "/online-paging", method = { RequestMethod.POST}) + 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 +// */ + @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); + } + + +} 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 new file mode 100644 index 0000000..c83a9e5 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java @@ -0,0 +1,101 @@ +package com.yf.exam.modules.exam.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 java.io.Serializable; +import java.util.Date; + +///** +//*

+//* 考试数据传输类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-07-25 16:18 +//*/ +@Data +@ApiModel(value="考试", description="考试") +public class ExamDTO implements Serializable { + + + private static final long serialVersionUID = 1L; + + + @ApiModelProperty(value = "ID", required=true) + private String id; + + @ApiModelProperty(value = "考试名称", required=true) + private String title; + + @ApiModelProperty(value = "考试描述", required=true) + private String content; + + @ApiModelProperty(value = "1公开2部门3定员", required=true) + private Integer openType; + + @ApiModelProperty(value = "考试状态", required=true) + private Integer state; + + @ApiModelProperty(value = "是否限时", required=true) + private Boolean timeLimit; + + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") + @DateTimeFormat(pattern = "yyyy-MM-dd") + @ApiModelProperty(value = "开始时间", required=true) + private Date startTime; + + @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; + + + + + /** + * 是否结束 + * @return + */ + 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() > startTime.getTime() + && System.currentTimeMillis() < endTime.getTime() + && !ExamState.DISABLED.equals(this.state)){ + return ExamState.ENABLE; + } + + } + + return this.state; + } +} 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 new file mode 100644 index 0000000..fb95681 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java @@ -0,0 +1,33 @@ +package com.yf.exam.modules.exam.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +///** +//*

+//* 考试部门数据传输类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-03 17:24 +//*/ +@Data +@ApiModel(value="考试部门", description="考试部门") +public class ExamDepartDTO 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 departId; + +} 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 new file mode 100644 index 0000000..4e3905f --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java @@ -0,0 +1,51 @@ +package com.yf.exam.modules.exam.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +///** +//*

+//* 考试题库数据传输类 +//*

+//* +//* @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; + +} 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 new file mode 100644 index 0000000..3c0e961 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java @@ -0,0 +1,32 @@ +package com.yf.exam.modules.exam.dto.ext; + +import com.yf.exam.modules.exam.dto.ExamRepoDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +///** +//*

+//* 考试题库数据传输类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-05 11:14 +//*/ +@Data +@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类") +public class ExamRepoExtDTO extends ExamRepoDTO { + + private static final long serialVersionUID = 1L; + + + @ApiModelProperty(value = "单选题总量", required=true) + private Integer totalRadio; + + @ApiModelProperty(value = "多选题总量", required=true) + private Integer totalMulti; + + @ApiModelProperty(value = "判断题总量", required=true) + 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 new file mode 100644 index 0000000..f7ac8da --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java @@ -0,0 +1,32 @@ +package com.yf.exam.modules.exam.dto.request; + +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 java.util.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; + +} 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 new file mode 100644 index 0000000..1243faa --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java @@ -0,0 +1,22 @@ +package com.yf.exam.modules.exam.dto.response; + +import com.yf.exam.modules.exam.dto.ExamDTO; +import io.swagger.annotations.ApiModel; +import lombok.Data; + + +//*

+//* 考试分页响应类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-07-25 16:18 +//*/ +@Data +@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类") +public class ExamOnlineRespDTO extends ExamDTO { + + private static final long serialVersionUID = 1L; + + +} 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 new file mode 100644 index 0000000..9da1b8b --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java @@ -0,0 +1,31 @@ +package com.yf.exam.modules.exam.dto.response; + +import com.yf.exam.modules.exam.dto.ExamDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +///** +//*

+//* 考试分页响应类 +//*

+//* +//* @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; + + + +} 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 new file mode 100644 index 0000000..1fbbba7 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/entity/Exam.java @@ -0,0 +1,100 @@ +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; + +///** +//*

+//* 考试实体类 +//*

+//* +//* @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; + +} 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 new file mode 100644 index 0000000..9c3af6b --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java @@ -0,0 +1,42 @@ +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; + +///** +//*

+//* 考试部门实体类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-03 17:24 +//*/ +@Data +@TableName("el_exam_depart") +public class ExamDepart extends Model { + + private static final long serialVersionUID = 1L; + +// /** +// * ID +// */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + +// /** +// * 考试ID +// */ + @TableField("exam_id") + private String examId; + +// /** +// * 部门ID +// */ + @TableField("depart_id") + private String departId; + +} 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 new file mode 100644 index 0000000..faffa1a --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java @@ -0,0 +1,78 @@ +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; + +///** +//*

+//* 考试题库实体类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-05 11:14 +//*/ +@Data +@TableName("el_exam_repo") +public class ExamRepo extends Model { + + private static final long serialVersionUID = 1L; + +// /** +// * ID +// */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + +// /** +// * 考试ID +// */ + @TableField("exam_id") + private String examId; + +// /** +// * 题库ID +// */ + @TableField("repo_id") + private String repoId; + +// /** +// * 单选题数量 +// */ + @TableField("radio_count") + private Integer radioCount; + +// /** +// * 单选题分数 +// */ + @TableField("radio_score") + private Integer radioScore; + +// /** +// * 多选题数量 +// */ + @TableField("multi_count") + private Integer multiCount; + +// /** +// * 多选题分数 +// */ + @TableField("multi_score") + private Integer multiScore; + +// /** +// * 判断题数量 +// */ + @TableField("judge_count") + private Integer judgeCount; + +// /** +// * 判断题分数 +// */ + @TableField("judge_score") + private Integer judgeScore; + +} 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 new file mode 100644 index 0000000..7bbc393 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java @@ -0,0 +1,15 @@ +package com.yf.exam.modules.exam.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 { + +} 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 new file mode 100644 index 0000000..e5b1c99 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java @@ -0,0 +1,45 @@ +package com.yf.exam.modules.exam.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; + +///** +//*

+//* 考试Mapper +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-07-25 16:18 +//*/ +public interface ExamMapper extends BaseMapper { + +// /** +// * 查找分页内容 +// * @param page +// * @param query +// * @return +// */ + IPage paging(Page page, @Param("query") ExamDTO query); + +// /** +// * 查找分页内容 +// * @param page +// * @param query +// * @return +// */ + IPage reviewPaging(Page page, @Param("query") ExamDTO query); + +// /** +// * 在线考试分页响应类-考生视角 +// * @param page +// * @param query +// * @return +// */ + IPage online(Page page, @Param("query") ExamDTO query); +} 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 new file mode 100644 index 0000000..88a8f92 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java @@ -0,0 +1,26 @@ +package com.yf.exam.modules.exam.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 java.util.List; + +///** +//*

+//* 考试题库Mapper +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-05 11:14 +//*/ +public interface ExamRepoMapper extends BaseMapper { + +// /** +// * 查找考试题库列表 +// * @param examId +// * @return +// */ + List listByExam(@Param("examId") String examId); +} 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 new file mode 100644 index 0000000..4499374 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java @@ -0,0 +1,32 @@ +package com.yf.exam.modules.exam.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.modules.exam.entity.ExamDepart; + +import java.util.List; + +///** +//*

+//* 考试部门业务类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-03 17:24 +//*/ +public interface ExamDepartService extends IService { + +// /** +// * 保存全部 +// * @param examId +// * @param departs +// */ + void saveAll(String examId, List departs); + + +// /** +// * 根据考试查找对应的部门 +// * @param examId +// * @return +// */ + List listByExam(String examId); +} 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 new file mode 100644 index 0000000..7a8420d --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java @@ -0,0 +1,40 @@ +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 java.util.List; + +///** +//*

+//* 考试题库业务类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-05 11:14 +//*/ +public interface ExamRepoService extends IService { + + +// /** +// * 保存全部 +// * @param examId +// * @param list +// */ + void saveAll(String examId, List list); +// +// /** +// * 查找考试题库列表 +// * @param examId +// * @return +// */ + List listByExam(String examId); + +// /** +// * 清理脏数据 +// * @param examId +// */ + void clear(String examId); + +} 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 new file mode 100644 index 0000000..d619d5a --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/service/ExamService.java @@ -0,0 +1,63 @@ +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; + +///** +//*

+//* 考试业务类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-07-25 16:18 +//*/ +public interface ExamService extends IService { + +// /** +// * 保存考试信息 +// * @param reqDTO +// */ + void save(ExamSaveReqDTO reqDTO); + +// /** +// * 查找考试详情 +// * @param id +// * @return +// */ + ExamSaveReqDTO findDetail(String id); + +// /** +// * 查找考试详情--简要信息 +// * @param id +// * @return +// */ + ExamDTO findById(String id); + +// /** +// * 分页查询数据 +// * @param reqDTO +// * @return +// */ + IPage paging(PagingReqDTO reqDTO); + + +// /** +// * 在线考试分页响应类-考生视角 +// * @param reqDTO +// * @return +// */ + IPage onlinePaging(PagingReqDTO reqDTO); + + +// * 待阅试卷列表 +// * @param reqDTO +// * @return +// */ + IPage reviewPaging(PagingReqDTO reqDTO); +} 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 new file mode 100644 index 0000000..0f4b20f --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java @@ -0,0 +1,66 @@ +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 java.util.ArrayList; +import java.util.List; + +///** +//*

+//* 考试部门业务实现类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-03 17:24 +//*/ +@Service +public class ExamDepartServiceImpl extends ServiceImpl implements ExamDepartService { + + @Override + public void saveAll(String examId, List departs) { + + // 先删除 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(ExamDepart::getExamId, examId); + this.remove(wrapper); + + // 再增加 + if(CollectionUtils.isEmpty(departs)){ + throw new ServiceException(1, "请至少选择选择一个部门!!"); + } + List list = new ArrayList<>(); + + for(String id: departs){ + ExamDepart depart = new ExamDepart(); + depart.setDepartId(id); + depart.setExamId(examId); + list.add(depart); + } + + 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()); + } + } + + return ids; + + } +} 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 new file mode 100644 index 0000000..84b81c9 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java @@ -0,0 +1,67 @@ +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; + +///** +//*

+//* 考试题库业务实现类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-09-05 11:14 +//*/ +@Service +public class ExamRepoServiceImpl extends ServiceImpl implements ExamRepoService { + + + @Transactional(rollbackFor = Exception.class) + @Override + public void saveAll(String examId, List list) { + + // 先删除 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(ExamRepo::getExamId, examId); + this.remove(wrapper); + + // 再增加 + 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()); + } + + this.saveBatch(repos); + } + + @Override + public List listByExam(String examId) { + return baseMapper.listByExam(examId); + } + + @Override + public void clear(String examId) { + + // 先删除 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(ExamRepo::getExamId, examId); + this.remove(wrapper); + } + + +} 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 new file mode 100644 index 0000000..3fac3c3 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java @@ -0,0 +1,194 @@ +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; + +///** +//*

+//* 考试业务实现类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-07-25 16:18 +//*/ +@Service +public class ExamServiceImpl extends ServiceImpl implements ExamService { + + + @Autowired + private ExamRepoService examRepoService; + + @Autowired + private ExamDepartService examDepartService; + + @Override + public void save(ExamSaveReqDTO reqDTO) { + + // ID + String id = reqDTO.getId(); + + if(StringUtils.isBlank(id)){ + id = IdWorker.getIdStr(); + } + + //复制参数 + Exam entity = new Exam(); + + // 计算分值 + this.calcScore(reqDTO); + + + // 复制基本数据 + BeanMapper.copy(reqDTO, entity); + entity.setId(id); + + // 修复状态 + if (reqDTO.getTimeLimit()!=null + && !reqDTO.getTimeLimit() + && reqDTO.getState()!=null + && reqDTO.getState() == 2) { + entity.setState(0); + } else { + entity.setState(reqDTO.getState()); + } + + // 题库组卷 + try { + examRepoService.saveAll(id, reqDTO.getRepoList()); + }catch (DuplicateKeyException e){ + throw new ServiceException(1, "不能选择重复的题库!"); + } + + + // 开放的部门 + if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){ + examDepartService.saveAll(id, reqDTO.getDepartIds()); + } + + this.saveOrUpdate(entity); + + } + + @Override + public ExamSaveReqDTO findDetail(String id) { + ExamSaveReqDTO respDTO = new ExamSaveReqDTO(); + Exam exam = this.getById(id); + BeanMapper.copy(exam, respDTO); + + // 考试部门 + List departIds = examDepartService.listByExam(id); + respDTO.setDepartIds(departIds); + + // 题库 + List repos = examRepoService.listByExam(id); + respDTO.setRepoList(repos); + + return respDTO; + } + + @Override + public ExamDTO findById(String id) { + ExamDTO respDTO = new ExamDTO(); + Exam exam = this.getById(id); + BeanMapper.copy(exam, respDTO); + return respDTO; + } + + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + + //转换结果 + IPage pageData = baseMapper.paging(page, reqDTO.getParams()); + return pageData; + } + + @Override + public IPage onlinePaging(PagingReqDTO reqDTO) { + + + // 创建分页对象 + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + + // 查找分页 + IPage pageData = baseMapper.online(page, reqDTO.getParams()); + + return pageData; + } + + @Override + public IPage reviewPaging(PagingReqDTO reqDTO) { + // 创建分页对象 + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + + // 查找分页 + IPage pageData = baseMapper.reviewPaging(page, reqDTO.getParams()); + + return pageData; + } + + +// /** +// * 计算分值 +// * @param 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(); + } + 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(); + } + } + + + + reqDTO.setTotalScore(objScore); + } + +} diff --git a/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java b/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java new file mode 100644 index 0000000..4df0c66 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java @@ -0,0 +1,159 @@ +package com.yf.exam.modules.paper.controller; + +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.BaseIdRespDTO; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; +import com.yf.exam.modules.paper.dto.request.PaperCreateReqDTO; +import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; +import com.yf.exam.modules.paper.dto.request.PaperQuQueryDTO; +import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; +import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; +import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; +import com.yf.exam.modules.paper.entity.Paper; +import com.yf.exam.modules.paper.service.PaperService; +import com.yf.exam.modules.user.UserUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.BeanUtils; +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; + +///** +//*

+//* 试卷控制器 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +@Api(tags={"试卷"}) +@RestController +@RequestMapping("/exam/api/paper/paper") +public class PaperController extends BaseController { + + @Autowired + private PaperService baseService; + +// /** +// * 分页查找 +// * @param reqDTO +// * @return +// */ + @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 +// */ + @ApiOperation(value = "创建试卷") + @RequestMapping(value = "/create-paper", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody PaperCreateReqDTO reqDTO) { + //复制参数 + String paperId = baseService.createPaper(UserUtils.getUserId(), reqDTO.getExamId()); + return super.success(new BaseIdRespDTO(paperId)); + } + +// /** +// * 批量删除 +// * @param reqDTO +// * @return +// */ + @ApiOperation(value = "试卷详情") + @RequestMapping(value = "/paper-detail", method = { RequestMethod.POST}) + public ApiRest paperDetail(@RequestBody BaseIdReqDTO reqDTO) { + //根据ID删除 + ExamDetailRespDTO respDTO = baseService.paperDetail(reqDTO.getId()); + return super.success(respDTO); + } + +// /** +// * 批量删除 +// * @param reqDTO +// * @return +// */ + @ApiOperation(value = "试题详情") + @RequestMapping(value = "/qu-detail", method = { RequestMethod.POST}) + public ApiRest quDetail(@RequestBody PaperQuQueryDTO reqDTO) { + //根据ID删除 + PaperQuDetailDTO respDTO = baseService.findQuDetail(reqDTO.getPaperId(), reqDTO.getQuId()); + return super.success(respDTO); + } + +// /** +// * 填充答案 +// * @param reqDTO +// * @return +// */ + @ApiOperation(value = "填充答案") + @RequestMapping(value = "/fill-answer", method = { RequestMethod.POST}) + public ApiRest fillAnswer(@RequestBody PaperAnswerDTO reqDTO) { + //根据ID删除 + baseService.fillAnswer(reqDTO); + return super.success(); + } + + +// /** +// * 交卷操作 +// * @param reqDTO +// * @return +// */ + @ApiOperation(value = "交卷操作") + @RequestMapping(value = "/hand-exam", method = { RequestMethod.POST}) + public ApiRest handleExam(@RequestBody BaseIdReqDTO reqDTO) { + //根据ID删除 + baseService.handExam(reqDTO.getId()); + return super.success(); + } + + +// /** +// * 批量删除 +// * @param reqDTO +// * @return +// */ + @ApiOperation(value = "试卷详情") + @RequestMapping(value = "/paper-result", method = { RequestMethod.POST}) + public ApiRest paperResult(@RequestBody BaseIdReqDTO reqDTO) { + //根据ID删除 + ExamResultRespDTO respDTO = baseService.paperResult(reqDTO.getId()); + return super.success(respDTO); + } + + +///** +// * 检测用户当前是否有进行中的考试 +// * 该方法会调用基础服务中的checkProcess方法来获取用户的考试状态 +// * @return ApiRest 包含用户进行中考试信息的ApiRest对象,如果没有进行中的考试,则PaperDTO为null +// */ +@ApiOperation(value = "检测进行中的考试") +@RequestMapping(value = "/check-process", method = { RequestMethod.POST}) +public ApiRest checkProcess() { + // 调用基础服务的checkProcess方法,传入当前用户ID,获取用户进行中的考试信息 + PaperDTO dto = baseService.checkProcess(UserUtils.getUserId()); + // 返回一个包含用户进行中考试信息的成功响应 + return super.success(dto); +} + +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java new file mode 100644 index 0000000..42dcde3 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java @@ -0,0 +1,148 @@ +package com.yf.exam.modules.paper.dto; + +import com.yf.exam.core.annon.Dict; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; +import java.util.Date; + +///** +// * 试卷请求类 +// * 该类用于表示试卷的详细信息 +// * +// * @author 聪明笨狗 +// * @since 2020-05-25 17:31 +// */ +@Data +@ApiModel(value = "试卷", description = "用于表示试卷详细信息的DTO类") +public class PaperDTO implements Serializable { +// /** +// * 序列化版本UID +// * 用于序列化和反序列化操作,保持版本一致性 +// */ + private static final long serialVersionUID = 1L; + +// /** +// * 试卷ID +// * 该字段是必填的,表示试卷的唯一标识符 +// */ + @ApiModelProperty(value = "试卷ID", required = true) + private String id; + +// /** +// * 用户ID +// * 该字段是必填的,表示试卷所属用户的唯一标识符 +// * 使用Dict注解关联sys_user表,根据id获取real_name +// */ + @Dict(dictTable = "sys_user", dicText = "real_name", dicCode = "id") + @ApiModelProperty(value = "用户ID", required = true) + private String userId; + +// /** +// * 部门ID +// * 该字段是必填的,表示试卷所属部门的唯一标识符 +// * 使用Dict注解关联sys_depart表,根据id获取dept_name +// */ + @Dict(dictTable = "sys_depart", dicText = "dept_name", dicCode = "id") + @ApiModelProperty(value = "部门ID", required = true) + private String departId; + +// /** +// * 规则ID +// * 该字段是必填的,表示试卷的考试规则的唯一标识符 +// */ + @ApiModelProperty(value = "规则ID", required = true) + private String examId; + +// /** +// * 考试标题 +// * 该字段是必填的,表示试卷的标题 +// */ + @ApiModelProperty(value = "考试标题", required = true) + private String title; + +// /** +// * 考试时长 +// * 该字段是必填的,表示试卷的考试总时长(分钟) +// */ + @ApiModelProperty(value = "考试时长", required = true) + private Integer totalTime; + +// /** +// * 用户时长 +// * 该字段是必填的,表示用户实际完成考试所用的时间(分钟) +// */ + @ApiModelProperty(value = "用户时长", required = true) + private Integer userTime; + +// /** +// * 试卷总分 +// * 该字段是必填的,表示试卷的总分值 +// */ + @ApiModelProperty(value = "试卷总分", required = true) + private Integer totalScore; + +// /** +// * 及格分 +// * 该字段是必填的,表示试卷的及格分数线 +// */ + @ApiModelProperty(value = "及格分", required = true) + private Integer qualifyScore; + +// /** +// * 客观分 +// * 该字段是必填的,表示试卷中客观题的总分值 +// */ + @ApiModelProperty(value = "客观分", required = true) + private Integer objScore; + +// /** +// * 主观分 +// * 该字段是必填的,表示试卷中主观题的总分值 +// */ + @ApiModelProperty(value = "主观分", required = true) + private Integer subjScore; + +// /** +// * 用户得分 +// * 该字段是必填的,表示用户在试卷中的实际得分 +// */ + @ApiModelProperty(value = "用户得分", required = true) + private Integer userScore; + +// /** +// * 是否包含简答题 +// * 该字段是必填的,表示试卷中是否包含简答题 +// */ + @ApiModelProperty(value = "是否包含简答题", required = true) + private Boolean hasSaq; + +// /** +// * 试卷状态 +// * 该字段是必填的,表示试卷的状态 +// */ + @ApiModelProperty(value = "试卷状态", required = true) + private Integer state; + +// /** +// * 创建时间 +// * 该字段是必填的,表示试卷的创建时间 +// */ + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +// /** +// * 更新时间 +// * 该字段是必填的,表示试卷的最后更新时间 +// */ + @ApiModelProperty(value = "更新时间", required = true) + private Date updateTime; + +// /** +// * 截止时间 +// * 该字段表示试卷的截止提交时间 +// */ + @ApiModelProperty(value = "截止时间") + private Date limitTime; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java new file mode 100644 index 0000000..9d2b0c5 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java @@ -0,0 +1,79 @@ +package com.yf.exam.modules.paper.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; + +///** +// * 试卷考题备选答案请求类 +// * 该类用于表示试卷考题的备选答案信息 +// * +// * @author 聪明笨狗 +// * @since 2020-05-25 17:31 +// */ +@Data +@ApiModel(value = "试卷考题备选答案", description = "用于表示试卷考题备选答案的DTO类") +public class PaperQuAnswerDTO implements Serializable { +// /** +// * 序列化版本UID +// * 用于序列化和反序列化操作,保持版本一致性 +// */ + private static final long serialVersionUID = 1L; + +// /** +// * 自增ID +// * 该字段是必填的,表示备选答案的唯一标识符 +// */ + @ApiModelProperty(value = "自增ID", required = true) + private String id; + +// /** +// * 试卷ID +// * 该字段是必填的,表示该备选答案所属试卷的唯一标识符 +// */ + @ApiModelProperty(value = "试卷ID", required = true) + private String paperId; + +// /** +// * 回答项ID +// * 该字段是必填的,表示该备选答案项的唯一标识符 +// */ + @ApiModelProperty(value = "回答项ID", required = true) + private String answerId; + +// /** +// * 题目ID +// * 该字段是必填的,表示该备选答案所属题目的唯一标识符 +// */ + @ApiModelProperty(value = "题目ID", required = true) + private String quId; + +// /** +// * 是否正确项 +// * 该字段是必填的,表示该备选答案是否为正确答案 +// */ + @ApiModelProperty(value = "是否正确项", required = true) + private Boolean isRight; + +// /** +// * 是否选中 +// * 该字段是必填的,表示该备选答案是否被用户选中 +// */ + @ApiModelProperty(value = "是否选中", required = true) + private Boolean checked; + +// /** +// * 排序 +// * 该字段是必填的,表示该备选答案在选项列表中的排序顺序 +// */ + @ApiModelProperty(value = "排序", required = true) + private Integer sort; + +// /** +// * 选项标签 +// * 该字段是必填的,表示该备选答案的选项标签(例如:A, B, C) +// */ + @ApiModelProperty(value = "选项标签", required = true) + private String abc; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java new file mode 100644 index 0000000..4c9f3b4 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java @@ -0,0 +1,93 @@ +package com.yf.exam.modules.paper.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; + +///** +// * 试卷考题请求类 +// * 该类用于表示试卷中考题的详细信息 +// * +// * @author 聪明笨狗 +// * @since 2020-05-25 17:31 +// */ +@Data +@ApiModel(value = "试卷考题", description = "用于表示试卷中考题详细信息的DTO类") +public class PaperQuDTO implements Serializable { +// /** +// * 序列化版本UID +// * 用于序列化和反序列化操作,保持版本一致性 +// */ + private static final long serialVersionUID = 1L; + +// /** +// * ID +// * 该字段是必填的,表示考题的唯一标识符 +// */ + @ApiModelProperty(value = "ID", required = true) + private String id; + +// /** +// * 试卷ID +// * 该字段是必填的,表示该考题所属试卷的唯一标识符 +// */ + @ApiModelProperty(value = "试卷ID", required = true) + private String paperId; + +// /** +// * 题目ID +// * 该字段是必填的,表示该考题本身的唯一标识符 +// */ + @ApiModelProperty(value = "题目ID", required = true) + private String quId; + +// /** +// * 题目类型 +// * 该字段是必填的,表示该考题的类型(例如:单选题、多选题、简答题等) +// */ + @ApiModelProperty(value = "题目类型", required = true) + private Integer quType; + +// /** +// * 是否已答 +// * 该字段是必填的,表示该考题是否已经被用户作答 +// */ + @ApiModelProperty(value = "是否已答", required = true) + private Boolean answered; + +// /** +// * 主观答案 +// * 该字段是必填的,表示用户对于主观题的回答内容 +// */ + @ApiModelProperty(value = "主观答案", required = true) + private String answer; + +// /** +// * 问题排序 +// * 该字段是必填的,表示该考题在试卷中的排序顺序 +// */ + @ApiModelProperty(value = "问题排序", required = true) + private Integer sort; + +// /** +// * 单题分分值 +// * 该字段是必填的,表示该考题的分值 +// */ + @ApiModelProperty(value = "单题分分值", required = true) + private Integer score; +// +// /** +// * 实际得分(主观题) +// * 该字段是必填的,表示用户在主观题上实际获得的得分 +// */ + @ApiModelProperty(value = "实际得分(主观题)", required = true) + private Integer actualScore; + +// /** +// * 是否答对 +// * 该字段是必填的,表示用户对于该考题的回答是否正确 +// */ + @ApiModelProperty(value = "是否答对", required = true) + private Boolean isRight; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java new file mode 100644 index 0000000..70af226 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java @@ -0,0 +1,35 @@ +package com.yf.exam.modules.paper.dto.ext; + +import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +///** +// *

+// * 扩展的试卷考题备选答案请求类 +// * 该类继承自PaperQuAnswerDTO,增加了试题图片和答案内容的字段 +// *

+// * +// * @author 聪明笨狗 +// * @since 2020-05-25 17:31 +// */ +@Data +@ApiModel(value = "试卷考题备选答案", description = "扩展的试卷考题备选答案请求类,包含试题图片和答案内容") +public class PaperQuAnswerExtDTO extends PaperQuAnswerDTO { + private static final long serialVersionUID = 1L; + +// /** +// * 试题图片的URL或路径 +// * 该字段是必填的,表示试题对应的图片 +// */ + @ApiModelProperty(value = "试题图片", required = true) + private String image; + +// /** +// * 答案内容 +// * 该字段是必填的,表示试题的答案内容 +// */ + @ApiModelProperty(value = "答案内容", required = true) + private String content; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java new file mode 100644 index 0000000..ab7797a --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java @@ -0,0 +1,43 @@ +package com.yf.exam.modules.paper.dto.ext; + +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.util.List; + +///** +// *

+// * 扩展的试卷考题请求类 +// * 该类继承自PaperQuDTO,增加了图片、题目内容和答案内容列表的字段 +// *

+// * +// * @author 聪明笨狗 +// * @since 2020-05-25 17:31 +// */ +@Data +@ApiModel(value = "试卷题目详情类", description = "扩展的试卷题目详情类,包含图片、题目内容和答案内容列表") +public class PaperQuDetailDTO extends PaperQuDTO { + private static final long serialVersionUID = 1L; + +// /** +// * 题目的图片URL或路径 +// * 该字段是必填的,表示题目的图片 +// */ + @ApiModelProperty(value = "图片", required = true) + private String image; + +// /** +// * 题目的内容 +// * 该字段是必填的,表示题目的详细内容 +// */ + @ApiModelProperty(value = "题目内容", required = true) + private String content; + +// /** +// * 答案内容列表 +// * 该字段是必填的,表示题目的所有答案内容 +// */ + @ApiModelProperty(value = "答案内容", required = true) + List answerList; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java new file mode 100644 index 0000000..91e735f --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java @@ -0,0 +1,30 @@ +package com.yf.exam.modules.paper.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.util.List; + +///** +// * 查找试卷题目详情请求类 +// * 该类继承自PaperQuQueryDTO,并添加了回答列表和主观答案的字段 +// * +// * @author bool +// */ +@Data +@ApiModel(value = "查找试卷题目详情请求类", description = "用于请求查找试卷题目详情的DTO类") +public class PaperAnswerDTO extends PaperQuQueryDTO { +// /** +// * 回答列表 +// * 该字段是必填的,包含用户对每个题目的回答 +// */ + @ApiModelProperty(value = "回答列表", required = true) + private List answers; + +// /** +// * 主观答案 +// * 该字段是必填的,包含用户对主观题目的回答 +// */ + @ApiModelProperty(value = "主观答案", required = true) + private String answer; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java new file mode 100644 index 0000000..b723c54 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java @@ -0,0 +1,31 @@ +package com.yf.exam.modules.paper.dto.request; + +import com.yf.exam.core.api.dto.BaseDTO; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +///** +// * 试卷创建请求类 +// * 该类继承自BaseDTO,用于创建试卷的请求参数 +// * +// * @author bool +// */ +@Data +@ApiModel(value = "试卷创建请求类", description = "用于请求创建试卷的DTO类") +public class PaperCreateReqDTO extends BaseDTO { +// /** +// * 用户ID +// * 该字段在JSON序列化时被忽略,用于内部处理 +// */ + @JsonIgnore + private String userId; + +// /** +// * 考试ID +// * 该字段是必填的,表示创建试卷所属的考试ID +// */ + @ApiModelProperty(value = "考试ID", required = true) + private String examId; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java new file mode 100644 index 0000000..940fd17 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java @@ -0,0 +1,56 @@ +package com.yf.exam.modules.paper.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; + +///** +// *

+// * 试卷列表请求类 +// * 该类用于请求试卷列表的参数 +// *

+// * +// * @author 聪明笨狗 +// * @since 2020-05-25 17:31 +// */ +@Data +@ApiModel(value = "试卷列表请求类", description = "用于请求试卷列表的DTO类") +public class PaperListReqDTO implements Serializable { + private static final long serialVersionUID = 1L; + +// /** +// * 用户ID +// * 该字段是必填的,表示请求试卷列表的用户ID +// */ + @ApiModelProperty(value = "用户ID", required = true) + private String userId; + +// /** +// * 部门ID +// * 该字段是必填的,表示请求试卷列表的部门ID +// */ + @ApiModelProperty(value = "部门ID", required = true) + private String departId; + +// /** +// * 规则ID +// * 该字段是必填的,表示请求试卷列表的规则ID(可能是考试ID) +// */ + @ApiModelProperty(value = "规则ID", required = true) + private String examId; + +// /** +// * 用户昵称 +// * 该字段是必填的,表示请求试卷列表的用户昵称 +// */ + @ApiModelProperty(value = "用户昵称", required = true) + private String realName; + +// /** +// * 试卷状态 +// * 该字段是必填的,表示请求试卷列表的试卷状态 +// */ + @ApiModelProperty(value = "试卷状态", required = true) + private Integer state; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java new file mode 100644 index 0000000..ed94b54 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java @@ -0,0 +1,30 @@ +package com.yf.exam.modules.paper.dto.request; + +import com.yf.exam.core.api.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +///** +// * 查找试卷题目详情请求类 +// * 该类继承自BaseDTO,用于请求查找试卷中某个题目的详情信息 +// * +// * @author bool +// */ +@Data +@ApiModel(value = "查找试卷题目详情请求类", description = "用于请求查找试卷中某个题目的详情信息的DTO类") +public class PaperQuQueryDTO extends BaseDTO { +// /** +// * 试卷ID +// * 该字段是必填的,表示要查找题目的试卷ID +// */ + @ApiModelProperty(value = "试卷ID", required = true) + private String paperId; + +// /** +// * 题目ID +// * 该字段是必填的,表示要查找的题目ID +// */ + @ApiModelProperty(value = "题目ID", required = true) + private String quId; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java new file mode 100644 index 0000000..5f351f3 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java @@ -0,0 +1,57 @@ +package com.yf.exam.modules.paper.dto.response; + +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.util.Calendar; +import java.util.List; + +///** +// * 考试详情响应类 +// * 该类继承自PaperDTO,用于返回考试的详细信息 +// * +// * @author 聪明笨狗 +// */ +@Data +@ApiModel(value = "考试详情", description = "用于返回考试详细信息的DTO类") +public class ExamDetailRespDTO extends PaperDTO { +// /** +// * 单选题列表 +// * 该字段是必填的,表示考试中的单选题列表 +// */ + @ApiModelProperty(value = "单选题列表", required = true) + private List radioList; + +// /** +// * 多选题列表 +// * 该字段是必填的,表示考试中的多选题列表 +// */ + @ApiModelProperty(value = "多选题列表", required = true) + private List multiList; + +// /** +// * 判断题列表 +// * 该字段是必填的,表示考试中的判断题列表 +// */ + @ApiModelProperty(value = "判断题列表", required = true) + private List judgeList; + +// /** +// * 获取剩余结束秒数 +// * 该方法计算并返回考试剩余的结束时间(秒) +// * +// * @return 剩余的秒数 +// */ + @ApiModelProperty(value = "剩余结束秒数", required = true) + public Long getLeftSeconds() { + // 创建日历实例并设置为当前考试的创建时间 + Calendar cl = Calendar.getInstance(); + cl.setTime(this.getCreateTime()); + // 添加考试的总时间(以分钟为单位) + cl.add(Calendar.MINUTE, getTotalTime()); + // 计算剩余时间的秒数 + return (cl.getTimeInMillis() - System.currentTimeMillis()) / 1000; + } +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java new file mode 100644 index 0000000..d0d27c5 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java @@ -0,0 +1,25 @@ +package com.yf.exam.modules.paper.dto.response; + +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.util.List; + +///** +// * 考试结果展示响应类 +// * 该类继承自PaperDTO,用于返回考试结果的详细信息 +// * +// * @author 聪明笨狗 +// */ +@Data +@ApiModel(value = "考试结果展示响应类", description = "用于返回考试结果详细信息的DTO类") +public class ExamResultRespDTO extends PaperDTO { +// /** +// * 问题列表 +// * 该字段是必填的,表示考试中的问题列表详细信息 +// */ + @ApiModelProperty(value = "问题列表", required = true) + private List quList; +} diff --git a/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java b/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java new file mode 100644 index 0000000..930339d --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java @@ -0,0 +1,30 @@ +package com.yf.exam.modules.paper.dto.response; + +import com.yf.exam.modules.paper.dto.PaperDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +///** +// * 试卷列表响应类 +// * 该类继承自PaperDTO,用于返回试卷列表的详细信息 +// * +// * @author 聪明笨狗 +// * @since 2020-05-25 17:31 +// */ +@Data +@ApiModel(value = "试卷列表响应类", description = "用于返回试卷列表详细信息的DTO类") +public class PaperListRespDTO extends PaperDTO { +// /** +// * 序列化版本UID +// * 用于序列化和反序列化操作,保持版本一致性 +// */ + private static final long serialVersionUID = 1L; +// +// /** +// * 人员姓名 +// * 该字段是必填的,表示试卷对应的人员姓名 +// */ + @ApiModelProperty(value = "人员姓名", required = true) + private String realName; +} diff --git a/src/main/java/com/yf/exam/modules/paper/entity/Paper.java b/src/main/java/com/yf/exam/modules/paper/entity/Paper.java new file mode 100644 index 0000000..852295b --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/entity/Paper.java @@ -0,0 +1,125 @@ +package com.yf.exam.modules.paper.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; + +///** +//*

+//* 试卷实体类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 17:31 +//*/ +@Data +@TableName("el_paper") +public class Paper extends Model { + + private static final long serialVersionUID = 1L; +// +// /** +// * 试卷ID +// */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; +// +// /** +// * 用户ID +// */ + @TableField("user_id") + private String userId; + +// /** +// * 部门ID +// */ + @TableField("depart_id") + private String departId; + +// /** +// * 规则ID +// */ + @TableField("exam_id") + private String examId; + +// /** +// * 考试标题 +// */ + private String title; +// +// /** +// * 考试时长 +// */ + @TableField("total_time") + private Integer totalTime; + +// /** +// * 用户时长 +// */ + @TableField("user_time") + private Integer userTime; + +// /** +// * 试卷总分 +// */ + @TableField("total_score") + private Integer totalScore; +// +// /** +// * 及格分 +// */ + @TableField("qualify_score") + private Integer qualifyScore; + +// /** +// * 客观分 +// */ + @TableField("obj_score") + private Integer objScore; + +// /** +// * 主观分 +// */ + @TableField("subj_score") + private Integer subjScore; + +// /** +// * 用户得分 +// */ + @TableField("user_score") + private Integer userScore; + +// /** +// * 是否包含简答题 +// */ + @TableField("has_saq") + private Boolean hasSaq; + +// /** +// * 试卷状态 +// */ + private Integer state; + +// /** +// * 创建时间 +// */ + @TableField("create_time") + private Date createTime; +// +// /** +// * 更新时间 +// */ + @TableField("update_time") + private Date updateTime; + +// /** +// * 截止时间 +// */ + @TableField("limit_time") + private Date limitTime; +} diff --git a/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java b/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java new file mode 100644 index 0000000..4b83b1b --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java @@ -0,0 +1,80 @@ +package com.yf.exam.modules.paper.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; +// +///** +//*

+//* 试卷考题实体类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 17:31 +//*/ +@Data +@TableName("el_paper_qu") +public class PaperQu extends Model { + + private static final long serialVersionUID = 1L; +// +// /** +// * ID +// */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; +// +// /** +// * 试卷ID +// */ + @TableField("paper_id") + private String paperId; + +// /** +// * 题目ID +// */ + @TableField("qu_id") + private String quId; + +// /** +// * 题目类型 +// */ + @TableField("qu_type") + private Integer quType; + +// /** +// * 是否已答 +// */ + private Boolean answered; + +// /** +// * 主观答案 +// */ + private String answer; +// +// /** +// * 问题排序 +// */ + private Integer sort; +// +// /** +// * 单题分分值 +// */ + private Integer score; + +// /** +// * 实际得分(主观题) +// */ + @TableField("actual_score") + private Integer actualScore; +// +// /** +// * 是否答对 +// */ + @TableField("is_right") + private Boolean isRight; + +} diff --git a/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java b/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java new file mode 100644 index 0000000..f119da8 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java @@ -0,0 +1,68 @@ +package com.yf.exam.modules.paper.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; + +///** +//*

+//* 试卷考题备选答案实体类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 17:31 +//*/ +@Data +@TableName("el_paper_qu_answer") +public class PaperQuAnswer extends Model { + + +// /** +// * 自增ID +// */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + +// /** +// * 试卷ID +// */ + @TableField("paper_id") + private String paperId; + +// /** +// * 回答项ID +// */ + @TableField("answer_id") + private String answerId; + +// /** +// * 题目ID +// */ + @TableField("qu_id") + private String quId; + +// /** +// * 是否正确项 +// */ + @TableField("is_right") + private Boolean isRight; +// +// /** +// * 是否选中 +// */ + private Boolean checked; + +// /** +// * 排序 +// */ + private Integer sort; + +// /** +// * 选项标签 +// */ + private String abc; + +} diff --git a/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java b/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java new file mode 100644 index 0000000..c85db62 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java @@ -0,0 +1,33 @@ +package com.yf.exam.modules.paper.enums; + + +///** +// * 考试状态 +// * @author bool +// * @date 2019-10-30 13:11 +// */ +public interface ExamState { + + +// /** +// * 考试中 +// */ + Integer ENABLE = 0; +// +// /** +// * 待阅卷 +// */ + Integer DISABLED = 1; + +// /** +// * 已完成 +// */ + Integer READY_START = 2; +// +// /** +// * 已结束 +// */ + Integer OVERDUE = 3; + + +} diff --git a/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java b/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java new file mode 100644 index 0000000..17f83d5 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java @@ -0,0 +1,33 @@ +package com.yf.exam.modules.paper.enums; + + +///** +// * 试卷状态 +// * @author bool +// * @date 2019-10-30 13:11 +// */ +public interface PaperState { + +// +// /** +// * 考试中 +// */ + Integer ING = 0; +// +// /** +// * 待阅卷 +// */ + Integer WAIT_OPT = 1; +// +// /** +// * 已完成 +// */ + Integer FINISHED = 2; + +// /** +// * 弃考 +// */ + Integer BREAK = 3; + + +} diff --git a/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java b/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java new file mode 100644 index 0000000..075d664 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java @@ -0,0 +1,57 @@ +package com.yf.exam.modules.paper.job; + +import com.yf.exam.ability.job.service.JobService; +import com.yf.exam.modules.paper.service.PaperService; +import lombok.extern.log4j.Log4j2; +import org.quartz.Job; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +///** +// * 超时自动交卷任务 +// * 该类实现了一个Quartz Job,用于在考试时间到期时自动提交试卷 +// * +// * @author bool +// */ +@Log4j2 +@Component +public class BreakExamJob implements Job { +// /** +// * 自动装配PaperService +// * 用于处理试卷相关的业务逻辑 +// */ + @Autowired + private PaperService paperService; + +// /** +// * 执行任务的方法 +// * 该方法会在Quartz调度器调用时执行,处理到期的交卷操作 +// * +// * @param jobExecutionContext 任务执行上下文 +// * @throws JobExecutionException 如果任务执行过程中发生异常,将抛出此异常 +// */ + @Override + public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { + // 获取任务详情 + JobDetail detail = jobExecutionContext.getJobDetail(); + + // 获取任务名称和组 + String name = detail.getKey().getName(); + String group = detail.getKey().getGroup(); + + // 从任务数据映射中获取任务数据 + String data = String.valueOf(detail.getJobDataMap().get(JobService.TASK_DATA)); + + // 记录日志信息 + log.info("++++++++++定时任务:处理到期的交卷"); + log.info("++++++++++jobName:{}", name); + log.info("++++++++++jobGroup:{}", group); + log.info("++++++++++taskData:{}", data); + + // 调用PaperService的handExam方法,强制交卷 + paperService.handExam(data); + } +} diff --git a/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java b/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java new file mode 100644 index 0000000..f2bb96c --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java @@ -0,0 +1,39 @@ +package com.yf.exam.modules.paper.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.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; +import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; +import com.yf.exam.modules.paper.entity.Paper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +///** +//*

+//* 试卷Mapper +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +public interface PaperMapper extends BaseMapper { + +// /** +// * 查找试卷分页 +// * @param page +// * @param query +// * @return +// */ + IPage paging(Page page, @Param("query") PaperListReqDTO query); + + +// /** +// * 试卷列表响应类 +// * @param query +// * @return +// */ + List list(@Param("query") PaperDTO query); +} diff --git a/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java b/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java new file mode 100644 index 0000000..ba41dfa --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java @@ -0,0 +1,27 @@ +package com.yf.exam.modules.paper.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; +import com.yf.exam.modules.paper.entity.PaperQuAnswer; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +///** +//*

+//* 试卷考题备选答案Mapper +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +public interface PaperQuAnswerMapper extends BaseMapper { + +// /** +// * 查找试卷试题答案列表 +// * @param paperId +// * @param quId +// * @return +// */ + List list(@Param("paperId") String paperId, @Param("quId") String quId); +} diff --git a/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java b/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java new file mode 100644 index 0000000..43b3c4f --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java @@ -0,0 +1,42 @@ +package com.yf.exam.modules.paper.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.entity.PaperQu; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +///** +//*

+//* 试卷考题Mapper +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +public interface PaperQuMapper extends BaseMapper { + +// /** +// * 统计客观分 +// * @param paperId +// * @return +// */ + int sumObjective(@Param("paperId") String paperId); +// +// /** +// * 统计主观分 +// * @param paperId +// * @return +// */ + int sumSubjective(@Param("paperId") String paperId); +// +// /** +// * 找出全部试题列表 +// * @param paperId +// * @return +// */ + List listByPaper(@Param("paperId") String paperId); +} + + diff --git a/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java b/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java new file mode 100644 index 0000000..01aeb09 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java @@ -0,0 +1,44 @@ +package com.yf.exam.modules.paper.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.paper.dto.PaperQuAnswerDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; +import com.yf.exam.modules.paper.entity.PaperQuAnswer; + +import java.util.List; + +///** +//*

+//* 试卷考题备选答案业务类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +public interface PaperQuAnswerService extends IService { + +// /** +// * 分页查询数据 +// * @param reqDTO +// * @return +// */ + IPage paging(PagingReqDTO reqDTO); + +// /** +// * 查找试卷试题答案列表 +// * @param paperId +// * @param quId +// * @return +// */ + List listForExam(String paperId, String quId); + +// /** +// * 查找答案列表,用来填充 +// * @param paperId +// * @param quId +// * @return +// */ + List listForFill(String paperId, String quId); +} diff --git a/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java b/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java new file mode 100644 index 0000000..6fa4f58 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java @@ -0,0 +1,70 @@ +package com.yf.exam.modules.paper.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.paper.dto.PaperQuDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.entity.PaperQu; + +import java.util.List; + +///** +//*

+//* 试卷考题业务类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +public interface PaperQuService extends IService { + +// /** +// * 分页查询数据 +// * @param reqDTO +// * @return +// */ + IPage paging(PagingReqDTO reqDTO); + +// /** +// * 根据试卷找出题目列表 +// * @param paperId +// * @return +// */ + List listByPaper(String paperId); + +// /** +// * 查找详情 +// * @param paperId +// * @param quId +// * @return +// */ + PaperQu findByKey(String paperId, String quId); +// +// /** +// * 根据组合索引更新 +// * @param qu +// */ + void updateByKey(PaperQu qu); + +// /** +// * 统计客观分 +// * @param paperId +// * @return +// */ + int sumObjective(String paperId); +// +// /** +// * 统计主观分 +// * @param paperId +// * @return +// */ + int sumSubjective(String paperId); + +// /** +// * 找出全部试题列表 +// * @param paperId +// * @return +// */ + List listForPaperResult(String paperId); +} diff --git a/src/main/java/com/yf/exam/modules/paper/service/PaperService.java b/src/main/java/com/yf/exam/modules/paper/service/PaperService.java new file mode 100644 index 0000000..2555044 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/service/PaperService.java @@ -0,0 +1,83 @@ +package com.yf.exam.modules.paper.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.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; +import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; +import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; +import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; +import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; +import com.yf.exam.modules.paper.entity.Paper; + +///** +//*

+//* 试卷业务类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +public interface PaperService extends IService { + +// /** +// * 创建试卷 +// * @param userId +// * @param examId +// * @return +// */ + String createPaper(String userId, String examId); + + +// /** +// * 查找详情 +// * @param paperId +// * @return +// */ + ExamDetailRespDTO paperDetail(String paperId); + +// /** +// * 考试结果 +// * @param paperId +// * @return +// */ + ExamResultRespDTO paperResult(String paperId); + +// /** +// * 查找题目详情 +// * @param paperId +// * @param quId +// * @return +// */ + PaperQuDetailDTO findQuDetail(String paperId, String quId); + +// /** +// * 填充答案 +// * @param reqDTO +// */ + void fillAnswer(PaperAnswerDTO reqDTO); + +// /** +// * 交卷操作 +// * @param paperId +// * @return +// */ + void handExam(String paperId); + +// /** +// * 试卷列表响应类 +// * @param reqDTO +// * @return +// */ + IPage paging(PagingReqDTO reqDTO); + +// /** +// * 检测是否有进行中的考试 +// * @param userId +// * @return +// */ + PaperDTO checkProcess(String userId); + +} diff --git a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java new file mode 100644 index 0000000..86e02c7 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java @@ -0,0 +1,61 @@ +package com.yf.exam.modules.paper.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +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.modules.paper.dto.PaperQuAnswerDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; +import com.yf.exam.modules.paper.entity.PaperQuAnswer; +import com.yf.exam.modules.paper.mapper.PaperQuAnswerMapper; +import com.yf.exam.modules.paper.service.PaperQuAnswerService; +import org.springframework.stereotype.Service; + +import java.util.List; + +///** +//*

+//* 语言设置 服务实现类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +@Service +public class PaperQuAnswerServiceImpl extends ServiceImpl implements PaperQuAnswerService { + + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + //获得数据 + IPage page = this.page(query, wrapper); + //转换结果 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + @Override + public List listForExam(String paperId, String quId) { + return baseMapper.list(paperId, quId); + } + + @Override + public List listForFill(String paperId, String quId) { + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(PaperQuAnswer::getPaperId, paperId) + .eq(PaperQuAnswer::getQuId, quId); + + return this.list(wrapper); + } +} diff --git a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java new file mode 100644 index 0000000..72ecab2 --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java @@ -0,0 +1,94 @@ +package com.yf.exam.modules.paper.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +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.utils.BeanMapper; +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.entity.PaperQu; +import com.yf.exam.modules.paper.mapper.PaperQuMapper; +import com.yf.exam.modules.paper.service.PaperQuService; +import org.springframework.stereotype.Service; + +import java.util.List; + +///** +//*

+//* 语言设置 服务实现类 +//*

+//* +//* @author 聪明笨狗 +//* @since 2020-05-25 16:33 +//*/ +@Service +public class PaperQuServiceImpl extends ServiceImpl implements PaperQuService { + + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + //获得数据 + IPage page = this.page(query, wrapper); + //转换结果 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + @Override + public List listByPaper(String paperId) { + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(PaperQu::getPaperId, paperId) + .orderByAsc(PaperQu::getSort); + + List list = this.list(wrapper); + return BeanMapper.mapList(list, PaperQuDTO.class); + } + + @Override + public PaperQu findByKey(String paperId, String quId) { + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(PaperQu::getPaperId, paperId) + .eq(PaperQu::getQuId, quId); + + return this.getOne(wrapper, false); + } + + @Override + public void updateByKey(PaperQu qu) { + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(PaperQu::getPaperId, qu.getPaperId()) + .eq(PaperQu::getQuId, qu.getQuId()); + + this.update(qu, wrapper); + } + + @Override + public int sumObjective(String paperId) { + return baseMapper.sumObjective(paperId); + } + + @Override + public int sumSubjective(String paperId) { + return baseMapper.sumSubjective(paperId); + } + + @Override + public List listForPaperResult(String paperId) { + return baseMapper.listByPaper(paperId); + } +} diff --git a/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java new file mode 100644 index 0000000..670d74b --- /dev/null +++ b/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java @@ -0,0 +1,477 @@ +package com.yf.exam.modules.paper.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.ability.job.enums.JobGroup; +import com.yf.exam.ability.job.enums.JobPrefix; +import com.yf.exam.ability.job.service.JobService; +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.core.utils.CronUtils; +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.service.ExamRepoService; +import com.yf.exam.modules.exam.service.ExamService; +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; +import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; +import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; +import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; +import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; +import com.yf.exam.modules.paper.entity.Paper; +import com.yf.exam.modules.paper.entity.PaperQu; +import com.yf.exam.modules.paper.entity.PaperQuAnswer; +import com.yf.exam.modules.paper.enums.ExamState; +import com.yf.exam.modules.paper.enums.PaperState; +import com.yf.exam.modules.paper.job.BreakExamJob; +import com.yf.exam.modules.paper.mapper.PaperMapper; +import com.yf.exam.modules.paper.service.PaperQuAnswerService; +import com.yf.exam.modules.paper.service.PaperQuService; +import com.yf.exam.modules.paper.service.PaperService; +import com.yf.exam.modules.qu.entity.Qu; +import com.yf.exam.modules.qu.entity.QuAnswer; +import com.yf.exam.modules.qu.enums.QuType; +import com.yf.exam.modules.qu.service.QuAnswerService; +import com.yf.exam.modules.qu.service.QuService; +import com.yf.exam.modules.sys.user.entity.SysUser; +import com.yf.exam.modules.sys.user.service.SysUserService; +import com.yf.exam.modules.user.book.service.UserBookService; +import com.yf.exam.modules.user.exam.service.UserExamService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.*; + +///** +// * 试卷服务实现类 +// * 该类实现了PaperService接口,用于处理试卷相关的业务逻辑 +// * +// * @author 聪明笨狗 +// * @since 2020-05-25 16:33 +// */ +@Service +public class PaperServiceImpl extends ServiceImpl implements PaperService { + @Autowired + private SysUserService sysUserService; + @Autowired + private ExamService examService; + @Autowired + private QuService quService; + @Autowired + private QuAnswerService quAnswerService; + @Autowired + private PaperService paperService; + @Autowired + private PaperQuService paperQuService; + @Autowired + private PaperQuAnswerService paperQuAnswerService; + @Autowired + private UserBookService userBookService; + @Autowired + private ExamRepoService examRepoService; + @Autowired + private UserExamService userExamService; + @Autowired + private JobService jobService; + +// /** +// * 展示的选项,ABC这样 +// */ + private static List ABC = Arrays.asList(new String[]{ + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", + "Y", "Z" + }); + + @Transactional(rollbackFor = Exception.class) + @Override + public String createPaper(String userId, String examId) { + // 校验是否有正在考试的试卷 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(Paper::getUserId, userId) + .eq(Paper::getState, PaperState.ING); + int exists = this.count(wrapper); + if (exists > 0) { + throw new ServiceException(ApiError.ERROR_20010002); + } + + // 查找考试 + ExamDTO exam = examService.findById(examId); + if (exam == null) { + throw new ServiceException(1, "考试不存在!"); + } + if (!ExamState.ENABLE.equals(exam.getState())) { + throw new ServiceException(1, "考试状态不正确!"); + } + + // 考试题目列表 + List quList = this.generateByRepo(examId); + if (CollectionUtils.isEmpty(quList)) { + throw new ServiceException(1, "规则不正确,无对应的考题!"); + } + + // 保存试卷内容 + Paper paper = this.savePaper(userId, exam, quList); + + // 强制交卷任务 + String jobName = JobPrefix.BREAK_EXAM + paper.getId(); + jobService.addCronJob(BreakExamJob.class, jobName, CronUtils.dateToCron(paper.getLimitTime()), paper.getId()); + + return paper.getId(); + } + + @Override + public ExamDetailRespDTO paperDetail(String paperId) { + ExamDetailRespDTO respDTO = new ExamDetailRespDTO(); + // 试题基本信息 + Paper paper = paperService.getById(paperId); + BeanMapper.copy(paper, respDTO); + + // 查找题目列表 + List list = paperQuService.listByPaper(paperId); + List radioList = new ArrayList<>(); + List multiList = new ArrayList<>(); + List judgeList = new ArrayList<>(); + + for (PaperQuDTO item : list) { + if (QuType.RADIO.equals(item.getQuType())) { + radioList.add(item); + } + if (QuType.MULTI.equals(item.getQuType())) { + multiList.add(item); + } + if (QuType.JUDGE.equals(item.getQuType())) { + judgeList.add(item); + } + } + + respDTO.setRadioList(radioList); + respDTO.setMultiList(multiList); + respDTO.setJudgeList(judgeList); + + return respDTO; + } + + @Override + public ExamResultRespDTO paperResult(String paperId) { + ExamResultRespDTO respDTO = new ExamResultRespDTO(); + // 试题基本信息 + Paper paper = paperService.getById(paperId); + BeanMapper.copy(paper, respDTO); + + List quList = paperQuService.listForPaperResult(paperId); + respDTO.setQuList(quList); + + return respDTO; + } + + @Override + public PaperQuDetailDTO findQuDetail(String paperId, String quId) { + PaperQuDetailDTO respDTO = new PaperQuDetailDTO(); + // 问题 + Qu qu = quService.getById(quId); + // 基本信息 + PaperQu paperQu = paperQuService.findByKey(paperId, quId); + BeanMapper.copy(paperQu, respDTO); + + respDTO.setContent(qu.getContent()); + respDTO.setImage(qu.getImage()); + + // 答案列表 + List list = paperQuAnswerService.listForExam(paperId, quId); + respDTO.setAnswerList(list); + + return respDTO; + } + +// /** +// * 题库组题方式产生题目列表 +// * @param examId +// * @return +// */ + private List generateByRepo(String examId) { + // 查找规则指定的题库 + List list = examRepoService.listByExam(examId); + // 最终的题目列表 + List quList = new ArrayList<>(); + // 排除ID,避免题目重复 + List excludes = new ArrayList<>(); + excludes.add("none"); + + if (!CollectionUtils.isEmpty(list)) { + for (ExamRepoExtDTO item : list) { + // 单选题 + if (item.getRadioCount() > 0) { + List radioList = quService.listByRandom(item.getRepoId(), QuType.RADIO, excludes, item.getRadioCount()); + for (Qu qu : radioList) { + PaperQu paperQu = this.processPaperQu(item, qu); + quList.add(paperQu); + excludes.add(qu.getId()); + } + } + // 多选题 + if (item.getMultiCount() > 0) { + List multiList = quService.listByRandom(item.getRepoId(), QuType.MULTI, excludes, item.getMultiCount()); + for (Qu qu : multiList) { + PaperQu paperQu = this.processPaperQu(item, qu); + quList.add(paperQu); + excludes.add(qu.getId()); + } + } + // 判断题 + if (item.getJudgeCount() > 0) { + List judgeList = quService.listByRandom(item.getRepoId(), QuType.JUDGE, excludes, item.getJudgeCount()); + for (Qu qu : judgeList) { + PaperQu paperQu = this.processPaperQu(item, qu); + quList.add(paperQu); + excludes.add(qu.getId()); + } + } + } + } + return quList; + } + +// /** +// * 填充试题题目信息 +// * @param repo +// * @param qu +// * @return +// */ + private PaperQu processPaperQu(ExamRepoDTO repo, Qu qu) { + // 保存试题信息 + PaperQu paperQu = new PaperQu(); + paperQu.setQuId(qu.getId()); + paperQu.setAnswered(false); + paperQu.setIsRight(false); + paperQu.setQuType(qu.getQuType()); + + if (QuType.RADIO.equals(qu.getQuType())) { + paperQu.setScore(repo.getRadioScore()); + paperQu.setActualScore(repo.getRadioScore()); + } + if (QuType.MULTI.equals(qu.getQuType())) { + paperQu.setScore(repo.getMultiScore()); + paperQu.setActualScore(repo.getMultiScore()); + } + if (QuType.JUDGE.equals(qu.getQuType())) { + paperQu.setScore(repo.getJudgeScore()); + paperQu.setActualScore(repo.getJudgeScore()); + } + + return paperQu; + } + +// /** +// * 保存试卷 +// * @param userId +// * @param exam +// * @param quList +// * @return +// */ + private Paper savePaper(String userId, ExamDTO exam, List quList) { + // 查找用户 + SysUser user = sysUserService.getById(userId); + + // 保存试卷基本信息 + Paper paper = new Paper(); + paper.setDepartId(user.getDepartId()); + paper.setExamId(exam.getId()); + paper.setTitle(exam.getTitle()); + paper.setTotalScore(exam.getTotalScore()); + paper.setTotalTime(exam.getTotalTime()); + paper.setUserScore(0); + paper.setUserId(userId); + paper.setCreateTime(new Date()); + paper.setUpdateTime(new Date()); + paper.setQualifyScore(exam.getQualifyScore()); + paper.setState(PaperState.ING); + paper.setHasSaq(false); + + // 截止时间 + Calendar cl = Calendar.getInstance(); + cl.setTimeInMillis(System.currentTimeMillis()); + cl.add(Calendar.MINUTE, exam.getTotalTime()); + paper.setLimitTime(cl.getTime()); + + paperService.save(paper); + + if (!CollectionUtils.isEmpty(quList)) { + this.savePaperQu(paper.getId(), quList); + } + + return paper; + } + +// /** +// * 保存试卷试题列表 +// * @param paperId +// * @param quList +// */ + private void savePaperQu(String paperId, List quList) { + List batchQuList = new ArrayList<>(); + List batchAnswerList = new ArrayList<>(); + int sort = 0; + + for (PaperQu item : quList) { + item.setPaperId(paperId); + item.setSort(sort); + item.setId(IdWorker.getIdStr()); + + // 回答列表 + List answerList = quAnswerService.listAnswerByRandom(item.getQuId()); + if (!CollectionUtils.isEmpty(answerList)) { + int ii = 0; + for (QuAnswer answer : answerList) { + PaperQuAnswer paperQuAnswer = new PaperQuAnswer(); + paperQuAnswer.setId(UUID.randomUUID().toString()); + paperQuAnswer.setPaperId(paperId); + paperQuAnswer.setQuId(answer.getQuId()); + paperQuAnswer.setAnswerId(answer.getId()); + paperQuAnswer.setChecked(false); + paperQuAnswer.setSort(ii); + paperQuAnswer.setAbc(ABC.get(ii)); + paperQuAnswer.setIsRight(answer.getIsRight()); + ii++; + batchAnswerList.add(paperQuAnswer); + } + } + batchQuList.add(item); + sort++; + } + + // 添加问题 + paperQuService.saveBatch(batchQuList); + + // 批量添加问题答案 + paperQuAnswerService.saveBatch(batchAnswerList); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void fillAnswer(PaperAnswerDTO reqDTO) { + // 未作答 + if (CollectionUtils.isEmpty(reqDTO.getAnswers()) && StringUtils.isBlank(reqDTO.getAnswer())) { + return; + } + + // 查找答案列表 + List list = paperQuAnswerService.listForFill(reqDTO.getPaperId(), reqDTO.getQuId()); + // 是否正确 + boolean right = true; + + // 更新正确答案 + for (PaperQuAnswer item : list) { + if (reqDTO.getAnswers().contains(item.getId())) { + item.setChecked(true); + } else { + item.setChecked(false); + } + + // 有一个对不上就是错的 + if (item.getIsRight() != null && !item.getIsRight().equals(item.getChecked())) { + right = false; + } + + paperQuAnswerService.updateById(item); + } + + // 修改为已回答 + PaperQu qu = new PaperQu(); + qu.setQuId(reqDTO.getQuId()); + qu.setPaperId(reqDTO.getPaperId()); + qu.setIsRight(right); + qu.setAnswer(reqDTO.getAnswer()); + qu.setAnswered(true); + + paperQuService.updateByKey(qu); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void handExam(String paperId) { + // 获取试卷信息 + Paper paper = paperService.getById(paperId); + + // 如果不是正常的,抛出异常 + if (!PaperState.ING.equals(paper.getState())) { + throw new ServiceException(1, "试卷状态不正确!"); + } + + // 客观分 + int objScore = paperQuService.sumObjective(paperId); + paper.setObjScore(objScore); + paper.setUserScore(objScore); + + // 主观分,因为要阅卷,所以给0 + paper.setSubjScore(0); + + // 待阅卷 + if (paper.getHasSaq()) { + paper.setState(PaperState.WAIT_OPT); + } else { + // 同步保存考试成绩 + userExamService.joinResult(paper.getUserId(), paper.getExamId(), objScore, objScore >= paper.getQualifyScore()); + paper.setState(PaperState.FINISHED); + } + + paper.setUpdateTime(new Date()); + + // 计算考试时长 + Calendar cl = Calendar.getInstance(); + cl.setTimeInMillis(System.currentTimeMillis()); + int userTime = (int) ((System.currentTimeMillis() - paper.getCreateTime().getTime()) / 1000 / 60); + if (userTime == 0) { + userTime = 1; + } + paper.setUserTime(userTime); + + // 更新试卷 + paperService.updateById(paper); + + // 终止定时任务 + String name = JobPrefix.BREAK_EXAM + paperId; + jobService.deleteJob(name, JobGroup.SYSTEM); + + // 把打错的问题加入错题本 + List list = paperQuService.listByPaper(paperId); + for (PaperQuDTO qu : list) { + // 主观题和对的都不加入错题库 + if (qu.getIsRight()) { + continue; + } + + // 加入错题本 + new Thread(() -> userBookService.addBook(paper.getExamId(), qu.getQuId())).run(); + } + } + + @Override + public IPage paging(PagingReqDTO reqDTO) { + return baseMapper.paging(reqDTO.toPage(), reqDTO.getParams()); + } + + @Override + public PaperDTO checkProcess(String userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(Paper::getUserId, userId) + .eq(Paper::getState, PaperState.ING); + Paper paper = this.getOne(wrapper, false); + if (paper != null) { + return BeanMapper.map(paper, PaperDTO.class); + } + return null; + } +} -- 2.34.1