fmy #1

Merged
pyc5qsxp3 merged 10 commits from fmy into main 1 year ago

@ -2,7 +2,7 @@ package cn.org.alan.exam.common.exception;
/**
* @Author WeiJin
* @Version 1.0
* @Version 1.022222
* @Date 2024/3/29 21:06
*/
public class AppException extends RuntimeException{

@ -14,8 +14,8 @@ import java.lang.reflect.Type;
/**
* @Author Alan
* @Version
* @Date 2024/6/8 11:47 AM
* @Version222
* @Date 2024/6/8 11:47 AM111
*/
@ControllerAdvice

@ -1,16 +1,25 @@
package cn.org.alan.exam.mapper;
// 指定当前类所在的包路径
import cn.org.alan.exam.model.entity.Certificate;
// 导入Certificate实体类代表证书的数据模型
import cn.org.alan.exam.model.form.CertificateForm;
// 导入CertificateForm表单类用于接收前端传递的表单数据
import cn.org.alan.exam.model.vo.certificate.MyCertificateVO;
// 导入MyCertificateVO视图对象类用于封装返回给前端的数据
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入MyBatis-Plus的BaseMapper接口提供基本的CRUD操作
import com.baomidou.mybatisplus.core.metadata.IPage;
// 导入IPage接口用于分页查询
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
// 导入Page类是IPage接口的一个实现用于分页查询
import org.apache.ibatis.annotations.Param;
// 导入Param注解用于MyBatis方法参数注解便于XML配置文件中引用
import org.springframework.stereotype.Repository;
// 导入Repository注解用于标识当前类是一个持久层DAO层的组件
import java.util.List;
// 导入List接口用于处理集合数据
/**
* <p>
@ -21,13 +30,19 @@ import java.util.List;
* @since 2024-03-21
*/
@Repository
// 使用Repository注解将当前接口标识为Spring管理的组件
public interface CertificateMapper extends BaseMapper<Certificate> {
// 声明CertificateMapper接口继承自BaseMapper<Certificate>,提供基本的证书数据操作
// 自定义查询方法根据分页信息、用户ID、考试名称查询用户的证书信息并返回Page<MyCertificateVO>对象
Page<MyCertificateVO> selectMyCertificate(Page<MyCertificateVO> myCertificateVOPage, Integer pageNum, Integer pageSize, Integer userId,String examName);
// 自定义统计方法根据用户ID、证书名称、认证单位统计符合条件的证书数量
int countByCondition(Integer userId, String certificateName, String certificationUnit);
// 自定义分页查询方法根据用户ID、证书名称、认证单位、偏移量、页面大小查询证书ID列表
List<Integer> selectCertificateIdsPage(Integer userId, String certificateName, String certificationUnit, Integer offset,Integer pageSize);
// 自定义批量查询方法根据证书ID列表批量查询证书信息
List<Certificate> batchSelectByIds(List<Integer> missIds);
}
}

@ -1,24 +1,25 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前接口所在的包路径
import cn.org.alan.exam.model.entity.CertificateUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.org.alan.exam.model.entity.CertificateUser; // 导入CertificateUser实体类代表用户证书关联的数据模型
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis-Plus的BaseMapper接口提供基本的CRUD操作
import java.util.List;
import java.util.List; // 导入List接口用于处理集合数据
/**
* <p>
* Mapper
* Mapper CertificateUser
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口自哪个版本开始可用
*/
public interface CertificateUserMapper extends BaseMapper<CertificateUser> {
public interface CertificateUserMapper extends BaseMapper<CertificateUser> { // 声明CertificateUserMapper接口继承自BaseMapper<CertificateUser>提供基本的CertificateUser数据操作
/**
* id
* @param userIds id
* @return
*/
Integer deleteByUserIds(List<Integer> userIds);
}
*
* @param userIds idID
* @return
*/ // 方法注释,描述方法的功能、参数和返回值
Integer deleteByUserIds(List<Integer> userIds); // 自定义删除方法根据用户ID列表批量删除证书用户关联记录
}

@ -1,50 +1,71 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前接口所在的包路径
import cn.org.alan.exam.model.entity.Exam;
import cn.org.alan.exam.model.entity.ExamGrade;
import cn.org.alan.exam.model.vo.exam.ExamGradeListVO;
import cn.org.alan.exam.model.vo.score.GradeScoreVO;
import cn.org.alan.exam.model.vo.stat.GradeExamVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import cn.org.alan.exam.model.entity.Exam; // 导入Exam实体类本接口未直接使用可能是遗留或用于其他目的
import cn.org.alan.exam.model.entity.ExamGrade; // 导入ExamGrade实体类代表考试与班级的关联数据模型
import cn.org.alan.exam.model.vo.exam.ExamGradeListVO; // 导入ExamGradeListVO视图对象类用于封装考试班级列表的数据
import cn.org.alan.exam.model.vo.score.GradeScoreVO; // 导入GradeScoreVO视图对象类用于封装班级分数的数据
import cn.org.alan.exam.model.vo.stat.GradeExamVO; // 导入GradeExamVO视图对象类本接口未直接使用可能是遗留或用于其他目的
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis-Plus的BaseMapper接口提供基本的CRUD操作
import com.baomidou.mybatisplus.core.metadata.IPage; // 导入IPage接口用于分页查询
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入Page类本接口未直接使用IPage的实现类但通常不直接引用
import org.apache.ibatis.annotations.Param; // 导入Param注解用于MyBatis方法参数注解
import java.util.List;
import java.util.List; // 导入List接口用于处理集合数据
/**
* <p>
* Mapper
* Mapper ExamGrade
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口自哪个版本开始可用
*/
public interface ExamGradeMapper extends BaseMapper<ExamGrade> {
public interface ExamGradeMapper extends BaseMapper<ExamGrade> { // 声明ExamGradeMapper接口继承自BaseMapper<ExamGrade>提供基本的ExamGrade数据操作
// 自定义方法为指定考试ID添加多个班级ID的关联记录
Integer addExamGrade(Integer id, List<Integer> gradeIds);
// 自定义方法根据考试ID删除考试与班级的关联记录
Integer delExamGrade(Integer id);
/**
*
* @param page
* @param roleId
* @return
*
* @param page
* @param examTitle
* @param userId ID
* @param gradeId ID
* @param roleId ID
* @return
*/
IPage<GradeScoreVO> getExamGrade(IPage<GradeScoreVO> page,String examTitle,Integer userId,Integer gradeId,Integer roleId);
IPage<GradeScoreVO> getExamGrade(IPage<GradeScoreVO> page, String examTitle, Integer userId, Integer gradeId, Integer roleId);
/**
* id
* @param id id
* @return
* ID
*
* @param id ID
* @return
*/
Integer selectClassSize(Integer id);
/**
*
*
* @param examPage
* @param userId ID
* @param title
* @return
*/
IPage<ExamGradeListVO> selectClassExam(IPage<ExamGradeListVO> examPage, Integer userId, String title);
IPage<ExamGradeListVO> selectClassExam(IPage<ExamGradeListVO> examPage, Integer userId,String title);
IPage<ExamGradeListVO> selectAdminClassExam(IPage<ExamGradeListVO> examPage, Integer userId,String title);
/**
*
*
* @param examPage
* @param userId ID
* @param title
* @return
*/
IPage<ExamGradeListVO> selectAdminClassExam(IPage<ExamGradeListVO> examPage, Integer userId, String title);
}
}

@ -1,49 +1,65 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前接口所在的包路径
import cn.org.alan.exam.model.entity.Exam;
import cn.org.alan.exam.model.vo.answer.AnswerExamVO;
import cn.org.alan.exam.model.vo.record.ExamRecordVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import cn.org.alan.exam.model.entity.Exam; // 导入Exam实体类代表考试数据模型
import cn.org.alan.exam.model.vo.answer.AnswerExamVO; // 导入AnswerExamVO视图对象类用于封装考试答题相关的数据
import cn.org.alan.exam.model.vo.record.ExamRecordVO; // 导入ExamRecordVO视图对象类用于封装考试记录相关的数据
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis-Plus的BaseMapper接口提供基本的CRUD操作
import com.baomidou.mybatisplus.core.metadata.IPage; // 导入IPage接口用于分页查询
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 导入Page类是IPage接口的一个实现用于分页查询的具体实现
import org.apache.ibatis.annotations.Param; // 导入Param注解用于MyBatis方法参数注解
import org.springframework.stereotype.Repository; // 导入Repository注解本接口未直接使用通常用于标注数据访问层组件
import java.util.List;
import java.util.List; // 导入List接口用于处理集合数据
/**
* Mapper
* Mapper Exam
*
* @author Alan
* @since 2024-03-21
* @author Alan // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口自哪个版本开始可用
*/
@Repository // 注解通常用于标注数据访问层组件但Mapper接口一般通过MyBatis框架扫描不需要显式添加
public interface ExamMapper extends BaseMapper<Exam> { // 声明ExamMapper接口继承自BaseMapper<Exam>提供基本的Exam数据操作
public interface ExamMapper extends BaseMapper<Exam> {
// 自定义方法根据考试ID列表删除多个考试记录
int deleteExams(List<Integer> examIds);
// 自定义方法根据考试ID列表删除多个考试与班级的关联记录可能是业务逻辑上的关联不一定直接对应数据库表
int deleteExamGrades(List<Integer> examIds);
// 自定义方法根据考试ID列表删除多个考试资源如试题、答案等
int deleteExamRepos(List<Integer> examIds);
// 自定义方法根据考试ID列表删除多个考试与试题的关联记录
int deleteExamQuestions(List<Integer> examIds);
/**
*
*
* @param userIds id
* @return
* @param userIds ID
* @return
*/
Integer deleteByUserIds(List<Integer> userIds);
/**
* id
* ID
*
* @param userId
* @return
* @param page
* @param userId ID
* @param role
* @param examName
* @return
*/
IPage<AnswerExamVO> selectMarkedList(@Param("page") IPage<AnswerExamVO> page, @Param("userId") Integer userId,String role,String examName);
IPage<AnswerExamVO> selectMarkedList(@Param("page") IPage<AnswerExamVO> page, @Param("userId") Integer userId, String role, String examName);
Page<ExamRecordVO> getExamRecordPage(Page<ExamRecordVO> examPage, Integer userId,String examName);
}
/**
*
*
* @param examPage 使Page<ExamRecordVO>
* @param userId ID
* @param examName
* @return
*/
Page<ExamRecordVO> getExamRecordPage(Page<ExamRecordVO> examPage, Integer userId, String examName);
// 注意在实际使用中通常建议统一使用IPage接口作为分页对象的类型以避免具体实现类的依赖。
// 如果MyBatis-Plus的版本或配置允许可以考虑将getExamRecordPage方法的参数和返回值类型改为IPage<ExamRecordVO>。
}

@ -1,45 +1,46 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前接口所在的包路径
import cn.org.alan.exam.model.entity.ExamQuAnswer;
import cn.org.alan.exam.model.vo.answer.UserAnswerDetailVO;
import cn.org.alan.exam.model.vo.exam.ExamQuAnswerExtVO;
import cn.org.alan.exam.model.vo.score.QuestionAnalyseVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.org.alan.exam.model.entity.ExamQuAnswer; // 导入ExamQuAnswer实体类代表考试问题回答的数据模型
import cn.org.alan.exam.model.vo.answer.UserAnswerDetailVO; // 导入UserAnswerDetailVO视图对象类用于封装用户回答主观题的信息
import cn.org.alan.exam.model.vo.exam.ExamQuAnswerExtVO; // 导入ExamQuAnswerExtVO视图对象类用于扩展封装考试问题回答的信息
import cn.org.alan.exam.model.vo.score.QuestionAnalyseVO; // 导入QuestionAnalyseVO视图对象类用于封装试题分析的信息
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis-Plus的BaseMapper接口提供基本的CRUD操作
import java.util.List;
import java.util.List; // 导入List接口用于处理集合数据
/**
* <p>
* Mapper
* Mapper ExamQuAnswer
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口自哪个版本开始可用
*/
public interface ExamQuAnswerMapper extends BaseMapper<ExamQuAnswer> {
public interface ExamQuAnswerMapper extends BaseMapper<ExamQuAnswer> { // 声明ExamQuAnswerMapper接口继承自BaseMapper<ExamQuAnswer>提供基本的ExamQuAnswer数据操作
List<ExamQuAnswerExtVO> list(String examId, String questionId);
// 自定义方法根据考试ID和试题ID列表查询考试问题回答信息扩展信息
List<ExamQuAnswerExtVO> list(String examId, String questionId); // 注意这里的参数类型通常为Integer除非有特定原因使用String
/**
*
* @param examId id
* @param questionId id
* @return
* @param examId ID
* @param questionId ID
* @return QuestionAnalyseVO
*/
QuestionAnalyseVO questionAnalyse(Integer examId, Integer questionId);
QuestionAnalyseVO questionAnalyse(Integer examId, Integer questionId); // 根据考试ID和试题ID查询试题作答分析信息
/**
*
* @param userIds id
* @return
* @param userIds ID
* @return
*/
Integer deleteByUserIds(List<Integer> userIds);
Integer deleteByUserIds(List<Integer> userIds); // 根据用户ID列表删除对应的考试作答记录
/**
*
* @param userId id
* @param examId id
* @return
* @param userId ID
* @param examId ID
* @return List<UserAnswerDetailVO>
*/
List<UserAnswerDetailVO> selectUserAnswer(Integer userId, Integer examId);
}
List<UserAnswerDetailVO> selectUserAnswer(Integer userId, Integer examId); // 根据用户ID和考试ID查询用户回答主观题的信息
}

@ -1,16 +1,25 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前接口所在的包路径
import cn.org.alan.exam.model.entity.ExamQuestion;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.org.alan.exam.model.entity.ExamQuestion; // 导入ExamQuestion实体类该类代表考试题目的数据模型
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入MyBatis-Plus的BaseMapper接口提供基本的CRUD操作
import java.util.List;
import java.util.Map;
import java.util.List; // 导入List接口用于处理集合数据
import java.util.Map; // 导入Map接口用于存储键值对数据
/**
* @Author Alan
* @Version
* @Date 2024/4/7 3:49 PM
* @Author Alan // 声明当前接口的作者
* @Version // 版本信息缺失,通常这里会指定接口的版本号或者留空
* @Date 2024/4/7 3:49 PM // 声明当前接口的创建日期和时间
*/
public interface ExamQuestionMapper extends BaseMapper<ExamQuestion> {
public interface ExamQuestionMapper extends BaseMapper<ExamQuestion> { // 声明ExamQuestionMapper接口继承自BaseMapper<ExamQuestion>提供基本的ExamQuestion数据操作
// 自定义方法,用于插入考试题目信息
// 参数说明:
// examId - 考试ID用于标识需要插入题目的考试
// quType - 题目类型,用于区分不同类型的题目(如单选题、多选题、判断题等)
// quScore - 题目分数,表示该题目的分值
// questionIdsAndSorts - 题目ID和排序的列表其中每个Map对象包含题目ID作为键和对应的排序值作为值
int insertQuestion(Integer examId, Integer quType, Integer quScore, List<Map<String, Object>> questionIdsAndSorts);
}
// 方法返回值为int类型通常表示插入操作影响的行数但在这里可能主要用于表示操作是否成功如返回1表示成功0或负数表示失败
// 注意这里的业务逻辑可能需要根据实际情况调整例如题目ID和排序的存储方式、如何确保数据的一致性等
}

@ -1,16 +1,25 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前Mapper接口所在的包路径
// 导入ExamRepo实体类该类是与数据库表对应的Java对象用于表示考试资源仓库的实体
import cn.org.alan.exam.model.entity.ExamRepo;
// 导入MyBatis-Plus提供的BaseMapper接口它提供了基础的CRUD操作方法
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper
* Mapper
* </p>
* <p>
* BaseMapperBaseMapper
* CRUD
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口的创建日期或版本号
*/
public interface ExamRepoMapper extends BaseMapper<ExamRepo> {
public interface ExamRepoMapper extends BaseMapper<ExamRepo> { // 声明ExamRepoMapper接口它继承自BaseMapper<ExamRepo>因此可以对ExamRepo实体进行基础的数据库操作
}
// 由于此接口仅继承了BaseMapper并未定义额外的方法
// 因此这里没有额外的代码需要解释。所有对ExamRepo实体的数据库操作都将通过BaseMapper中定义的方法来完成。
}

@ -1,24 +1,29 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前Mapper接口所在的包路径
// 导入ExerciseRecord实体类该类是与数据库表对应的Java对象用于表示用户的练习作答记录
import cn.org.alan.exam.model.entity.ExerciseRecord;
// 导入MyBatis-Plus提供的BaseMapper接口它提供了基础的CRUD操作方法
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入Java的List接口用于处理用户ID的集合
import java.util.List;
/**
* <p>
* Mapper
* Mapper ExerciseRecord
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口的创建日期或版本号
*/
public interface ExerciseRecordMapper extends BaseMapper<ExerciseRecord> {
public interface ExerciseRecordMapper extends BaseMapper<ExerciseRecord> { // 声明ExerciseRecordMapper接口它继承自BaseMapper<ExerciseRecord>因此可以对ExerciseRecord实体进行基础的数据库操作
/**
*
* @param userIds id
* @return
*
*
* @param userIds IDID
* @return 00
*/
Integer deleteByUserIds(List<Integer> userIds);
}
Integer deleteByUserIds(List<Integer> userIds); // 定义一个自定义方法用于根据用户ID列表删除对应的练习作答记录并返回被影响的记录数
}

@ -1,24 +1,29 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前Mapper接口所在的包路径
// 导入GradeExercise实体类该类是与数据库表对应的Java对象用于表示用户创建的可供学生练习的题库关联信息
import cn.org.alan.exam.model.entity.GradeExercise;
// 导入MyBatis-Plus提供的BaseMapper接口它提供了基础的CRUD操作方法
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入Java的List接口用于处理用户ID的集合
import java.util.List;
/**
* <p>
* Mapper
* Mapper GradeExercise
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口的创建日期或版本号
*/
public interface GradeExerciseMapper extends BaseMapper<GradeExercise> {
public interface GradeExerciseMapper extends BaseMapper<GradeExercise> { // 声明GradeExerciseMapper接口它继承自BaseMapper<GradeExercise>因此可以对GradeExercise实体进行基础的数据库操作
/**
*
* @param userIds id
* @return
*
*
* @param userIds IDID
* @return 00
*/
Integer deleteByUserIds(List<Integer> userIds);
}
Integer deleteByUserIds(List<Integer> userIds); // 定义一个自定义方法用于根据用户ID列表删除对应的题库关联记录并返回被影响的记录数
}

@ -1,32 +1,44 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前Mapper接口所在的包路径
// 导入Grade实体类该类是与数据库表对应的Java对象用于表示成绩信息
import cn.org.alan.exam.model.entity.Grade;
// 导入GradeVO类这是一个值对象Value Object通常用于封装从数据库查询后需要展示给前端的数据
import cn.org.alan.exam.model.vo.GradeVO;
// 导入MyBatis-Plus提供的BaseMapper接口它提供了基础的CRUD操作方法
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入MyBatis-Plus分页插件提供的Page类用于实现分页查询功能
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
// 导入Spring框架的Repository注解用于标识这是一个数据访问层的组件
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* <p>
* Mapper
* Mapper Grade
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口的创建日期或版本号
*/
@Repository
public interface GradeMapper extends BaseMapper<Grade> {
@Repository // 使用@Repository注解标识这是一个数据访问层的组件Spring会将其识别为一个Bean并进行管理
public interface GradeMapper extends BaseMapper<Grade> { // 声明GradeMapper接口它继承自BaseMapper<Grade>因此可以对Grade实体进行基础的数据库操作
// 根据用户ID列表查询对应的成绩ID列表
List<Integer> selectIdsByUserIds(List<Integer> userIds);
// 根据用户ID列表删除对应的成绩记录
Integer deleteByUserId(List<Integer> userIds);
Page<GradeVO> selectGradePage(Page<GradeVO> page, Integer userId , String gradeName, Integer role);
// 分页查询成绩信息返回Page<GradeVO>对象,包含分页信息和查询结果
Page<GradeVO> selectGradePage(Page<GradeVO> page, Integer userId, String gradeName, Integer role);
// 根据用户ID、角色、成绩名称和分页参数偏移量和页面大小查询成绩ID列表用于分页显示
List<Integer> selectGradeIdsPage(Integer userId, Integer role, String gradeName, int offset, Integer pageSize);
// 根据ID列表批量查询成绩信息返回GradeVO对象列表
List<GradeVO> batchSelectByIds(List<Integer> missIds);
// 根据用户ID、成绩名称和角色统计符合条件的成绩记录数
int countByCondition(Integer userId, String gradeName, Integer role);
}
}

@ -1,31 +1,35 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前Mapper接口所在的包路径
// 导入ManualScore实体类该类是与数据库表对应的Java对象用于表示用户批改的主观题分数信息
import cn.org.alan.exam.model.entity.ManualScore;
// 导入MyBatis-Plus提供的BaseMapper接口它提供了基础的CRUD操作方法
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
import java.util.List; // 导入Java的List接口用于处理集合数据
/**
* <p>
* Mapper
* Mapper ManualScore
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口的创建日期或版本号
*/
public interface ManualScoreMapper extends BaseMapper<ManualScore> {
public interface ManualScoreMapper extends BaseMapper<ManualScore> { // 声明ManualScoreMapper接口它继承自BaseMapper<ManualScore>因此可以对ManualScore实体进行基础的数据库操作
/**
*
* @param userIds id
* @return
* ID
*
* @param userIds IDID
* @return 00
*/
Integer deleteByUserIds(List<Integer> userIds);
Integer deleteByUserIds(List<Integer> userIds); // 定义一个自定义方法用于根据用户ID列表删除对应的主观题分数记录并返回被影响的记录数
/**
*
* @param manualScores
* @return
*
*
* @param manualScores
* @return 00
*/
Integer insertList(List<ManualScore> manualScores);
}
Integer insertList(List<ManualScore> manualScores); // 定义一个自定义方法,用于批量添加主观题分数记录到数据库,并返回被影响的记录数
}

@ -1,27 +1,38 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 声明当前Mapper接口所在的包路径
// 导入Grade实体类该类表示学生的成绩信息
import cn.org.alan.exam.model.entity.Grade;
// 导入NoticeGrade实体类该类表示公告与班级的关联信息
import cn.org.alan.exam.model.entity.NoticeGrade;
// 导入MyBatis-Plus提供的BaseMapper接口提供基础的CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
import java.util.List; // 导入Java的List接口用于处理集合数据
/**
* <p>
* Mapper
* Mapper NoticeGrade
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口的创建日期或版本号
*/
public interface NoticeGradeMapper extends BaseMapper<NoticeGrade> {
public interface NoticeGradeMapper extends BaseMapper<NoticeGrade> { // 声明NoticeGradeMapper接口继承自BaseMapper<NoticeGrade>提供基础的CRUD操作
/**
* id
* @param noticeIds id
* @return
* ID
*
* @param noticeIds IDID
* @return 00
*/
Integer deleteByNoticeIds(List<Integer> noticeIds);
Integer deleteByNoticeIds(List<Integer> noticeIds); // 定义一个自定义方法用于根据公告ID列表删除对应的公告与班级关联记录并返回被影响的记录数
int addNoticeGrade(Integer noticeId, List<Grade> grades);
}
/**
*
*
* @param noticeId ID
* @param grades
* @return 00
*/
int addNoticeGrade(Integer noticeId, List<Grade> grades); // 定义一个自定义方法,用于添加公告与成绩的关联记录到数据库,并返回被影响的记录数
}

@ -1,45 +1,94 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 指定当前Mapper接口所在的包路径
// 导入Notice实体类表示公告信息
import cn.org.alan.exam.model.entity.Notice;
// 导入NoticeVO值对象类通常用于封装传递给前端的数据可能包含Notice实体类的数据以及其他相关信息
import cn.org.alan.exam.model.vo.NoticeVO;
// 导入MyBatis-Plus提供的BaseMapper接口提供基础的CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入MyBatis-Plus分页插件提供的Page类用于分页查询
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.List; // 导入Java的List接口用于处理集合数据
/**
* <p>
* Mapper
* Mapper Notice
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明当前接口的作者
* @since 2024-03-21 // 声明当前接口的创建日期或版本号
*/
public interface NoticeMapper extends BaseMapper<Notice> {
public interface NoticeMapper extends BaseMapper<Notice> { // 声明NoticeMapper接口继承自BaseMapper<Notice>提供基础的CRUD操作
int removeNotice(List<Integer> noticeIds);
/**
* ID
*
* @param noticeIds IDID
* @return 00
*/
int removeNotice(List<Integer> noticeIds); // 定义一个自定义方法用于根据公告ID列表删除公告记录并返回被影响的记录数
/**
*
* @param userIds id
* @return
*
*
* @param userIds IDID
* @return 00
*/
Integer deleteByUserIds(List<Integer> userIds);
Integer deleteByUserIds(List<Integer> userIds); // 定义一个自定义方法用于根据用户ID列表删除用户创建的公告记录并返回被影响的记录数
/**
* idids
* @param userIds id
* @return
* IDID
*
* @param userIds IDIDID
* @return ID
*/
List<Integer> selectIdsByUserIds(List<Integer> userIds);
List<Integer> selectIdsByUserIds(List<Integer> userIds); // 定义一个自定义方法用于根据用户ID列表查询并返回对应的公告ID列表
Page<NoticeVO> selectNewNoticePage(Page<NoticeVO> page, Integer userId);
/**
*
*
* @param page
* @param userId ID
* @return NoticeVOPage
*/
Page<NoticeVO> selectNewNoticePage(Page<NoticeVO> page, Integer userId); // 定义一个自定义方法用于分页查询并返回用户的新公告信息结果封装在NoticeVO对象中
int countByCondition(Integer userId, String title);
/**
*
*
* @param userId IDID
* @param title
* @return
*/
int countByCondition(Integer userId, String title); // 定义一个自定义方法用于根据用户ID和公告标题统计公告数量并返回结果
List<Integer> selectNoticeIdsPage(Integer userId, String title, int offset, Integer pageSize);
/**
* ID
*
* @param userId IDIDID
* @param title
* @param offset
* @param pageSize
* @return ID
*/
List<Integer> selectNoticeIdsPage(Integer userId, String title, int offset, Integer pageSize); // 定义一个自定义方法用于分页查询并返回符合条件的公告ID列表
List<NoticeVO> batchSelectByIds(List<Integer> missIds);
/**
*
*
* @param missIds IDID
* @return NoticeVO
*/
List<NoticeVO> batchSelectByIds(List<Integer> missIds); // 定义一个自定义方法用于批量查询并返回对应的公告信息结果封装在NoticeVO对象中
List<Integer> selectNewNoticeIdsPage(Integer userId, int offset, Integer pageSize);
}
/**
* ID
*
* @param userId IDID
* @param offset
* @param pageSize
* @return ID
*/
List<Integer> selectNewNoticeIdsPage(Integer userId, int offset, Integer pageSize); // 定义一个自定义方法用于分页查询并返回用户的新公告ID列表
}

@ -1,52 +1,57 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 声明包路径,指明当前接口所在的包
import cn.org.alan.exam.model.entity.Option;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.org.alan.exam.model.entity.Option; // 导入Option实体类代表试题选项的数据模型
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入BaseMapper接口提供基础的CRUD操作
import java.util.List;
import java.util.List; // 导入List接口用于处理集合数据
/**
* <p>
* Mapper
* Mapper Option
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明接口作者
* @since 2024-03-21 // 声明接口创建日期或版本号
*/
public interface OptionMapper extends BaseMapper<Option> {
public interface OptionMapper extends BaseMapper<Option> { // 声明OptionMapper接口继承自BaseMapper<Option>提供基础的CRUD操作
/**
*
* @param options
* @return
*
*
* @param options Option
* @return
*/
Integer insertBatch(List<Option> options);
Integer insertBatch(List<Option> options); // 定义一个自定义方法,用于批量添加选项到数据库,并返回影响的记录数
/**
* id
* @param quIdList id
* @return
* ID
*
* @param quIdList IDID
* @return
*/
Integer deleteBatchByQuIds(List<Integer> quIdList);
Integer deleteBatchByQuIds(List<Integer> quIdList); // 定义一个自定义方法用于根据试题ID列表批量删除选项并返回影响的记录数
/**
* id
* @param id id
* @return
* ID
*
* @param id ID
* @return List<Option>
*/
List<Option> selectAllByQuestionId(Integer id);
/**
* id
* @param id id
* @return
List<Option> selectAllByQuestionId(Integer id); // 定义一个自定义方法用于根据试题ID查询并返回其所有选项的集合
/**
* ID
*
* @param id ID
* @return List<Option>
*/
List<Option> selectByQuestionId(Integer id);
List<Option> selectByQuestionId(Integer id); // 定义一个自定义方法用于根据试题ID查询并返回其所有选项不包含是否正确信息的集合
/**
* Id
* @param optionIds Id
* @return
* ID
*
* @param optionIds IDID
* @return ID
*/
Integer selectRightCountByIds(List<Integer> optionIds);
}
Integer selectRightCountByIds(List<Integer> optionIds); // 定义一个自定义方法用于根据选项ID列表查询并返回正确的选项个数
}

@ -1,74 +1,114 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 声明包路径包含当前Mapper接口
import cn.org.alan.exam.model.entity.Question;
import cn.org.alan.exam.model.vo.QuestionVO;
import cn.org.alan.exam.model.vo.exercise.QuestionSheetVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.org.alan.exam.model.entity.Question; // 导入Question实体类代表试题的数据模型
import cn.org.alan.exam.model.vo.QuestionVO; // 导入QuestionVO类用于视图层展示试题的详细信息
import cn.org.alan.exam.model.vo.exercise.QuestionSheetVO; // 导入QuestionSheetVO类用于表示用户的试题答题情况
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 导入BaseMapper接口提供基础的CRUD操作
import com.baomidou.mybatisplus.core.metadata.IPage; // 导入IPage接口用于分页查询
import java.util.List;
import java.util.List; // 导入List接口用于处理集合数据
/**
* <p>
* Mapper
* Mapper Question
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 声明接口作者
* @since 2024-03-21 // 声明接口创建日期或版本号
*/
public interface QuestionMapper extends BaseMapper<Question> {
public interface QuestionMapper extends BaseMapper<Question> { // 声明QuestionMapper接口继承自BaseMapper<Question>提供基础的CRUD操作
/**
*
*
* @param page
* @param content
* @param repoId id
* @param type
* @param userId id
* @return
* @param page
* @param content
* @param repoId ID
* @param type
* @param userId ID
* @return IPage<QuestionVO>
*/
IPage<QuestionVO> pagingQuestion(IPage<QuestionVO> page, String content, Integer repoId, Integer type, Integer userId);
/**
* id
* @param id id
* @return
* ID
*
* @param id ID
* @return QuestionVO
*/
QuestionVO selectSingle(Integer id);
/**
*
* @param userIds id
* @return
*
* @param userIds ID
* @return
*/
Integer deleteByUserIds(List<Integer> userIds);
/**
* id
* @param userIds id
* @return
* ID
*
* @param userIds ID
* @return ID
*/
List<Integer> selectIdsByUserIds(List<Integer> userIds);
/**
* Id
* @param repoId Id
* @param quType
* @param userId Id
* @return
* ID
*
* @param repoId ID
* @param quType
* @param userId ID
* @return QuestionSheetVO
*/
List<QuestionSheetVO> selectQuestionSheet(Integer repoId, Integer quType, Integer userId);
/**
* ID
*
* @param id ID
* @return QuestionVOselectSingle
*/
QuestionVO selectDetail(Integer id);
/**
* ID
*
* @param list ID
* @return void
*/
void deleteBatchIdsQu(List<Integer> list);
/**
*
*
* @param userId ID
* @param title
* @param type
* @param repoId ID
* @return
*/
int countByCondition(Integer userId, String title, Integer type, Integer repoId);
/**
* ID
*
* @param userId ID
* @param title
* @param type
* @param repoId ID
* @param offset
* @param pageSize
* @return ID
*/
List<Integer> selectQuestionIdsPage(Integer userId, String title, Integer type, Integer repoId, int offset, Integer pageSize);
/**
* ID
*
* @param missIds ID
* @return QuestionVO
*/
List<QuestionVO> batchSelectByIds(List<Integer> missIds);
}
}

@ -1,30 +1,30 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 定义mapper接口所在的包
import cn.org.alan.exam.model.entity.Repo;
import cn.org.alan.exam.model.vo.repo.RepoListVO;
import cn.org.alan.exam.model.vo.repo.RepoVO;
import cn.org.alan.exam.model.vo.exercise.ExerciseRepoVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import cn.org.alan.exam.model.entity.Repo; // 引入Repo实体类
import cn.org.alan.exam.model.vo.repo.RepoListVO; // 引入RepoListVO视图对象
import cn.org.alan.exam.model.vo.repo.RepoVO; // 引入RepoVO视图对象
import cn.org.alan.exam.model.vo.exercise.ExerciseRepoVO; // 引入ExerciseRepoVO视图对象
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 引入MyBatis Plus的BaseMapper接口
import com.baomidou.mybatisplus.core.metadata.IPage; // 引入MyBatis Plus的分页接口
import org.apache.ibatis.annotations.Param; // 引入MyBatis的Param注解用于传递参数
import org.springframework.stereotype.Repository; // 引入Spring的Repository注解标记为持久层组件
import java.util.List;
import java.util.List; // 引入Java的List接口
/**
* @author WeiJin
* @since 2024-03-21
*/
@Repository
public interface RepoMapper extends BaseMapper<Repo> {
*/ // 类注释,说明作者和创建时间
@Repository // 标记为Spring的持久层组件
public interface RepoMapper extends BaseMapper<Repo> { // 定义RepoMapper接口继承BaseMapper提供基础的CRUD操作
/**
*
*
* @param page
* @param title
* @param userId
* @return
* @param page
* @param title
* @param userId ID
* @return
*/
IPage<RepoVO> pagingRepo(@Param("page") IPage<RepoVO> page, @Param("title") String title,
@Param("userId") Integer userId);
@ -32,20 +32,27 @@ public interface RepoMapper extends BaseMapper<Repo> {
/**
*
*
* @param userIds id
* @return
* @param userIds id
* @return
*/
Integer deleteByUserIds(List<Integer> userIds);
/**
*
*
* @param page
* @param title
* @return
* @param page
* @param title
* @return ExerciseRepoVO
*/
IPage<ExerciseRepoVO> selectRepo(IPage<ExerciseRepoVO> page,
String title);
/**
* ID
*
* @param repoTitle
* @param userId ID
* @return RepoListVO
*/
List<RepoListVO> selectRepoList(String repoTitle, int userId);
}
}

@ -1,19 +1,25 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 定义RoleMapper接口所在的包路径
import cn.org.alan.exam.model.entity.Role;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.org.alan.exam.model.entity.Role; // 引入Role实体类该类与数据库中的角色表对应
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 引入MyBatis Plus的BaseMapper接口提供基础的CRUD操作
import java.util.List;
import java.util.List; // 引入Java的List接口用于定义返回结果类型
/**
* <p>
* Mapper
* Mapper Role
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 作者信息
* @since 2024-03-21 // 创建时间
*/
public interface RoleMapper extends BaseMapper<Role> {
public interface RoleMapper extends BaseMapper<Role> { // 定义RoleMapper接口继承BaseMapper<Role>提供对Role实体的基础CRUD操作
List<String> selectCodeById(Integer roleId);
}
/**
* ID
*
* @param roleId ID
* @return
*/
List<String> selectCodeById(Integer roleId); // 自定义查询方法根据角色ID返回对应的角色编码列表
}

@ -1,35 +1,40 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 定义StatMapper接口所在的包路径
import cn.org.alan.exam.model.entity.Grade;
import cn.org.alan.exam.model.vo.stat.AllStatsVO;
import cn.org.alan.exam.model.vo.stat.GradeExamVO;
import cn.org.alan.exam.model.vo.stat.GradeStudentVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import cn.org.alan.exam.model.entity.Grade; // 引入Grade实体类代表年级或班级信息
import cn.org.alan.exam.model.vo.stat.AllStatsVO; // 引入AllStatsVO视图对象可能用于其他统计功能当前接口未使用
import cn.org.alan.exam.model.vo.stat.GradeExamVO; // 引入GradeExamVO视图对象用于封装各班试卷数的统计信息
import cn.org.alan.exam.model.vo.stat.GradeStudentVO; // 引入GradeStudentVO视图对象用于封装各班人数的统计信息
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 引入MyBatis Plus的BaseMapper接口提供基础的CRUD操作
import org.apache.ibatis.annotations.Param; // 引入MyBatis的Param注解用于传递参数
import org.springframework.stereotype.Repository; // 引入Spring的Repository注解标记为持久层组件
import java.util.List;
import java.util.List; // 引入Java的List接口用于定义返回结果类型
/**
* @ Author JinXi
* @ Version 1.0
* @ Date 2024/5/12 14:56
* @ Author JinXi // 作者信息
* @ Version 1.0 // 版本信息
* @ Date 2024/5/12 14:56 // 创建时间
*/
@Repository
public interface StatMapper extends BaseMapper<Grade> {
@Repository // 标记为Spring的持久层组件
public interface StatMapper extends BaseMapper<Grade> { // 定义StatMapper接口继承BaseMapper<Grade>提供对Grade实体的基础CRUD操作
/**
*
* @return
*
* @param roleId ID
* @param id ID
* @return GradeStudentVO
*/
List<GradeStudentVO> StudentGradeCount(@Param("roleId")Integer roleId, Integer id);
List<GradeStudentVO> StudentGradeCount(@Param("roleId") Integer roleId, Integer id);
/**
*
* @return
*
* @param roleId ID
* @param id ID
* @return GradeExamVO
*/
List<GradeExamVO> ExamGradeCount(@Param("roleId") Integer roleId, Integer id);
}
// 注意接口中缺少对AllStatsVO的使用如果后续需要使用应添加相应的方法
}

@ -1,31 +1,46 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 定义UserBookMapper接口所在的包路径
import cn.org.alan.exam.model.entity.UserBook;
import cn.org.alan.exam.model.vo.userbook.UserPageBookVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import cn.org.alan.exam.model.entity.UserBook; // 引入UserBook实体类代表用户的错题本记录
import cn.org.alan.exam.model.vo.userbook.UserPageBookVO; // 引入UserPageBookVO视图对象用于封装分页查询结果
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 引入MyBatis Plus的BaseMapper接口提供基础的CRUD操作
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // 引入MyBatis Plus的分页插件Page类用于分页查询
import java.util.List;
import java.util.List; // 引入Java的List接口用于定义返回结果类型或传递参数类型
/**
* <p>
* Mapper
* Mapper UserBook
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 作者信息
* @since 2024-03-21 // 创建时间
*/
public interface UserBookMapper extends BaseMapper<UserBook> {
public interface UserBookMapper extends BaseMapper<UserBook> { // 定义UserBookMapper接口继承BaseMapper<UserBook>提供对UserBook实体的基础CRUD操作
/**
*
* @param userIds id
* @return
*
*
* @param userIds ID
* @return
*/
Integer deleteByUserIds(List<Integer> userIds);
Integer deleteByUserIds(List<Integer> userIds); // 自定义删除方法根据用户ID列表删除对应的错题本记录
/**
*
*
* @param userBookArrayList UserBook
* @return
*/
int addUserBookList(List<UserBook> userBookArrayList); // 自定义添加方法,批量添加错题本记录
int addUserBookList(List<UserBook> userBookArrayList);
Page<UserPageBookVO> selectPageVo(Page<UserPageBookVO> page, String examName, Integer userId,Integer role);
}
/**
*
*
* @param page
* @param examName
* @param userId ID
* @param role ID
* @return
*/
Page<UserPageBookVO> selectPageVo(Page<UserPageBookVO> page, String examName, Integer userId, Integer role); // 自定义分页查询方法根据分页信息、考试名称、用户ID和角色ID查询错题本记录
}

@ -1,17 +1,24 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 定义UserDailyLoginDurationMapper接口所在的包路径
import cn.org.alan.exam.model.entity.UserDailyLoginDuration;
import cn.org.alan.exam.model.entity.UserExerciseRecord;
import cn.org.alan.exam.model.vo.stat.DailyVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.org.alan.exam.model.entity.UserDailyLoginDuration; // 引入UserDailyLoginDuration实体类表示用户的每日登录时长记录
import cn.org.alan.exam.model.entity.UserExerciseRecord; // 引入UserExerciseRecord实体类虽然在此接口中未直接使用可能是为了后续扩展或代码未完全展示
import cn.org.alan.exam.model.vo.stat.DailyVO; // 引入DailyVO视图对象用于封装每日统计信息
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 引入MyBatis Plus的BaseMapper接口提供基础的CRUD操作
import java.util.List;
// 不需要为import语句添加注释
/**
* @Author Alan
* @Version
* @Date 2024/5/28 10:47 PM
* @Author Alan // 作者信息
* @Version // 版本信息(此处未填写具体内容)
* @Date 2024/5/28 10:47 PM // 创建时间
*/
public interface UserDailyLoginDurationMapper extends BaseMapper<UserDailyLoginDuration> {
List<DailyVO> getDaily(Integer userId);
}
public interface UserDailyLoginDurationMapper extends BaseMapper<UserDailyLoginDuration> { // 定义UserDailyLoginDurationMapper接口继承BaseMapper<UserDailyLoginDuration>提供对UserDailyLoginDuration实体的基础CRUD操作
/**
* ID
*
* @param userId ID
* @return DailyVO
*/
List<DailyVO> getDaily(Integer userId); // 自定义查询方法根据用户ID获取用户的每日登录时长统计信息并封装为DailyVO对象列表返回
}

@ -1,66 +1,83 @@
package cn.org.alan.exam.mapper;
package cn.org.alan.exam.mapper; // 定义UserExamsScoreMapper接口所在的包路径
import cn.org.alan.exam.model.entity.UserExamsScore;
import cn.org.alan.exam.model.vo.answer.UncorrectedUserVO;
import cn.org.alan.exam.model.vo.score.ExportScoreVO;
import cn.org.alan.exam.model.vo.score.GradeScoreVO;
import cn.org.alan.exam.model.vo.score.UserScoreVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
// 引入相关的实体类和视图对象
import cn.org.alan.exam.model.entity.UserExamsScore; // 引入UserExamsScore实体类表示用户的考试成绩
import cn.org.alan.exam.model.vo.answer.UncorrectedUserVO; // 引入UncorrectedUserVO视图对象用于封装未考试用户的信息
import cn.org.alan.exam.model.vo.score.ExportScoreVO; // 引入ExportScoreVO视图对象用于导出考试成绩
import cn.org.alan.exam.model.vo.score.GradeScoreVO; // 引入GradeScoreVO视图对象用于封装班级用户的成绩分析信息
import cn.org.alan.exam.model.vo.score.UserScoreVO; // 引入UserScoreVO视图对象用于封装分页查询的用户成绩信息
import java.util.List;
// 引入MyBatis Plus的相关类
import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 引入BaseMapper接口提供基础的CRUD操作
import com.baomidou.mybatisplus.core.metadata.IPage; // 引入IPage接口用于分页查询
import java.util.List; // 引入Java的List接口用于定义返回结果类型或传递参数类型
/**
* <p>
* Mapper
* Mapper UserExamsScore
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 作者信息
* @since 2024-03-21 // 创建时间
*/
public interface UserExamsScoreMapper extends BaseMapper<UserExamsScore> {
public interface UserExamsScoreMapper extends BaseMapper<UserExamsScore> { // 定义UserExamsScoreMapper接口继承BaseMapper<UserExamsScore>提供对UserExamsScore实体的基础CRUD操作
/**
*
* @param page
* @param gradeId Id
* @param examTitle
* @param userId Id
* @param roleId Id
* @return
*
* @param page
* @param gradeId Id
* @param examTitle
* @param userId Idnull
* @param roleId Id
* @return
*/
IPage<GradeScoreVO> scoreStatistics(IPage<GradeScoreVO> page ,Integer gradeId, String examTitle,Integer userId,Integer roleId);
IPage<GradeScoreVO> scoreStatistics(IPage<GradeScoreVO> page, Integer gradeId, String examTitle, Integer userId, Integer roleId);
/**
*
*
* @param page
* @param gradeId Id
* @param examId Id
* @param realName
* @return
* @param page
* @param gradeId Id
* @param examId Id
* @param realName null
* @return
*/
IPage<UserScoreVO> pagingScore(IPage<UserScoreVO> page, Integer gradeId, Integer examId, String realName);
/**
*
*
* @param examId Id
* @return
*/
Integer getNumberOfApplicants(Integer examId);
/**
*
*
* @param examId Id
* @return
*/
Integer getCorrectedPaper(Integer examId);
/**
*
*
*
* @param examId id
* @param gradeId id
* @return
* @param examId id
* @param gradeId idnull
* @return idid
*/
List<ExportScoreVO> selectScores(Integer examId, Integer gradeId);
/**
* id
*
* @param page
* @param examId id
* @return
* @param page
* @param examId id
* @param realName null
* @return
*/
IPage<UncorrectedUserVO> uncorrectedUser(IPage<UncorrectedUserVO> page, Integer examId,String realName);
}
IPage<UncorrectedUserVO> uncorrectedUser(IPage<UncorrectedUserVO> page, Integer examId, String realName);
}

@ -1,16 +1,24 @@
// 定义UserExerciseRecordMapper接口所在的包路径
package cn.org.alan.exam.mapper;
// 引入UserExerciseRecord实体类该类表示用户的练习记录
import cn.org.alan.exam.model.entity.UserExerciseRecord;
// 引入MyBatis Plus的BaseMapper接口它提供了基础的CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper
* Mapper UserExerciseRecord
* </p>
* <p>
* BaseMapper<UserExerciseRecord>UserExerciseRecordCRUD
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 作者信息
* @since 2024-03-21 // 接口创建时间
*/
public interface UserExerciseRecordMapper extends BaseMapper<UserExerciseRecord> {
}
// 此处为空因为该接口仅继承了BaseMapper未定义额外的方法。
// 若需要自定义数据库操作方法,可在此处添加。
}

@ -1,34 +1,43 @@
// 定义UserMapper接口所在的包路径
package cn.org.alan.exam.mapper;
// 引入User实体类代表用户信息
import cn.org.alan.exam.model.entity.User;
// 引入ClassCountResult类用于统计和分组的结果
import cn.org.alan.exam.model.form.count.ClassCountResult;
// 引入UserVO类用于视图层展示的用户信息
import cn.org.alan.exam.model.vo.UserVO;
// 引入MyBatis Plus的BaseMapper接口提供基础的CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 引入MyBatis Plus的IPage接口用于分页查询
import com.baomidou.mybatisplus.core.metadata.IPage;
// 引入Spring的Repository注解标记这是一个持久层组件
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* <p>
* Mapper
* Mapper User
* </p>
*
* @author WeiJin
* @since 2024-03-21
* @author WeiJin // 作者信息
* @since 2024-03-21 // 接口创建时间
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
@Repository // 标记为Spring的Repository组件
public interface UserMapper extends BaseMapper<User> { // 继承BaseMapper<User>提供User的基础CRUD操作
// 已被注释的方法原意是根据id获取用户列表但通常id应唯一此处逻辑可能有误
// List<User> getText(Integer id);
// 根据用户ID列表删除用户等级信息注意方法名可能不完全准确反映其功能需根据业务逻辑确认
Integer removeUserGrade(List<Integer> userIds);
/**
*
*
* @param list
* @return
* @param list
* @return
*/
Integer insertBatchUser(List<User> list);
@ -36,40 +45,51 @@ public interface UserMapper extends BaseMapper<User> {
*
*
* @param userId id
* @return
* @return UserVO
*/
UserVO info(Integer userId);
/**
* IdId
* IdId
*
* @param classId Id
* @return
* @return Id
*/
List<Integer> selectIdsByClassId(Integer classId);
/**
*
*
* @param page
* @param gradeId Id
* @param realName
* @param roleId Id
* @return
* @param page
* @param gradeId Id
* @param realName
* @param userId IdremoveUserGradeuserIdsId
* @param roleId Id
* @return
*/
IPage<UserVO> pagingUser(IPage<UserVO> page, Integer gradeId, String realName,Integer userId, Integer roleId);
IPage<UserVO> pagingUser(IPage<UserVO> page, Integer gradeId, String realName, Integer userId, Integer roleId);
/**
*
* Id
*
* @param gradeIds id
* @return
*/
Integer removeGradeIdByGradeIds(List<Integer> gradeIds);
/**
* Id
*
* @param roleId Id
* @return IdId
*/
List<ClassCountResult> countAndGroupByGradeAndRoleId(Integer roleId);
/**
* Id
*
* @param userIds Id
* @return
*/
Integer deleteByUserIds(List<Integer> userIds);
}
}

@ -1,6 +1,5 @@
package cn.org.alan.exam.security;
import cn.org.alan.exam.model.entity.User;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -12,59 +11,75 @@ import java.util.Collection;
import java.util.List;
/**
* Spring Security
* Spring Security Spring SecurityUserDetails
* 便
*
* @author haoxr
* @since 3.0.0
*/
// 使用Lombok的 @Data注解自动生成类的Getter、Setter、toString、equals、hashCode等方法简化代码编写
@Data
// 使用Lombok的 @NoArgsConstructor注解自动生成无参构造函数方便对象的创建和初始化
@NoArgsConstructor
public class SysUserDetails implements UserDetails {
// 用于存储用户的权限列表权限类型为SimpleGrantedAuthority它是Spring Security中表示权限的一种简单实现通常包含权限名称如 "ROLE_ADMIN"、"ROLE_USER" 等)
private List<SimpleGrantedAuthority> permissions;
// 关联的实际业务中的用户实体对象,包含了如用户名、密码、用户其他属性等详细信息,用于获取和传递与用户相关的业务数据
private User user;
// 用户名用于在Spring Security认证等流程中作为用户的标识与UserDetails接口中定义的方法对应此处可能后续会根据业务逻辑有具体赋值或使用情况
private String username;
// 构造函数接收一个User对象用于初始化SysUserDetails对象时关联对应的业务用户实体方便后续获取用户相关信息进行认证授权等操作
public SysUserDetails(User user) {
this.user = user;
}
// 实现UserDetails接口的方法用于获取用户的权限集合在Spring Security的授权流程中会调用该方法来判断用户具有哪些权限这里直接返回之前定义的permissions列表
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return permissions;
}
// 设置用户权限的方法,外部可以通过调用该方法来为当前用户对象设置相应的权限列表,方便在用户权限管理等业务场景中更新用户权限信息
public void setPermissions(List<SimpleGrantedAuthority> permissions) {
this.permissions = permissions;
}
// 实现UserDetails接口的方法用于获取用户的密码从关联的User实体对象中获取密码信息并在获取后将User实体中的密码字段设置为空字符串可能出于安全考虑防止密码在后续不必要的场景中被意外获取到然后返回密码
@Override
public String getPassword() {
String myPassword=user.getPassword();
String myPassword = user.getPassword();
user.setPassword("");
return myPassword;
}
// 实现UserDetails接口的方法用于获取用户的用户名这里从关联的User实体对象中获取用户名通常是业务中定义的用户标识字段如账号名等并返回作为Spring Security认证流程中识别用户的重要依据
@Override
public String getUsername() {
return user.getUserName();
}
// 实现UserDetails接口的方法用于判断用户账号是否未过期在Spring Security的认证逻辑中会参考该方法的返回值来确定账号有效性这里直接返回true表示默认账号未过期可根据实际业务需求进行具体逻辑修改
@Override
public boolean isAccountNonExpired() {
return true;
}
// 实现UserDetails接口的方法用于判断用户账号是否未锁定同样在Spring Security的认证检查中起作用返回true表示默认账号未锁定可根据业务规则调整该逻辑比如根据数据库中账号锁定字段等来判断
@Override
public boolean isAccountNonLocked() {
return true;
}
// 实现UserDetails接口的方法用于判断用户的凭证通常就是密码是否未过期此处返回true表示默认凭证未过期若有密码有效期等业务需求可在此处实现相应逻辑判断
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 实现UserDetails接口的方法用于判断用户账号是否启用返回true表示默认用户账号是启用状态可根据业务情况如用户是否被禁用的标识字段等来具体实现该方法的逻辑判断
@Override
public boolean isEnabled() {
return true;
}
}
}

@ -40,177 +40,223 @@ import java.util.stream.Collectors;
*/
@Service
public class GradeServiceImpl extends ServiceImpl<GradeMapper, Grade> implements IGradeService {
// 注入GradeMapper用于操作班级数据的持久层接口
@Resource
private GradeMapper gradeMapper;
// 注入ExamMapper可能用于操作考试相关数据的持久层接口此处从命名推测
@Resource
private ExamMapper examMapper;
// 注入QuestionMapper可能用于操作题目相关数据的持久层接口此处从命名推测
@Resource
private QuestionMapper questionMapper;
// 注入GradeConverter用于不同班级相关对象之间的转换比如表单对象和实体对象等
@Resource
private GradeConverter gradeConverter;
// 注入UserMapper用于操作用户相关数据的持久层接口
@Resource
private UserMapper userMapper;
// 注入IGradeService自身接口注入可能用于调用自身定义的其他服务方法此处从代码结构推测
@Resource
private IGradeService gradeService;
// 注入StringRedisTemplate用于操作Redis字符串类型数据常用于缓存相关操作
@Resource
private StringRedisTemplate stringRedisTemplate;
// 注入CacheClient自定义的缓存客户端用于更方便地进行缓存操作
@Resource
private CacheClient cacheClient;
// 添加班级的方法,事务性操作,成功返回添加成功的提示信息,失败返回相应错误提示
@Override
@Transactional
public Result<String> addGrade(GradeForm gradeForm) {
// 生成班级口令
// 生成班级口令调用工具类方法生成一个长度为18的班级口令
gradeForm.setCode(ClassTokenGenerator.generateClassToken(18));
// 实体转换
// 将GradeForm对象转换为Grade实体对象方便后续持久化操作
Grade grade = gradeConverter.formToEntity(gradeForm);
// 添加数据
// 调用gradeMapper的insert方法将班级数据插入到数据库中返回受影响的行数
int rows = gradeMapper.insert(grade);
// 如果插入操作没有影响到任何行,即插入失败,返回添加失败的结果信息
if (rows == 0) {
return Result.failed("添加失败");
}
// 更新缓存
if (grade.getId() != null) { // 确保ID有效
// 如果是更新操作,先从缓存中移除旧数据,然后重新放入最新的数据
stringRedisTemplate.delete("cache:grade:getPaging:"+grade.getId().toString()); // 删除旧缓存
// 如果插入成功班级数据有了有效的ID这里假设插入后会自动生成ID进行缓存相关操作
if (grade.getId()!= null) { // 确保ID有效
// 如果是更新操作(此处逻辑可能不太准确,实际上是添加操作,但代码结构类似更新时的缓存处理逻辑),先从缓存中移除旧数据
// 根据班级ID删除对应的缓存数据缓存键格式为"cache:grade:getPaging:" + 班级ID
stringRedisTemplate.delete("cache:grade:getPaging:" + grade.getId().toString());
// GradeVO updatedGradeVO = gradeConverter.GradeToGradeVO(grade); // 转换为视图对象
// Map<Integer, GradeVO> map = Map.of(updatedGradeVO.getId(), updatedGradeVO);
// cacheClient.batchPut("cache:grade:getPaging:",map,10L,TimeUnit.MINUTES); // 存储新数据
}
stringRedisTemplate.delete("cache:grade:getAllGrade:"+SecurityUtil.getUserId());
// 根据当前用户ID删除获取所有班级的缓存数据缓存键格式为"cache:grade:getAllGrade:" + 当前用户ID
stringRedisTemplate.delete("cache:grade:getAllGrade:" + SecurityUtil.getUserId());
// 返回添加成功的结果信息
return Result.success("添加成功");
}
// 更新班级信息的方法,事务性操作,成功返回更新成功的提示信息,失败返回相应错误提示
@Override
@Transactional
public Result<String> updateGrade(Integer id, GradeForm gradeForm) {
// 建立更新条件
// 创建一个LambdaUpdateWrapper对象用于构建更新班级信息的条件和要更新的字段
LambdaUpdateWrapper<Grade> gradeUpdateWrapper = new LambdaUpdateWrapper<>();
// 设置要更新的班级名称字段值为传入的gradeForm中的班级名称
gradeUpdateWrapper
.set(Grade::getGradeName, gradeForm.getGradeName())
// 设置更新条件为班级ID等于传入的id参数
.eq(Grade::getId, id);
// 更新班级
// 调用gradeMapper的update方法根据上面构建的更新条件和要更新的字段来更新班级数据,返回受影响的行数
int rows = gradeMapper.update(gradeUpdateWrapper);
// 如果更新操作没有影响到任何行,即更新失败,返回修改失败的结果信息
if (rows == 0) {
return Result.failed("修改失败");
}
// 根据ID获取更新后的班级对象用于后续缓存操作等可能需要获取最新数据来更新缓存等
Grade byId = getById(id);
// 更新缓存
if (byId.getId() != null) { // 确保ID有效
// 如果是更新操作,先从缓存中移除旧数据,然后重新放入最新的数据
stringRedisTemplate.delete("cache:grade:getPaging:"+byId.getId().toString()); // 删除旧缓存
// 如果获取到的班级对象有有效的ID进行缓存相关操作
if (byId.getId()!= null) { // 确保ID有效
// 如果是更新操作,先从缓存中移除旧数据,根据班级ID删除对应的缓存数据缓存键格式为"cache:grade:getPaging:" + 班级ID
stringRedisTemplate.delete("cache:grade:getPaging:" + byId.getId().toString());
// GradeVO updatedGradeVO = gradeConverter.GradeToGradeVO(byId); // 转换为视图对象
// Map<Integer, GradeVO> map = Map.of(updatedGradeVO.getId(), updatedGradeVO);
// cacheClient.batchPut("cache:grade:getPaging:",map,10L,TimeUnit.MINUTES); // 存储新数据
}
stringRedisTemplate.delete("cache:grade:getAllGrade:"+SecurityUtil.getUserId());
// 根据当前用户ID删除获取所有班级的缓存数据缓存键格式为"cache:grade:getAllGrade:" + 当前用户ID
stringRedisTemplate.delete("cache:grade:getAllGrade:" + SecurityUtil.getUserId());
// 返回更新成功的结果信息
return Result.success("修改成功");
}
// 删除班级的方法,事务性操作,成功返回删除成功的提示信息,失败返回相应错误提示
@Override
@Transactional
public Result<String> deleteGrade(Integer id) {
// 删除班级
// 创建一个LambdaUpdateWrapper对象用于构建删除班级的逻辑(这里实际是逻辑删除,设置删除标记字段)
LambdaUpdateWrapper<Grade> gradeLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
gradeLambdaUpdateWrapper.eq(Grade::getId,id)
.set(Grade::getIsDeleted,1);
// 设置更新条件为班级ID等于传入的id参数
gradeLambdaUpdateWrapper.eq(Grade::getId, id)
// 设置班级的删除标记字段假设名为isDeleted为1表示已删除
.set(Grade::getIsDeleted, 1);
// 调用gradeMapper的update方法根据上面构建的更新条件来执行逻辑删除操作返回受影响的行数
int rows = gradeMapper.update(gradeLambdaUpdateWrapper);
// 如果删除操作没有影响到任何行,即删除失败,返回删除失败的结果信息
if (rows == 0) {
return Result.failed("删除失败");
}
// 删除缓存
stringRedisTemplate.delete("cache:grade:getPaging:"+id.toString());
stringRedisTemplate.delete("cache:grade:getAllGrade:"+SecurityUtil.getUserId());
// 根据班级ID删除对应的缓存数据缓存键格式为"cache:grade:getPaging:" + 班级ID
stringRedisTemplate.delete("cache:grade:getPaging:" + id.toString());
// 根据当前用户ID删除获取所有班级的缓存数据缓存键格式为"cache:grade:getAllGrade:" + 当前用户ID
stringRedisTemplate.delete("cache:grade:getAllGrade:" + SecurityUtil.getUserId());
// 返回删除成功的结果信息
return Result.success("删除成功");
}
// 获取班级分页数据的方法,返回包含分页信息和班级视图对象列表的结果
@Override
public Result<IPage<GradeVO>> getPaging(Integer pageNum, Integer pageSize, String gradeName) {
// 初始化角色变量默认为0这里可能根据不同角色有不同的查询逻辑从后续代码推测
Integer role = 0;
if("role_teacher".equals(SecurityUtil.getRole())){
// 如果当前用户角色为"role_teacher"则将角色设置为2具体角色值含义根据业务定义
if ("role_teacher".equals(SecurityUtil.getRole())) {
role = 2;
}
// 查询满足条件的总记录数
int total = gradeMapper.countByCondition(SecurityUtil.getUserId(), gradeName,role); // 假设gradeMapper中实现了根据条件计数的方法
// 计算偏移量
// 调用gradeMapper的countByCondition方法根据当前用户ID、班级名称和角色来统计满足条件的班级总记录数假设gradeMapper中实现了该方法
int total = gradeMapper.countByCondition(SecurityUtil.getUserId(), gradeName, role);
// 计算分页查询的偏移量,根据当前页码和每页显示数量来计算
int offset = (pageNum - 1) * pageSize;
// 查询分页ID列表
List<Integer> gradeIds = gradeMapper.selectGradeIdsPage(SecurityUtil.getUserId(), role ,gradeName, offset, pageSize);
// 调用gradeMapper的selectGradeIdsPage方法根据当前用户ID、角色、班级名称以及偏移量和每页显示数量来查询分页的班级ID列表假设gradeMapper中实现了该方法
List<Integer> gradeIds = gradeMapper.selectGradeIdsPage(SecurityUtil.getUserId(), role, gradeName, offset, pageSize);
// 批量从缓存中获取GradeVO对象
Map<Integer, GradeVO> cachedGradesMap = cacheClient.batchGet("cache:grade:getPaging:",gradeIds, GradeVO.class);
// 使用cacheClient的batchGet方法从缓存中批量获取对应班级ID的GradeVO对象缓存键前缀为"cache:grade:getPaging:"
Map<Integer, GradeVO> cachedGradesMap = cacheClient.batchGet("cache:grade:getPaging:", gradeIds, GradeVO.class);
// 确定未命中的ID列表
// 创建一个列表用于存储在缓存中未命中的班级ID即缓存中不存在对应数据的班级ID
List<Integer> missIds = new ArrayList<>();
// 遍历查询到的班级ID列表检查每个ID在缓存中是否存在如果不存在则添加到未命中ID列表中
for (Integer id : gradeIds) {
if (!cachedGradesMap.containsKey(id)) {
missIds.add(id);
}
}
// 如果有未命中的ID从数据库批量查询并更新缓存
// 如果存在未命中缓存的班级ID即missIds列表不为空
if (!missIds.isEmpty()) {
// 调用gradeMapper的batchSelectByIds方法从数据库中批量查询未命中缓存的班级数据并转换为GradeVO对象列表假设gradeMapper中实现了该方法
List<GradeVO> missedGrades = gradeMapper.batchSelectByIds(missIds);
// 假设GradeVO的ID为getId()使用Collectors.toMap转换
// 使用Java 8的Stream API将GradeVO对象列表转换为以班级ID为键GradeVO对象为值的Map方便后续操作
Map<Integer, GradeVO> missedGradesMap = missedGrades.stream()
.collect(Collectors.toMap(GradeVO::getId, Function.identity()));
// 更新缓存
cacheClient.batchPut("cache:grade:getPaging:",missedGradesMap,10L,TimeUnit.MINUTES);
// 合并缓存结果
// 使用cacheClient的batchPut方法将从数据库中查询到的未命中缓存的班级数据更新缓存缓存有效期为10分钟
cacheClient.batchPut("cache:grade:getPaging:", missedGradesMap, 10L, TimeUnit.MINUTES);
// 将从数据库中查询到的未命中缓存的班级数据合并缓存结果的Map中确保缓存数据的完整性
cachedGradesMap.putAll(missedGradesMap);
}
// 根据ID列表从缓存中获取完整的GradeVO对象列表
// 创建一个列表用于存储最终要返回的完整的GradeVO对象列表初始大小为查询到的班级ID列表的大小
List<GradeVO> finalResult = new ArrayList<>(gradeIds.size());
// 遍历班级ID列表从缓存结果的Map中获取对应的GradeVO对象并添加到最终结果列表中
for (Integer id : gradeIds) {
finalResult.add(cachedGradesMap.get(id));
}
// 构建并返回IPage对象
// 创建一个IPage对象用于封装分页信息和最终的班级视图对象列表设置当前页码、每页显示数量以及总记录数
IPage<GradeVO> resultPage = new Page<>(pageNum, pageSize, Long.valueOf(total));
// 将最终的班级视图对象列表设置到IPage对象中
resultPage.setRecords(finalResult);
// 返回包含分页信息和班级视图对象列表的成功结果信息
return Result.success("查询成功", resultPage);
}
// 移除用户班级关联的方法,成功返回移除成功的提示信息,失败返回相应错误提示
@Override
public Result<String> removeUserGrade(String ids) {
// 字符串转换为列表
// 将传入的以逗号分隔的字符串形式的用户ID列表转换为Integer类型的列表使用Java 8的Stream API进行转换
List<Integer> userIds = Arrays.stream(ids.split(","))
.map(Integer::parseInt)
.toList();
// 移出班级
// 调用userMapper的removeUserGrade方法根据用户ID列表移除用户与班级的关联关系返回受影响的行数假设userMapper中实现了该方法
int rows = userMapper.removeUserGrade(userIds);
// 如果移除操作没有影响到任何行,即移除失败,返回移除失败的结果信息
if (rows == 0) {
return Result.failed("移除失败");
}
userIds.forEach(id->{
stringRedisTemplate.delete("cache:grade:getPaging:"+id.toString());
// 遍历用户ID列表根据每个用户ID删除对应的获取班级分页数据的缓存这里的逻辑可能需要根据实际业务进一步确认是否合理
userIds.forEach(id -> {
stringRedisTemplate.delete("cache:grade:getPaging:" + id.toString());
});
// 返回移除成功的结果信息
return Result.success("移除成功");
}
// 获取所有班级信息的方法,返回包含所有班级视图对象列表的结果
@Override
public Result<List<GradeVO>> getAllGrade() {
// 创建一个匿名内部类实现Function接口用于定义从用户ID获取班级视图对象列表的逻辑
Function<Integer, List> function = new Function<>() {
@Override
public List apply(Integer userId) {
// 创建一个LambdaQueryWrapper对象用于构建查询班级信息的条件这里查询当前用户ID下未删除的班级假设isDeleted字段表示是否删除
LambdaQueryWrapper<Grade> gradeLambdaQueryWrapper = new LambdaQueryWrapper<>();
gradeLambdaQueryWrapper.eq(Grade::getUserId, userId)
.eq(Grade::getIsDeleted,0);
.eq(Grade::getIsDeleted, 0);
// 调用gradeMapper的selectList方法根据上面构建的查询条件获取班级实体对象列表
List<Grade> grades = gradeMapper.selectList(gradeLambdaQueryWrapper);
// 使用gradeConverter的listEntityToVo方法将班级实体对象列表转换为班级视图对象列表
List<GradeVO> gradeVOS = gradeConverter.listEntityToVo(grades);
// 返回班级视图对象列表
return gradeVOS;
}
};
// 使用cacheClient的queryWithPassThrough方法通过缓存穿透机制获取所有班级信息缓存键前缀为"cache:grade:getAllGrade:"传入当前用户ID、期望返回的类型、查询逻辑函数、返回对象类型、缓存有效期等参数
List<GradeVO> result = cacheClient.queryWithPassThrough("cache:grade:getAllGrade:", SecurityUtil.getUserId(), List.class, function, GradeVO.class, 10L, TimeUnit.MINUTES);
if(result==null){
// 如果获取结果为null即查询失败返回查询失败的结果信息
if (result == null) {
return Result.failed("查询失败");
}
// 返回包含所有班级视图对象列表的成功结果信息
return Result.success("查询成功", result);
}
}
}

@ -23,134 +23,177 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>
*
* </p>
*
* @author WeiJin
* @since 2024-03-21
*/
@Service
public class ManualScoreServiceImpl extends ServiceImpl<ManualScoreMapper, ManualScore> implements IManualScoreService {
// 注入ExamMapper用于操作考试相关数据的持久层接口从命名推测其功能
@Resource
private ExamMapper examMapper;
// 注入ExamGradeMapper可能用于操作考试与班级关联相关数据的持久层接口根据命名推测
@Resource
private ExamGradeMapper examGradeMapper;
// 注入UserExamsScoreMapper用于操作用户考试成绩相关数据的持久层接口推测用途
@Resource
private UserExamsScoreMapper userExamsScoreMapper;
// 注入ExamQuAnswerMapper可能用于操作考试题目作答相关数据的持久层接口从名字判断
@Resource
private ExamQuAnswerMapper examQuAnswerMapper;
// 注入ManualScoreMapper用于操作手动评分相关数据的持久层接口对应业务功能
@Resource
private ManualScoreMapper manualScoreMapper;
// 注入CertificateUserMapper可能用于操作证书与用户关联相关数据的持久层接口依据命名猜测
@Resource
private CertificateUserMapper certificateUserMapper;
// 获取用户答题详情的方法根据用户ID和考试ID从数据库获取相关答题详情信息并返回
@Override
public Result<List<UserAnswerDetailVO>> getDetail(Integer userId, Integer examId) {
// 调用examQuAnswerMapper的selectUserAnswer方法传入用户ID和考试ID获取用户答题详情列表
List<UserAnswerDetailVO> list = examQuAnswerMapper.selectUserAnswer(userId, examId);
// 返回包含答题详情列表的成功结果信息这里第一个参数为null可能是预留用于传递其他提示信息等情况
return Result.success(null, list);
}
// 批改答题的方法,事务性操作,用于处理答题批改相关逻辑,包括记录手动评分、更新用户考试记录、颁发证书等操作
@Override
@Transactional
public Result<String> correct(List<CorrectAnswerFrom> correctAnswerFroms) {
// 创建一个ManualScore类型的列表用于存储要插入数据库的手动评分记录初始大小为传入的答题批改表单列表的大小
List<ManualScore> list = new ArrayList<>(correctAnswerFroms.size());
// 创建一个AtomicInteger对象用于原子性地累加手动评分的总分方便在多线程环境下安全地更新总分虽然此处可能并非多线程场景但使用它保证操作的原子性
AtomicInteger manualTotalScore = new AtomicInteger();
// 遍历传入的答题批改表单列表,处理每个答题的批改信息
correctAnswerFroms.forEach(correctAnswerFrom -> {
//获取用户作答信息id
// 创建一个LambdaQueryWrapper对象用于构建查询考试题目作答信息的条件这里只选择作答信息的ID字段
LambdaQueryWrapper<ExamQuAnswer> wrapper = new LambdaQueryWrapper<ExamQuAnswer>()
.select(ExamQuAnswer::getId)
// 设置查询条件为考试ID等于当前答题批改表单中的考试ID
.eq(ExamQuAnswer::getExamId, correctAnswerFrom.getExamId())
// 设置查询条件为用户ID等于当前答题批改表单中的用户ID
.eq(ExamQuAnswer::getUserId, correctAnswerFrom.getUserId())
// 设置查询条件为题目ID等于当前答题批改表单中的题目ID
.eq(ExamQuAnswer::getQuestionId, correctAnswerFrom.getQuestionId());
// 创建一个ManualScore对象用于存储当前答题的手动评分记录信息
ManualScore manualScore = new ManualScore();
// 设置手动评分记录中的考试题目作答信息ID通过查询获取对应的作答信息ID并设置
manualScore.setExamQuAnswerId(examQuAnswerMapper.selectOne(wrapper).getId());
// 设置手动评分记录中的分数,值为当前答题批改表单中的分数
manualScore.setScore(correctAnswerFrom.getScore());
// 将当前手动评分记录添加到列表中,后续批量插入数据库
list.add(manualScore);
// 原子性地累加当前答题的分数到总分中
manualTotalScore.addAndGet(correctAnswerFrom.getScore());
});
// 调用manualScoreMapper的insertList方法将整理好的手动评分记录列表批量插入到数据库中
manualScoreMapper.insertList(list);
//把用户考试记录修改为已批改,并把简答题分数添加进去
// 把用户考试记录修改为已批改,并把简答题分数添加进去
// 获取答题批改表单列表中的第一个表单对象,用于后续构建更新用户考试记录的条件(这里假设所有表单对应的考试和用户是相同的,若不同则逻辑可能需要调整)
CorrectAnswerFrom correctAnswerFrom = correctAnswerFroms.get(0);
// 创建一个LambdaUpdateWrapper对象用于构建更新用户考试成绩记录的条件和要更新的字段
LambdaUpdateWrapper<UserExamsScore> userExamsScoreLambdaUpdateWrapper = new LambdaUpdateWrapper<UserExamsScore>()
// 设置更新条件为考试ID等于当前答题批改表单中的考试ID
.eq(UserExamsScore::getExamId, correctAnswerFrom.getExamId())
// 设置更新条件为用户ID等于当前答题批改表单中的用户ID
.eq(UserExamsScore::getUserId, correctAnswerFrom.getUserId())
// 设置要更新的字段为是否已批改标记为1表示已批改
.set(UserExamsScore::getWhetherMark, 1)
// 使用SQL语句设置更新用户成绩将原用户成绩加上手动评分的总分这里使用了原生SQL语句拼接可能存在SQL注入风险若有安全需求可考虑参数化方式
.setSql("user_score = user_score + " + manualTotalScore.get());
// 调用userExamsScoreMapper的update方法根据构建的更新条件和字段来更新用户考试成绩记录
userExamsScoreMapper.update(userExamsScoreLambdaUpdateWrapper);
//根据该考试是否有证书来给用户颁发对应证书
//判断该考试是否有证书
// 根据该考试是否有证书来给用户颁发对应证书
// 判断该考试是否有证书
LambdaQueryWrapper<Exam> examWrapper = new LambdaQueryWrapper<Exam>()
// 选择查询考试的ID、证书ID以及及格分数字段
.select(Exam::getId, Exam::getCertificateId, Exam::getPassedScore)
// 设置查询条件为考试ID等于当前答题批改表单中的考试ID
.eq(Exam::getId, correctAnswerFrom.getExamId());
// 调用examMapper的selectOne方法根据构建的查询条件获取对应的考试信息对象
Exam exam = examMapper.selectOne(examWrapper);
//不必对exam做非空验证这里一定不为null
if (exam.getCertificateId() != null && exam.getCertificateId() > 0) {
//有证书 获取用户得分
// 不必对exam做非空验证这里一定不为null(此处根据代码逻辑的前置假设,认为该查询必然能获取到数据,如果实际情况可能为空则需要添加空值判断处理)
if (exam.getCertificateId()!= null && exam.getCertificateId() > 0) {
// 有证书 获取用户得分
LambdaQueryWrapper<UserExamsScore> examsScoreWrapper = new LambdaQueryWrapper<UserExamsScore>()
// 选择查询用户考试成绩记录的ID和用户分数字段
.select(UserExamsScore::getId, UserExamsScore::getUserScore)
// 设置查询条件为考试ID等于当前答题批改表单中的考试ID
.eq(UserExamsScore::getExamId, correctAnswerFrom.getExamId())
// 设置查询条件为用户ID等于当前答题批改表单中的用户ID
.eq(UserExamsScore::getUserId, correctAnswerFrom.getUserId());
// 调用userExamsScoreMapper的selectOne方法根据构建的查询条件获取对应的用户考试成绩记录对象
UserExamsScore userExamsScore = userExamsScoreMapper.selectOne(examsScoreWrapper);
//不必对userExamsScore做非空验证这里一定不为null
// 不必对userExamsScore做非空验证这里一定不为null(同样基于前置假设,如果实际可能为空需处理)
if (userExamsScore.getUserScore() >= exam.getPassedScore()) {
//分数合格,判罚证书
// 分数合格,判罚证书(这里可能是“颁发”证书的意思,疑似笔误写为“判罚”,以下按颁发理解)
CertificateUser certificateUser = new CertificateUser();
certificateUser.setUserId(correctAnswerFrom.getUserId());
certificateUser.setExamId(correctAnswerFrom.getExamId());
certificateUser.setCode(ClassTokenGenerator.generateClassToken(18));
certificateUser.setCertificateId(exam.getCertificateId());
// 调用certificateUserMapper的insert方法将生成的证书与用户关联信息插入到数据库中完成证书颁发操作
certificateUserMapper.insert(certificateUser);
}
}
// 返回批改成功的结果信息
return Result.success("批改成功");
}
// 获取考试分页信息的方法,根据页码、每页数量以及考试名称等条件,返回包含相关考试信息的分页结果
@Override
public Result<IPage<AnswerExamVO>> examPage(Integer pageNum, Integer pageSize,String examName) {
public Result<IPage<AnswerExamVO>> examPage(Integer pageNum, Integer pageSize, String examName) {
// 创建一个Page对象用于封装分页相关信息设置当前页码和每页显示数量
Page<AnswerExamVO> page = new Page<>(pageNum, pageSize);
//获取自己创建的考试
List<AnswerExamVO> list = examMapper.selectMarkedList(page, SecurityUtil.getUserId(), SecurityUtil.getRole(),examName).getRecords();
// 调用examMapper的selectMarkedList方法传入分页对象、当前用户ID、当前用户角色以及考试名称获取自己创建的考试信息列表并获取其记录列表这里假设selectMarkedList方法返回的是包含分页信息和记录的对象
List<AnswerExamVO> list = examMapper.selectMarkedList(page, SecurityUtil.getUserId(), SecurityUtil.getRole(), examName).getRecords();
//获取相关信息
// 遍历获取到的考试信息列表,为每个考试信息对象补充相关统计信息
list.forEach(answerExamVO -> {
//需要参加考试人数
// 设置需要参加考试的人数调用examGradeMapper的selectClassSize方法传入考试ID获取班级规模即需要参加考试的人数
answerExamVO.setClassSize(examGradeMapper.selectClassSize(answerExamVO.getExamId()));
//实际参加考试人数
// 设置实际参加考试的人数创建一个LambdaQueryWrapper对象用于构建查询条件查询对应考试的用户考试成绩记录数量即实际参加考试的人数
LambdaQueryWrapper<UserExamsScore> numberWrapper = new LambdaQueryWrapper<UserExamsScore>()
.eq(UserExamsScore::getExamId, answerExamVO.getExamId());
answerExamVO.setNumberOfApplicants(userExamsScoreMapper.selectCount(numberWrapper).intValue());
//已阅人数
// 设置已阅试卷的数量创建一个LambdaQueryWrapper对象用于构建查询条件查询对应考试中已批改是否已批改标记为1的用户考试成绩记录数量即已阅试卷的数量
LambdaQueryWrapper<UserExamsScore> correctedWrapper = new LambdaQueryWrapper<UserExamsScore>()
.eq(UserExamsScore::getWhetherMark, 1)
.eq(UserExamsScore::getExamId,answerExamVO.getExamId());
.eq(UserExamsScore::getExamId, answerExamVO.getExamId());
answerExamVO.setCorrectedPaper(userExamsScoreMapper.selectCount(correctedWrapper).intValue());
});
//移除不需要批改的试卷
// 移除不需要批改的试卷通过流操作过滤出需要批改neededMark为1的考试信息列表重新设置到分页对象的记录列表中
page.setRecords(list.stream()
.filter(answerExamVO -> answerExamVO.getNeededMark() == 1)
.toList());
// 返回包含处理后考试信息分页结果的成功结果信息这里第一个参数为null可能是预留用于传递其他提示信息等情况
return Result.success(null, page);
}
// 获取学生考试分页信息的方法根据页码、每页数量、考试ID以及学生真实姓名等条件返回包含未批改学生考试信息的分页结果
@Override
public Result<IPage<UncorrectedUserVO>> stuExamPage(Integer pageNum, Integer pageSize, Integer examId,String realName) {
public Result<IPage<UncorrectedUserVO>> stuExamPage(Integer pageNum, Integer pageSize, Integer examId, String realName) {
// 创建一个IPage对象用于封装分页相关信息设置当前页码和每页显示数量
IPage<UncorrectedUserVO> page = new Page<>(pageNum, pageSize);
page = userExamsScoreMapper.uncorrectedUser(page, examId,realName);
// 调用userExamsScoreMapper的uncorrectedUser方法传入分页对象、考试ID以及学生真实姓名获取未批改学生考试信息的分页结果并赋值给page对象
page = userExamsScoreMapper.uncorrectedUser(page, examId, realName);
// 返回包含未批改学生考试信息分页结果的成功结果信息这里第一个参数为null可能是预留用于传递其他提示信息等情况
return Result.success(null, page);
}
}
}

@ -1,20 +1,29 @@
package cn.org.alan.exam.service.impl;
// 导入NoticeGradeMapper接口用于操作与通知班级关联相关的数据持久化操作从命名推测功能
import cn.org.alan.exam.mapper.NoticeGradeMapper;
// 导入NoticeGrade实体类代表通知班级相关的实体对象
import cn.org.alan.exam.model.entity.NoticeGrade;
// 导入INoticeGradeService接口定义了通知班级相关的业务服务方法
import cn.org.alan.exam.service.INoticeGradeService;
// 导入ServiceImpl类这是MyBatis Plus提供的基础服务实现类方便进行常见的数据库操作服务实现
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入Spring的Service注解用于标记该类是一个服务层组件由Spring容器进行管理
import org.springframework.stereotype.Service;
/**
* <p>
*
*
* </p>
*
* @author WeiJin
* @since 2024-03-21
*/
// 使用@Service注解将该类标记为Spring容器管理的服务层组件方便在其他地方通过依赖注入使用
@Service
// 定义NoticeGradeServiceImpl类继承自ServiceImpl<NoticeGradeMapper, NoticeGrade>实现了INoticeGradeService接口
// 这样可以复用ServiceImpl中的通用数据库操作方法并按照INoticeGradeService接口定义实现具体业务逻辑
public class NoticeGradeServiceImpl extends ServiceImpl<NoticeGradeMapper, NoticeGrade> implements INoticeGradeService {
}
// 此处类体为空意味着目前它继承了ServiceImpl中的通用方法还没有额外实现INoticeGradeService接口中特定的业务方法
// 如果后续需要添加如添加通知班级、删除通知班级、查询通知班级等业务逻辑相关的方法,会在这里进行具体实现
}

@ -35,205 +35,266 @@ import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
*
*
* @author Alan
* @since 2024-03-21
*/
@Service
// NoticeServiceImpl类继承自ServiceImpl<NoticeMapper, Notice>实现了INoticeService接口
// 这样可以复用ServiceImpl中的通用数据库操作方法并按照INoticeService接口定义实现具体业务逻辑
public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> implements INoticeService {
// 注入NoticeMapper用于操作公告数据的持久层接口比如插入、删除、查询公告等操作
@Resource
private NoticeMapper noticeMapper;
// 注入NoticeConverter用于不同公告相关对象之间的转换比如表单对象和实体对象等
@Resource
private NoticeConverter noticeConverter;
// 注入NoticeGradeMapper可能用于操作公告与班级关联相关数据的持久层接口从命名推测其功能
@Resource
private NoticeGradeMapper noticeGradeMapper;
// 注入GradeMapper用于操作班级相关数据的持久层接口可能在公告关联班级等业务中会用到
@Resource
private GradeMapper gradeMapper;
// 注入CacheClient自定义的缓存客户端用于更方便地进行缓存操作如批量获取、批量存入缓存等
@Resource
private CacheClient cacheClient;
// 注入StringRedisTemplate用于操作Redis字符串类型数据常用于缓存相关操作比如删除缓存等
@Resource
private StringRedisTemplate stringRedisTemplate;
// 添加公告的方法,事务性操作,成功返回添加成功的提示信息,失败返回相应错误提示
@Override
@Transactional
public Result<String> addNotice(NoticeForm noticeForm) {
// 设置创建人
// 设置公告的创建人ID为当前登录用户的ID通过SecurityUtil工具类获取当前用户ID
noticeForm.setUserId(SecurityUtil.getUserId());
// 添加公告
// 将NoticeForm对象转换为Notice实体对象方便后续持久化操作使用NoticeConverter进行转换
Notice notice = noticeConverter.formToEntity(noticeForm);
// 调用noticeMapper的insert方法将公告数据插入到数据库中返回受影响的行数即插入操作影响的记录数
int rowsAffected = noticeMapper.insert(notice);
// 如果插入操作没有影响到任何行,即插入失败,返回添加失败的结果信息
if (rowsAffected == 0) {
return Result.failed("添加失败");
}
// 创建一个LambdaQueryWrapper对象用于构建查询班级信息的条件这里查询当前用户创建的班级通过用户ID关联
LambdaQueryWrapper<Grade> gradeLambdaQueryWrapper = new LambdaQueryWrapper<>();
gradeLambdaQueryWrapper.eq(Grade::getUserId,SecurityUtil.getUserId());
gradeLambdaQueryWrapper.eq(Grade::getUserId, SecurityUtil.getUserId());
// 调用gradeMapper的selectList方法根据上面构建的查询条件获取班级实体对象列表即获取当前用户创建的所有班级
List<Grade> grades = gradeMapper.selectList(gradeLambdaQueryWrapper);
// 获取刚插入公告的ID用于后续关联班级等操作假设插入后会自动生成ID
Integer noticeId = notice.getId();
if(grades.isEmpty()){
return Result.failed("您还没有创建班级,请先创建班级后再添加公告尝试");
// 如果当前用户没有创建任何班级,返回相应提示信息,提示先创建班级再添加公告
if (grades.isEmpty()) {
return Result.failed("您还没有创建班级,请先创建班级后再添加公告尝试");
}
int addNoticeGradeRow = noticeGradeMapper.addNoticeGrade(noticeId,grades);
// 调用noticeGradeMapper的addNoticeGrade方法将公告与班级进行关联传入公告ID和班级列表返回受影响的行数假设该方法实现了关联操作并返回影响行数
int addNoticeGradeRow = noticeGradeMapper.addNoticeGrade(noticeId, grades);
// 如果关联操作没有影响到任何行,即关联失败,返回添加失败的结果信息
if (addNoticeGradeRow == 0) {
return Result.failed("添加失败");
}
// 更新缓存
if (notice.getId() != null) { // 确保ID有效
// 如果是更新操作,先从缓存中移除旧数据,然后重新放入最新的数据
stringRedisTemplate.delete("cache:notice:getNotice:"+notice.getId().toString()); // 删除旧缓存
stringRedisTemplate.delete("cache:notice:getNewNotice:"+notice.getId().toString());
// 如果公告插入成功且关联班级等操作也成功公告数据有了有效的ID进行缓存相关操作
if (notice.getId()!= null) { // 确保ID有效
// 如果是更新操作(此处逻辑可能不太准确,实际上是添加操作,但代码结构类似更新时的缓存处理逻辑),先从缓存中移除旧数据
// 根据公告ID删除对应的缓存数据缓存键格式为"cache:notice:getNotice:" + 公告ID
stringRedisTemplate.delete("cache:notice:getNotice:" + notice.getId().toString());
// 根据公告ID删除对应的另一个缓存数据可能是用于获取新公告相关的缓存从命名推测缓存键格式为"cache:notice:getNewNotice:" + 公告ID
stringRedisTemplate.delete("cache:notice:getNewNotice:" + notice.getId().toString());
// NoticeVO updatedNoticeVO = noticeConverter.NoticeToNoticeVO(notice); // 转换为视图对象
// Map<Integer, NoticeVO> map = Map.of(updatedNoticeVO.getId(), updatedNoticeVO);
// cacheClient.batchPut("cache:notice:getNotice:",map,10L,TimeUnit.MINUTES); // 存储新数据
// cacheClient.batchPut("cache:notice:getNewNotice:",map,10L,TimeUnit.MINUTES); // 存储新数据
}
// 返回添加成功的结果信息
return Result.success("添加成功");
}
// 删除公告的方法,事务性操作,成功返回删除成功的提示信息,失败返回相应错误提示
@Override
@Transactional
public Result<String> deleteNotice(String ids) {
// 转换为集合
// 将传入的以逗号分隔的字符串形式的公告ID列表转换为Integer类型的列表使用Java 8的Stream API进行转换
List<Integer> noticeIds = Arrays.stream(ids.split(","))
.map(Integer::parseInt)
.toList();
// 删除公告
// 调用noticeMapper的removeNotice方法根据公告ID列表删除对应的公告数据,返回受影响的行数,即删除操作影响的记录数
int rowsAffected = noticeMapper.removeNotice(noticeIds);
// 如果删除操作没有影响到任何行,即删除失败,返回删除失败的结果信息
if (rowsAffected == 0) {
return Result.failed("删除失败");
}
noticeIds.forEach(id->{
stringRedisTemplate.delete("cache:notice:getNewNotice:"+id);
stringRedisTemplate.delete("cache:notice:getNotice:"+id);
// 遍历公告ID列表根据每个公告ID删除对应的两个缓存数据分别用于获取公告和获取新公告相关的缓存从缓存键命名推测
noticeIds.forEach(id -> {
stringRedisTemplate.delete("cache:notice:getNewNotice:" + id);
stringRedisTemplate.delete("cache:notice:getNotice:" + id);
});
// 返回删除成功的结果信息
return Result.success("删除成功");
}
// 更新公告的方法,事务性操作,成功返回更新成功的提示信息,失败返回相应错误提示
@Override
@Transactional
public Result<String> updateNotice(String id, NoticeForm noticeForm) {
// 创建更新条件
// 创建一个LambdaUpdateWrapper对象用于构建更新公告信息的条件和要更新的字段
LambdaUpdateWrapper<Notice> noticeWrapper = new LambdaUpdateWrapper<>();
// 设置更新条件为公告ID等于传入的id参数
noticeWrapper.eq(Notice::getId, id)
.eq(Notice::getIsDeleted,0)
// 设置更新条件为公告未被删除假设isDeleted字段表示是否删除0表示未删除
.eq(Notice::getIsDeleted, 0)
// 设置要更新的公告内容字段值为传入的noticeForm中的公告内容
.set(Notice::getContent, noticeForm.getContent());
// 更新公告
// 调用noticeMapper的update方法根据上面构建的更新条件和要更新的字段来更新公告数据,返回受影响的行数,即更新操作影响的记录数
int rowsAffected = noticeMapper.update(noticeWrapper);
// 如果更新操作没有影响到任何行,即更新失败,返回修改失败的结果信息
if (rowsAffected == 0) {
return Result.failed("修改失败");
}
// 根据ID获取更新后的公告对象用于后续缓存操作等可能需要获取最新数据来更新缓存等
Notice byId = getById(id);
if (byId.getId() != null) { // 确保ID有效
// 如果是更新操作,先从缓存中移除旧数据,然后重新放入最新的数据
stringRedisTemplate.delete("cache:notice:getNotice:"+byId.getId().toString()); // 删除旧缓存
stringRedisTemplate.delete("cache:notice:getNewNotice:"+byId.getId().toString()); // 删除旧缓存
// 如果获取到的公告对象有有效的ID进行缓存相关操作
if (byId.getId()!= null) { // 确保ID有效
// 如果是更新操作先从缓存中移除旧数据根据公告ID删除对应的缓存数据缓存键格式为"cache:notice:getNotice:" + 公告ID
stringRedisTemplate.delete("cache:notice:getNotice:" + byId.getId().toString());
// 根据公告ID删除对应的另一个缓存数据可能是用于获取新公告相关的缓存从命名推测缓存键格式为"cache:notice:getNewNotice:" + 公告ID
stringRedisTemplate.delete("cache:notice:getNewNotice:" + byId.getId().toString());
// NoticeVO updatedNoticeVO = noticeConverter.NoticeToNoticeVO(byId); // 转换为视图对象
// Map<Integer, NoticeVO> map = Map.of(updatedNoticeVO.getId(), updatedNoticeVO);
// cacheClient.batchPut("cache:notice:getNotice:",map,10L,TimeUnit.MINUTES); // 存储新数据
// cacheClient.batchPut("cache:notice:getNewNotice:",map,10L,TimeUnit.MINUTES); // 存储新数据
}
// 返回更新成功的结果信息
return Result.success("修改成功");
}
// 获取公告分页数据的方法,返回包含分页信息和公告视图对象列表的结果
@Override
public Result<IPage<NoticeVO>> getNotice(Integer pageNum, Integer pageSize, String title) {
// 查询满足条件的总记录数
int total = noticeMapper.countByCondition(SecurityUtil.getUserId(), title); // 假设gradeMapper中实现了根据条件计数的方法
// 计算偏移量
// 调用noticeMapper的countByCondition方法根据当前用户ID和公告标题来统计满足条件的公告总记录数假设noticeMapper中实现了该方法
int total = noticeMapper.countByCondition(SecurityUtil.getUserId(), title);
// 计算分页查询的偏移量,根据当前页码和每页显示数量来计算
int offset = (pageNum - 1) * pageSize;
// 查询分页ID列表
// 调用noticeMapper的selectNoticeIdsPage方法根据当前用户ID、公告标题以及偏移量和每页显示数量来查询分页的公告ID列表假设noticeMapper中实现了该方法
List<Integer> noticeIds = noticeMapper.selectNoticeIdsPage(SecurityUtil.getUserId(), title, offset, pageSize);
// 批量从缓存中获取GradeVO对象
Map<Integer, NoticeVO> cachedGradesMap = cacheClient.batchGet("cache:notice:getNotice:",noticeIds, NoticeVO.class);
// 使用cacheClient的batchGet方法从缓存中批量获取对应公告ID的NoticeVO对象缓存键前缀为"cache:notice:getNotice:"
Map<Integer, NoticeVO> cachedGradesMap = cacheClient.batchGet("cache:notice:getNotice:", noticeIds, NoticeVO.class);
// 确定未命中的ID列表
// 创建一个列表用于存储在缓存中未命中的公告ID即缓存中不存在对应数据的公告ID
List<Integer> missIds = new ArrayList<>();
// 遍历查询到的公告ID列表检查每个ID在缓存中是否存在如果不存在则添加到未命中ID列表中
for (Integer id : noticeIds) {
if (!cachedGradesMap.containsKey(id)) {
missIds.add(id);
}
}
// 如果有未命中的ID从数据库批量查询并更新缓存
// 如果存在未命中缓存的公告ID即missIds列表不为空
if (!missIds.isEmpty()) {
// 调用noticeMapper的batchSelectByIds方法从数据库中批量查询未命中缓存的公告数据并转换为NoticeVO对象列表假设noticeMapper中实现了该方法
List<NoticeVO> missedGrades = noticeMapper.batchSelectByIds(missIds);
// 假设GradeVO的ID为getId()使用Collectors.toMap转换
// 使用Java 8的Stream API将NoticeVO对象列表转换为以公告ID为键NoticeVO对象为值的Map方便后续操作
Map<Integer, NoticeVO> missedGradesMap = missedGrades.stream()
.collect(Collectors.toMap(NoticeVO::getId, Function.identity()));
// 更新缓存
cacheClient.batchPut("cache:notice:getNotice:",missedGradesMap,10L, TimeUnit.MINUTES);
// 合并缓存结果
// 使用cacheClient的batchPut方法将从数据库中查询到的未命中缓存的公告数据更新缓存缓存有效期为10分钟
cacheClient.batchPut("cache:notice:getNotice:", missedGradesMap, 10L, TimeUnit.MINUTES);
// 将从数据库中查询到的未命中缓存的公告数据合并缓存结果的Map中确保缓存数据的完整性
cachedGradesMap.putAll(missedGradesMap);
}
// 根据ID列表从缓存中获取完整的GradeVO对象列表
// 创建一个列表用于存储最终要返回的完整的NoticeVO对象列表初始大小为查询到的公告ID列表的大小
List<NoticeVO> finalResult = new ArrayList<>(noticeIds.size());
// 遍历公告ID列表从缓存结果的Map中获取对应的NoticeVO对象并添加到最终结果列表中
for (Integer id : noticeIds) {
finalResult.add(cachedGradesMap.get(id));
}
// 构建并返回IPage对象
// 创建一个IPage对象用于封装分页信息和最终的公告视图对象列表设置当前页码、每页显示数量以及总记录数
IPage<NoticeVO> resultPage = new Page<>(pageNum, pageSize, Long.valueOf(total));
// 将最终的公告视图对象列表设置到IPage对象中
resultPage.setRecords(finalResult);
// 返回包含分页信息和公告视图对象列表的成功结果信息
return Result.success("查询成功", resultPage);
}
// 获取新公告分页数据的方法返回包含分页信息和公告视图对象列表的结果与getNotice方法逻辑有部分重复可能可进行适当优化抽取公共逻辑
@Override
public Result<IPage<NoticeVO>> getNewNotice(Integer pageNum, Integer pageSize) {
// 创建分页对象
// 创建一个Page对象用于封装分页相关信息设置当前页码和每页显示数量
Page<NoticeVO> page = new Page<>(pageNum, pageSize);
// 学生分页查询公告
page = noticeMapper.selectNewNoticePage(page,SecurityUtil.getUserId());
// 调用noticeMapper的selectNewNoticePage方法传入分页对象和当前用户ID进行学生分页查询公告操作(具体查询逻辑在该方法内部实现,从命名推测是获取新公告相关的分页数据)
page = noticeMapper.selectNewNoticePage(page, SecurityUtil.getUserId());
// return Result.success("查询成功", page);
// 查询满足条件的总记录数
int total = noticeMapper.countByCondition(SecurityUtil.getUserId(), null); // 假设gradeMapper中实现了根据条件计数的方法
// 计算偏移量
// 调用noticeMapper的countByCondition方法根据当前用户ID标题为null可能表示不按标题筛选来统计满足条件的公告总记录数假设noticeMapper中实现了该方法
int total = noticeMapper.countByCondition(SecurityUtil.getUserId(), null);
// 计算分页查询的偏移量,根据当前页码和每页显示数量来计算
int offset = (pageNum - 1) * pageSize;
// 查询分页ID列表
// 调用noticeMapper的selectNewNoticeIdsPage方法根据当前用户ID、偏移量和每页显示数量来查询分页的公告ID列表假设noticeMapper中实现了该方法
List<Integer> noticeIds = noticeMapper.selectNewNoticeIdsPage(SecurityUtil.getUserId(), offset, pageSize);
// 批量从缓存中获取GradeVO对象
Map<Integer, NoticeVO> cachedGradesMap = cacheClient.batchGet("cache:notice:getNewNotice:",noticeIds, NoticeVO.class);
// 使用cacheClient的batchGet方法从缓存中批量获取对应公告ID的NoticeVO对象缓存键前缀为"cache:notice:getNewNotice:"
Map<Integer, NoticeVO> cachedGradesMap = cacheClient.batchGet("cache:notice:getNewNotice:", noticeIds, NoticeVO.class);
// 确定未命中的ID列表
// 创建一个列表用于存储在缓存中未命中的公告ID即缓存中不存在对应数据的公告ID
List<Integer> missIds = new ArrayList<>();
// 遍历查询到的公告ID列表检查每个ID在缓存中是否存在如果不存在则添加到未命中ID列表中
for (Integer id : noticeIds) {
// 遍历noticeIds列表noticeIds可能是存储了通知Notice相关记录的ID集合这里每次循环取出一个通知的ID用于后续的缓存判断等操作。
if (!cachedGradesMap.containsKey(id)) {
// 判断cachedGradesMap推测是一个缓存中存储的已查询到的通知对象的映射键为通知ID值为对应的通知对象这里从命名来看可能是缓存了NoticeVO类型的对象但需结合上下文确定中是否包含当前遍历到的通知ID如果不包含表示该通知ID对应的通知对象在缓存中未找到缓存未命中
missIds.add(id);
// 将未在缓存中找到的通知ID添加到missIds列表中missIds用于收集所有缓存未命中的通知ID后续会根据这些ID从数据库中查询相应的通知信息并更新缓存。
}
}
// 如果有未命中的ID从数据库批量查询并更新缓存
// 如果有未命中的ID从数据库批量查询并更新缓存
if (!missIds.isEmpty()) {
// 判断missIds列表是否为空即是否存在缓存未命中的通知ID如果不为空表示有需要从数据库查询并更新缓存的数据执行以下操作。
List<NoticeVO> missedGrades = noticeMapper.batchSelectByIds(missIds);
// 调用noticeMapper可能是操作通知数据的持久层接口用于与数据库交互的batchSelectByIds方法传入收集到的缓存未命中的通知ID列表missIds从数据库中批量查询对应的通知信息返回的是NoticeVO类型的列表每个NoticeVO对象代表一条通知记录的相关信息存储在missedGrades变量中。
// 假设GradeVO的ID为getId()使用Collectors.toMap转换
Map<Integer, NoticeVO> missedGradesMap = missedGrades.stream()
.collect(Collectors.toMap(NoticeVO::getId, Function.identity()));
// 使用Java 8的Stream API对查询到的通知信息列表missedGrades进行处理将其转换为一个以通知ID为键通知对象NoticeVO为值的Map。Collectors.toMap方法接收两个参数第一个参数NoticeVO::getId表示使用NoticeVO对象的getId方法获取的返回值即通知ID作为键第二个参数Function.identity()表示直接使用原NoticeVO对象作为值这样就构建了方便后续操作的映射关系存储在missedGradesMap变量中。
// 更新缓存
cacheClient.batchPut("cache:notice:getNotice:",missedGradesMap,10L, TimeUnit.MINUTES);
cacheClient.batchPut("cache:notice:getNotice:", missedGradesMap, 10L, TimeUnit.MINUTES);
// 调用cacheClient可能是用于操作缓存的客户端工具类的batchPut方法将构建好的包含未命中通知对象的映射missedGradesMap存储到缓存中缓存的键前缀为 "cache:notice:getNotice:"同时设置缓存的过期时间为10分钟时间单位为分钟TimeUnit.MINUTES这样可以在一定时间内缓存这些通知信息提高后续查询效率。
// 合并缓存结果
cachedGradesMap.putAll(missedGradesMap);
// 将新查询到并存储到missedGradesMap中的通知对象映射合并到cachedGradesMap中使得cachedGradesMap包含了最新的缓存数据无论是之前已存在的缓存数据还是本次新查询并更新的缓存数据保证缓存数据的完整性。
}
// 根据ID列表从缓存中获取完整的GradeVO对象列表
// 根据ID列表从缓存中获取完整的GradeVO对象列表
List<NoticeVO> finalResult = new ArrayList<>(noticeIds.size());
// 创建一个ArrayList对象用于存储最终要返回的通知对象列表初始容量设置为noticeIds的大小这样可以在一定程度上避免列表扩容带来的性能开销提高性能虽然ArrayList会自动扩容但预先指定合适容量可以优化性能
for (Integer id : noticeIds) {
finalResult.add(cachedGradesMap.get(id));
// 再次遍历noticeIds列表从合并后的缓存映射cachedGradesMap中获取每个通知ID对应的通知对象NoticeVO并添加到finalResult列表中这样finalResult就包含了根据传入的通知ID列表从缓存中获取到的完整的通知对象集合。
}
// 构建并返回IPage对象
// 构建并返回IPage对象
IPage<NoticeVO> resultPage = new Page<>(pageNum, pageSize, Long.valueOf(total));
// 创建一个IPage类型的分页对象resultPage使用Page的构造函数进行初始化传入当前页码pageNum、每页显示数量pageSize以及总记录数total这里将total转换为Long类型传入具体total的来源和含义需结合上下文确定用于构建分页相关的数据结构方便前端进行分页展示等操作。
resultPage.setRecords(finalResult);
// 将包含通知对象的finalResult列表设置为分页对象resultPage的记录列表即将查询到的通知数据填充到分页对象中使其符合分页展示的要求包含了分页信息以及具体的数据记录。
return Result.success("查询成功", resultPage);
}
}
// 返回一个表示操作成功的Result结果对象其中包含提示信息“查询成功”以及构建好的分页对象resultPage方便前端等调用者获取查询结果并根据分页信息进行展示和后续处理具体Result对象的结构和使用方式需结合项目中相关定义来看。

@ -1,20 +1,29 @@
package cn.org.alan.exam.service.impl;
// 导入OptionMapper接口它是用于操作题目选项相关数据的持久层接口例如对选项数据进行增删改查等操作从命名推测其功能
import cn.org.alan.exam.mapper.OptionMapper;
// 导入Option实体类代表题目选项相关的实体对象包含了选项的各种属性信息如选项内容、是否正确等
import cn.org.alan.exam.model.entity.Option;
// 导入IOptionService接口该接口定义了题目选项相关的业务服务方法规定了具体有哪些业务操作需要实现
import cn.org.alan.exam.service.IOptionService;
// 导入ServiceImpl类这是MyBatis Plus提供的基础服务实现类它提供了很多通用的数据库操作方法方便在此基础上扩展具体业务逻辑的实现
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入Spring的Service注解用于标记该类是一个服务层组件Spring容器会对其进行管理使其能够参与依赖注入等相关机制
import org.springframework.stereotype.Service;
/**
* <p>
*
*
* </p>
*
* @author WeiJin
* @since 2024-03-21
*/
// 使用@Service注解将该类标记为Spring容器管理的服务层组件意味着在Spring应用中可以通过依赖注入的方式在其他地方使用这个服务类
@Service
// 定义OptionServiceImpl类它继承自ServiceImpl<OptionMapper, Option>同时实现了IOptionService接口
// 通过继承ServiceImpl可以复用其中已有的通用数据库操作方法再按照IOptionService接口中定义的业务方法来实现具体针对题目选项的业务逻辑
// 目前类体为空,表示暂时没有添加额外的业务方法实现,如果后续有添加选项、删除选项、查询选项等业务逻辑相关的方法,就需要在这里进行具体的编写
public class OptionServiceImpl extends ServiceImpl<OptionMapper, Option> implements IOptionService {
}
}

@ -38,7 +38,7 @@ import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
*
*
* @author WeiJin
* @since 2024-03-21
@ -46,215 +46,292 @@ import java.util.stream.Collectors;
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements IQuestionService {
// 注入QuestionConverter用于不同题目相关对象之间的转换比如表单对象和实体对象等
@Resource
private QuestionConverter questionConverter;
// 注入QuestionMapper用于操作题目数据的持久层接口例如插入、查询、更新、删除题目等操作
@Resource
private QuestionMapper questionMapper;
// 注入OptionMapper用于操作题目选项数据的持久层接口像插入、更新、删除题目选项等操作与题目相关联
@Resource
private OptionMapper optionMapper;
// 注入AliOSSUtil可能是用于与阿里云对象存储服务OSS交互的工具类比如上传图片等操作从命名推测
@Resource
private AliOSSUtil aliOSSUtil;
// 注入ExerciseRecordMapper可能用于操作练习记录相关数据的持久层接口推测与用户做题目练习的记录有关
@Resource
private ExerciseRecordMapper exerciseRecordMapper;
// 注入CacheClient自定义的缓存客户端用于更方便地进行缓存操作如批量获取、批量存入缓存等
@Resource
private CacheClient cacheClient;
// 注入StringRedisTemplate用于操作Redis字符串类型数据常用于缓存相关操作比如删除缓存等
@Resource
private StringRedisTemplate stringRedisTemplate;
// 添加单个题目(可能是单选题、多选题、判断题、简答题等不同类型)的方法,事务性操作,成功返回添加成功的提示信息,失败返回相应错误提示
@Override
@Transactional
public Result<String> addSingleQuestion(QuestionFrom questionFrom) {
// 入参校验
// 入参校验,获取题目表单中的选项列表
List<Option> options = questionFrom.getOptions();
// 判断题目类型不是简答题假设题目类型4代表简答题且选项为空或者选项数量小于2时不符合要求返回错误提示
if (questionFrom.getQuType() != 4 && (Objects.isNull(options) || options.size() < 2)) {
return Result.failed("非简答题的试题选项不能少于两个");
}
// 将QuestionFrom对象转换为Question实体对象方便后续持久化操作使用QuestionConverter进行转换
Question question = questionConverter.fromToEntity(questionFrom);
// 调用questionMapper的insert方法将题目数据插入到数据库中
questionMapper.insert(question);
if (question.getQuType() == 4) {
// 简答题添加选项
// 如果题目类型是简答题
if (question.getQuType() == 4) {
// 简答题添加选项,获取题目表单中第一个选项(简答题通常可能只有一个用于填写答案的选项,这里从代码逻辑推测)
Option option = questionFrom.getOptions().get(0);
// 设置该选项所属题目的ID为刚插入题目的ID建立关联
option.setQuId(question.getId());
// 调用optionMapper的insert方法将选项数据插入到数据库中
optionMapper.insert(option);
} else {
// 非简答题添加选项
// 把新建试题获取的id填入选项中
// 遍历题目表单中的所有选项将每个选项所属题目的ID设置为刚插入题目的ID建立关联
options.forEach(option -> {
option.setQuId(question.getId());
});
// 调用optionMapper的insertBatch方法批量将选项数据插入到数据库中
optionMapper.insertBatch(options);
}
// 如果题目插入成功题目数据有了有效的ID这里假设插入后会自动生成ID进行缓存相关操作
if (question.getId() != null) { // 确保ID有效
// 如果是更新操作,先从缓存中移除旧数据,然后重新放入最新的数据
stringRedisTemplate.delete("cache:question:pagingQuestion:"+question.getId().toString()); // 删除旧缓存
//
// 如果是更新操作(此处逻辑可能不太准确,实际上是添加操作,但代码结构类似更新时的缓存处理逻辑),先从缓存中移除旧数据
// 根据题目ID删除对应的缓存数据缓存键格式为"cache:question:pagingQuestion:" + 题目ID
stringRedisTemplate.delete("cache:question:pagingQuestion:" + question.getId().toString());
// QuestionVO questionVO = questionConverter.QuestionToQuestionVO(question); // 转换为视图对象
// Map<Integer, QuestionVO> map = Map.of(questionVO.getId(), questionVO);
// cacheClient.batchPut("cache:question:pagingQuestion:",map,10L,TimeUnit.MINUTES); // 存储新数据
}
// 返回添加成功的结果信息
return Result.success("添加成功");
}
// 批量删除题目根据题目ID列表的方法事务性操作成功返回删除成功的提示信息失败返回相应错误提示
@Override
@Transactional
public Result<String> deleteBatchByIds(String ids) {
// 将传入的以逗号分隔的字符串形式的题目ID列表转换为Integer类型的列表使用Java 8的Stream API进行转换
List<Integer> list = Arrays.stream(ids.split(",")).map(Integer::parseInt).toList();
//删除用户刷题记录表
// 删除用户刷题记录表中与要删除题目相关的记录创建一个LambdaUpdateWrapper对象用于构建删除条件
LambdaUpdateWrapper<ExerciseRecord> updateWrapper =
new LambdaUpdateWrapper<ExerciseRecord>().in(ExerciseRecord::getQuestionId, list);
// 调用exerciseRecordMapper的delete方法根据构建的条件删除相关记录返回受影响的行数
int delete = exerciseRecordMapper.delete(updateWrapper);
// 先删除选项
// 先删除题目对应的选项调用optionMapper的deleteBatchByQuIds方法传入题目ID列表批量删除选项假设该方法实现了批量删除逻辑
optionMapper.deleteBatchByQuIds(list);
// 再删除试题
// 再删除题目调用questionMapper的deleteBatchIdsQu方法传入题目ID列表删除对应的题目假设该方法实现了批量删除逻辑
questionMapper.deleteBatchIdsQu(list);
list.forEach(id->{
stringRedisTemplate.delete("cache:question:pagingQuestion:"+id);
// 遍历题目ID列表根据每个题目ID删除对应的缓存数据缓存键格式为"cache:question:pagingQuestion:" + 题目ID
list.forEach(id -> {
stringRedisTemplate.delete("cache:question:pagingQuestion:" + id);
});
// 返回删除成功的结果信息
return Result.success("删除成功");
}
// 获取题目分页数据的方法根据页码、每页数量、题目标题、题目类型以及题库ID等条件返回包含分页信息和题目视图对象列表的结果
@Override
public Result<IPage<QuestionVO>> pagingQuestion(Integer pageNum, Integer pageSize, String title, Integer type, Integer repoId) {
// 初始化用户ID变量默认为null后续根据用户角色进行赋值
Integer userId = null;
// 如果当前用户角色为"role_teacher"可能代表教师角色具体根据业务定义则将用户ID设置为当前登录用户的ID
if ("role_teacher".equals(SecurityUtil.getRole())) {
userId = SecurityUtil.getUserId();
} else {
// 否则设置为0这里0可能代表其他情况比如游客或者无特定用户身份等具体看业务逻辑
userId = 0;
}
// 查询满足条件的总记录数
int total = questionMapper.countByCondition(userId, title,type,repoId); // 假设gradeMapper中实现了根据条件计数的方法
// 计算偏移量
// 调用questionMapper的countByCondition方法根据用户ID、题目标题、题目类型以及题库ID来统计满足条件的题目总记录数假设questionMapper中实现了该方法
int total = questionMapper.countByCondition(userId, title, type, repoId);
// 计算分页查询的偏移量,根据当前页码和每页显示数量来计算
int offset = (pageNum - 1) * pageSize;
// 查询分页ID列表
List<Integer> quIds = questionMapper.selectQuestionIdsPage(userId, title,type,repoId, offset, pageSize);
// 调用questionMapper的selectQuestionIdsPage方法根据用户ID、题目标题、题目类型、题库ID以及偏移量和每页显示数量来查询分页的题目ID列表假设questionMapper中实现了该方法
List<Integer> quIds = questionMapper.selectQuestionIdsPage(userId, title, type, repoId, offset, pageSize);
// 批量从缓存中获取GradeVO对象
Map<Integer, QuestionVO> cachedQuestionsMap = cacheClient.batchGet("cache:question:pagingQuestion:",quIds, QuestionVO.class);
// 使用cacheClient的batchGet方法从缓存中批量获取对应题目ID的QuestionVO对象缓存键前缀为"cache:question:pagingQuestion:"
Map<Integer, QuestionVO> cachedQuestionsMap = cacheClient.batchGet("cache:question:pagingQuestion:", quIds, QuestionVO.class);
// 确定未命中的ID列表
// 创建一个列表用于存储在缓存中未命中的题目ID即缓存中不存在对应数据的题目ID
List<Integer> missIds = new ArrayList<>();
// 遍历查询到的题目ID列表检查每个ID在缓存中是否存在如果不存在则添加到未命中ID列表中
for (Integer id : quIds) {
if (!cachedQuestionsMap.containsKey(id)) {
missIds.add(id);
}
}
// 如果有未命中的ID从数据库批量查询并更新缓存
// 如果存在未命中缓存的题目ID即missIds列表不为空
if (!missIds.isEmpty()) {
// 调用questionMapper的batchSelectByIds方法从数据库中批量查询未命中缓存的题目数据并转换为QuestionVO对象列表假设questionMapper中实现了该方法
List<QuestionVO> missedGrades = questionMapper.batchSelectByIds(missIds);
// 假设GradeVO的ID为getId()使用Collectors.toMap转换
// 使用Java 8的Stream API将QuestionVO对象列表转换为以题目ID为键QuestionVO对象为值的Map方便后续操作
Map<Integer, QuestionVO> missedGradesMap = missedGrades.stream()
.collect(Collectors.toMap(QuestionVO::getId, Function.identity()));
// 更新缓存
cacheClient.batchPut("cache:question:pagingQuestion:",missedGradesMap,10L, TimeUnit.MINUTES);
// 合并缓存结果
// 使用cacheClient的batchPut方法将从数据库中查询到的未命中缓存的题目数据更新缓存缓存有效期为10分钟
cacheClient.batchPut("cache:question:pagingQuestion:", missedGradesMap, 10L, TimeUnit.MINUTES);
// 将从数据库中查询到的未命中缓存的题目数据合并缓存结果的Map中确保缓存数据的完整性
cachedQuestionsMap.putAll(missedGradesMap);
}
// 根据ID列表从缓存中获取完整的GradeVO对象列表
// 创建一个列表用于存储最终要返回的完整的QuestionVO对象列表初始大小为查询到的题目ID列表的大小
List<QuestionVO> finalResult = new ArrayList<>(quIds.size());
// 遍历题目ID列表从缓存结果的Map中获取对应的QuestionVO对象并添加到最终结果列表中
for (Integer id : quIds) {
finalResult.add(cachedQuestionsMap.get(id));
}
// 构建并返回IPage对象
// 创建一个IPage对象用于封装分页信息和最终的题目视图对象列表设置当前页码、每页显示数量以及总记录数
IPage<QuestionVO> resultPage = new Page<>(pageNum, pageSize, Long.valueOf(total));
// 将最终的题目视图对象列表设置到IPage对象中
resultPage.setRecords(finalResult);
// 返回包含分页信息和题目视图对象列表的成功结果信息这里第一个参数为null可能是预留用于传递其他提示信息等情况
return Result.success(null, resultPage);
}
// 查询单个题目的方法根据题目ID查询并返回对应的题目视图对象返回包含题目视图对象的结果信息
@Override
public Result<QuestionVO> querySingle(Integer id) {
// 调用questionMapper的selectSingle方法传入题目ID查询对应的题目数据并将其包装在成功结果信息中返回
return Result.success(null, questionMapper.selectSingle(id));
}
// 更新题目的方法,事务性操作,成功返回更新成功的提示信息,失败返回相应错误提示
@Override
@Transactional
public Result<String> updateQuestion(QuestionFrom questionFrom) {
// 修改试题
// 修改试题将QuestionFrom对象转换为Question实体对象方便后续更新操作使用QuestionConverter进行转换
Question question = questionConverter.fromToEntity(questionFrom);
// 调用questionMapper的updateById方法根据题目实体对象的ID更新对应的题目数据假设ID是唯一标识且不为空
questionMapper.updateById(question);
// 修改选项
// 修改选项,获取题目表单中的选项列表
List<Option> options = questionFrom.getOptions();
// 遍历题目表单中的每个选项调用optionMapper的updateById方法根据选项实体对象的ID更新对应的选项数据假设ID是唯一标识且不为空
for (Option option : options) {
optionMapper.updateById(option);
}
// 如果题目更新成功题目数据有了有效的ID这里假设更新前后ID不变且不为空进行缓存相关操作
if (question.getId() != null) { // 确保ID有效
// 如果是更新操作,先从缓存中移除旧数据,然后重新放入最新的数据
stringRedisTemplate.delete("cache:question:pagingQuestion:"+question.getId().toString()); // 删除旧缓存
// 如果是更新操作,先从缓存中移除旧数据,根据题目ID删除对应的缓存数据缓存键格式为"cache:question:pagingQuestion:" + 题目ID
stringRedisTemplate.delete("cache:question:pagingQuestion:" + question.getId().toString());
// QuestionVO questionVO = questionConverter.QuestionToQuestionVO(question); // 转换为视图对象
// Map<Integer, QuestionVO> map = Map.of(questionVO.getId(), questionVO);
// cacheClient.batchPut("cache:question:pagingQuestion:",map,10L,TimeUnit.MINUTES); // 存储新数据
}
// 返回更新成功的结果信息
return Result.success("修改成功");
}
// 导入题目从Excel文件导入的方法事务性操作成功返回导入成功的提示信息失败返回相应错误提示使用了异常处理注解来简化异常抛出处理
@SneakyThrows(Exception.class)
@Override
@Transactional
public Result<String> importQuestion(Integer id, MultipartFile file) {
// 检查传入的文件是否是合法的Excel文件调用ExcelUtils工具类的isExcel方法进行判断
if (!ExcelUtils.isExcel(Objects.requireNonNull(file.getOriginalFilename()))) {
return Result.failed("该文件不是一个合法的Excel文件");
}
// 调用ExcelUtils的readMultipartFile方法读取传入的MultipartFile类型的Excel文件并转换为QuestionExcelFrom对象列表假设该方法实现了读取并转换逻辑
List<QuestionExcelFrom> questionExcelFroms = ExcelUtils.readMultipartFile(file, QuestionExcelFrom.class);
// 类型转换
// 进行类型转换将QuestionExcelFrom对象列表转换为QuestionFrom对象列表调用QuestionExcelFrom类中的静态方法进行转换假设该方法实现了转换逻辑
List<QuestionFrom> list = QuestionExcelFrom.converterQuestionFrom(questionExcelFroms);
// 遍历转换后的QuestionFrom对象列表处理每个题目表单对象对应的题目及选项数据插入逻辑
for (QuestionFrom questionFrom : list) {
// 遍历传入的QuestionFrom类型的列表QuestionFrom可能是用于接收前端传递过来的题目相关数据的表单对象从命名推测其功能每次循环处理一个题目相关的数据对象。
Question question = questionConverter.fromToEntity(questionFrom);
// 使用questionConverter可能是一个用于对象转换的工具类将QuestionFrom对象转换为Question实体对象方便后续持久化操作Question实体对象应该是对应数据库中存储题目信息的实体类实例包含了如题目内容、类型等各种属性。
question.setRepoId(id);
// 将题目实体对象的RepoId属性设置为传入的id值推测RepoId可能是用于关联题目所属的题库ID等相关信息具体含义需结合业务逻辑确定
// 添加单题获取Id
questionMapper.insert(question);
// 调用questionMapper可能是操作题目数据的持久层接口用于与数据库交互的insert方法将转换后的题目实体对象插入到数据库中插入成功后数据库会为该题目记录分配一个唯一的ID通常是自增长的主键并且这个ID会回填到question对象中假设使用了相应的数据库框架支持此功能
// 批量添加选项
List<Option> options = questionFrom.getOptions();
// 从QuestionFrom对象中获取其包含的选项列表Option对象应该是代表题目选项相关信息的实体类每个Option实例存储了一个选项的具体内容、是否正确等属性这里获取到的是当前题目对应的所有选项信息列表。
final int[] count = {0};
// 创建一个长度为1的整数数组用于记录选项的序号初始值设为0通过数组的方式可以在Lambda表达式中修改其值类似一种简单的引用传递效果因为Java的Lambda表达式中修改外部局部变量有限制
options.forEach(option -> {
//简答题答案默认给正确
// 简答题答案默认给正确
if (question.getQuType() == 4) {
// 判断当前题目question对象的题目类型QuType属性是否为4推测4代表简答题类型具体需结合业务定义如果是简答题类型则将当前选项的IsRight属性设置为1表示该选项为正确答案对于简答题可能默认只有一个正确答案的设定
option.setIsRight(1);
}
option.setSort(++count[0]);
// 为当前选项设置序号,通过自增操作(++count[0]给选项的Sort属性赋值使其按照顺序依次递增用于表示选项的排列顺序方便后续展示等操作。
option.setQuId(question.getId());
// 将当前选项的QuId属性设置为当前题目的ID即前面插入题目记录后回填到question对象中的ID值建立选项与题目的关联关系表明该选项属于当前这个题目。
});
// 避免简答题没有答案
if (!options.isEmpty()) {
// 判断选项列表是否为空,即确保当前题目有选项信息(虽然简答题可能默认有一个答案选项,但也需要排除没有任何选项数据的情况),如果不为空,则执行以下批量插入选项的操作。
optionMapper.insertBatch(options);
// 调用optionMapper可能是操作选项数据的持久层接口的insertBatch方法将整理好的选项列表批量插入到数据库中实现一次插入多个选项记录的操作提高插入效率。
}
if (question.getId() != null) { // 确保ID有效
// 如果题目实体对象的ID不为空即前面插入题目操作成功获取到了有效的ID执行以下缓存相关的操作可能是为了保证数据一致性更新缓存中的题目数据
// 如果是更新操作,先从缓存中移除旧数据,然后重新放入最新的数据
stringRedisTemplate.delete("cache:question:pagingQuestion:"+question.getId().toString()); // 删除旧缓存
stringRedisTemplate.delete("cache:question:pagingQuestion:" + question.getId().toString()); // 删除旧缓存
// 调用stringRedisTemplate用于操作Redis字符串类型数据的工具类常用于缓存相关操作的delete方法根据构建的缓存键格式为 "cache:question:pagingQuestion:" 加上题目ID的字符串形式删除对应的缓存数据目的是移除旧的题目缓存信息避免缓存数据不一致。
// QuestionVO questionVO = questionConverter.QuestionToQuestionVO(question); // 转换为视图对象
// 注释掉的这行代码原本可能是使用questionConverter前面用于对象转换的工具类将Question实体对象转换为QuestionVO对象推测是用于展示给前端的视图对象包含了前端需要展示的题目相关信息但当前代码中这行被注释掉了可能暂不需要此转换操作或者后续会有其他处理方式。
// Map<Integer, QuestionVO> map = Map.of(questionVO.getId(), questionVO);
// cacheClient.batchPut("cache:question:pagingQuestion:",map,10L,TimeUnit.MINUTES); // 存储新数据
// 注释掉的这行代码原本可能是创建一个只包含当前题目视图对象questionVO的Map以题目ID为键题目视图对象为值可能用于后续缓存存储等操作但同样被注释掉了可能暂不执行此逻辑。
// cacheClient.batchPut("cache:question:pagingQuestion:", map, 10L, TimeUnit.MINUTES); // 存储新数据
// 注释掉的这行代码原本可能是使用cacheClient可能是缓存操作相关的客户端工具类的batchPut方法将包含最新题目视图对象的Map数据存储到缓存中设置缓存过期时间为10分钟以实现缓存更新保证下次获取题目数据时能获取到最新的信息但目前代码中此操作被注释掉了暂不执行缓存存储新数据的逻辑。
}
}
return Result.success("导入成功");
}
// 当遍历完所有题目相关的数据对象并完成相应的插入、关联以及缓存相关操作如果有的话返回一个表示操作成功的Result结果对象其中包含提示信息“导入成功”用于告知调用者题目数据导入操作已顺利完成具体Result对象的结构和使用方式需结合项目中相关定义来看。
@SneakyThrows(IOException.class)
@Override
public Result<String> uploadImage(MultipartFile file) {
if (!aliOSSUtil.isImage(Objects.requireNonNull(file.getOriginalFilename()))) {
return Result.failed("该文件不是常用图片格式(png、jpg、jpeg、bmp)");
}
if (aliOSSUtil.isOverSize(file)) {
return Result.failed("图片大小不能超过50KB");
}
String url = aliOSSUtil.upload(file);
if (StringUtils.isBlank(url)) {
return Result.failed("图片上传失败");
@SneakyThrows(IOException.class)
// 使用 @SneakyThrows注解用于简化异常处理当方法内部抛出IOException异常时会自动将异常向上抛出避免了显式编写try-catch块来处理异常但需要注意在合适的调用层次上进行统一的异常处理这里表示该方法可能会抛出输入输出相关的异常比如文件读取等操作可能出现的异常
@Override
public Result<String> uploadImage (MultipartFile file){
// 实现接口中定义的上传图片方法接收一个MultipartFile类型的文件对象用于上传图片文件该方法的功能是将传入的图片文件上传到指定位置可能是云存储等地方并返回相应的结果信息。
if (!aliOSSUtil.isImage(Objects.requireNonNull(file.getOriginalFilename()))) {
// 调用aliOSSUtil可能是用于与阿里云对象存储服务交互或者进行文件类型判断等相关操作的工具类的isImage方法传入文件的原始文件名通过Objects.requireNonNull确保文件名不为空判断该文件是否是图片格式如果不是图片格式则执行以下返回操作。
return Result.failed("该文件不是常用图片格式(png、jpg、jpeg、bmp)");
// 返回一个表示操作失败的Result结果对象包含提示信息“该文件不是常用图片格式(png、jpg、jpeg、bmp)”告知调用者上传的文件不符合图片格式要求具体Result对象的结构和使用方式需结合项目中相关定义来看。
}
if (aliOSSUtil.isOverSize(file)) {
// 调用aliOSSUtil的isOverSize方法传入文件对象判断该文件大小是否超过了规定的限制从方法名推测是用于判断文件大小是否超出限制的功能如果文件大小超过限制则执行以下返回操作。
return Result.failed("图片大小不能超过50KB");
// 返回一个表示操作失败的Result结果对象包含提示信息“图片大小不能超过50KB”告知调用者上传的图片文件大小不符合要求具体Result对象的结构和使用方式需结合项目中相关定义来看。
}
String url = aliOSSUtil.upload(file);
// 调用aliOSSUtil的upload方法传入文件对象将图片文件上传到指定位置比如阿里云对象存储等地方并返回上传后文件的访问地址如果上传成功存储在url变量中。
if (StringUtils.isBlank(url)) {
// 判断返回的文件访问地址是否为空字符串,如果为空,表示图片上传失败,执行以下返回操作。
return Result.failed("图片上传失败");
// 返回一个表示操作失败的Result结果对象包含提示信息“图片上传失败”告知调用者图片上传过程出现问题具体Result对象的结构和使用方式需结合项目中相关定义来看。
}
return Result.success("图片上传成功", url);
// 如果文件访问地址不为空说明图片上传成功返回一个表示操作成功的Result结果对象包含提示信息“图片上传成功”以及上传后文件的访问地址通过第二个参数传递方便调用者获取图片的访问路径进行后续操作具体Result对象的结构和使用方式需结合项目中相关定义来看。
}
return Result.success("图片上传成功", url);
}
}
}

@ -30,95 +30,128 @@ import java.util.List;
/**
* @author WeiJin
* @since 2024-03-21
* IRepoServiceRepo
*/
@Service
public class RepoServiceImpl extends ServiceImpl<RepoMapper, Repo> implements IRepoService {
// 注入RepoMapper用于操作题库数据的持久层接口比如插入、更新、查询、删除题库记录等操作。
@Resource
private RepoMapper repoMapper;
// 注入QuestionMapper用于操作题目数据的持久层接口可能在涉及题库与题目关联的业务逻辑中使用比如更新题目所属题库等操作。
@Resource
private QuestionMapper questionMapper;
// 注入ExerciseRecordMapper用于操作练习记录数据的持久层接口或许在获取题库相关统计信息如刷题次数等时会用到。
@Resource
private ExerciseRecordMapper exerciseRecordMapper;
// 添加题库的方法将传入的Repo对象插入到数据库中成功返回保存成功的提示信息。
@Override
public Result<String> addRepo(Repo repo) {
// 调用repoMapper的insert方法将传入的Repo对象插入到数据库中执行插入操作。
repoMapper.insert(repo);
// 返回表示保存成功的结果信息,通常可以在前端等地方展示给用户相应提示。
return Result.success("保存成功");
}
// 更新题库信息的方法根据传入的Repo对象和要更新的题库ID更新对应题库的标题信息成功返回修改成功的提示信息。
@Override
public Result<String> updateRepo(Repo repo, Integer id) {
//修改题库
// 创建一个LambdaUpdateWrapper对象用于构建更新题库信息的条件和要更新的字段。
LambdaUpdateWrapper<Repo> updateWrapper = new LambdaUpdateWrapper<Repo>()
.eq(Repo::getId, id).set(Repo::getTitle, repo.getTitle());
// 设置更新条件为题库的ID等于传入的id参数确保更新的是指定的题库记录。
.eq(Repo::getId, id)
// 设置要更新的字段为题库的标题将其更新为传入Repo对象中的标题值。
.set(Repo::getTitle, repo.getTitle());
// 调用repoMapper的update方法根据构建好的更新条件和要更新的字段来更新数据库中的题库记录。
repoMapper.update(updateWrapper);
// 返回表示修改成功的结果信息,方便前端等地方知晓操作结果并展示相应提示给用户。
return Result.success("修改成功");
}
// 删除题库的方法事务性操作先清空题库内所有题目所属的题库ID然后将该题库标记为已删除逻辑删除根据操作结果返回相应提示信息。
@Override
@Transactional
public Result<String> deleteRepoById(Integer id) {
//题库内试题清空所属题库id
// 清空题库内试题所属的题库ID创建一个LambdaUpdateWrapper对象用于构建更新题目信息的条件和要更新的字段。
LambdaUpdateWrapper<Question> wrapper = new LambdaUpdateWrapper<Question>()
// 设置更新条件为题目所属的题库ID等于传入的要删除的题库ID。
.eq(Question::getRepoId, id)
// 设置要更新的字段为题目所属的题库ID将其更新为null表示清空所属题库关系。
.set(Question::getRepoId, null);
// 调用questionMapper的update方法根据构建的条件更新数据库中相关题目的所属题库ID字段。
questionMapper.update(wrapper);
//删除题库
// 创建一个LambdaUpdateWrapper对象用于构建删除题库的逻辑这里采用逻辑删除设置删除标记字段
LambdaUpdateWrapper<Repo> repoLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
repoLambdaUpdateWrapper.eq(Repo::getId,id)
.set(Repo::getIsDeleted,1);
// 设置更新条件为题库的ID等于传入的要删除的题库ID。
repoLambdaUpdateWrapper.eq(Repo::getId, id)
// 设置题库的删除标记字段假设名为isDeleted为1表示已删除。
.set(Repo::getIsDeleted, 1);
// 调用repoMapper的update方法根据构建的条件执行逻辑删除操作返回受影响的行数即实际更新的记录数。
int result = repoMapper.update(repoLambdaUpdateWrapper);
// 如果更新操作影响的行数大于0表示删除操作成功返回删除成功的结果信息。
if (result > 0) {
return Result.success("删除成功");
}
// 如果更新操作影响的行数为0表示删除操作失败返回删除失败的结果信息。
return Result.failed("删除失败");
}
// 获取题库列表的方法,根据当前用户角色(教师或其他角色)以及传入的题库标题模糊查询条件,从数据库中获取相应的题库列表信息,返回包含查询结果的成功提示信息。
@Override
public Result<List<RepoListVO>> getRepoList(String repoTitle) {
List<RepoListVO> list;
// 判断当前用户角色是否为"role_teacher"(通常代表教师角色,具体含义由业务定义)。
if ("role_teacher".equals(SecurityUtil.getRole())) {
// 如果是教师角色调用repoMapper的selectRepoList方法传入题库标题和当前用户ID获取该教师创建的符合标题模糊查询条件的题库列表。
list = repoMapper.selectRepoList(repoTitle, SecurityUtil.getUserId());
} else {
// 如果不是教师角色可能是其他角色比如管理员等调用repoMapper的selectRepoList方法传入题库标题和默认值0具体含义由业务决定可能表示获取所有或公共的题库等情况获取相应的题库列表。
list = repoMapper.selectRepoList(repoTitle, 0);
}
// 返回包含查询到的题库列表信息的成功结果信息,方便前端展示给用户查看。
return Result.success("获取成功", list);
}
// 获取题库分页信息的方法,根据当前用户角色(教师或其他角色)、页码、每页数量以及传入的题库标题模糊查询条件,从数据库中获取相应的分页题库信息,返回包含分页结果的成功提示信息。
@Override
public Result<IPage<RepoVO>> pagingRepo(Integer pageNum, Integer pageSize, String title) {
// 创建一个IPage对象用于封装分页相关信息设置当前页码和每页显示数量。
IPage<RepoVO> page = new Page<>(pageNum, pageSize);
// 判断当前用户角色是否为"role_teacher"(通常代表教师角色,具体含义由业务定义)。
if ("role_teacher".equals(SecurityUtil.getRole())) {
//教师只查询自己的题库
// 如果是教师角色调用repoMapper的pagingRepo方法传入分页对象、题库标题和当前用户ID获取该教师创建的符合标题模糊查询条件的分页题库信息将结果赋值给page对象。
page = repoMapper.pagingRepo(page, title, SecurityUtil.getUserId());
} else {
//管理员可以获取所有题库
// 如果不是教师角色可能是其他角色比如管理员等调用repoMapper的pagingRepo方法传入分页对象、题库标题和默认值0具体含义由业务决定可能表示获取所有或公共的题库等情况获取相应的分页题库信息将结果赋值给page对象。
page = repoMapper.pagingRepo(page, title, 0);
}
// 返回包含分页题库信息的成功结果信息方便前端进行分页展示等操作这里第一个参数为null可能是预留用于传递其他提示信息等情况。
return Result.success(null, page);
}
// 获取包含练习相关信息的题库信息的方法,根据页码、每页数量以及传入的题库标题模糊查询条件,从数据库中获取相应的分页题库信息,并可以进一步填充每个题库的刷题次数等相关练习信息(当前代码中填充刷题次数的逻辑被注释掉了),返回包含分页结果的成功提示信息。
@Override
public Result<IPage<ExerciseRepoVO>> getRepo(Integer pageNum, Integer pageSize, String title) {
// 创建一个IPage对象用于封装分页相关信息设置当前页码和每页显示数量。
IPage<ExerciseRepoVO> page = new Page<>(pageNum, pageSize);
// 调用repoMapper的selectRepo方法传入分页对象和题库标题获取相应的分页题库信息将结果赋值给page对象。
page = repoMapper.selectRepo(page, title);
// page.getRecords().forEach(repoVO -> {
// //填充以刷题数
// LambdaQueryWrapper<ExerciseRecord> wrapper = new LambdaQueryWrapper<ExerciseRecord>().eq(ExerciseRecord::getRepoId, repoVO.getId())
// .eq(ExerciseRecord::getUserId, SecurityUtil.getUserId());
// .eq(ExerciseRecord::getUserId, SecurityUtil.getUserId());
//
// repoVO.setExerciseCount(exerciseRecordMapper.selectCount(wrapper).intValue());
// });
// 返回包含分页题库信息的成功结果信息方便前端进行分页展示等操作这里第一个参数为null可能是预留用于传递其他提示信息等情况。
return Result.success(null, page);
}
}
}

@ -1,20 +1,29 @@
package cn.org.alan.exam.service.impl;
// 导入RoleMapper接口它是用于操作角色Role相关数据的持久层接口比如对角色数据进行增删改查等操作从命名推测其功能
import cn.org.alan.exam.mapper.RoleMapper;
// 导入Role实体类代表角色相关的实体对象包含了角色的各种属性信息例如角色名称、角色权限等
import cn.org.alan.exam.model.entity.Role;
// 导入IRoleService接口该接口定义了角色相关的业务服务方法规定了具体有哪些业务操作需要实现例如获取角色列表、添加角色等
import cn.org.alan.exam.service.IRoleService;
// 导入ServiceImpl类这是MyBatis Plus提供的基础服务实现类它提供了很多通用的数据库操作方法方便在此基础上扩展具体业务逻辑的实现
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入Spring的Service注解用于标记该类是一个服务层组件Spring容器会对其进行管理使其能够参与依赖注入等相关机制
import org.springframework.stereotype.Service;
/**
* <p>
*
*
* </p>
*
* @author WeiJin
* @since 2024-03-21
*/
// 使用@Service注解将该类标记为Spring容器管理的服务层组件意味着在Spring应用中可以通过依赖注入的方式在其他地方使用这个服务类
@Service
// 定义RoleServiceImpl类它继承自ServiceImpl<RoleMapper, Role>同时实现了IRoleService接口
// 通过继承ServiceImpl可以复用其中已有的通用数据库操作方法再按照IRoleService接口中定义的业务方法来实现具体针对角色的业务逻辑
// 目前类体为空,表示暂时没有添加额外的业务方法实现,如果后续有添加角色、删除角色、查询角色等业务逻辑相关的方法,就需要在这里进行具体的编写
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IRoleService {
}
}

@ -22,7 +22,7 @@ import java.util.List;
/**
* <p>
*
*
* </p>
*
* @author WeiJin
@ -31,82 +31,117 @@ import java.util.List;
@Service
public class StatServiceImpl extends ServiceImpl<ExamGradeMapper, ExamGrade> implements IStatService {
// 使用 @Resource 注解注入 StatMapper用于操作统计相关数据的持久层接口可能包含各种统计查询方法从命名推测其功能
@Resource
private StatMapper statMapper;
// 注入 GradeMapper用于操作班级相关数据的持久层接口比如查询班级数量等操作在统计班级相关信息时可能会用到
@Resource
private GradeMapper gradeMapper;
// 注入 ExamMapper用于操作考试相关数据的持久层接口例如查询考试数量等操作在统计考试相关信息时可能会用到
@Resource
private ExamMapper examMapper;
// 注入 QuestionMapper用于操作题目相关数据的持久层接口像查询题目数量等操作在统计题目相关信息时可能会用到
@Resource
private QuestionMapper questionMapper;
// 注入 UserDailyLoginDurationMapper用于操作用户每日登录时长相关数据的持久层接口推测在获取每日相关统计信息时会用到
@Resource
private UserDailyLoginDurationMapper userDailyLoginDurationMapper;
/**
*
*
*
* @return
* @return Result
*/
@Override
public Result<List<GradeStudentVO>> getStudentGradeCount() {
//获取班级
// 定义用于存储各班人数统计信息的列表,类型为 GradeStudentVO初始未赋值。
List<GradeStudentVO> gradeStudentVOs;
// 判断当前用户角色是否为 "role_teacher"(通常代表教师角色,具体含义由业务定义)。
if ("role_teacher".equals(SecurityUtil.getRole())) {
// 如果是教师角色,调用 statMapper 的 StudentGradeCount 方法,传入参数 2 和当前用户 ID获取该教师相关的各班人数统计信息。
// 这里的参数 2 具体含义可能由业务逻辑决定,可能是用于区分不同的统计条件或筛选情况等。
gradeStudentVOs = statMapper.StudentGradeCount(2, SecurityUtil.getUserId());
} else {
// 如果不是教师角色(可能是其他角色,比如管理员等),调用 statMapper 的 StudentGradeCount 方法,传入参数 3 和当前用户 ID获取相应的各班人数统计信息。
// 同样,参数 3 的具体含义由业务逻辑确定,用于特定的统计筛选等情况。
gradeStudentVOs = statMapper.StudentGradeCount(3, SecurityUtil.getUserId());
}
// 返回包含各班人数统计信息的成功结果信息,方便前端展示给用户查看,"查询成功" 为提示消息gradeStudentVOs 为具体的数据列表。
return Result.success("查询成功", gradeStudentVOs);
}
/**
*
*
*
* @return
* @return Result
*/
@Override
public Result<List<GradeExamVO>> getExamGradeCount() {
//获取班级
// 定义用于存储各班试卷统计信息的列表,类型为 GradeExamVO初始未赋值。
List<GradeExamVO> gradeExamVOs;
// 判断当前用户角色是否为 "role_teacher"(通常代表教师角色,具体含义由业务定义)。
if ("role_teacher".equals(SecurityUtil.getRole())) {
// 如果是教师角色,调用 statMapper 的 ExamGradeCount 方法,传入参数 2 和当前用户 ID获取该教师相关的各班试卷统计信息。
// 参数 2 的具体含义同上述类似,由业务逻辑决定,用于特定的统计条件或筛选情况。
gradeExamVOs = statMapper.ExamGradeCount(2, SecurityUtil.getUserId());
} else {
// 如果不是教师角色(可能是其他角色,比如管理员等),调用 statMapper 的 ExamGradeCount 方法,传入参数 3 和当前用户 ID获取相应的各班试卷统计信息。
gradeExamVOs = statMapper.ExamGradeCount(3, SecurityUtil.getUserId());
}
// 返回包含各班试卷统计信息的成功结果信息,便于前端展示给用户,"查询成功" 为提示消息gradeExamVOs 为具体的数据列表。
return Result.success("查询成功", gradeExamVOs);
}
/**
*
* AllStatsVO
*
* @return
* @return Result
*/
@Override
public Result<AllStatsVO> getAllCount() {
// 创建一个 AllStatsVO 对象,用于封装所有班级、试卷、试题数量的统计信息,初始各属性值为空。
AllStatsVO allStatsVO = new AllStatsVO();
// 获取当前用户的角色信息,用于后续判断是管理员还是教师角色来进行不同的统计逻辑。
String role = SecurityUtil.getRole();
if("role_admin".equals(role)){
// 判断当前用户角色是否为 "role_admin"(通常代表管理员角色,具体含义由业务定义)。
if ("role_admin".equals(role)) {
// 如果是管理员角色,通过 gradeMapper 的 selectCount 方法,传入 null表示无额外查询条件统计所有班级数量获取班级总数并设置到 allStatsVO 对象中。
allStatsVO.setClassCount(gradeMapper.selectCount(null).intValue());
// 同样,通过 examMapper 的 selectCount 方法,传入 null统计所有考试数量获取考试总数设置到 allStatsVO 对象中。
allStatsVO.setExamCount(examMapper.selectCount(null).intValue());
// 通过 questionMapper 的 selectCount 方法,传入 null统计所有题目数量获取题目总数设置到 allStatsVO 对象中。
allStatsVO.setQuestionCount(questionMapper.selectCount(null).intValue());
}else if("role_teacher".equals(role)){
} else if ("role_teacher".equals(role)) {
// 如果是教师角色,通过 gradeMapper 的 selectCount 方法,传入一个 LambdaQueryWrapper 对象,设置查询条件为班级所属用户 ID 等于当前教师的用户 ID获取该教师创建的班级数量并设置到 allStatsVO 对象中。
allStatsVO.setClassCount(gradeMapper.selectCount(new LambdaQueryWrapper<Grade>().eq(Grade::getUserId,
SecurityUtil.getUserId())).intValue());
SecurityUtil.getUserId()))
.intValue());
// 通过 examMapper 的 selectCount 方法,传入一个 LambdaQueryWrapper 对象,设置查询条件为考试创建者用户 ID 等于当前教师的用户 ID获取该教师创建的考试数量并设置到 allStatsVO 对象中。
allStatsVO.setExamCount(examMapper.selectCount(
new LambdaQueryWrapper<Exam>().eq(Exam::getUserId,SecurityUtil.getUserId())).intValue());
new LambdaQueryWrapper<Exam>().eq(Exam::getUserId, SecurityUtil.getUserId()))
.intValue());
// 通过 questionMapper 的 selectCount 方法,传入一个 LambdaQueryWrapper 对象,设置查询条件为题目创建者用户 ID 等于当前教师的用户 ID获取该教师创建的题目数量并设置到 allStatsVO 对象中。
allStatsVO.setQuestionCount(questionMapper.selectCount(
new LambdaQueryWrapper<Question>().eq(Question::getUserId,SecurityUtil.getUserId())).intValue());
new LambdaQueryWrapper<Question>().eq(Question::getUserId, SecurityUtil.getUserId()))
.intValue());
}
// 返回包含所有统计信息的成功结果信息,方便前端展示给用户查看,"查询成功" 为提示消息allStatsVO 为包含具体统计数据的对象。
return Result.success("查询成功", allStatsVO);
}
/**
* ID
*
* @return Result
*/
@Override
public Result<List<DailyVO>> getDaily() {
// 调用 userDailyLoginDurationMapper 的 getDaily 方法,传入当前用户 ID获取该用户的每日相关统计信息从方法名推测可能是登录时长等相关信息返回一个 DailyVO 类型的列表。
List<DailyVO> daily = userDailyLoginDurationMapper.getDaily(SecurityUtil.getUserId());
return Result.success("请求成功",daily);
// 返回包含每日统计信息的成功结果信息,便于前端展示给用户,"请求成功" 为提示消息daily 为具体的数据列表。
return Result.success("请求成功", daily);
}
}
}

@ -28,145 +28,192 @@ import java.util.*;
import java.util.stream.Collectors;
/**
*
*
*
* @author Alan
* @since 2024-03-21
*/
@Service
public class UserBookServiceImpl extends ServiceImpl<UserBookMapper, UserBook> implements IUserBookService {
// 注入UserBookMapper用于操作错题本数据的持久层接口比如查询错题本记录、插入、删除错题本相关数据等操作。
@Resource
private UserBookMapper userBookMapper;
// 注入QuestionMapper用于操作题目数据的持久层接口可能在获取错题对应的题目详情等操作中使用。
@Resource
private QuestionMapper questionMapper;
// 注入OptionMapper用于操作题目选项数据的持久层接口例如获取题目选项信息等操作与题目相关联
@Resource
private OptionMapper optionMapper;
// 注入ExamQuAnswerMapper可能用于操作考试题目答案相关数据的持久层接口从命名推测其功能
@Resource
private ExamQuAnswerMapper examQuAnswerMapper;
// 注入UserBookConverter用于不同错题本相关对象之间的转换比如实体对象和视图对象等
@Resource
private UserBookConverter userBookConverter;
// 注入IQuestionService这是题目相关的业务服务接口可调用其中已定义的题目相关业务方法如获取题目详情等
@Resource
private IQuestionService questionService;
// 注入IOptionService这是题目选项相关的业务服务接口可调用其中已定义的选项相关业务方法如获取选项详情等
@Resource
private IOptionService optionService;
// 获取错题本分页数据的方法,根据页码、每页数量以及考试名称等条件,返回包含分页信息和错题本视图对象列表的结果。
@Override
public Result<IPage<UserPageBookVO>> getPage(Integer pageNum, Integer pageSize, String examName) {
// 创建一个Page对象用于封装分页相关信息设置当前页码和每页显示数量。
Page<UserPageBookVO> page = new Page<>(pageNum, pageSize);
// 获取当前用户的角色信息,用于后续判断不同角色的逻辑处理(可能涉及不同权限查看不同数据等情况)。
String role = SecurityUtil.getRole();
int roleId = 0;
if("role_admin".equals(role)){
roleId =3;
}else if ("role_teacher".equals(role)){
// 根据用户角色进行不同的角色ID赋值这里假设不同角色有对应的数字标识具体含义由业务定义
if ("role_admin".equals(role)) {
roleId = 3;
} else if ("role_teacher".equals(role)) {
roleId = 2;
}else {
} else {
roleId = 1;
}
Page<UserPageBookVO> userPageBookVOPage = userBookMapper.selectPageVo(page, examName, SecurityUtil.getUserId(),roleId);
// 调用userBookMapper的selectPageVo方法传入分页对象、考试名称、当前用户ID以及角色ID获取符合条件的错题本分页数据具体查询逻辑在该方法内部实现并将结果赋值给userPageBookVOPage对象。
Page<UserPageBookVO> userPageBookVOPage = userBookMapper.selectPageVo(page, examName, SecurityUtil.getUserId(), roleId);
// 返回包含分页信息和错题本视图对象列表的成功结果信息,方便前端进行分页展示等操作,"查询成功" 为提示消息userPageBookVOPage为具体的分页数据对象。
return Result.success("查询成功", userPageBookVOPage);
}
// 根据考试ID获取对应的错题本信息转换为特定视图对象列表的方法返回包含查询结果的成功提示信息。
@Override
public Result<List<ReUserExamBookVO>> getReUserExamBook(Integer examId) {
// 根据考试id查询考试试题表
// 根据考试id查询考试试题表创建一个LambdaQueryWrapper对象用于构建查询错题本记录的条件这里设置考试ID和用户ID作为查询条件。
LambdaQueryWrapper<UserBook> userBookLambdaQueryWrapper = new LambdaQueryWrapper<>();
userBookLambdaQueryWrapper.eq(UserBook::getExamId, examId)
.eq(UserBook::getUserId, SecurityUtil.getUserId());
// 调用userBookMapper的selectList方法根据构建的查询条件获取符合条件的错题本记录列表即UserBook实体对象列表
List<UserBook> userBook = userBookMapper.selectList(userBookLambdaQueryWrapper);
// 实体转换
// 进行实体转换调用userBookConverter的listEntityToVo方法将获取到的UserBook实体对象列表转换为ReUserExamBookVO视图对象列表方便后续返回展示给前端等操作。
List<ReUserExamBookVO> reUserExamBookVOS = userBookConverter.listEntityToVo(userBook);
// 返回包含查询到的错题本视图对象列表的成功结果信息,便于前端展示给用户查看,"查询成功" 为提示消息reUserExamBookVOS为具体的数据列表。
return Result.success("查询成功", reUserExamBookVOS);
}
// 根据题目ID获取错题本中一道题目的详细信息的方法返回包含该题目详细信息的视图对象的成功提示信息如果题目不存在则返回相应错误提示。
@Override
public Result<BookOneQuVO> getBookOne(Integer quId) {
// 创建一个BookOneQuVO对象用于封装要返回的错题本中一道题目的详细信息初始各属性值为空。
BookOneQuVO bookOneQuVO = new BookOneQuVO();
// 问题
// 通过questionService的getById方法调用了Question相关业务服务接口的方法根据题目ID获取对应的题目详细信息Question实体对象用于后续设置错题本中题目详情相关属性。
Question quById = questionService.getById(quId);
if( quById==null){
// 如果获取到的题目实体对象为空,即该题不存在,返回相应的错误提示信息。
if (quById == null) {
return Result.failed("该题不存在");
}
// 基本信息
// 设置错题本中题目视图对象的图片信息属性,取值为从题目实体对象中获取的图片信息(假设题目实体中有保存图片相关字段)。
bookOneQuVO.setImage(quById.getImage());
// 设置错题本中题目视图对象的题目内容属性,取值为从题目实体对象中获取的题目内容。
bookOneQuVO.setContent(quById.getContent());
// 设置错题本中题目视图对象的题目类型属性,取值为从题目实体对象中获取的题目类型(例如单选题、多选题等不同类型的标识)。
bookOneQuVO.setQuType(quById.getQuType());
// 答案列表
// 查询该题目的答案列表创建一个LambdaQueryWrapper对象用于构建查询题目选项的条件这里设置题目ID作为查询条件用于获取该题对应的所有选项信息。
LambdaQueryWrapper<Option> optionLambdaQuery = new LambdaQueryWrapper<>();
optionLambdaQuery.eq(Option::getQuId, quId);
// 调用optionMapper的selectList方法根据构建的查询条件获取符合条件的题目选项列表即Option实体对象列表并设置到错题本中题目视图对象的答案列表属性中。
List<Option> list = optionMapper.selectList(optionLambdaQuery);
bookOneQuVO.setAnswerList(list);
// 返回包含错题本中一道题目的详细信息的成功结果信息,方便前端展示给用户查看,"获取成功" 为提示消息bookOneQuVO为具体的题目详细信息视图对象。
return Result.success("获取成功", bookOneQuVO);
}
// 处理错题本中题目作答情况并返回相应结果的方法,根据传入的错题作答表单信息,判断作答是否正确、移除已答对的错题等操作,并返回对应的结果信息。
@Override
public Result<AddBookAnswerVO> addBookAnswer(ReUserBookForm reUserBookForm) {
// 创建返回视图
// 创建一个AddBookAnswerVO对象用于封装要返回的错题作答相关结果信息初始各属性值为空。
AddBookAnswerVO addBookAnswerVO = new AddBookAnswerVO();
// 未作答
// 判断用户是否未作答如果传入的答案为空字符串通过StringUtils工具类判断则直接返回表示未作答的成功提示信息。
if (StringUtils.isBlank(reUserBookForm.getAnswer())) {
return Result.success("未作答");
}
// 查询试题类型
// 查询试题类型创建一个LambdaQueryWrapper对象用于构建查询题目的条件这里设置题目ID作为查询条件用于获取对应的题目详细信息主要是获取题目类型
LambdaQueryWrapper<Question> QuWrapper = new LambdaQueryWrapper<>();
QuWrapper.eq(Question::getId, reUserBookForm.getQuId());
// 调用questionMapper的selectOne方法根据构建的查询条件获取对应的题目详细信息这里获取到的是Question实体对象因为使用selectOne预期返回单个题目对象
Question qu = questionMapper.selectOne(QuWrapper);
// 获取题目类型(从题目实体对象中获取题目类型字段的值),用于后续根据不同题目类型进行不同的作答判断逻辑。
Integer quType = qu.getQuType();
// 设置试题分析
// 设置试题分析属性,取值为从题目实体对象中获取的试题分析内容(假设题目实体中有保存试题分析相关字段),将其设置到要返回的结果视图对象中。
addBookAnswerVO.setAnalysis(qu.getAnalysis());
// 设置正确答案
// 设置正确答案属性先创建一个LambdaQueryWrapper对象用于构建查询题目选项的条件这里设置题目ID作为查询条件用于获取该题对应的所有选项信息。
LambdaQueryWrapper<Option> opWrapper = new LambdaQueryWrapper<>();
opWrapper.eq(Option::getQuId, reUserBookForm.getQuId());
// 调用optionMapper的selectList方法根据构建的查询条件获取符合条件的题目选项列表即Option实体对象列表
List<Option> options = optionMapper.selectList(opWrapper);
// 初始化一个空字符串,用于后续拼接正确答案(在某些题目类型下可能需要拼接多个正确答案等情况)。
String current = "";
// 创建一个ArrayList用于存储正确答案对应的序号在某些题目类型下可能需要根据序号来判断答案情况等初始为空。
ArrayList<Integer> strings = new ArrayList<>();
// 遍历获取到的题目选项列表找出其中标记为正确答案的选项通过判断选项实体对象中的是否正确标识字段假设为isRight值为1表示正确将其序号添加到strings列表中。
for (Option temp : options) {
if (temp.getIsRight() == 1) {
strings.add(temp.getSort());
}
}
// 将strings列表中的序号转换为字符串列表方便后续拼接等操作使用Java 8的Stream API进行转换。
List<String> stringList = strings.stream().map(String::valueOf).collect(Collectors.toList());
// 使用逗号将字符串列表中的元素拼接成一个字符串,得到正确答案的表示形式(例如对于多选题可能有多个正确答案,用逗号分隔)。
String result = String.join(",", stringList);
if(quType ==4){
// 如果题目类型是简答题假设题目类型4代表简答题设置正确答案属性为题目选项列表中的第一个选项的内容简答题通常可能只有一个用于填写答案的选项这里从代码逻辑推测
if (quType == 4) {
addBookAnswerVO.setRightAnswers(options.get(0).getContent());
}else{
} else {
// 如果不是简答题,设置正确答案属性为前面拼接好的正确答案字符串(包含多个正确答案用逗号分隔的情况等)。
addBookAnswerVO.setRightAnswers(result);
}
// 判断是否正确并移除正确试题
// 根据不同的题目类型进行不同的作答判断逻辑使用Java 17的新特性switch表达式来简化代码结构根据作答情况返回相应的结果信息。
return switch (quType) {
// 题目类型为单选题的情况
case 1 -> {
// 通过optionService的getById方法调用了Option相关业务服务接口的方法根据用户作答的选项ID获取对应的选项详细信息Option实体对象用于判断作答是否正确。
Option byId = optionService.getById(reUserBookForm.getAnswer());
// 如果获取到的选项对象中标记为正确答案通过判断选项实体对象中的是否正确标识字段值为1表示正确说明回答正确进行相应操作。
if (byId.getIsRight() == 1) {
// 创建一个LambdaQueryWrapper对象用于构建删除错题本记录的条件这里设置用户ID、考试ID和题目ID作为查询条件用于定位要删除的错题本记录即已答对的错题从错题本中移除
LambdaQueryWrapper<UserBook> userBookLambdaQueryWrapper = new LambdaQueryWrapper<>();
userBookLambdaQueryWrapper.eq(UserBook::getUserId, SecurityUtil.getUserId())
.eq(UserBook::getExamId, reUserBookForm.getExamId())
.eq(UserBook::getQuId, reUserBookForm.getQuId());
// 调用userBookMapper的delete方法根据构建的条件删除对应的错题本记录返回受影响的行数即实际删除的记录数这里暂时未使用该返回值。
int row = userBookMapper.delete(userBookLambdaQueryWrapper);
// 设置回答正确标识为1表示回答正确将其设置到要返回的结果视图对象中。
addBookAnswerVO.setCorrect(1);
// 使用yield关键字返回表示回答正确且已移出错题本的成功提示信息包含更新后的结果视图对象addBookAnswerVO。
yield Result.success("回答正确,已移出错题本", addBookAnswerVO);
} else {
// 如果选项对象不是正确答案设置回答正确标识为0表示回答错误将其设置到要返回的结果视图对象中。
addBookAnswerVO.setCorrect(0);
// 使用yield关键字返回表示回答错误的成功提示信息包含结果视图对象addBookAnswerVO。
yield Result.success("回答错误", addBookAnswerVO);
}
}
// 题目类型为多选题的情况
case 2 -> {
// 查找正确答案
// 查找正确答案创建一个LambdaQueryWrapper对象用于构建查询正确选项的条件这里设置选项为正确答案通过判断选项实体对象中的是否正确标识字段值为1表示正确以及题目ID作为查询条件用于获取该题对应的所有正确选项信息。
LambdaQueryWrapper<Option> optionWrapper = new LambdaQueryWrapper<>();
optionWrapper.eq(Option::getIsRight, 1)
.eq(Option::getQuId, reUserBookForm.getQuId());
// 调用optionMapper的selectList方法根据构建的查询条件获取符合条件的正确选项列表即Option实体对象列表
List<Option> examQuAnswers = optionMapper.selectList(optionWrapper);
// 解析用户作答
// 解析用户作答将用户传入的以逗号分隔的作答字符串转换为整数列表假设用户作答的是选项的ID用逗号分隔多个选项ID使用Java 8的Stream API进行转换。
List<Integer> quIds = Arrays.stream(reUserBookForm.getAnswer().split(","))
.map(Integer::parseInt)
.toList();
// 判读啊是否正确
// 判读啊是否正确这里可能代码有拼写错误正确应为“判断”以下按照正确逻辑解读注释遍历正确选项列表检查用户作答的选项ID列表中是否包含当前正确选项的ID如果包含则说明回答错误因为多选题需要全部选对才正确
for (Option temp : examQuAnswers) {
boolean containsBanana = quIds.contains(temp.getId());
if (containsBanana) {
yield Result.success("回答错误");
}
}
// 如果遍历完所有正确选项用户作答的选项ID列表都不包含正确选项的ID说明回答正确进行相应操作。
LambdaQueryWrapper<UserBook> userBookWrapper = new LambdaQueryWrapper<>();
userBookWrapper.eq(UserBook::getUserId, SecurityUtil.getUserId())
.eq(UserBook::getExamId, reUserBookForm.getExamId())
@ -175,36 +222,62 @@ public class UserBookServiceImpl extends ServiceImpl<UserBookMapper, UserBook> i
yield Result.success("回答正确");
}
case 3 -> {
// 当满足条件为3时的逻辑分支同样可能对应不同的题型或业务场景下的处理逻辑具体依业务而定
// 通过optionService可能是选项相关的业务服务类的getById方法根据从reUserBookForm对象中获取的答案推测此处答案可能是一个选项ID之类的标识从数据库中获取对应的选项信息存储在byId变量中。
Option byId = optionService.getById(reUserBookForm.getAnswer());
if (byId.getIsRight() == 1) {
// 判断获取到的选项的“是否为正确答案”字段isRight的值是否为1即判断用户选择的这个选项是否是正确答案。
// 如果是正确答案,执行以下操作,准备从错题本中移除对应的题目记录。
// 创建一个LambdaQueryWrapper<UserBook>对象,用于构建删除错题本记录的条件,方式和前面类似,目的是准确找到要删除的错题本记录。
LambdaQueryWrapper<UserBook> userBookLambdaQueryWrapper = new LambdaQueryWrapper<>();
// 添加查询条件设置错题本记录对应的用户ID与通过SecurityUtil获取的当前登录用户的ID相等限定操作的是当前用户的错题本记录。
userBookLambdaQueryWrapper.eq(UserBook::getUserId, SecurityUtil.getUserId())
// 继续添加条件使错题本记录中的考试ID与从reUserBookForm对象中获取的考试ID相等进一步确定要删除的错题本记录范围。
.eq(UserBook::getExamId, reUserBookForm.getExamId())
// 再添加条件让错题本记录中的题目ID与从reUserBookForm对象中获取的题目ID相等确保精准定位到要删除的那条错题本记录。
.eq(UserBook::getQuId, reUserBookForm.getQuId());
// 使用userBookMapper的delete方法传入构建好的删除条件userBookLambdaQueryWrapper从数据库中删除对应的错题本记录实现移除错题本中已答对题目的功能。
userBookMapper.delete(userBookLambdaQueryWrapper);
// 设置addBookAnswerVO可能是用于封装答题相关信息返回给前端等的对象中的correct字段为1表示回答正确具体addBookAnswerVO的结构和使用场景需结合项目整体逻辑来看。
addBookAnswerVO.setCorrect(1);
// 返回一个表示回答正确的Result结果对象同时包含提示信息“回答正确已移除错题本”以及答题相关信息的addBookAnswerVO对象方便前端展示和后续处理。
yield Result.success("回答正确,已移除错题本", addBookAnswerVO);
} else {
// 如果用户选择的选项不是正确答案,执行以下操作。
// 设置addBookAnswerVO中的correct字段为0表示回答错误同样addBookAnswerVO的使用和含义需结合项目整体来看。
addBookAnswerVO.setCorrect(0);
// 返回一个表示回答错误的Result结果对象同时包含答题相关信息的addBookAnswerVO对象方便前端展示和后续处理。
yield Result.success("回答错误", addBookAnswerVO);
}
}
case 4 -> {
// 当满足条件为4时的逻辑分支也是对应特定业务场景下的处理逻辑具体由业务规则决定
if("1".equals(reUserBookForm.getAnswer())){
// 判断用户作答的答案是否为 "1"(具体含义需结合业务来看,可能代表某种特定选择或情况),如果是则执行以下操作,准备从错题本中移除对应的题目记录。
// 创建一个LambdaQueryWrapper<UserBook>对象,用于构建删除错题本记录的条件,和前面类似,用于准确找到要删除的错题本记录。
LambdaQueryWrapper<UserBook> userBookLambdaQueryWrapper = new LambdaQueryWrapper<>();
// 添加查询条件设置错题本记录对应的用户ID与通过SecurityUtil获取的当前登录用户的ID相等确保操作的是当前用户的错题本记录。
userBookLambdaQueryWrapper.eq(UserBook::getUserId, SecurityUtil.getUserId())
// 继续添加条件使错题本记录中的考试ID与从reUserBookForm对象中获取的考试ID相等进一步确定要删除的错题本记录范围。
.eq(UserBook::getExamId, reUserBookForm.getExamId())
// 再添加条件让错题本记录中的题目ID与从reUserBookForm对象中获取的题目ID相等确保精准定位到要删除的那条错题本记录。
.eq(UserBook::getQuId, reUserBookForm.getQuId());
// 使用userBookMapper的delete方法传入构建好的删除条件userBookLambdaQueryWrapper从数据库中删除对应的错题本记录实现移除错题本中已答对题目的功能。
userBookMapper.delete(userBookLambdaQueryWrapper);
// 返回一个表示回答正确的Result结果对象同时包含提示信息“回答正确已移除错题本”以及答题相关信息的addBookAnswerVO对象方便前端展示和后续处理。
yield Result.success("回答正确,已移除错题本", addBookAnswerVO);
}
// 如果用户作答的答案不是 "1",执行以下操作,表示回答错误。
addBookAnswerVO.setCorrect(0);
// 返回一个表示回答错误的Result结果对象同时包含答题相关信息的addBookAnswerVO对象方便前端展示和后续处理。
yield Result.success("回答错误", addBookAnswerVO);
}
default -> {
// 当以上所有case条件都不满足时的默认处理逻辑即处理其他未明确列举的情况。
// 返回一个表示请求错误的Result结果对象包含提示信息“请求错误请联系管理员解决”提示用户出现了不符合预期的情况需要联系管理员进一步排查处理。
yield Result.failed("请求错误,请联系管理员解决");
}
};
}
}
}

@ -1,19 +1,32 @@
package cn.org.alan.exam.service.impl;
// 导入UserDailyLoginDurationMapper接口它是用于操作用户每日登录时长UserDailyLoginDuration相关数据的持久层接口比如对用户每日登录时长数据进行增删改查等操作从命名推测其功能
import cn.org.alan.exam.mapper.UserDailyLoginDurationMapper;
// 导入UserExamsScoreMapper接口它是用于操作用户考试成绩UserExamsScore相关数据的持久层接口可用于对用户考试成绩数据进行相应的数据库操作从命名推测其功能此处虽然当前类未直接使用但可能是在整个项目的相关业务逻辑中存在关联导入的情况
import cn.org.alan.exam.mapper.UserExamsScoreMapper;
// 导入UserDailyLoginDuration实体类代表用户每日登录时长相关的实体对象包含了如登录日期、登录时长等各种属性信息用于存储和传递与用户每日登录时长相关的数据
import cn.org.alan.exam.model.entity.UserDailyLoginDuration;
// 导入UserExamsScore实体类代表用户考试成绩相关的实体对象包含了如考试ID、用户ID、成绩分数等属性信息用于体现用户在不同考试中的成绩情况
import cn.org.alan.exam.model.entity.UserExamsScore;
// 导入IUserDailyLoginDurationService接口该接口定义了用户每日登录时长相关的业务服务方法规定了具体有哪些业务操作需要实现例如获取用户某段时间内的登录时长统计、记录每日登录时长等
import cn.org.alan.exam.service.IUserDailyLoginDurationService;
// 导入IUserExamsScoreService接口该接口定义了用户考试成绩相关的业务服务方法像查询用户特定考试成绩、更新成绩等操作同样虽当前类未直接关联使用但可能在整体业务中有联系
import cn.org.alan.exam.service.IUserExamsScoreService;
// 导入ServiceImpl类这是MyBatis Plus提供的基础服务实现类它提供了很多通用的数据库操作方法方便在此基础上扩展具体业务逻辑的实现减少重复代码编写提高开发效率
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入Spring的Service注解用于标记该类是一个服务层组件Spring容器会对其进行管理使其能够参与依赖注入等相关机制便于在其他组件中方便地调用该服务类的方法
import org.springframework.stereotype.Service;
/**
* @Author Alan
* @Version
* @Date 2024/5/28 10:46 PM
*
*/
@Service
// 定义UserDailyLoginDurationServiceImpl类它继承自ServiceImpl<UserDailyLoginDurationMapper, UserDailyLoginDuration>同时实现了IUserDailyLoginDurationService接口
// 通过继承ServiceImpl可以复用其中已有的通用数据库操作方法再按照IUserDailyLoginDurationService接口中定义的业务方法来实现具体针对用户每日登录时长的业务逻辑
// 目前类体为空,表示暂时没有添加额外的业务方法实现,如果后续有添加记录用户每日登录时长、查询用户登录时长统计等业务逻辑相关的方法,就需要在这里进行具体的编写
public class UserDailyLoginDurationServiceImpl extends ServiceImpl<UserDailyLoginDurationMapper, UserDailyLoginDuration> implements IUserDailyLoginDurationService {
}
}

@ -24,7 +24,7 @@ import java.util.List;
/**
* <p>
*
*
* </p>
*
* @author WeiJin
@ -32,46 +32,87 @@ import java.util.List;
*/
@Service
public class UserExamsScoreServiceImpl extends ServiceImpl<UserExamsScoreMapper, UserExamsScore> implements IUserExamsScoreService {
// 使用 @Resource 注解注入 UserExamsScoreMapper用于操作用户考试成绩数据的持久层接口比如查询成绩、插入成绩等数据库操作从命名推测其功能
@Resource
private UserExamsScoreMapper userExamsScoreMapper;
// 注入 ExamMapper用于操作考试相关数据的持久层接口例如查询考试名称等操作在获取成绩相关信息时可能会用到
@Resource
private ExamMapper examMapper;
// 注入 ExamGradeMapper用于操作考试成绩等级相关数据的持久层接口可能涉及成绩等级划分等相关业务逻辑具体根据业务定义
@Resource
private ExamGradeMapper examGradeMapper;
/**
* IDID
*
* @param pageNum
* @param pageSize
* @param gradeId IDnull
* @param examId IDnull
* @param realName null
* @return Result
*/
@Override
public Result<IPage<UserScoreVO>> pagingScore(Integer pageNum, Integer pageSize, Integer gradeId, Integer examId, String realName) {
// 创建一个 IPage 对象,用于封装分页相关信息,设置当前页码和每页显示数量。
IPage<UserScoreVO> page = new Page<>(pageNum, pageSize);
// 调用 userExamsScoreMapper 的 pagingScore 方法传入分页对象、班级ID、考试ID以及真实姓名获取符合条件的分页用户成绩信息并将结果赋值给 page 对象(具体查询逻辑在该方法内部实现)。
page = userExamsScoreMapper.pagingScore(page, gradeId, examId, realName);
// 返回包含分页用户成绩信息的成功结果信息方便前端进行分页展示等操作这里第一个参数为null可能是预留用于传递其他提示信息等情况page 为具体的分页数据对象。
return Result.success(null, page);
}
/**
* IDID使 Excel Excel HttpServletResponse
*
* @param response HttpServletResponse Excel
* @param examId ID
* @param gradeId IDnull
*/
@Override
public void exportScores(HttpServletResponse response, Integer examId, Integer gradeId) {
//获取成绩信息
// 获取成绩信息,调用 userExamsScoreMapper 的 selectScores 方法传入考试ID和班级ID从数据库中获取符合条件的成绩数据以 ExportScoreVO 列表形式返回),这些成绩数据将用于生成 Excel 文件。
List<ExportScoreVO> scores = userExamsScoreMapper.selectScores(examId, gradeId);
// 创建一个长度为1的数组用于记录排名序号初始值为0这里通过数组实现类似引用传递的效果方便在Lambda表达式中修改值
final int[] sort = {0};
// 使用 Lambda 表达式遍历成绩列表为每个成绩记录设置排名信息排名序号从1开始自增通过 ++sort[0] 操作实现自增并赋值给排名字段)。
scores.forEach(exportScoreVO -> exportScoreVO.setRanking(++sort[0]));
//获取考试名
// 获取考试名,创建一个 LambdaQueryWrapper 对象用于构建查询考试信息的条件这里设置考试ID作为查询条件并指定只查询考试标题字段通过 select 方法指定),用于获取对应的考试名称(后续用于 Excel 文件的命名等情况)。
LambdaQueryWrapper<Exam> wrapper = new LambdaQueryWrapper<Exam>().eq(Exam::getId, examId).select(Exam::getTitle);
// 调用 examMapper 的 selectOne 方法根据构建的查询条件获取对应的考试信息这里获取到的是Exam实体对象因为使用selectOne预期返回单个考试对象从中获取考试名称。
Exam exam = examMapper.selectOne(wrapper);
//生成表格并响应
// 生成表格并响应,调用 ExcelUtils 工具类的 export 方法,传入 HttpServletResponse 对象、考试名称、成绩数据列表以及成绩数据对应的实体类ExportScoreVO.class该方法内部会将成绩数据生成为 Excel 文件,并通过 response 设置响应头和输出流将 Excel 文件发送给客户端。
ExcelUtils.export(response, exam.getTitle(), scores, ExportScoreVO.class);
}
/**
* ID
*
* @param pageNum
* @param pageSize
* @param examTitle null
* @param gradeId IDnull
* @return Result
*/
@Override
public Result<IPage<GradeScoreVO>> getExamScoreInfo(Integer pageNum, Integer pageSize, String examTitle,Integer gradeId) {
public Result<IPage<GradeScoreVO>> getExamScoreInfo(Integer pageNum, Integer pageSize, String examTitle, Integer gradeId) {
// 创建一个 IPage 对象,用于封装分页相关信息,设置当前页码和每页显示数量。
IPage<GradeScoreVO> page = new Page<>(pageNum, pageSize);
// 判断当前用户角色是否为 "role_teacher"(通常代表教师角色,具体含义由业务定义)。
if ("role_teacher".equals(SecurityUtil.getRole())) {
// 如果是教师角色,调用 userExamsScoreMapper 的 scoreStatistics 方法传入分页对象、班级ID、考试标题、当前用户ID以及角色标识2具体含义由业务逻辑决定可能用于区分不同的统计条件或权限等情况获取符合教师角色相关条件的分页考试成绩信息并将结果赋值给 page 对象(具体查询统计逻辑在该方法内部实现)。
page = userExamsScoreMapper
.scoreStatistics(page,gradeId,examTitle,SecurityUtil.getUserId(),2);
.scoreStatistics(page, gradeId, examTitle, SecurityUtil.getUserId(), 2);
} else {
// 如果不是教师角色(可能是其他角色,比如管理员等),调用 userExamsScoreMapper 的 scoreStatistics 方法传入分页对象、班级ID、考试标题、当前用户ID以及角色标识3同样由业务逻辑确定其含义用于特定的统计筛选等情况获取相应的分页考试成绩信息并将结果赋值给 page 对象。
page = userExamsScoreMapper
.scoreStatistics(page, gradeId, examTitle, SecurityUtil.getUserId(), 3);
}
// 返回包含分页考试成绩信息的成功结果信息,方便前端进行展示等操作,"查询成功" 为提示消息page 为具体的分页数据对象。
return Result.success("查询成功", page);
}
}
}

@ -1,20 +1,29 @@
package cn.org.alan.exam.service.impl;
// 导入UserExerciseRecordMapper接口它是用于操作用户练习记录UserExerciseRecord相关数据的持久层接口可实现诸如对用户练习记录数据进行增删改查等操作从命名推测其功能
import cn.org.alan.exam.mapper.UserExerciseRecordMapper;
// 导入UserExerciseRecord实体类代表用户练习记录相关的实体对象包含了像练习时间、练习题目、练习得分等各种属性信息用于存储和传递与用户练习情况相关的数据
import cn.org.alan.exam.model.entity.UserExerciseRecord;
// 导入IUserExerciseRecordService接口该接口定义了用户练习记录相关的业务服务方法规定了具体有哪些业务操作需要实现例如记录用户每次练习的详细情况、查询用户的练习历史记录等
import cn.org.alan.exam.service.IUserExerciseRecordService;
// 导入ServiceImpl类这是MyBatis Plus提供的基础服务实现类它提供了很多通用的数据库操作方法方便在此基础上扩展具体业务逻辑的实现减少重复编写代码提高开发效率
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入Spring的Service注解用于标记该类是一个服务层组件Spring容器会对其进行管理使其能够参与依赖注入等相关机制方便在其他地方通过依赖注入的方式使用这个服务类
import org.springframework.stereotype.Service;
/**
* <p>
*
*
* </p>
*
* @author WeiJin
* @since 2024-03-21
*/
// 使用@Service注解将该类标记为Spring容器管理的服务层组件意味着在Spring应用中可以通过依赖注入的方式在其他组件中调用这个服务类的方法
@Service
// 定义UserExerciseRecordServiceImpl类它继承自ServiceImpl<UserExerciseRecordMapper, UserExerciseRecord>同时实现了IUserExerciseRecordService接口
// 通过继承ServiceImpl可以复用其中已有的通用数据库操作方法后续可按照IUserExerciseRecordService接口中定义的业务方法来实现具体针对用户练习记录的业务逻辑
// 目前类体为空,表示暂时没有添加额外的业务方法实现,如果后续有添加记录用户练习情况、查询用户练习记录统计信息等业务逻辑相关的方法,就需要在这里进行具体的编写
public class UserExerciseRecordServiceImpl extends ServiceImpl<UserExerciseRecordMapper, UserExerciseRecord> implements IUserExerciseRecordService {
}
}

@ -31,228 +31,306 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* @author WeiJin
* @since 2024-03-21
* IUserService
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
// 注入UserMapper用于操作用户数据的持久层接口比如插入、更新、查询、删除用户记录等操作。
@Resource
private UserMapper userMapper;
// 注入StringRedisTemplate用于操作Redis字符串类型数据常用于缓存相关操作比如删除缓存、存储用户登录相关的token等。
@Resource
private StringRedisTemplate stringRedisTemplate;
// 注入HttpServletRequest用于获取HTTP请求相关信息例如在后续可能用于获取请求中的会话信息等如删除密码修改成功后对应会话的token
@Resource
private HttpServletRequest request;
// 注入UserConverter用于不同用户相关对象之间的转换比如表单对象和实体对象等方便在不同业务逻辑中进行数据格式的适配。
@Resource
private UserConverter userConverter;
// 注入GradeMapper用于操作班级相关数据的持久层接口可能在涉及用户与班级关联的业务逻辑中使用比如查询用户所在班级、添加用户到班级等操作。
@Resource
private GradeMapper gradeMapper;
// 注入CertificateUserMapper可能用于操作用户证书相关数据的持久层接口从命名推测其功能可能涉及用户资质认证等相关业务逻辑
@Resource
private CertificateUserMapper certificateUserMapper;
// 注入ExamMapper用于操作考试相关数据的持久层接口例如查询用户创建的考试、删除用户相关考试记录等操作与用户在考试方面的业务相关
@Resource
private ExamMapper examMapper;
// 注入ExamQuAnswerMapper可能用于操作考试题目答案相关数据的持久层接口推测用于处理用户在考试中作答的题目答案等相关业务逻辑
@Resource
private ExamQuAnswerMapper examQuAnswerMapper;
// 注入ExerciseRecordMapper用于操作用户练习记录相关数据的持久层接口比如删除用户的练习记录等操作与用户练习情况相关
@Resource
private ExerciseRecordMapper exerciseRecordMapper;
// 注入GradeExerciseMapper可能用于操作班级与练习相关关联数据的持久层接口从命名推测其功能也许涉及班级内学生练习相关业务逻辑
@Resource
private GradeExerciseMapper gradeExerciseMapper;
// 注入ManualScoreMapper可能用于操作手动评分相关数据的持久层接口可能用于处理如主观题老师手动给分等相关业务逻辑
@Resource
private ManualScoreMapper manualScoreMapper;
// 注入NoticeMapper用于操作公告相关数据的持久层接口例如查询用户创建的公告、删除用户相关公告记录等操作与系统公告业务相关
@Resource
private NoticeMapper noticeMapper;
// 注入QuestionMapper用于操作题目数据的持久层接口可能在涉及用户创建的题目、删除用户相关题目等业务逻辑中使用。
@Resource
private QuestionMapper questionMapper;
// 注入RepoMapper用于操作题库相关数据的持久层接口比如查询用户创建的题库、删除用户相关题库记录等操作与用户在题库方面的业务相关
@Resource
private RepoMapper repoMapper;
// 注入UserBookMapper用于操作用户错题本相关数据的持久层接口例如删除用户的错题本记录等操作与用户错题本业务相关
@Resource
private UserBookMapper userBookMapper;
// 注入OptionMapper用于操作题目选项数据的持久层接口例如删除用户创建题目对应的选项等操作与题目选项相关且和用户创建题目等业务有关联
@Resource
private OptionMapper optionMapper;
// 注入NoticeGradeMapper可能用于操作公告与班级关联相关数据的持久层接口推测用于处理公告针对哪些班级展示等相关业务逻辑
@Resource
private NoticeGradeMapper noticeGradeMapper;
// 注入IQuestionService这是题目相关的业务服务接口此处用于调用题目相关业务方法如上传头像复用了上传图片的方法这里调用其上传图片功能
@Resource
private IQuestionService iQuestionService;
// 创建用户的方法根据传入的UserForm对象创建新用户进行一些必要的属性设置和验证后插入到数据库中成功返回创建成功的提示信息失败返回相应错误提示。
@Override
public Result<String> createUser(UserForm userForm) {
// 使用BCryptPasswordEncoder对默认密码"123456"进行加密,并设置到用户表单对象的密码字段中,通常新用户会有一个初始密码,后续可自行修改。
userForm.setPassword(new BCryptPasswordEncoder().encode("123456"));
//教师只能创建学生
// 判断当前用户角色是否为"role_teacher"(通常代表教师角色,具体含义由业务定义),如果是教师角色,只能创建学生用户这里假设学生角色的ID为1具体由业务规定设置用户表单对象的角色ID为学生角色ID。
if ("role_teacher".equals(SecurityUtil.getRole())) {
userForm.setRoleId(1);
}
//避免管理员创建用户不传递角色
// 避免管理员创建用户不传递角色信息的情况如果用户表单对象中的角色ID为null或者为0返回提示要求选择用户角色的错误信息。
if (userForm.getRoleId() == null || userForm.getRoleId() == 0) {
return Result.failed("请选择用户角色");
}
// 将UserForm对象转换为User实体对象方便后续持久化操作使用UserConverter进行转换。
User user = userConverter.fromToEntity(userForm);
// 调用userMapper的insert方法将用户数据插入到数据库中执行插入操作。
userMapper.insert(user);
// 返回表示用户创建成功的结果信息,通常可以在前端等地方展示给用户相应提示。
return Result.success("用户创建成功");
}
// 修改用户密码的方法根据传入的UserForm对象中的新旧密码信息进行验证和更新操作密码修改成功后清除Redis中对应的token用于让用户重新登录并返回相应的提示信息验证失败返回错误提示。
@Override
public Result<String> updatePassword(UserForm userForm) {
// 验证两次输入的新密码是否一致,如果不一致,返回两次密码不一致的错误提示信息。
if (!userForm.getNewPassword().equals(userForm.getCheckedPassword())) {
return Result.failed("两次密码不一致");
}
// 获取当前登录用户的ID通过SecurityUtil工具类获取具体获取逻辑在该工具类中实现可能从登录信息等方面获取当前用户的唯一标识ID
Integer userId = SecurityUtil.getUserId();
// 使用BCryptPasswordEncoder验证输入的旧密码是否与数据库中存储的当前用户的密码匹配如果不匹配返回旧密码错误的提示信息。
if (!new BCryptPasswordEncoder()
.matches(userForm.getOriginPassword(),userMapper.selectById(userId).getPassword())) {
.matches(userForm.getOriginPassword(), userMapper.selectById(userId).getPassword())) {
return Result.failed("旧密码错误");
}
//密码加密
// 对新密码进行加密使用BCryptPasswordEncoder将新密码加密后设置到用户表单对象的密码字段中,用于后续更新到数据库中存储加密后的密码。
userForm.setPassword(new BCryptPasswordEncoder().encode(userForm.getNewPassword()));
// 设置用户表单对象的ID为当前登录用户的ID确保更新的是当前登录用户的密码信息。
userForm.setId(userId);
// 调用userMapper的updateById方法根据用户表单对象转换后的用户实体对象通过UserConverter转换来更新数据库中当前用户的密码信息返回受影响的行数即实际更新的记录数。
int updated = userMapper.updateById(userConverter.fromToEntity(userForm));
//密码修改成功清除redis的token让用户重新登录
// 如果更新操作影响的行数大于0表示密码修改成功清除Redis中对应会话的token这里假设token存储在Redis中键为请求会话的ID加上"token"字符串),让用户重新登录,然后返回修改成功并提示重新登录的结果信息。
if (updated > 0) {
stringRedisTemplate.delete(request.getSession().getId() + "token");
return Result.success("修改成功,请重新登录");
}
// 如果更新操作影响的行数为0表示密码修改失败可能是其他原因导致更新未成功返回旧密码错误的提示信息这里可以根据实际情况细化错误提示
return Result.failed("旧密码错误");
}
// 批量删除用户的方法事务性操作根据传入的以逗号分隔的用户ID字符串将对应的用户及其相关联的数据如用户创建的考试、练习记录、题目等多方面相关数据进行删除操作成功返回删除成功的提示信息具体删除逻辑中部分代码被注释掉可按需取消注释启用。
@Override
@Transactional
public Result<String> deleteBatchByIds(String ids) {
// 将传入的以逗号分隔的字符串形式的用户ID列表转换为Integer类型的列表使用Java 8的Stream API进行转换。
List<Integer> userIds = Arrays.stream(ids.split(",")).map(Integer::parseInt).toList();
// //删除用户证书
// //删除用户证书调用certificateUserMapper的deleteByUserIds方法传入用户ID列表批量删除用户证书相关记录假设该方法实现了批量删除逻辑当前代码中此部分被注释掉可按需启用
// certificateUserMapper.deleteByUserIds(userIds);
// //删除用户创建的考试
// //删除用户创建的考试调用examMapper的deleteByUserIds方法传入用户ID列表批量删除用户创建的考试记录同样被注释掉可按需启用
// examMapper.deleteByUserIds(userIds);
// //删除用户考试作答记录
// //删除用户考试作答记录调用examQuAnswerMapper的deleteByUserIds方法传入用户ID列表批量删除用户考试作答记录注释状态可按需启用
// examQuAnswerMapper.deleteByUserIds(userIds);
// //删除用户练习记录
// //删除用户练习记录调用exerciseRecordMapper的deleteByUserIds方法传入用户ID列表批量删除用户练习记录注释部分可按需启用
// exerciseRecordMapper.deleteByUserIds(userIds);
// //清除用户表中的班级id
// //清除用户表中的班级id先调用gradeMapper的selectIdsByUserIds方法传入用户ID列表获取这些用户对应的班级ID列表假设该方法实现了查询逻辑
// List<Integer> gradeIds = gradeMapper.selectIdsByUserIds(userIds);
// //如果获取到的班级ID列表不为空表示有相关班级需要处理调用userMapper的removeGradeIdByGradeIds方法传入班级ID列表清除用户表中对应的班级ID关联假设该方法实现了清除逻辑当前代码中这部分整体被注释掉可按需启用
// if (!gradeIds.isEmpty()) {
// userMapper.removeGradeIdByGradeIds(gradeIds);
// }
//
// //删除用户创建的班级
// //删除用户创建的班级调用gradeMapper的deleteByUserId方法传入用户ID列表批量删除用户创建的班级记录注释部分可按需启用
// gradeMapper.deleteByUserId(userIds);
// //删除用户创建的可供学生练习的题库关联表
// //删除用户创建的可供学生练习的题库关联表调用gradeExerciseMapper的deleteByUserIds方法传入用户ID列表批量删除相关记录注释状态可按需启用
// gradeExerciseMapper.deleteByUserIds(userIds);
// //删除用户批改的主观题分数
// //删除用户批改的主观题分数调用manualScoreMapper的deleteByUserIds方法传入用户ID列表批量删除用户批改主观题分数的相关记录注释内容可按需启用
// manualScoreMapper.deleteByUserIds(userIds);
// //删除公告与班级关联表
// //1.获取公告id
// //删除公告与班级关联表以下是分步骤操作的逻辑先获取公告id再根据公告id删除关联。
// //1.获取公告id调用noticeMapper的selectIdsByUserIds方法传入用户ID列表获取这些用户创建的公告对应的公告ID列表假设该方法实现了查询逻辑当前被注释可按需启用
// List<Integer> noticeIds = noticeMapper.selectIdsByUserIds(userIds);
// //2.删除公告与班级关联
// //2.删除公告与班级关联如果获取到的公告ID列表不为空表示有相关公告需要处理调用noticeGradeMapper的deleteByNoticeIds方法传入公告ID列表删除公告与班级的关联记录假设该方法实现了删除逻辑这部分整体被注释掉可按需启用
// if (!noticeIds.isEmpty()) {
// noticeGradeMapper.deleteByNoticeIds(noticeIds);
// }
// //删除用户创建的公告
// //删除用户创建的公告调用noticeMapper的deleteByUserIds方法传入用户ID列表批量删除用户创建的公告记录注释部分可按需启用
// noticeMapper.deleteByUserIds(userIds);
//
// //删除试题选项
// //1.获取用户创建的试题id
// //删除试题选项先获取用户创建的试题id调用questionMapper的selectIdsByUserIds方法传入用户ID列表获取这些用户创建的试题对应的试题ID列表假设该方法实现了查询逻辑当前代码中处于注释状态可按需启用
// List<Integer> quIds = questionMapper.selectIdsByUserIds(userIds);
// //2.根据试题id列表删除选项
// //2.根据试题id列表删除选项如果获取到的试题ID列表不为空表示有相关试题选项需要处理调用optionMapper的deleteBatchByQuIds方法传入试题ID列表批量删除对应的试题选项假设该方法实现了批量删除逻辑这部分被注释掉可按需启用
// if (!quIds.isEmpty()) {
// optionMapper.deleteBatchByQuIds(quIds);
// }
// //删除用户创建的试题
// //删除用户创建的试题调用questionMapper的deleteByUserIds方法传入用户ID列表批量删除用户创建的试题记录注释内容可按需启用
// questionMapper.deleteByUserIds(userIds);
// //删除用户创建的题库
// //删除用户创建的题库调用repoMapper的deleteByUserIds方法传入用户ID列表批量删除用户创建的题库记录注释部分可按需启用
// repoMapper.deleteByUserIds(userIds);
// //删除用户的错题本
// //删除用户的错题本调用userBookMapper的deleteByUserIds方法传入用户ID列表批量删除用户的错题本记录注释状态可按需启用
// userBookMapper.deleteByUserIds(userIds);
// //删除用户
// //删除用户调用userMapper的deleteByUserIds方法传入用户ID列表删除对应的用户记录返回受影响的行数即实际删除的用户数量。
Integer row = userMapper.deleteByUserIds(userIds);
// //
// 返回删除成功的结果信息目前代码中只是简单返回成功提示实际可根据row的值进一步判断是否真的删除成功等情况进行更准确的提示信息返回比如如果row为0可以返回更详细的失败提示等
return Result.success("删除成功");
}
// 从Excel文件导入用户数据的方法事务性操作先对文件类型进行判断读取文件内容并补充必要的参数然后将用户数据批量插入到数据库中成功返回导入成功的提示信息失败返回相应错误提示使用了异常处理注解来简化异常抛出处理。
@SneakyThrows(Exception.class)
// 使用 @SneakyThrows 注解,当方法内部抛出 Exception 异常时,会自动将异常向上抛出,避免了显式编写 try-catch 块来处理异常(但需要注意在合适的调用层次上进行统一的异常处理),这里表示该方法可能会出现各种异常情况,都统一按此方式处理。
@Override
// 表示重写了接口中定义的方法,说明该方法是实现某个接口规范要求的具体实现逻辑。
@Transactional
// 声明该方法是事务性的,意味着在这个方法执行过程中涉及到的数据库操作(如插入、更新、删除等)会被当作一个整体事务来处理,要么全部成功提交,要么全部回滚,保证数据的一致性和完整性。
public Result<String> importUsers(MultipartFile file) {
//文件类型判断
// 文件类型判断
// 检查传入的文件是否是合法的 Excel 文件xls 或 xlsx 格式),调用 ExcelUtils 工具类的 isExcel 方法进行判断,传入文件的原始文件名(通过 Objects.requireNonNull 确保文件名不为空)。
if (!ExcelUtils.isExcel(Objects.requireNonNull(file.getOriginalFilename()))) {
return Result.failed("文件类型必须是xls或xlsx");
// 如果不是合法的 Excel 文件格式,返回一个表示操作失败的 Result 结果对象,其中包含提示信息“文件类型必须是 xls 或 xlsx”告知调用者文件格式不符合要求具体 Result 对象的结构和使用方式需结合项目中相关定义来看。
return Result.failed("文件类型必须是 xls 或 xlsx");
}
//读取文件
// 读取文件
// 调用 ExcelUtils 工具类的 readMultipartFile 方法,传入文件对象和 UserForm 类信息,读取 Excel 文件内容并转换为 UserForm 类型的列表UserForm 可能是用于接收 Excel 文件中每行数据对应的用户信息的表单对象,存储在 list 变量中。
List<UserForm> list = ExcelUtils.readMultipartFile(file, UserForm.class);
//参数补充
// 参数补充
// 遍历读取到的 UserForm 列表,对每个 UserForm 对象进行一些必要参数的补充设置操作,以下是具体的设置逻辑。
list.forEach(userForm -> {
// 使用 BCryptPasswordEncoderSpring Security 提供的密码加密工具类对默认密码“123456”进行加密并将加密后的密码设置到 UserForm 对象的密码字段中,通常用于为新导入的用户设置初始密码。
userForm.setPassword(new BCryptPasswordEncoder().encode("123456"));
// 调用 DateTimeUtil 工具类的 getDateTime 方法获取当前日期时间信息,并设置到 UserForm 对象的创建时间createTime字段中用于记录用户的创建时间。
userForm.setCreateTime(DateTimeUtil.getDateTime());
// 判断 UserForm 对象中的角色 IDroleId字段是否为 null如果是表示没有指定角色这里默认将角色 ID 设置为 1具体角色 ID 为 1 代表什么角色需结合业务逻辑确定,可能是学生等默认角色)。
if (userForm.getRoleId() == null) {
userForm.setRoleId(1);
}
});
// 判断读取到的用户信息列表的大小是否超过 300如果超过了限制数量返回一个表示操作失败的 Result 结果对象,包含提示信息“表中最多存放 300 条数据”,告知调用者导入的数据量超出了允许范围。
if (list.size() > 300) {
return Result.failed("表中最多存放300条数据");
return Result.failed("表中最多存放 300 条数据");
}
// 调用 userMapper可能是操作用户数据的持久层接口用于与数据库交互的 insertBatchUser 方法,先使用 userConverter可能是用于对象转换的工具类的 listFromToEntity 方法将 UserForm 列表转换为对应的用户实体对象列表,然后将这些用户实体对象批量插入到数据库中,实现批量导入用户的功能。
userMapper.insertBatchUser(userConverter.listFromToEntity(list));
// 如果用户数据批量插入成功,返回一个表示操作成功的 Result 结果对象,包含提示信息“用户导入成功”,告知调用者用户导入操作已顺利完成,具体 Result 对象的结构和使用方式需结合项目中相关定义来看。
return Result.success("用户导入成功");
}
@Override
// 重写接口中定义的方法,用于获取用户信息相关的业务逻辑实现。
public Result<UserVO> info() {
// 调用 userMapper操作用户数据的持久层接口的 info 方法,传入通过 SecurityUtil 工具类获取的当前登录用户的 IDSecurityUtil.getUserId() 获取用户 ID 的具体实现逻辑在该工具类中定义),从数据库中查询并获取当前用户的详细信息,返回的是 UserVO 类型的对象UserVO 可能是用于展示给前端等调用者的用户视图对象,包含了部分用户信息,存储在 userVo 变量中。
UserVO userVo = userMapper.info(SecurityUtil.getUserId());
// 将获取到的用户视图对象 userVo 中的密码字段设置为 null可能出于安全考虑防止将密码信息传递到前端等地方保证用户信息的安全性。
userVo.setPassword(null);
// 返回一个表示操作成功的 Result 结果对象,第二个参数传入处理后的用户视图对象 userVo告知调用者获取用户信息成功并将用户信息传递给调用者具体 Result 对象的结构和使用方式需结合项目中相关定义来看,这里第一个参数传入 null 可能是符合 Result 类的构造函数要求或者业务上不需要额外传递特定的提示信息(具体依项目设定)。
return Result.success(null, userVo);
}
@Override
// 重写接口中定义的方法,用于用户加入班级相关的业务逻辑实现。
public Result<String> joinGrade(String code) {
//获取班级信息
// 获取班级信息
// 创建一个 LambdaQueryWrapper<Grade> 对象用于构建查询班级信息的条件LambdaQueryWrapper 是 MyBatis Plus 提供的方便构建查询条件的工具类,这里用于查询 Grade 类型(可能代表班级实体类)的数据。
LambdaQueryWrapper<Grade> wrapper = new LambdaQueryWrapper<Grade>().eq(Grade::getCode, code);
// 调用 gradeMapper可能是操作班级数据的持久层接口的 selectOne 方法,传入构建好的查询条件 wrapper从数据库中查询符合班级口令code的班级信息返回的是 Grade 类型的对象,代表查询到的班级实体信息,存储在 grade 变量中,如果没有找到匹配的班级则返回 null。
Grade grade = gradeMapper.selectOne(wrapper);
// 判断查询到的班级对象是否为 null如果是表示没有找到对应的班级班级口令不存在返回一个表示操作失败的 Result 结果对象,包含提示信息“班级口令不存在”,告知调用者输入的班级口令有误,无法加入班级,具体 Result 对象的结构和使用方式需结合项目中相关定义来看。
if (Objects.isNull(grade)) {
return Result.failed("班级口令不存在");
}
// 创建一个 User 类型的用户对象,用于后续更新用户与班级关联信息的操作,这里先初始化该对象。
User user = new User();
// 设置用户对象的 ID 为通过 SecurityUtil 工具类获取的当前登录用户的 ID确保操作的是当前登录用户的信息。
user.setId(SecurityUtil.getUserId());
// 设置用户对象的班级 IDgradeId为查询到的班级的 IDgrade.getId()),建立用户与班级的关联关系,准备将当前用户加入到该班级中。
user.setGradeId(grade.getId());
// 调用 userMapper 的 updateById 方法,传入构建好的用户对象,根据用户 ID 更新数据库中对应用户的班级关联信息,返回受影响的行数,即实际更新的记录数,存储在 updated 变量中。
int updated = userMapper.updateById(user);
// 判断更新操作影响的行数是否大于 0如果大于 0表示用户成功加入班级执行以下操作。
if (updated > 0) {
stringRedisTemplate.delete("cache:grade:getPaging:"+SecurityUtil.getUserId().toString());
// 调用 stringRedisTemplate用于操作 Redis 字符串类型数据的工具类,常用于缓存相关操作)的 delete 方法,删除 Redis 中对应缓存键(格式为 "cache:grade:getPaging:" 加上当前登录用户的 ID 的字符串形式)的缓存数据,目的可能是清除与用户班级相关的缓存信息,确保后续获取的班级信息是最新的。
stringRedisTemplate.delete("cache:grade:getPaging:" + SecurityUtil.getUserId().toString());
// 返回一个表示操作成功的 Result 结果对象包含提示信息“加入班级”加上班级名称grade.getGradeName())再加上“成功”,告知调用者已成功加入指定班级,具体 Result 对象的结构和使用方式需结合项目中相关定义来看。
return Result.success("加入班级:" + grade.getGradeName() + "成功");
}
// 如果更新操作影响的行数不大于 0表示用户加入班级操作失败返回一个表示操作失败的 Result 结果对象,包含提示信息“加入失败”,告知调用者加入班级的操作未成功,具体 Result 对象的结构和使用方式需结合项目中相关定义来看。
return Result.failed("加入失败");
}
@Override
// 重写接口中定义的方法,用于实现用户分页查询相关的业务逻辑。
public Result<IPage<UserVO>> pagingUser(Integer pageNum, Integer pageSize, Integer gradeId, String realName) {
// 创建一个 IPage<UserVO> 类型的分页对象 page使用 Page 的构造函数进行初始化,传入当前页码 pageNum 和每页显示数量 pageSize用于构建分页相关的数据结构方便后续设置分页数据以及返回给前端进行分页展示等操作。
IPage<UserVO> page = new Page<>(pageNum, pageSize);
// 判断当前用户的角色(通过 SecurityUtil 工具类的 getRole 方法获取具体获取逻辑在该工具类中定义是否为“role_teacher”可能代表教师角色具体含义由业务定义如果是教师角色执行以下操作。
if ("role_teacher".equals(SecurityUtil.getRole())) {
page = userMapper.pagingUser(page, gradeId, realName, SecurityUtil.getUserId(),1);
// 调用 userMapper 的 pagingUser 方法,传入分页对象 page、班级 IDgradeId、真实姓名realName、当前登录用户的 IDSecurityUtil.getUserId())以及参数 1具体该参数含义需结合业务逻辑确定可能与教师角色下的查询条件等相关根据这些条件查询并设置分页对象 page 中的用户数据信息,用于教师角色下的用户分页查询逻辑。
page = userMapper.pagingUser(page, gradeId, realName, SecurityUtil.getUserId(), 1);
} else {
page = userMapper.pagingUser(page, gradeId, realName, SecurityUtil.getUserId(),null);
// 如果当前用户不是教师角色,执行以下操作,同样调用 userMapper 的 pagingUser 方法,但传入的最后一个参数为 null可能对应非教师角色下不同的查询条件或业务逻辑根据相应条件查询并设置分页对象 page 中的用户数据信息,用于其他角色下的用户分页查询逻辑。
page = userMapper.pagingUser(page, gradeId, realName, SecurityUtil.getUserId(), null);
}
// 返回一个表示操作成功的 Result 结果对象,第二个参数传入构建好的分页对象 page告知调用者用户分页查询成功并将分页查询结果传递给调用者具体 Result 对象的结构和使用方式需结合项目中相关定义来看,这里第一个参数传入 null 可能是符合 Result 类的构造函数要求或者业务上不需要额外传递特定的提示信息(具体依项目设定)。
return Result.success(null, page);
}
@Transactional
// 声明该方法是事务性的,保证方法内涉及的数据库操作作为一个整体事务处理,确保数据一致性和完整性。
@Override
// 重写接口中定义的方法,用于实现用户上传头像相关的业务逻辑。
public Result<String> uploadAvatar(MultipartFile file) {
// 调用 iQuestionService可能是与题目相关的业务服务类这里复用了其上传图片的功能具体原因需结合业务逻辑确定的 uploadImage 方法,传入文件对象,尝试上传用户头像文件,并获取上传操作的结果信息,存储在 result 变量中。
Result<String> result = iQuestionService.uploadImage(file);
// 打印上传操作的结果信息,可能用于调试等目的,查看上传过程中的返回结果情况(在实际生产环境中可能需要根据日志级别等进行合理配置,避免过多不必要的输出)。
System.out.println(result);
// 判断上传结果的状态码(通过 result.getCode() 获取,具体 Result 类中 code 字段的定义和含义需结合项目中相关定义来看)是否为 0如果是 0表示图片上传失败执行以下返回操作。
if (result.getCode() == 0) {
return Result.failed("图片上传失败");
}
// 如果上传结果状态码不为 0表示图片上传成功获取上传后图片的访问地址通过 result.getData() 获取,具体 Result 类中 data 字段用于存储相关数据的定义需结合项目中相关定义来看),存储在 url 变量中。
String url = result.getData();
// 再次打印图片的访问地址,同样可能用于调试等目的,查看获取到的地址是否正确(在实际应用中可根据情况决定是否保留此输出语句)。
System.out.println(url);
// 创建一个 User 类型的用户对象,用于后续更新用户头像信息的操作,这里先初始化该对象。
User user = new User();
// 设置用户对象的 ID 为通过 SecurityUtil 工具类获取的当前登录用户的 ID确保操作的是当前登录用户的头像信息。
user.setId(SecurityUtil.getUserId());
// 设置用户对象的头像字段avatar为获取到的图片访问地址 url准备将用户头像更新为新上传的图片。
user.setAvatar(url);
// 调用 userMapper 的 updateById 方法,传入构建好的用户对象,根据用户 ID 更新数据库中对应用户的头像信息,返回受影响的行数,即实际更新的记录数,判断该行数是否大于 0如果大于 0表示头像更新成功执行以下返回操作。
if (userMapper.updateById(user) > 0) {
// 返回一个表示操作成功的 Result 结果对象,包含提示信息“上传成功”以及上传后图片的访问地址 url告知调用者头像上传成功并将图片地址传递给调用者方便后续使用比如在前端展示用户头像等具体 Result 对象的结构和使用方式需结合项目中相关定义来看。
return Result.success("上传成功", url);
}
// 如果更新操作影响的行数不大于 0表示头像更新失败返回一个表示操作失败的 Result 结果对象,包含提示信息“图片上传失败”,告知调用者头像上传操作未成功,具体 Result 对象的结构和使用方式需结合项目中相关定义来看。
return Result.failed("图片上传失败");
}
}
}

@ -1,7 +1,5 @@
package cn.org.alan.exam.util;
import cn.org.alan.exam.util.CryptoUtils.Algorithm.Encryption;
import cn.org.alan.exam.util.CryptoUtils.Algorithm.Signing;
import org.apache.commons.lang3.StringUtils;
@ -11,7 +9,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
@ -40,7 +37,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* AESDESRSA
* AESDESRSA便
*/
/**
@ -51,311 +48,493 @@ import java.util.concurrent.ConcurrentHashMap;
@Component
public class CryptoUtils {
// 定义默认的字符编码格式为UTF-8用于处理字符串和字节之间的转换操作保证统一的编码标准。
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
// 获取Base64的编码器实例用于将字节数据编码为Base64格式的字符串常用于加密后数据的编码传输等场景。
private static final Encoder BASE64_ENCODER = Base64.getEncoder();
// 获取Base64的解码器实例用于将Base64格式的字符串解码为原始字节数据比如在解密等操作前对密文进行解码。
private static final Decoder BASE64_DECODER = Base64.getDecoder();
// 使用ConcurrentHashMap缓存KeyFactory实例以算法Algorithm类型为键方便后续快速获取相应算法的KeyFactory避免重复创建提高性能并且支持并发访问。
private static final Map<Algorithm, KeyFactory> KEY_FACTORY_CACHE = new ConcurrentHashMap<>();
// 使用HashMap缓存Cipher实例同样以算法Algorithm类型为键用于存储加密、解密操作时对应的Cipher对象减少创建开销但需注意非并发安全情况下的使用场景此处代码中有相应的同步处理机制保证线程安全
private static final Map<Algorithm, Cipher> CIPHER_CACHE = new HashMap<>();
/**
* AESDES
* @param algorithm
* @return
* @throws NoSuchAlgorithmException
* AESDESBase64
*
* @param algorithm AlgorithmAlgorithm.AES_ECB_PKCS5
* @return Base64
* @throws NoSuchAlgorithmException
*/
public static String generateSymmetricKey(Algorithm algorithm) throws NoSuchAlgorithmException {
// 根据指定的算法名称获取对应的KeyGenerator实例用于生成对称密钥。
KeyGenerator generator = KeyGenerator.getInstance(algorithm.getName());
// 使用算法对应的默认密钥长度通过Algorithm中定义的keySize属性获取初始化KeyGenerator准备生成密钥。
generator.init(algorithm.getKeySize());
// 生成对称密钥返回的SecretKey对象包含了实际的密钥数据。
SecretKey secretKey = generator.generateKey();
// 将生成的对称密钥转换为字节数组后使用Base64编码器将其编码为字符串并返回方便存储和传输。
return BASE64_ENCODER.encodeToString(secretKey.getEncoded());
}
/**
* RSADSAPKCS8
* @param algorithm
* @return
* @throws NoSuchAlgorithmException
* RSADSAPKCS8Base64AsymmetricKeyPair
*
* @param algorithm AlgorithmAlgorithm.RSA_ECB_PKCS1
* @return Base64AsymmetricKeyPair
* @throws NoSuchAlgorithmException
*/
public static AsymmetricKeyPair generateAsymmetricKeyPair(Algorithm algorithm) throws NoSuchAlgorithmException {
// 根据指定的算法名称获取对应的KeyPairGenerator实例用于生成非对称密钥对。
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm.getName());
// 使用算法对应的默认密钥长度通过Algorithm中定义的keySize属性获取初始化KeyPairGenerator准备生成密钥对。
generator.initialize(algorithm.getKeySize());
// 生成非对称密钥对返回的KeyPair对象包含了公钥和私钥。
KeyPair keyPair = generator.generateKeyPair();
// 将公钥转换为字节数组后使用Base64编码器将其编码为字符串得到公钥的Base64编码表示形式。
String publicKey = BASE64_ENCODER.encodeToString(keyPair.getPublic().getEncoded());
// 将私钥转换为字节数组后使用Base64编码器将其编码为字符串得到私钥的Base64编码表示形式。
String privateKey = BASE64_ENCODER.encodeToString(keyPair.getPrivate().getEncoded());
// 创建AsymmetricKeyPair对象将公钥和私钥的Base64编码字符串传入封装后返回。
return new AsymmetricKeyPair(publicKey, privateKey);
}
/**
* 使RSARSA_ECB_PKCS1encryptAsymmetrically
*
* @param publicKeyText Base64
* @param plainText
* @return Base64
* @throws Exception
*/
public static String encryptByRSA(String publicKeyText, String plainText) throws Exception {
return encryptAsymmetrically(publicKeyText, plainText, Encryption.RSA_ECB_PKCS1);
}
/**
* 使RSARSA_ECB_PKCS1decryptAsymmetrically
*
* @param privateKeyText Base64
* @param ciphertext Base64
* @return
* @throws Exception
*/
public static String decryptByRSA(String privateKeyText, String ciphertext) throws Exception {
return decryptAsymmetrically(privateKeyText, ciphertext, Encryption.RSA_ECB_PKCS1);
}
/**
* SHA1DSA使
* @param privateKeyText
* @param msg
* @return
* @throws Exception
* SHA1DSA使doSign
*
* @param privateKeyText Base64
* @param msg
* @return Base64
* @throws Exception
*/
public static String signBySHA1WithDSA(String privateKeyText, String msg) throws Exception {
return doSign(privateKeyText, msg, Encryption.DSA, Signing.SHA1WithDSA);
}
/**
* SHA1RSA使
* @param privateKeyText
* @param msg
* @return
* @throws Exception
* SHA1RSA使doSign
*
* @param privateKeyText Base64
* @param msg
* @return Base64
* @throws Exception
*/
public static String signBySHA1WithRSA(String privateKeyText, String msg) throws Exception {
return doSign(privateKeyText, msg, Encryption.RSA_ECB_PKCS1, Signing.SHA1WithRSA);
}
/**
* SHA256RSA使
* @param privateKeyText
* @param msg
* @return
* @throws Exception
* SHA256RSA使doSign
*
* @param privateKeyText Base64
* @param msg
* @return Base64
* @throws Exception
*/
public static String signBySHA256WithRSA(String privateKeyText, String msg) throws Exception {
return doSign(privateKeyText, msg, Encryption.RSA_ECB_PKCS1, Signing.SHA256WithRSA);
}
/**
* SHA1DSA
* @param publicKeyText
* @param msg
* @param signatureText
* @return
* @throws Exception
* SHA1DSAdoVerify
*
* @param publicKeyText Base64
* @param msg
* @param signatureText Base64
* @return truefalse
* @throws Exception
*/
public static boolean verifyBySHA1WithDSA(String publicKeyText, String msg, String signatureText) throws Exception {
return doVerify(publicKeyText, msg, signatureText, Encryption.DSA, Signing.SHA1WithDSA);
}
/**
* SHA1RSA
* @param publicKeyText
* @param msg
* @param signatureText
* @return
* @throws Exception
* SHA1RSAdoVerify
*
* @param publicKeyText Base64
* @param msg
* @param signatureText Base64
* @return truefalse
* @throws Exception
*/
public static boolean verifyBySHA1WithRSA(String publicKeyText, String msg, String signatureText) throws Exception {
return doVerify(publicKeyText, msg, signatureText, Encryption.RSA_ECB_PKCS1, Signing.SHA1WithRSA);
}
/**
* SHA256RSA
* @param publicKeyText
* @param msg
* @param signatureText
* @return
* @throws Exception
* SHA256RSAdoVerify
*
* @param publicKeyText Base64
* @param msg
* @param signatureText Base64
* @return truefalse
* @throws Exception
*/
public static boolean verifyBySHA256WithRSA(String publicKeyText, String msg, String signatureText) throws Exception {
public static boolean verifyBySHA256WithRSA(String publicKeyText, String msg, signatureText) throws Exception {
return doVerify(publicKeyText, msg, signatureText, Encryption.RSA_ECB_PKCS1, Signing.SHA256WithRSA);
}
/**
* Base64
*
* @param secretKey Base64
* @param iv CBCCBC使
* @param plainText
* @param algorithm AlgorithmAlgorithm.AES_ECB_PKCS5
* @return Base64
* @throws Exception
*/
public static String encryptSymmetrically(String secretKey, String iv, String plainText, Algorithm algorithm) throws Exception {
// 先将Base64编码的对称密钥解码并重新生成SecretKey实例用于后续的加密操作。
SecretKey key = decodeSymmetricKey(secretKey, algorithm);
// 根据传入的加密向量字符串如果非空解码生成IvParameterSpec实例用于指定加密的初始向量信息CBC模式等需要若为空则设置为null表示不需要初始向量如ECB模式
IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv)? null : decodeIv(iv);
// 将明文字符串按照默认字符编码格式UTF-8转换为字节数组准备进行加密操作。
byte[] plainTextInBytes = plainText.getBytes(DEFAULT_CHARSET);
// 调用transform方法传入算法、加密模式、密钥、加密向量以及明文字节数组执行实际的加密操作得到加密后的字节数组。
byte[] ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, key, ivParameterSpec, plainTextInBytes);
// 将加密后的字节数组使用Base64编码器转换为字符串并返回作为最终的加密结果密文
return BASE64_ENCODER.encodeToString(ciphertextInBytes);
}
/**
*
*
* @param secretKey Base64
* @param iv CBCCBC
* @param ciphertext Base64
* @param algorithm AlgorithmAlgorithm.AES_ECB_PKCS5
* @return
* @throws Exception
*/
public static boolean verifyBySHA256WithRSA(String publicKeyText, String msg, String signatureText) throws Exception {
// 调用doVerify方法传入公钥文本publicKeyText、待验证的消息msg、数字签名文本signatureText以及指定的加密算法Encryption.RSA_ECB_PKCS1和签名算法Signing.SHA256WithRSA
// 目的是使用RSA加密算法结合SHA256签名算法来验证给定的数字签名是否与消息匹配最终返回验证结果true表示验证通过false表示验证失败
return doVerify(publicKeyText, msg, signatureText, Encryption.RSA_ECB_PKCS1, Signing.SHA256WithRSA);
}
/**
*
* @param secretKey
* @param iv CBCCBC
* @param plainText
* @param algorithm AESDES
* @return
* @throws Exception
* @param secretKey Base64
* @param iv CBCCBC使CBC
* @param plainText
* @param algorithm AESDESAlgorithm使
* @return Base64便
* @throws Exception
*/
public static String encryptSymmetrically(String secretKey, String iv, String plainText, Algorithm algorithm) throws Exception {
// 调用decodeSymmetricKey方法传入对称密钥secretKey和指定的对称加密算法algorithm将Base64编码的对称密钥解码并重新生成SecretKey实例用于后续的加密操作得到实际可用的对称密钥对象存储在key变量中。
SecretKey key = decodeSymmetricKey(secretKey, algorithm);
IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv) ? null : decodeIv(iv);
// 通过StringUtils.isBlank判断传入的加密向量iv是否为空字符串如果为空则将ivParameterSpec设置为null表示不需要加密向量例如在ECB等不需要加密向量的加密模式下
// 如果不为空则调用decodeIv方法对加密向量进行解码并生成IvParameterSpec实例用于指定加密的初始向量信息在CBC模式等需要的情况下存储在ivParameterSpec变量中。
IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv)? null : decodeIv(iv);
// 将明文字符串plainText按照默认字符编码格式DEFAULT_CHARSET即UTF-8转换为字节数组准备进行加密操作得到的字节数组存储在plainTextInBytes变量中这是因为加密算法通常操作的是字节数据。
byte[] plainTextInBytes = plainText.getBytes(DEFAULT_CHARSET);
// 调用transform方法传入对称加密算法algorithm、加密模式Cipher.ENCRYPT_MODE表示加密操作、生成的对称密钥key、加密向量ivParameterSpec以及明文字节数组plainTextInBytes
// 执行实际的对称加密操作得到加密后的字节数组存储在ciphertextInBytes变量中。
byte[] ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, key, ivParameterSpec, plainTextInBytes);
// 将加密后的字节数组ciphertextInBytes使用Base64编码器BASE64_ENCODER转换为字符串即将二进制的加密数据编码为可方便存储和传输的Base64编码字符串形式然后返回该字符串作为最终的加密结果密文
return BASE64_ENCODER.encodeToString(ciphertextInBytes);
}
/**
*
* @param secretKey
* @param iv CBCCBC
* @param ciphertext
* @param algorithm AESDES
* @return
* @throws Exception
* @param secretKey Base64使
* @param iv CBCCBC使
* @param ciphertext Base64
* @param algorithm AESDESAlgorithm使使
* @return
* @throws Exception
*/
public static String decryptSymmetrically(String secretKey, String iv, String ciphertext, Algorithm algorithm) throws Exception {
// 调用decodeSymmetricKey方法传入对称密钥secretKey和指定的对称加密算法algorithm将Base64编码的对称密钥解码并重新生成SecretKey实例用于后续的解密操作得到实际可用的对称密钥对象存储在key变量中。
SecretKey key = decodeSymmetricKey(secretKey, algorithm);
IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv) ? null : decodeIv(iv);
// 通过StringUtils.isBlank判断传入的加密向量iv是否为空字符串如果为空则将ivParameterSpec设置为null表示不需要加密向量对应不需要加密向量的加密模式下的解密情况
// 如果不为空则调用decodeIv方法对加密向量进行解码并生成IvParameterSpec实例用于指定解密时的初始向量信息在CBC模式等需要的情况下存储在ivParameterSpec变量中。
IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv)? null : decodeIv(iv);
// 使用Base64解码器BASE64_DECODER对传入的密文ciphertext进行解码将Base64编码的字符串转换回原始的字节数组得到的字节数组存储在ciphertextInBytes变量中这是因为后续解密操作需要操作字节数据。
byte[] ciphertextInBytes = BASE64_DECODER.decode(ciphertext);
// 调用transform方法传入对称加密算法algorithm、解密模式Cipher.DECRYPT_MODE表示解密操作、生成的对称密钥key、加密向量ivParameterSpec以及密文字节数组ciphertextInBytes
// 执行实际的对称解密操作得到解密后的字节数组存储在plainTextInBytes变量中。
byte[] plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, key, ivParameterSpec, ciphertextInBytes);
// 将解密后的字节数组plainTextInBytes按照默认字符编码格式DEFAULT_CHARSET即UTF-8转换为字符串即将字节数据还原为原始的文本内容然后返回该字符串作为最终的解密结果明文
return new String(plainTextInBytes, DEFAULT_CHARSET);
}
/**
*
* @param publicKeyText
* @param plainText
* @param algorithm
* @return
* @throws Exception
* @param publicKeyText Base64
* @param plainText
* @param algorithm Algorithm使RSA
* @return Base64便
* @throws Exception
*/
public static String encryptAsymmetrically(String publicKeyText, String plainText, Algorithm algorithm) throws Exception {
// 调用regeneratePublicKey方法传入公钥文本publicKeyText和指定的非对称加密算法algorithm将Base64编码的公钥解码并重新生成PublicKey实例用于后续的加密操作得到实际可用的公钥对象存储在publicKey变量中。
PublicKey publicKey = regeneratePublicKey(publicKeyText, algorithm);
// 将明文字符串plainText按照默认字符编码格式DEFAULT_CHARSET即UTF-8转换为字节数组准备进行加密操作得到的字节数组存储在plainTextInBytes变量中因为加密算法通常是基于字节数据进行操作的。
byte[] plainTextInBytes = plainText.getBytes(DEFAULT_CHARSET);
// 调用transform方法传入非对称加密算法algorithm、加密模式Cipher.ENCRYPT_MODE表示加密操作、生成的公钥publicKey以及明文字节数组plainTextInBytes
// 执行实际的非对称加密操作得到加密后的字节数组存储在ciphertextInBytes变量中。
byte[] ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, publicKey, plainTextInBytes);
// 将加密后的字节数组ciphertextInBytes使用Base64编码器BASE64_ENCODER转换为字符串即将二进制的加密数据编码为可方便存储和传输的Base64编码字符串形式然后返回该字符串作为最终的加密结果密文
return BASE64_ENCODER.encodeToString(ciphertextInBytes);
}
/**
*
* @param privateKeyText
* @param ciphertext
* @param algorithm
* @return
* @throws Exception
*/
public static String decryptAsymmetrically(String privateKeyText, String ciphertext, Algorithm algorithm) throws Exception {
PrivateKey privateKey = regeneratePrivateKey(privateKeyText, algorithm);
byte[] ciphertextInBytes = BASE64_DECODER.decode(ciphertext);
byte[] plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, privateKey, ciphertextInBytes);
return new String(plainTextInBytes, DEFAULT_CHARSET);
}
/**
*
* @param privateKeyText
* @param ciphertext
* @param algorithm
* @return
* @throws Exception
*/
public static String decryptAsymmetrically(String privateKeyText, String ciphertext, Algorithm algorithm) throws Exception {
// 调用regeneratePrivateKey方法传入私钥文本privateKeyText和指定的非对称加密算法algorithm
// 目的是将Base64编码格式的私钥文本解码并重新生成对应的PrivateKey实例以便后续用于解密操作生成的私钥对象存储在privateKey变量中。
PrivateKey privateKey = regeneratePrivateKey(privateKeyText, algorithm);
// 使用Base64解码器BASE64_DECODER对传入的密文ciphertext进行解码操作将Base64编码的密文字符串转换为原始的字节数组形式
// 得到的字节数组存储在ciphertextInBytes变量中因为后续的解密操作需要基于字节数据进行处理。
byte[] ciphertextInBytes = BASE64_DECODER.decode(ciphertext);
// 调用transform方法传入非对称加密算法algorithm、解密模式Cipher.DECRYPT_MODE表示进行解密操作、刚生成的私钥privateKey以及密文字节数组ciphertextInBytes
// 执行实际的非对称解密操作将密文字节数组还原为原始的明文字节数组得到的明文字节数组存储在plainTextInBytes变量中。
byte[] plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, privateKey, ciphertextInBytes);
// 将解密得到的明文字节数组plainTextInBytes按照默认字符编码格式DEFAULT_CHARSET这里通常是UTF-8转换为字符串形式
// 从而将字节数据还原为原始的文本内容,最终返回这个解密后的字符串作为非对称解密的结果,也就是原始的明文信息。
return new String(plainTextInBytes, DEFAULT_CHARSET);
}
/**
*
* @param privateKeyText
* @param msg
* @param encryptionAlgorithm Algorithm
* @param signatureAlgorithm Algorithm
* @return
* @throws Exception
* @param privateKeyText Base64
* @param msg
* @param encryptionAlgorithm AlgorithmRSA
* @param signatureAlgorithm AlgorithmSHA1WithRSA
* @return Base64便
* @throws Exception
*/
public static String doSign(String privateKeyText, String msg, Algorithm encryptionAlgorithm, Algorithm signatureAlgorithm)
throws Exception {
// 调用regeneratePrivateKey方法传入私钥文本privateKeyText和指定的加密算法encryptionAlgorithm
// 作用是将Base64编码的私钥解码并重新生成对应的PrivateKey实例以便后续利用该私钥进行数字签名操作生成的私钥对象存储在privateKey变量中。
PrivateKey privateKey = regeneratePrivateKey(privateKeyText, encryptionAlgorithm);
// Signature只支持签名算法
// 通过Signature.getInstance方法根据传入的签名算法名称signatureAlgorithm.getName()获取对应的Signature实例
// Signature是Java中用于处理数字签名相关操作的类这里获取到的实例用于后续具体的签名生成操作因为它只支持签名算法相关功能。
Signature signature = Signature.getInstance(signatureAlgorithm.getName());
// 使用获取到的Signature实例调用initSign方法并传入私钥privateKey进行初始化操作
// 此操作是为了告知签名对象后续将使用该私钥来生成数字签名,为签名过程做准备。
signature.initSign(privateKey);
// 调用signature的update方法将需要签名的数据msg按照默认字符编码格式DEFAULT_CHARSET一般为UTF-8转换为字节数组后传入
// 这个操作是将待签名的数据传递给签名对象,以便它基于这些数据和之前初始化的私钥来生成数字签名,类似于告知签名对象要对哪些具体内容进行签名。
signature.update(msg.getBytes(DEFAULT_CHARSET));
// 调用signature的sign方法执行实际的数字签名生成操作该方法会基于前面设置的私钥、待签名数据等信息生成数字签名返回的是字节数组形式的签名结果存储在signatureInBytes变量中。
byte[] signatureInBytes = signature.sign();
// 将生成的字节数组形式的数字签名signatureInBytes使用Base64编码器BASE64_ENCODER进行编码转换为Base64编码字符串形式
// 方便在网络传输等场景中传递该数字签名,最终返回这个编码后的字符串作为生成的数字签名结果。
return BASE64_ENCODER.encodeToString(signatureInBytes);
}
/**
*
* @param publicKeyText
* @param msg
* @param signatureText
* @param encryptionAlgorithm Algorithm
* @param signatureAlgorithm Algorithm
* @return
* @throws Exception
* @param publicKeyText Base64使
* @param msg 使
* @param signatureText Base64使
* @param encryptionAlgorithm Algorithm使
* @param signatureAlgorithm Algorithm使
* @return truefalse
* @throws Exception
*/
public static boolean doVerify(String publicKeyText, String msg, String signatureText, Algorithm encryptionAlgorithm,
Algorithm signatureAlgorithm) throws Exception {
// 调用regeneratePublicKey方法传入公钥文本publicKeyText和指定的加密算法encryptionAlgorithm
// 目的是将Base64编码格式的公钥文本解码并重新生成对应的PublicKey实例以便后续利用该公钥进行数字签名验证操作生成的公钥对象存储在publicKey变量中。
PublicKey publicKey = regeneratePublicKey(publicKeyText, encryptionAlgorithm);
// 通过Signature.getInstance方法根据传入的签名算法名称signatureAlgorithm.getName()获取对应的Signature实例
// 用于后续具体的数字签名验证操作,因为它提供了验证签名有效性的相关功能。
Signature signature = Signature.getInstance(signatureAlgorithm.getName());
// 使用获取到的Signature实例调用initVerify方法并传入公钥publicKey进行初始化操作
// 此操作是为了告知验证对象后续将使用该公钥来验证数字签名,为验证过程做准备。
signature.initVerify(publicKey);
// 调用signature的update方法将需要验证的数据msg按照默认字符编码格式DEFAULT_CHARSET一般为UTF-8转换为字节数组后传入
// 这个操作是将原始数据传递给验证对象,使其基于这些数据和之前初始化的公钥来进行数字签名验证,类似于告知验证对象要验证的原始数据内容是什么。
signature.update(msg.getBytes(DEFAULT_CHARSET));
// 调用signature的verify方法执行实际的数字签名验证操作传入经过Base64解码后的数字签名通过BASE64_DECODER.decode(signatureText)获取原始字节数组形式的签名),
// 该方法会基于前面设置的公钥、原始数据以及传入的数字签名信息进行验证,返回一个布尔值,表示验证是否成功,直接返回这个布尔值作为最终的验证结果。
return signature.verify(BASE64_DECODER.decode(signatureText));
}
/**
* Base64SecretKey
* @param secretKey
* @param algorithm
* @return
*/
private static SecretKey decodeSymmetricKey(String secretKey, Algorithm algorithm) {
byte[] key = BASE64_DECODER.decode(secretKey);
return new SecretKeySpec(key, algorithm.getName());
}
/**
* Base64SecretKey
* @param secretKey
* @param algorithm
* @return
*/
private static SecretKey decodeSymmetricKey(String secretKey, Algorithm algorithm) {
// 使用BASE64_DECODERBase64解码器在类中前面已定义对传入的对称密钥字符串secretKey进行解码操作
// 将Base64编码格式的密钥字符串转换为原始的字节数组形式得到的字节数组存储在key变量中以便后续基于字节数据构建SecretKey对象。
byte[] key = BASE64_DECODER.decode(secretKey);
// 使用SecretKeySpec类根据解码后的字节数组key和指定的算法名称algorithm.getName()创建一个SecretKey实例
// SecretKeySpec实现了SecretKey接口用于包装字节数组形式的密钥数据使其成为符合特定对称加密算法要求的密钥对象最终返回这个SecretKey对象用于后续的对称加密或解密操作等。
return new SecretKeySpec(key, algorithm.getName());
}
private static IvParameterSpec decodeIv(String iv) {
// 使用BASE64_DECODERBase64解码器对传入的加密向量字符串iv进行解码操作
// 将Base64编码格式的加密向量字符串转换为原始的字节数组形式得到的字节数组存储在ivInBytes变量中后续将基于此字节数组构建IvParameterSpec对象用于加密或解密操作在需要加密向量的模式下
byte[] ivInBytes = BASE64_DECODER.decode(iv);
// 使用IvParameterSpec类根据解码后的字节数组ivInBytes创建一个IvParameterSpec实例
// IvParameterSpec用于指定加密或解密操作中使用的初始向量信息在如CBC模式等需要加密向量的情况下最终返回这个IvParameterSpec对象用于相关加密或解密流程。
return new IvParameterSpec(ivInBytes);
}
private static PublicKey regeneratePublicKey(String publicKeyText, Algorithm algorithm)
throws NoSuchAlgorithmException, InvalidKeySpecException {
// 使用BASE64_DECODERBase64解码器对传入的公钥字符串publicKeyText进行解码操作
// 将Base64编码格式的公钥字符串转换为原始的字节数组形式得到的字节数组存储在keyInBytes变量中以便后续基于这些字节数据构建PublicKey对象。
byte[] keyInBytes = BASE64_DECODER.decode(publicKeyText);
// 调用getKeyFactory方法传入指定的算法algorithm获取对应的KeyFactory实例KeyFactory用于根据特定算法创建密钥相关的对象如公钥、私钥等存储在keyFactory变量中。
KeyFactory keyFactory = getKeyFactory(algorithm);
// 公钥必须使用RSAPublicKeySpec或者X509EncodedKeySpec
// 按照Java加密规范要求公钥必须使用RSAPublicKeySpec或者X509EncodedKeySpec来构建这里使用X509EncodedKeySpec类
// 根据解码后的字节数组keyInBytes创建一个公钥规范对象KeySpec用于后续通过KeyFactory生成具体的PublicKey对象此规范定义了公钥的格式和数据内容等信息。
KeySpec publicKeySpec = new X509EncodedKeySpec(keyInBytes);
// 使用获取到的KeyFactory实例keyFactory调用其generatePublic方法并传入公钥规范对象publicKeySpec
// 执行实际的公钥生成操作将符合规范的字节数据转换为可用的PublicKey对象存储在publicKey变量中该PublicKey对象可用于后续的非对称加密、数字签名验证等操作。
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
// 返回生成的PublicKey对象供调用者在需要使用公钥的地方如加密、验证等操作使用。
return publicKey;
}
private static PrivateKey regeneratePrivateKey(String key, Algorithm algorithm) throws Exception {
// 使用BASE64_DECODERBase64解码器对传入的私钥字符串key进行解码操作
// 将Base64编码格式的私钥字符串转换为原始的字节数组形式得到的字节数组存储在keyInBytes变量中以便后续基于这些字节数据构建PrivateKey对象。
byte[] keyInBytes = BASE64_DECODER.decode(key);
// 调用getKeyFactory方法传入指定的算法algorithm获取对应的KeyFactory实例用于后续创建私钥对象存储在keyFactory变量中。
KeyFactory keyFactory = getKeyFactory(algorithm);
// 私钥必须使用RSAPrivateCrtKeySpec或者PKCS8EncodedKeySpec
// 按照Java加密规范要求私钥必须使用RSAPrivateCrtKeySpec或者PKCS8EncodedKeySpec来构建这里使用PKCS8EncodedKeySpec类
// 根据解码后的字节数组keyInBytes创建一个私钥规范对象KeySpec用于定义私钥的格式和数据内容等信息为生成私钥做准备。
KeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyInBytes);
// 使用获取到的KeyFactory实例keyFactory调用其generatePrivate方法并传入私钥规范对象privateKeySpec
// 执行实际的私钥生成操作将符合规范的字节数据转换为可用的PrivateKey对象存储在privateKey变量中该PrivateKey对象可用于后续的非对称解密、数字签名生成等操作。
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
// 返回生成的PrivateKey对象供调用者在需要使用私钥的地方如解密、签名等操作使用。
return privateKey;
}
private static KeyFactory getKeyFactory(Algorithm algorithm) throws NoSuchAlgorithmException {
// 尝试从KEY_FACTORY_CACHE一个以Algorithm为键存储KeyFactory实例的缓存Map在类中前面已定义中获取指定算法algorithm对应的KeyFactory实例
// 目的是先查看缓存中是否已经存在该算法的KeyFactory如果存在则直接复用避免重复创建提高性能获取到的实例存储在keyFactory变量中。
KeyFactory keyFactory = KEY_FACTORY_CACHE.get(algorithm);
// 判断从缓存中获取到的KeyFactory实例是否为null如果是null表示缓存中还没有该算法对应的KeyFactory实例需要创建新的实例执行以下操作。
if (keyFactory == null) {
// 通过KeyFactory.getInstance方法根据指定算法的名称algorithm.getName()创建一个新的KeyFactory实例
// 此方法会根据Java运行环境中支持的加密算法来查找并创建对应的KeyFactory用于后续创建密钥相关对象公钥、私钥等
keyFactory = KeyFactory.getInstance(algorithm.getName());
// 将新创建的KeyFactory实例放入KEY_FACTORY_CACHE缓存中以算法algorithm为键进行存储方便下次获取时直接使用避免再次创建提高效率。
KEY_FACTORY_CACHE.put(algorithm, keyFactory);
}
// 返回获取到从缓存中或者新创建的的KeyFactory实例供调用者使用例如用于生成公钥、私钥等操作。
return keyFactory;
}
private static byte[] transform(Algorithm algorithm, int mode, Key key, byte[] msg) throws Exception {
// 此方法重载了另一个transform方法这里直接调用另一个带有更多参数的transform方法传入算法algorithm、模式mode、密钥key、null表示加密向量为默认值具体由内部逻辑处理以及消息字节数组msg
// 目的是统一通过另一个更完整参数的方法来处理字节数据的转换(加密或解密等操作),最终返回转换后的字节数组结果。
return transform(algorithm, mode, key, null, msg);
}
private static byte[] transform(Algorithm algorithm, int mode, Key key, IvParameterSpec iv, byte[] msg) throws Exception {
// 尝试从CIPHER_CACHE一个以Algorithm为键存储Cipher实例的缓存Map在类中前面已定义中获取指定算法algorithm对应的Cipher实例
// Cipher实例用于执行加密或解密等实际操作先查看缓存中是否已经存在该算法对应的Cipher如果存在则直接复用避免重复创建减少开销获取到的实例存储在cipher变量中。
Cipher cipher = CIPHER_CACHE.get(algorithm);
// double check减少上下文切换
// 再次检查获取到的Cipher实例是否为null这是一种双重检查机制因为在多线程环境下可能存在多个线程同时发现缓存中没有对应Cipher实例的情况
// 通过再次检查来确保只有一个线程去创建并放入缓存,避免重复创建和不必要的上下文切换开销,提高并发性能。
if (cipher == null) {
// 使用synchronized关键字对CryptoUtils类进行同步锁控制确保在同一时刻只有一个线程能进入以下代码块执行创建Cipher实例并放入缓存的操作避免多线程并发冲突。
synchronized (CryptoUtils.class) {
// 再次检查在进入同步块后缓存中是否已经存在该算法对应的Cipher实例因为可能在等待进入同步块的过程中其他线程已经创建并放入缓存了
// 如果还是null表示确实需要创建新的Cipher实例执行以下操作。
if ((cipher = CIPHER_CACHE.get(algorithm)) == null) {
// 调用determineWhichCipherToUse方法传入指定算法algorithm根据算法相关信息确定并创建对应的Cipher实例
// 该方法内部会根据算法的具体配置如算法名称、转换模式等来创建合适的Cipher对象用于后续的加密或解密操作。
cipher = determineWhichCipherToUse(algorithm);
// 将新创建的Cipher实例放入CIPHER_CACHE缓存中以算法algorithm为键进行存储方便后续获取时直接使用避免再次创建提高效率。
CIPHER_CACHE.put(algorithm, cipher);
}
// 使用获取到从缓存中或者新创建的的Cipher实例调用其init方法传入操作模式mode如加密模式或解密模式等、密钥key以及加密向量iv可能为null取决于具体算法和模式需求
// 对Cipher实例进行初始化操作使其准备好执行后续的加密或解密操作根据传入的模式和密钥等信息来配置其内部状态。
cipher.init(mode, key, iv);
// 调用Cipher实例的doFinal方法传入要处理的消息字节数组msg执行实际的加密或解密操作取决于前面传入的模式是加密还是解密
// 该方法会对消息字节数组进行相应处理,并返回处理后的字节数组结果,也就是加密或解密后的字节数组,最终返回这个结果字节数组供调用者使用。
return cipher.doFinal(msg);
}
}
// 如果第一次获取到的Cipher实例不为null即缓存中已经存在可用的Cipher实例执行以下操作同样进行初始化和执行加密或解密操作。
synchronized (CryptoUtils.class) {
cipher.init(mode, key, iv);
return cipher.doFinal(msg);
}
}
private static Cipher determineWhichCipherToUse(Algorithm algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
// 声明一个Cipher类型的变量cipher用于存储后续创建的Cipher实例Cipher类用于执行加密或解密操作这里先进行变量声明后续会根据具体情况创建并赋值。
Cipher cipher;
// 获取传入的Algorithm对象中定义的转换字符串transformation这个转换字符串通常遵循algorithm/mode/padding的格式例如 "AES/CBC/PKCS5Padding"
// 它用于指定加密或解密操作中具体的算法、模式如ECB、CBC等加密模式以及填充方式如PKCS5Padding等等信息存储在transformation变量中。
String transformation = algorithm.getTransformation();
// 官方推荐的transformation使用algorithm/mode/padding组合SunJCE使用ECB作为默认模式使用PKCS5Padding作为默认填充
// 判断获取到的转换字符串transformation是否不为空即是否有指定具体的算法、模式和填充相关信息如果不为空表示有明确的转换要求执行以下操作。
if (StringUtils.isNotEmpty(transformation)) {
// 通过Cipher.getInstance方法传入指定的转换字符串transformation创建一个对应的Cipher实例
// 该方法会根据传入的转换字符串所表示的算法、模式和填充等信息在Java加密框架中查找并创建合适的Cipher对象用于后续的加密或解密操作创建的实例赋值给cipher变量。
cipher = Cipher.getInstance(transformation);
} else {
// 如果转换字符串为空表示没有明确指定具体的转换信息那么只根据算法名称algorithm.getName()来创建Cipher实例
// 此时会使用默认的模式和填充方式根据具体加密框架的默认设置如SunJCE的默认情况来创建Cipher对象同样将创建的实例赋值给cipher变量。
cipher = Cipher.getInstance(algorithm.getName());
}
// 返回创建好的Cipher实例供调用者在后续的加密、解密等操作中使用例如在transform方法中会利用这个Cipher实例来执行具体的字节数据转换操作。
return cipher;
}
/**
* <br/>
* <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#impl">jdk8</a>
* Algorithm便使
*/
public static class Algorithm {
// 定义一个内部接口Encryption用于列举各种加密算法相关的常量配置这些常量都是Algorithm类型的实例每个实例代表一种具体的加密算法及其相关属性。
public interface Encryption {
// 创建一个Algorithm实例表示AES算法在ECB模式下使用PKCS5Padding填充的配置算法名称为 "AES",转换字符串为 "AES/ECB/PKCS5Padding"密钥长度为128位
// 通过这种方式定义了一种具体的加密算法配置,方便在代码中直接引用这个常量来表示该加密算法,后续在加密操作等场景中可以基于此配置来创建对应的加密对象等。
Algorithm AES_ECB_PKCS5 = new Algorithm("AES", "AES/ECB/PKCS5Padding", 128);
Algorithm AES_CBC_PKCS5 = new Algorithm("AES", "AES/CBC/PKCS5Padding", 128);
Algorithm DES_ECB_PKCS5 = new Algorithm("DES", "DES/ECB/PKCS5Padding", 56);
@ -364,23 +543,31 @@ public class CryptoUtils {
Algorithm DSA = new Algorithm("DSA", 1024);
}
// 定义一个内部接口Signing用于列举各种签名算法相关的常量配置同样每个常量都是Algorithm类型的实例代表一种具体的签名算法及其相关属性。
public interface Signing {
Algorithm SHA1WithDSA = new Algorithm("SHA1withDSA", 1024);
Algorithm SHA1WithRSA = new Algorithm("SHA1WithRSA", 2048);
Algorithm SHA256WithRSA = new Algorithm("SHA256WithRSA", 2048);
}
// 使用Lombok的@Getter注解自动生成获取name属性的getter方法name属性用于存储算法的名称例如 "AES"、"RSA" 等,方便外部获取该算法的名称信息。
@Getter
private String name;
// 使用Lombok的@Getter注解自动生成获取transformation属性的getter方法transformation属性用于存储算法对应的转换字符串包含算法、模式和填充等相关信息便于外部获取该算法的具体转换配置。
@Getter
private String transformation;
// 使用Lombok的@Getter注解自动生成获取keySize属性的getter方法keySize属性用于存储算法对应的密钥长度以位为单位例如128、1024等方便外部获取该算法的密钥长度信息。
@Getter
private int keySize;
// 构造函数用于创建Algorithm实例接收算法名称name和密钥长度keySize两个参数调用另一个构造函数this(name, null, keySize))并传递相应参数,
// 主要用于在创建算法实例时只明确算法名称和密钥长度,而不指定具体转换字符串(使用默认的转换配置情况)的场景,比如某些算法只需要名称和密钥长度就能确定基本配置的情况。
public Algorithm(String name, int keySize) {
this(name, null, keySize);
}
// 完整的构造函数用于创建Algorithm实例接收算法名称name、转换字符串transformation和密钥长度keySize三个参数
// 分别将这些参数赋值给对应的实例属性this.name、this.transformation、this.keySize用于初始化一个完整的算法配置对象在定义各种具体算法常量时会调用此构造函数来明确算法的各项属性。
public Algorithm(String name, String transformation, int keySize) {
this.name = name;
this.transformation = transformation;
@ -389,15 +576,18 @@ public class CryptoUtils {
}
// 使用Lombok的@Data注解自动生成该类的常用方法如getter、setter、toString、hashCode和equals等方法方便对类的属性进行操作和对象间的比较等操作。
// 使用Lombok的@NoArgsConstructor注解自动生成无参构造函数方便在某些场景下创建该类的默认实例例如在一些框架进行对象实例化时如果需要无参构造函数的情况。
// 使用Lombok的@AllArgsConstructor注解自动生成全参构造函数接收两个参数publicKey和privateKey用于初始化类的属性方便在创建对象时一次性传入所有必要属性值进行初始化。
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class AsymmetricKeyPair {
// 定义一个字符串类型的属性publicKey用于存储非对称加密中的公钥信息通常是以Base64编码的字符串形式存储公钥内容方便在加密、验证等操作中传递和使用公钥。
private String publicKey;
// 定义一个字符串类型的属性privateKey用于存储非对称加密中的私钥信息同样一般是以Base64编码的字符串形式存储私钥内容用于在解密、签名等操作中传递和使用私钥。
private String privateKey;
}
}
}
Loading…
Cancel
Save