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); + } +}