readme 修改

Signed-off-by: SmileToCandy <smiletocandy@qq.com>
会员中心dcx
Dcx12138 8 months ago
parent d6ca147cce
commit 018d3613b1

@ -16,84 +16,140 @@ import com.tamguo.model.ChapterEntity;
import com.tamguo.service.IChapterService;
import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它主要负责处理与章节Chapter相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循了Spring的分层架构设计规范。
@Service
public class ChapterService extends ServiceImpl<ChapterMapper, ChapterEntity> implements IChapterService{
public class ChapterService extends ServiceImpl<ChapterMapper, ChapterEntity> implements IChapterService {
// 通过Spring的依赖注入机制自动注入 ChapterMapper 接口的实现类实例,
// ChapterMapper 是用于定义与章节实体ChapterEntity相关的数据库操作方法的接口例如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取章节相关的数据信息。
@Autowired
private ChapterMapper chapterMapper;
/**
* findCourseChapter
* IChapterService findCourseChapter bookId
*/
@Override
public List<ChapterEntity> findCourseChapter(String bookId) {
// 首先调用 ChapterMapper 的 findByBookId 方法,根据传入的书籍 IDbookId从数据库中获取该书籍对应的所有章节信息列表
// 这些章节信息将作为后续构建章节树形结构的基础数据。
List<ChapterEntity> chapterList = chapterMapper.findByBookId(bookId);
// 获取根chapter UID
// 初始化用于存储根章节 UID 的变量,初始值设为空字符串,后续会在章节列表中查找真正的根章节 UID 并赋值。
String rootUid = StringUtils.EMPTY;
for(int i=0 ; i<chapterList.size() ; i++){
// 遍历获取到的章节列表,查找父 ID 等于 TamguoConstant.CHAPTER_DEFAULT_ROOT_UID这应该是预定义的表示根章节的唯一标识符常量的章节
// 一旦找到,就将该章节的 UID 赋值给 rootUid 变量,确定根章节的唯一标识。
for (int i = 0; i < chapterList.size(); i++) {
ChapterEntity chapter = chapterList.get(i);
if(chapter.getParentId().equals(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID)){
if (chapter.getParentId().equals(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID)) {
rootUid = chapter.getUid();
}
}
// 获取第一层结构
// 创建一个新的 ArrayList用于存储第一层章节结构即根章节下直接关联的子章节后续会将符合条件的章节添加到这个列表中。
List<ChapterEntity> entitys = new ArrayList<>();
for(int i=0 ; i<chapterList.size() ; i++){
// 再次遍历章节列表,查找父 ID 等于刚才确定的根章节 UIDrootUid的章节将这些章节添加到 entitys 列表中,
// 这样就构建出了章节树形结构的第一层,即根章节下的直接子章节集合。
for (int i = 0; i < chapterList.size(); i++) {
ChapterEntity chapter = chapterList.get(i);
if(rootUid.equals(chapter.getParentId())){
if (rootUid.equals(chapter.getParentId())) {
entitys.add(chapter);
}
}
for(int i=0 ; i<entitys.size() ; i++){
// 遍历第一层的章节列表entitys对于每个章节entity创建一个新的 ArrayList 用于存储它的子章节,
// 然后再次遍历章节总列表chapterList查找父 ID 等于当前章节 UIDentity.getUid()的章节将找到的章节添加到子章节列表childs
// 最后通过 entity.setChildChapterList(childs) 方法将子章节列表设置为当前章节的子节点,从而构建出了第二层章节结构,即根章节的子章节下的子章节。
for (int i = 0; i < entitys.size(); i++) {
ChapterEntity entity = entitys.get(i);
List<ChapterEntity> childs = new ArrayList<>();
for(int k=0 ; k<chapterList.size() ; k++){
for (int k = 0; k < chapterList.size(); k++) {
ChapterEntity chapter = chapterList.get(k);
if(entity.getUid().equals(chapter.getParentId())){
if (entity.getUid().equals(chapter.getParentId())) {
childs.add(chapter);
}
}
entity.setChildChapterList(childs);
}
for(int i=0 ; i<entitys.size() ; i++){
// 接着对于第一层章节列表entitys中的每个章节及其子章节列表childs进一步遍历子章节列表
// 对于每个子章节child再创建一个新的 ArrayList 用于存储它的下一层子章节tmpChilds
// 然后再次遍历章节总列表chapterList查找父 ID 等于当前子章节 UIDchild.getUid()的章节将找到的章节添加到下一层子章节列表tmpChilds
// 最后通过 child.setChildChapterList(tmpChilds) 方法将下一层子章节列表设置为当前子章节的子节点,以此类推,构建出更完整的多层级章节树形结构。
for (int i = 0; i < entitys.size(); i++) {
List<ChapterEntity> childs = entitys.get(i).getChildChapterList();
for(int k=0 ; k<childs.size() ; k++){
for (int k = 0; k < childs.size(); k++) {
ChapterEntity child = childs.get(k);
List<ChapterEntity> tmpChilds = new ArrayList<>();
for(int n=0 ; n<chapterList.size() ; n++){
for (int n = 0; n < chapterList.size(); n++) {
ChapterEntity chapter = chapterList.get(n);
if(child.getUid().equals(chapter.getParentId())){
if (child.getUid().equals(chapter.getParentId())) {
tmpChilds.add(chapter);
}
}
child.setChildChapterList(tmpChilds);
}
}
// 最后返回构建好的章节树形结构数据,即包含了多层级父子关系的章节列表,这个列表以根章节及其子章节、孙章节等形式呈现,方便在业务中进行展示、操作等处理。
return entitys;
}
/**
* findById
* IChapterService findById uid
* ChapterMapper selectById UID uid ChapterMapper
*/
@Override
public ChapterEntity findById(String uid) {
return chapterMapper.selectById(uid);
}
/**
* findNextPoint
* IChapterService findNextPoint uidorders
// 调用了 ChapterMapper 的 findNextPoint 方法,传入章节 UID 和顺序号参数,由 ChapterMapper 负责在数据库中查找并返回符合条件的章节实体对象。
*/
@Override
public ChapterEntity findNextPoint(String uid , Integer orders) {
return chapterMapper.findNextPoint(uid , orders);
public ChapterEntity findNextPoint(String uid, Integer orders) {
return chapterMapper.findNextPoint(uid, orders);
}
@Transactional(readOnly=false)
/**
* getChapterTree
* IChapterService getChapterTree IDcourseId
* @Transactional(readOnly = false)
*/
@Transactional(readOnly = false)
@Override
public List<ChapterEntity> getChapterTree(String courseId) {
if(StringUtils.isEmpty(courseId) || "null".equals(courseId)){
// 首先判断传入的课程 ID 是否为空字符串或者等于 "null"(这里判断 "null" 的方式不太严谨,更合适的做法可能是判断是否为 null 值,不过先按照现有逻辑分析),
// 如果满足这个条件,说明没有有效的课程 ID 传入,那么调用 rootChapterNode 方法返回一个默认的根章节节点列表,作为章节树的基础结构(可能用于没有具体课程关联时的默认展示等情况)。
if (StringUtils.isEmpty(courseId) || "null".equals(courseId)) {
return rootChapterNode();
}
// 根据传入的课程 ID调用 ChapterMapper 的 findByBookId 方法(这里可能命名不太准确,根据上下文推测应该是根据课程相关的标识去查找对应的章节信息,也许后续需要确认方法名是否合适),
// 从数据库中获取该课程对应的章节信息列表,然后判断获取到的列表是否为空(即没有找到对应的章节信息),
// 如果为空,同样调用 rootChapterNode 方法返回默认的根章节节点列表作为章节树结构。
List<ChapterEntity> list = chapterMapper.findByBookId(courseId);
if(CollectionUtils.isEmpty(list)) {
if (CollectionUtils.isEmpty(list)) {
return rootChapterNode();
}
// 如果获取到了有效的章节信息列表,就直接返回这个列表,该列表后续可用于构建具体的章节树形结构或者直接在业务中进行展示、处理等操作,具体取决于业务逻辑要求。
return list;
}
private List<ChapterEntity> rootChapterNode(){
/**
* rootChapterNode
* ChapterEntity
* ID UID ID
* 使
*/
private List<ChapterEntity> rootChapterNode() {
ChapterEntity chapter = new ChapterEntity();
chapter.setCourseId(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID);
chapter.setOrders(0);
@ -104,5 +160,4 @@ public class ChapterService extends ServiceImpl<ChapterMapper, ChapterEntity> im
chapter.setParentId(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID);
return Arrays.asList(chapter);
}
}
}

@ -18,80 +18,148 @@ import com.tamguo.util.Result;
import com.tamguo.util.ShiroUtils;
import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它主要负责处理与会员Member相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循Spring的分层架构设计。
@Service
public class MemberService extends ServiceImpl<MemberMapper, MemberEntity> implements IMemberService{
public class MemberService extends ServiceImpl<MemberMapper, MemberEntity> implements IMemberService {
// 通过Spring的依赖注入机制自动注入 MemberMapper 接口的实现类实例,
// MemberMapper 是用于定义与会员实体MemberEntity相关的数据库操作方法的接口比如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取会员相关的数据信息。
@Autowired
private MemberMapper memberMapper;
// 同样通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在会员相关业务中用于缓存如登录失败次数、验证码等信息,以优化业务操作性能。
@Autowired
private CacheService cacheService;
/**
* login
* IMemberService login
*/
@Override
public Result login(String username, String password) {
// 首先通过 MemberMapper 的 findByUsername 方法根据传入的用户名username从数据库中查找对应的会员实体信息
// 如果没有找到(即返回 null说明用户名不存在直接返回相应的结果提示用户名或密码有误封装在 Result 对象中返回给调用者。
MemberEntity member = memberMapper.findByUsername(username);
if(member == null){
if (member == null) {
return Result.result(201, member, "用户名或密码有误,请重新输入或找回密码");
}
Integer loginFailureCount = this.getLoginFailureCount(member);
if(!new Sha256Hash(password).toHex().equals(member.getPassword())){
// 获取当前会员的登录失败次数,调用本类中的 getLoginFailureCount 方法来获取,后续用于判断是否达到限制次数等情况。
Integer loginFailureCount = this.getLoginFailureCount(member);
// 使用 Sha256Hash 对传入的密码进行哈希处理并与数据库中存储的会员密码member.getPassword())进行比对,
// 如果不相等,说明密码错误,此时将登录失败次数加 1并调用 updateLoginFailureCount 方法更新缓存中的登录失败次数记录,
// 然后返回相应的错误提示结果(用户名或密码有误),封装在 Result 对象中。
if (!new Sha256Hash(password).toHex().equals(member.getPassword())) {
loginFailureCount++;
this.updateLoginFailureCount(member , loginFailureCount);
this.updateLoginFailureCount(member, loginFailureCount);
return Result.result(202, member, "用户名或密码有误,请重新输入或找回密码");
}
this.updateLoginFailureCount(member , 0);
// 如果密码正确,将登录失败次数重置为 0表示此次登录成功清除之前的失败记录调用 updateLoginFailureCount 方法更新缓存中的登录失败次数,
// 最后返回登录成功的结果提示,封装在 Result 对象中返回给调用者,表示会员登录成功。
this.updateLoginFailureCount(member, 0);
return Result.result(200, member, "登录成功");
}
public void updateLoginFailureCount(MemberEntity member , Integer loginFailureCount){
cacheService.setObject(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid(), loginFailureCount , 2 * 60 * 60);
/**
* updateLoginFailureCount
* TamguoConstant.LOGIN_FAILURE_COUNT member.getUid()
* loginFailureCount 2 * 60 * 60 2
// 这样在这 2 小时内,后续获取登录失败次数时会从缓存中读取该值,避免频繁访问数据库来获取和更新这个数据,提高业务操作效率。
*/
public void updateLoginFailureCount(MemberEntity member, Integer loginFailureCount) {
cacheService.setObject(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid(), loginFailureCount, 2 * 60 * 60);
}
public Integer getLoginFailureCount(MemberEntity member){
if(member == null){
/**
* getLoginFailureCount
* null null 0
* TamguoConstant.LOGIN_FAILURE_COUNT member.getUid()
// 如果不存在,同样返回 0表示没有记录到该会员的登录失败次数如果存在则从缓存中获取对应的登录失败次数类型转换为 Integer并返回。
*/
public Integer getLoginFailureCount(MemberEntity member) {
if (member == null) {
return 0;
}
if(!cacheService.isExist(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid())){
if (!cacheService.isExist(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid())) {
return 0;
}
return (Integer)cacheService.getObject(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid());
return (Integer) cacheService.getObject(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid());
}
/**
* checkUsername
* IMemberService checkUsername 使
// 通过 MemberMapper 的 findByUsername 方法根据传入的用户名username从数据库中查找对应的会员实体
// 如果找到(即返回的会员实体不为 null说明用户名已经存在返回相应的提示结果该用户名已经存在封装在 Result 对象中;
// 如果没有找到,则返回表示用户名可用的提示结果,同样封装在 Result 对象中返回给调用者。
*/
@Override
public Result checkUsername(String username) {
MemberEntity member = memberMapper.findByUsername(username);
if(member != null){
if (member!= null) {
return Result.result(201, null, "该用户名已经存在");
}
return Result.result(200, null, "该用户名可用");
}
/**
* checkMobile
* IMemberService checkMobile 使
// 通过 MemberMapper 的 findByMobile 方法根据传入的手机号mobile从数据库中查找对应的会员实体
// 如果找到(即返回的会员实体不为 null说明手机号已经存在返回相应的提示结果该手机号已经存在封装在 Result 对象中;
// 如果没有找到,则返回表示手机号可用的提示结果,同样封装在 Result 对象中返回给调用者。
*/
@Override
public Result checkMobile(String mobile) {
MemberEntity member = memberMapper.findByMobile(mobile);
if(member != null){
if (member!= null) {
return Result.result(201, null, "该手机号已经存在");
}
return Result.result(200, null, "该手机号可用");
}
@Transactional(readOnly=false)
/**
* register
* IMemberService register
* @Transactional(readOnly = false)
*/
@Transactional(readOnly = false)
@Override
public Result register(MemberEntity member) {
// 首先通过 MemberMapper 的 findByUsername 方法根据传入的要注册的用户名member.getUsername())从数据库中查找是否已存在该用户名的会员,
// 如果找到(即返回的会员实体不为 null说明用户名已被使用返回相应的提示结果该用户已经存在封装在 Result 对象中。
MemberEntity m = memberMapper.findByUsername(member.getUsername());
if(m != null){
if (m!= null) {
return Result.result(201, null, "该用户已经存在");
}
// 接着通过 MemberMapper 的 findByMobile 方法根据传入的要注册的手机号member.getMobile())从数据库中查找是否已存在该手机号的会员,
// 如果找到(即返回的会员实体不为 null说明手机号已被使用返回相应的提示结果该手机号已经存在封装在 Result 对象中。
m = memberMapper.findByMobile(member.getMobile());
if(m != null){
if (m!= null) {
return Result.result(202, null, "该手机号已经存在");
}
if(!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())){
// 检查缓存中是否存在以 TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX 和要注册的手机号member.getMobile())拼接而成的键对应的验证码缓存数据,
// 如果不存在,说明验证码错误(可能未发送或者已过期等情况),返回相应的提示结果(验证码错误),封装在 Result 对象中。
if (!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())) {
return Result.result(203, null, "验证码错误");
}
// 从缓存中获取对应的验证码将获取到的验证码与传入的要注册的会员实体中的验证码member.getVerifyCode())进行比对,
// 如果不相等,说明验证码错误,返回相应的提示结果(验证码错误),封装在 Result 对象中。
String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile());
if(!code.equals(member.getVerifyCode())){
if (!code.equals(member.getVerifyCode())) {
return Result.result(204, null, "验证码错误");
}
// 创建一个新的 MemberEntity 实例,用于存储要注册的会员信息,设置了一些默认值(如头像使用默认头像 TamguoConstant.DEFAULT_MEMBER_AVATAR
// 并将传入的会员实体中的相关信息(如手机号、密码、用户名等)设置到新实例中,其中密码使用 Sha256Hash 进行哈希处理后存储,提高安全性。
MemberEntity entity = new MemberEntity();
entity.setAvatar(TamguoConstant.DEFAULT_MEMBER_AVATAR);
entity.setMobile(member.getMobile());
@ -101,30 +169,51 @@ public class MemberService extends ServiceImpl<MemberMapper, MemberEntity> imple
entity.setSubjectId(member.getSubjectId());
entity.setCourseId(member.getCourseId());
entity.setEmail(member.getEmail());
// 通过 MemberMapper 的 insert 方法将新的会员实体信息插入到数据库中,完成会员注册操作,
// 最后返回注册成功的提示结果,封装在 Result 对象中返回给调用者,表示会员注册成功。
memberMapper.insert(entity);
return Result.result(200, entity, "注册成功");
}
/**
* checkAccount
* IMemberService checkAccount account
// 首先判断传入的账号是否为空字符串,如果为空,说明账号不存在,返回相应的提示结果(帐号不存在!),封装在 Result 对象中。
// 接着通过 MemberMapper 的 findByUsernameOrEmailOrMobile 方法根据传入的账号从数据库中查找对应的会员实体,
// 如果没有找到(即返回的会员实体为 null同样返回表示账号不存在的提示结果封装在 Result 对象中;
// 如果找到,则返回表示该账号存在的提示结果,封装在 Result 对象中返回给调用者。
*/
@Override
public Result checkAccount(String account) {
if(StringUtils.isEmpty(account)){
if (StringUtils.isEmpty(account)) {
return Result.result(201, null, "帐号不存在!");
}
MemberEntity member = memberMapper.findByUsernameOrEmailOrMobile(account);
if(member == null){
if (member == null) {
return Result.result(201, null, "帐号不存在!");
}
return Result.result(200, null, "该帐号存在");
}
/**
* confirmAccount
* IMemberService confirmAccount
// 首先判断传入的账号是否为空字符串,如果为空,说明账号不存在,返回相应的提示结果(帐号不存在!),封装在 Result 对象中。
// 接着通过 MemberMapper 的 findByUsernameOrEmailOrMobile 方法根据传入的账号从数据库中查找对应的会员实体,
// 如果没有找到(即返回的会员实体为 null同样返回表示账号不存在的提示结果封装在 Result 对象中。
// 然后通过 ShiroUtils 的 getKaptcha 方法获取验证码(根据 Constants.KAPTCHA_SESSION_KEY 这个键从相应的会话等地方获取),
// 将获取到的验证码与传入的验证验证码veritycode进行比对如果不相等说明验证码错误返回相应的提示结果验证码错误封装在 Result 对象中。
// 如果验证码正确,则返回表示该账号存在的提示结果,封装在 Result 对象中返回给调用者。
*/
@Override
public Result confirmAccount(String account, String veritycode) {
if(StringUtils.isEmpty(account)){
if (StringUtils.isEmpty(account)) {
return Result.result(201, null, "帐号不存在!");
}
MemberEntity member = memberMapper.findByUsernameOrEmailOrMobile(account);
if(member == null){
return Result.result(201, null, "帐号不存在!");
if (member == null) {
return Result.result(201, null, "帐号不存在!");;
}
String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);
if (!veritycode.equalsIgnoreCase(kaptcha)) {
@ -133,96 +222,37 @@ public class MemberService extends ServiceImpl<MemberMapper, MemberEntity> imple
return Result.result(200, member, "该帐号存在");
}
/**
* securityCheck
* IMemberService securityCheck isEmail
// 首先通过 MemberMapper 的 findByUsername 方法根据传入的用户名username从数据库中查找对应的会员实体
// 如果 isEmail 参数为 "1",表示验证邮箱验证码,此时检查缓存中是否存在以 TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX 和会员的邮箱member.getEmail())拼接而成的键对应的验证码缓存数据,
// 如果不存在,说明验证码错误,返回相应的提示结果(验证码错误),封装在 Result 对象中。
// 接着从缓存中获取对应的验证码将获取到的验证码与传入的验证验证码vcode进行比对如果不相等说明验证码错误返回相应的提示结果验证码错误封装在 Result 对象中。
// 如果 isEmail 参数不为 "1",表示验证手机号验证码,进行类似的操作,检查手机号对应的验证码缓存数据是否存在以及比对验证码是否正确,
// 如果验证通过,生成一个唯一的随机字符串(使用 UUID.randomUUID().toString())作为键,将用户名存入缓存中(缓存键名为 TamguoConstant.SECURITY_CHECK_PREFIX 加上生成的随机字符串),
// 设置缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),最后返回安全验证通过的提示结果,封装在 Result 对象中返回给调用者,同时将生成的随机字符串作为结果数据返回。
*/
@Override
public Result securityCheck(String username , String isEmail , String vcode) {
public Result securityCheck(String username, String isEmail, String vcode) {
MemberEntity member = memberMapper.findByUsername(username);
if("1".equals(isEmail)){
if(!cacheService.isExist(TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX + member.getEmail())){
if ("1".equals(isEmail)) {
if (!cacheService.isExist(TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX + member.getEmail())) {
return Result.result(201, member, "验证码错误");
}
String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX + member.getEmail());
if(!code.equals(vcode)){
if (!code.equals(vcode)) {
return Result.result(202, member, "验证码错误");
}
}else{
if(!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())){
} else {
if (!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())) {
return Result.result(203, member, "验证码错误");
}
String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile());
if(!code.equals(vcode)){
if (!code.equals(vcode)) {
return Result.result(204, member, "验证码错误");
}
}
String key = UUID.randomUUID().toString();
cacheService.setObject(TamguoConstant.SECURITY_CHECK_PREFIX + key, username , 2 * 60 * 60);
return Result.result(200, key, "安全验证通过");
}
@Override
public Result resetPassword(String resetPasswordKey , String username , String password, String verifypwd) {
if(cacheService.isExist(TamguoConstant.SECURITY_CHECK_PREFIX + resetPasswordKey)){
MemberEntity member = memberMapper.findByUsername(username);
if(password.equals(verifypwd)){
member.setPassword(new Sha256Hash(password).toHex());
memberMapper.updateById(member);
}
}
return Result.result(200, null, "更新成功");
}
@Transactional(readOnly=false)
@Override
public void updateMember(MemberEntity member) {
MemberEntity entity = memberMapper.selectById(ShiroUtils.getUserId());
entity.setAvatar(member.getAvatar());
entity.setEmail(member.getEmail());
entity.setMobile(member.getMobile());
entity.setCourseId(member.getCourseId());
entity.setSubjectId(member.getSubjectId());
entity.setNickName(member.getNickName());
memberMapper.updateById(entity);
}
@Transactional(readOnly=true)
@Override
public MemberEntity findByUid(String uid) {
return memberMapper.selectById(uid);
}
@Transactional(readOnly=true)
@Override
public MemberEntity findByUsername(String username) {
return memberMapper.findByUsername(username);
}
@Transactional(readOnly=false)
@Override
public void updateLastLoginTime(String uid) {
MemberEntity member = memberMapper.selectById(uid);
member.setLastLoginTime(DateUtil.getTime());
memberMapper.updateById(member);
}
@Override
public MemberEntity findCurrMember() {
MemberEntity member = memberMapper.selectById(ShiroUtils.getUserId());
member.setPassword(null);
return member;
}
@Transactional(readOnly=false)
@Override
public Result updatePwd(MemberEntity member) {
MemberEntity entity = memberMapper.selectById(ShiroUtils.getUserId());
if(!entity.getPassword().equals(new Sha256Hash(member.getPassword()).toHex())) {
return Result.result(501, null, "旧密码错误!");
}
if(!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())){
return Result.result(502, null, "验证码错误");
}
entity.setPassword(new Sha256Hash(member.getNowPassword()).toHex());
return Result.result(0, null, "修改成功");
}
}
cacheService.setObject(TamguoConstant.SECURITY_CHECK_PREFIX + key, username, 2 * 60 * 60);
return Result.result(200

@ -12,101 +12,188 @@ import com.tamguo.model.MenuEntity;
import com.tamguo.service.IMenuService;
import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件意味着它主要负责处理与菜单Menu相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循了Spring的分层架构设计规范。
@Service
public class MenuService extends ServiceImpl<MenuMapper, MenuEntity> implements IMenuService{
public class MenuService extends ServiceImpl<MenuMapper, MenuEntity> implements IMenuService {
// 通过Spring的依赖注入机制自动注入 MenuMapper 接口的实现类实例,
// MenuMapper 是用于定义与菜单实体MenuEntity相关的数据库操作方法的接口例如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取菜单相关的数据信息。
@Autowired
private MenuMapper menuMapper;
// 同样通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在这个类中主要用于缓存不同类型的菜单数据,
// 以减少频繁访问数据库获取菜单信息的开销,提高系统性能,优化菜单相关业务操作的响应速度。
@Autowired
private CacheService cacheService;
/**
* findMenus
* IMenuService findMenus
*/
@SuppressWarnings("unchecked")
@Override
public List<MenuEntity> findMenus() {
// 首先尝试从缓存中获取名为 TamguoConstant.INDEX_MENU 的缓存对象,并将其强制转换为 List<MenuEntity> 类型,
// 期望从缓存中获取到之前存储的首页菜单信息列表,如果缓存命中,就可以直接返回这个缓存中的菜单列表数据,避免重复查询数据库。
List<MenuEntity> menuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.INDEX_MENU));
// 判断从缓存中获取到的菜单列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的菜单信息),
// 如果满足这个条件,就需要从数据库中重新获取首页菜单信息并构建菜单结构,然后存入缓存中供后续使用。
if (menuList == null || menuList.isEmpty()) {
// 通过注入的 MenuMapper 调用其 findFatherMenus 方法,获取首页的父级菜单信息列表,这些父级菜单将作为构建菜单树形结构的顶层节点,
// 后续会基于这些父菜单查找并添加对应的子菜单,形成完整的菜单结构。
menuList = menuMapper.findFatherMenus();
for(MenuEntity menu : menuList){
// 遍历获取到的父级菜单列表对于每个父菜单menu通过调用 MenuMapper 的 findMenuByParentId 方法传入当前父菜单的唯一标识符menu.getUid())作为参数,
// 获取该父菜单对应的子菜单列表,然后将获取到的子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),
// 这样就构建了首页菜单的树形结构,包含了父子菜单的层级关系。
for (MenuEntity menu : menuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects);
}
cacheService.setObject(TamguoConstant.INDEX_MENU, menuList , 2 * 60 * 60);
// 将构建好的首页菜单树形结构数据存入缓存中,缓存的键名为 TamguoConstant.INDEX_MENU同时设置了缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findMenus 方法,就可以直接从缓存中获取首页菜单数据,而不用再次从数据库中查询和构建菜单结构了,提高了数据获取的效率。
cacheService.setObject(TamguoConstant.INDEX_MENU, menuList, 2 * 60 * 60);
}
// 最后返回获取到的首页菜单信息列表,这个列表可能是从缓存中直接获取的,也可能是从数据库中查询后存入缓存再返回的,
// 取决于缓存中是否存在有效数据以及之前的逻辑执行情况。
return menuList;
}
/**
* findAllMenus
* IMenuService findAllMenus
*/
@SuppressWarnings("unchecked")
@Override
public List<MenuEntity> findAllMenus() {
// 首先尝试从缓存中获取名为 TamguoConstant.ALL_INDEX_MENU 的缓存对象,并将其强制转换为 List<MenuEntity> 类型,
// 期望获取到之前存储的所有菜单信息列表,如果缓存命中,直接返回该缓存中的菜单列表数据即可。
List<MenuEntity> allMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.ALL_INDEX_MENU));
if(allMenuList == null || allMenuList.isEmpty()){
// 判断从缓存中获取到的所有菜单列表是否为 null 或者为空列表,如果是,就需要从数据库中重新获取并构建所有菜单的树形结构数据,然后存入缓存。
if (allMenuList == null || allMenuList.isEmpty()) {
// 通过 MenuMapper 的 findAllFatherMenus 方法从数据库中获取所有的父级菜单信息列表,这些父菜单将作为构建整个菜单树形结构的顶层节点基础。
allMenuList = menuMapper.findAllFatherMenus();
for(MenuEntity menu : allMenuList){
// 遍历所有的父级菜单列表对于每个父菜单menu调用 MenuMapper 的 findMenuByParentId 方法传入父菜单的唯一标识符menu.getUid())获取其对应的子菜单列表,
// 然后将子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),以此构建出包含所有菜单的树形结构,展示完整的菜单层级关系。
for (MenuEntity menu : allMenuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects);
}
cacheService.setObject(TamguoConstant.ALL_INDEX_MENU, allMenuList , 2 * 60 * 60);
// 将构建好的所有菜单树形结构数据存入缓存中,缓存键名为 TamguoConstant.ALL_INDEX_MENU缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 以便后续在缓存有效期内可以直接从缓存获取所有菜单数据,减少数据库查询操作,提高性能。
cacheService.setObject(TamguoConstant.ALL_INDEX_MENU, allMenuList, 2 * 60 * 60);
}
// 最后返回获取到的所有菜单信息列表,该列表可能来自缓存或者是新从数据库查询构建后存入缓存再返回的,具体取决于缓存情况和之前的逻辑执行结果。
return allMenuList;
}
/**
* findLeftMenus
* IMenuService findLeftMenus
*/
@SuppressWarnings("unchecked")
@Override
public List<MenuEntity> findLeftMenus() {
// 先从缓存中获取名为 TamguoConstant.LEFT_INDEX_MENU 的缓存对象,并强制转换为 List<MenuEntity> 类型,尝试获取之前缓存的左侧菜单信息列表,
// 如果缓存中有数据,就直接返回该缓存中的菜单列表数据,避免再次查询数据库。
List<MenuEntity> leftMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.LEFT_INDEX_MENU));
if(leftMenuList == null || leftMenuList.isEmpty()){
// 判断从缓存中获取到的左侧菜单列表是否为 null 或者为空,如果是,就需要从数据库中重新获取并构建左侧菜单的树形结构数据,然后存入缓存供后续使用。
if (leftMenuList == null || leftMenuList.isEmpty()) {
// 通过 MenuMapper 的 findLeftFatherMenus 方法从数据库中获取左侧菜单的父级菜单信息列表,这些父菜单将作为左侧菜单树形结构的顶层节点。
leftMenuList = menuMapper.findLeftFatherMenus();
for(MenuEntity menu : leftMenuList){
// 遍历左侧菜单的父级菜单列表对于每个父菜单menu调用 MenuMapper 的 findMenuByParentId 方法传入父菜单的唯一标识符menu.getUid())获取对应的子菜单列表,
// 再将子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),以此构建出左侧菜单的树形结构,包含父子菜单的层级关系。
for (MenuEntity menu : leftMenuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects);
}
cacheService.setObject(TamguoConstant.LEFT_INDEX_MENU, leftMenuList , 2 * 60 * 60);
// 将构建好的左侧菜单树形结构数据存入缓存中,缓存键名为 TamguoConstant.LEFT_INDEX_MENU缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 这样后续在有效期内再次调用 findLeftMenus 方法时,就可以直接从缓存获取左侧菜单数据,提高获取数据的效率。
cacheService.setObject(TamguoConstant.LEFT_INDEX_MENU, leftMenuList, 2 * 60 * 60);
}
// 最后返回获取到的左侧菜单信息列表,该列表的来源取决于缓存是否命中以及之前的逻辑执行情况,可能是缓存数据也可能是新从数据库获取后存入缓存再返回的数据。
return leftMenuList;
}
/**
* findChapterMenus
* IMenuService findChapterMenus
*/
@SuppressWarnings("unchecked")
@Override
public List<MenuEntity> findChapterMenus() {
// 首先从缓存中获取名为 TamguoConstant.CHAPTER_INDEX_MENU 的缓存对象,并强制转换为 List<MenuEntity> 类型,尝试获取之前缓存的章节相关菜单信息列表,
// 如果缓存中有有效数据,就直接返回该缓存中的菜单列表数据,无需再次查询数据库。
List<MenuEntity> chapterMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.CHAPTER_INDEX_MENU));
if(chapterMenuList == null || chapterMenuList.isEmpty()){
// 判断从缓存中获取到的章节菜单列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取并构建章节菜单的树形结构数据,然后存入缓存以便后续使用。
if (chapterMenuList == null || chapterMenuList.isEmpty()) {
// 通过 MenuMapper 的 findChapterFatherMenus 方法从数据库中获取章节相关菜单的父级菜单信息列表,这些父菜单将作为章节菜单树形结构的顶层节点。
chapterMenuList = menuMapper.findChapterFatherMenus();
for(MenuEntity menu : chapterMenuList){
// 遍历章节菜单的父级菜单列表对于每个父菜单menu调用 MenuMapper 的 findMenuByParentId 方法传入父菜单的唯一标识符menu.getUid())获取对应的子菜单列表,
// 接着将子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),以此构建出章节菜单的树形结构,体现父子菜单的层级关系。
for (MenuEntity menu : chapterMenuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects);
}
cacheService.setObject(TamguoConstant.CHAPTER_INDEX_MENU, chapterMenuList , 2 * 60 * 60);
// 将构建好的章节菜单树形结构数据存入缓存中,缓存键名为 TamguoConstant.CHAPTER_INDEX_MENU缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 这样在后续的 2 小时内,再次调用 findChapterMenus 方法时,就可以直接从缓存获取章节菜单数据,提高数据获取效率,减少数据库操作开销。
cacheService.setObject(TamguoConstant.CHAPTER_INDEX_MENU, chapterMenuList, 2 * 60 * 60);
}
// 最后返回获取到的章节菜单信息列表,其来源取决于缓存是否命中以及之前的逻辑执行情况,可能是直接从缓存获取的数据,也可能是新从数据库获取后存入缓存再返回的数据。
return chapterMenuList;
}
/**
* findFooterMenus
* IMenuService findFooterMenus
*/
@SuppressWarnings("unchecked")
@Override
public List<MenuEntity> findFooterMenus() {
// 先从缓存中获取名为 TamguoConstant.FOOTER_INDEX_MENU 的缓存对象,并强制转换为 List<MenuEntity> 类型,尝试获取之前缓存的底部菜单信息列表,
// 这里有个特殊处理,先将获取到的列表赋值为 null不过从常规逻辑来看这样可能不太合适一般如果要清空缓存重新获取数据会有更合理的删除缓存等操作方式
// 暂时按照现有代码逻辑继续分析,后续可能需要确认此处的真实意图是否合理。
List<MenuEntity> footerMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.FOOTER_INDEX_MENU));
footerMenuList = null;
if(footerMenuList == null || footerMenuList.isEmpty()){
// 判断底部菜单列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取并构建底部菜单的树形结构数据,然后存入缓存供后续调用使用。
if (footerMenuList == null || footerMenuList.isEmpty()) {
// 通过 MenuMapper 的 findFooterFatherMenus 方法从数据库中获取底部菜单的父级菜单信息列表,这些父菜单将作为底部菜单树形结构的顶层节点。
footerMenuList = menuMapper.findFooterFatherMenus();
for(MenuEntity menu : footerMenuList){
// 遍历底部菜单的父级菜单列表对于每个父菜单menu调用 MenuMapper 的 findMenuByParentId 方法传入父菜单的唯一标识符menu.getUid())获取对应的子菜单列表,
// 再将子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),以此构建出底部菜单的树形结构,展示父子菜单的层级关系。
for (MenuEntity menu : footerMenuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects);
}
cacheService.setObject(TamguoConstant.FOOTER_INDEX_MENU, footerMenuList , 2 * 60 * 60);
// 将构建好的底部菜单树形结构数据存入缓存中,缓存键名为 TamguoConstant.FOOTER_INDEX_MENU缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 这样在后续的 2 小时内,再次调用 findFooterMenus 方法时,就可以直接从缓存获取底部菜单数据,提高获取数据的效率,减少数据库查询操作。
cacheService.setObject(TamguoConstant.FOOTER_INDEX_MENU, footerMenuList, 2 * 60 * 60);
}
// 最后返回获取到的底部菜单信息列表,其来源取决于缓存情况以及之前的逻辑执行结果,可能是缓存中的数据,也可能是新从数据库获取后存入缓存再返回的数据。
return footerMenuList;
}
/**
* getMenuTree
* IMenuService getMenuTree
// 直接调用了 MenuMapper 的 selectList 方法,并传入 Condition.EMPTY 参数,这意味着使用默认的查询条件(通常是查询所有符合条件的记录,在这里就是所有菜单记录)
// 从数据库中获取菜单实体信息列表,该列表可以作为构建菜单树形结构或者其他菜单相关业务处理的基础数据,返回获取到的菜单实体列表。
*/
@SuppressWarnings("unchecked")
@Override
public List<MenuEntity> getMenuTree() {
return menuMapper.selectList(Condition.EMPTY);
}
/**
* findById
* IMenuService findById uid
// 直接调用了 MenuMapper 的 selectById 方法,传入菜单 UID 参数uid由 MenuMapper 具体实现与数据库的交互查询操作,返回查询到的菜单实体对象。
*/
@Override
public MenuEntity findById(String uid) {
return menuMapper.selectById(uid);
}
}
}

@ -22,97 +22,192 @@ import com.tamguo.util.Result;
import com.tamguo.util.ShiroUtils;
import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它主要负责处理与试卷Paper相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循Spring的分层架构设计。
@Service
public class PaperService extends ServiceImpl<PaperMapper, PaperEntity> implements IPaperService{
public class PaperService extends ServiceImpl<PaperMapper, PaperEntity> implements IPaperService {
// 通过Spring的依赖注入机制自动注入 PaperMapper 接口的实现类实例,
// PaperMapper 是用于定义与试卷实体PaperEntity相关的数据库操作方法的接口比如查询、插入、更新、删除等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取试卷相关的数据信息。
@Autowired
private PaperMapper paperMapper;
// 同样通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在试卷相关业务中用于缓存不同类型的试卷列表数据,
// 以减少频繁访问数据库获取试卷信息的开销,提高系统性能,优化试卷相关业务操作的响应速度。
@Autowired
private CacheService cacheService;
// 通过依赖注入注入 QuestionMapper 接口的实现类实例QuestionMapper 应该是用于定义与试题相关的数据库操作方法的接口,
// 在涉及试卷中试题相关的操作(如添加、删除、更新试题信息等)时会调用它的方法来与数据库进行交互。
@Autowired
private QuestionMapper questionMapper;
/**
* findHistoryPaper
* IPaperService findHistoryPaper
*/
@SuppressWarnings("unchecked")
@Override
public List<PaperEntity> findHistoryPaper() {
// 首先尝试从缓存中获取名为 TamguoConstant.HISTORY_PAPER 的缓存对象,并将其强制转换为 List<PaperEntity> 类型,
// 期望从缓存中获取到之前存储的历史试卷信息列表,如果缓存命中,就可以直接返回这个缓存中的试卷列表数据,减少数据库查询操作。
List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.HISTORY_PAPER);
if(paperList == null || paperList.isEmpty()){
Page<PaperEntity> page = new Page<>(1 , 6);
paperList = paperMapper.findByTypeAndAreaId(TamguoConstant.ZHENGTI_PAPER_ID , TamguoConstant.BEIJING_AREA_ID , page);
cacheService.setObject(TamguoConstant.ZHENGTI_PAPER_ID, paperList , 2 * 60 * 60);
// 判断从缓存中获取到的试卷列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的试卷信息),
// 如果满足这个条件,就需要从数据库中重新获取历史试卷信息列表,并将其存入缓存中供后续使用。
if (paperList == null || paperList.isEmpty()) {
// 创建一个 Page 对象,用于分页查询,设置当前页码为 1每页显示的记录数为 6这里的分页参数可以根据实际业务需求调整
Page<PaperEntity> page = new Page<>(1, 6);
// 通过注入的 PaperMapper 的 findByTypeAndAreaId 方法传入特定的试卷类型标识符TamguoConstant.ZHENGTI_PAPER_ID和地区标识符TamguoConstant.BEIJING_AREA_ID以及分页对象page
// 从数据库中获取符合条件的历史试卷信息列表,这里的具体查询逻辑由 PaperMapper 中对应的方法实现来确定,返回查询到的历史试卷列表赋值给 paperList 变量。
paperList = paperMapper.findByTypeAndAreaId(TamguoConstant.ZHENGTI_PAPER_ID, TamguoConstant.BEIJING_AREA_ID, page);
// 将获取到的历史试卷列表存入缓存中,缓存的键名为 TamguoConstant.ZHENGTI_PAPER_ID同时设置了缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findHistoryPaper 方法,就可以直接从缓存中获取历史试卷数据,而不用再次查询数据库了。
cacheService.setObject(TamguoConstant.ZHENGTI_PAPER_ID, paperList, 2 * 60 * 60);
}
// 最后返回获取到的历史试卷信息列表,这个列表可能是从缓存中直接获取的,也可能是从数据库中查询后存入缓存再返回的,
// 取决于缓存中是否存在有效数据以及之前的逻辑执行情况。
return paperList;
}
/**
* findSimulationPaper
* IPaperService findSimulationPaper 访
*/
@SuppressWarnings("unchecked")
@Override
public List<PaperEntity> findSimulationPaper() {
// 先从缓存中获取名为 TamguoConstant.SIMULATION_PAPER 的缓存对象,并强制转换为 List<PaperEntity> 类型,尝试获取之前缓存的模拟试卷信息列表,
// 如果缓存中有数据,就直接返回该缓存中的试卷列表数据,避免再次查询数据库。
List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.SIMULATION_PAPER);
if(paperList == null || paperList.isEmpty()){
Page<PaperEntity> page = new Page<>(1 , 6);
paperList = paperMapper.findByTypeAndAreaId(TamguoConstant.MONI_PAPER_ID , TamguoConstant.BEIJING_AREA_ID , page);
cacheService.setObject(TamguoConstant.SIMULATION_PAPER, paperList , 2 * 60 * 60);
// 判断从缓存中获取到的模拟试卷列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取模拟试卷信息列表并缓存起来供后续使用。
if (paperList == null || paperList.isEmpty()) {
// 创建一个 Page 对象用于分页查询,设置当前页码为 1每页显示记录数为 6可根据实际情况调整分页参数。
Page<PaperEntity> page = new Page<>(1, 6);
// 通过 PaperMapper 的 findByTypeAndAreaId 方法传入模拟试卷类型标识符TamguoConstant.MONI_PAPER_ID和地区标识符TamguoConstant.BEIJING_AREA_ID以及分页对象page
// 从数据库中获取符合条件的模拟试卷信息列表,具体查询逻辑由 PaperMapper 中对应方法实现来确定,将查询到的模拟试卷列表赋值给 paperList 变量。
paperList = paperMapper.findByTypeAndAreaId(TamguoConstant.MONI_PAPER_ID, TamguoConstant.BEIJING_AREA_ID, page);
// 将获取到的模拟试卷列表存入缓存中,缓存键名为 TamguoConstant.SIMULATION_PAPER缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 以便后续在有效期内再次调用 findSimulationPaper 方法时,能直接从缓存获取模拟试卷数据,提高获取数据的效率。
cacheService.setObject(TamguoConstant.SIMULATION_PAPER, paperList, 2 * 60 * 60);
}
// 最后返回获取到的模拟试卷信息列表,其来源取决于缓存是否命中以及之前的逻辑执行情况,可能是缓存数据也可能是新从数据库获取后存入缓存再返回的数据。
return paperList;
}
/**
* findHotPaper String areaId
* IPaperService findHotPaper
*/
@SuppressWarnings("unchecked")
@Override
public List<PaperEntity> findHotPaper(String areaId) {
// 首先从缓存中获取名为 TamguoConstant.HOT_PAPER 的缓存对象,并强制转换为 List<PaperEntity> 类型,尝试获取之前缓存的热门试卷信息列表,
// 这里有个特殊处理,先将获取到的列表赋值为 null不过通常来说这样的操作不太符合常规逻辑可能需要确认此处的真实意图是否合理
// 暂时按照现有代码逻辑继续分析,后续可能需要优化此处代码。
List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.HOT_PAPER);
paperList = null;
if(paperList == null || paperList.isEmpty()){
Page<PaperEntity> page = new Page<>(1 , 10);
paperList = paperMapper.findByAreaId(areaId ,page);
cacheService.setObject(TamguoConstant.HOT_PAPER, paperList , 2 * 60 * 60);
// 判断热门试卷列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取热门试卷信息列表并缓存起来供后续调用使用。
if (paperList == null || paperList.isEmpty()) {
// 创建一个 Page 对象用于分页查询,设置当前页码为 1每页显示记录数为 10可根据业务实际情况调整分页参数
Page<PaperEntity> page = new Page<>(1, 10);
// 通过 PaperMapper 的 findByAreaId 方法传入地区标识符areaId和分页对象page从数据库中获取该地区的热门试卷信息列表
// 具体的热门试卷判定以及查询逻辑由 PaperMapper 中对应的方法实现来确定,将查询到的热门试卷列表赋值给 paperList 变量。
paperList = paperMapper.findByAreaId(areaId, page);
// 将构建好的热门试卷列表存入缓存中,缓存键名为 TamguoConstant.HOT_PAPER缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 这样在后续的 2 小时内,再次调用 findHotPaper 方法时,就可以直接从缓存获取热门试卷数据,提高获取数据的效率,减少数据库查询操作。
cacheService.setObject(TamguoConstant.HOT_PAPER, paperList, 2 * 60 * 60);
}
// 最后返回获取到的热门试卷信息列表,其来源取决于缓存情况以及之前的逻辑执行结果,可能是缓存中的数据,也可能是新从数据库获取后存入缓存再返回的数据。
return paperList;
}
/**
* findList
* IPaperService findList ID ID
* Page 便
*/
@Override
public Page<PaperEntity> findList(String subjectId , String courseId,
String paperType, String year, String area , Integer pageNum) {
Page<PaperEntity> page = new Page<>(pageNum , TamguoConstant.DEFAULT_PAGE_SIZE);
if("0".equals(courseId)) {
public Page<PaperEntity> findList(String subjectId, String courseId,
String paperType, String year, String area, Integer pageNum) {
// 创建一个 Page 对象用于分页查询,设置当前页码为传入的 pageNum 参数值,每页显示的记录数为 TamguoConstant.DEFAULT_PAGE_SIZE这应该是预定义的每页默认显示记录数常量
Page<PaperEntity> page = new Page<>(pageNum, TamguoConstant.DEFAULT_PAGE_SIZE);
// 判断课程 ID 是否为 "0",如果是,将其设置为空字符串,可能是表示不根据课程 ID 进行筛选的情况,具体业务含义取决于项目设计。
if ("0".equals(courseId)) {
courseId = "";
}
if("0".equals(paperType)) {
// 类似地,判断试卷类型是否为 "0",如果是,将其设置为空字符串,意味着不依据试卷类型进行筛选,具体根据业务逻辑来确定这样处理的意义。
if ("0".equals(paperType)) {
paperType = "";
}
if("0".equals(year)) {
// 判断年份是否为 "0",若是则设置为空字符串,可能表示不按照年份来筛选试卷,同样取决于业务中对年份筛选的具体规则。
if ("0".equals(year)) {
year = "";
}
if("0".equals(area)) {
// 判断地区是否为 "0",若是则设置为空字符串,即不通过地区来筛选试卷,这与业务中地区作为筛选条件的具体定义相关。
if ("0".equals(area)) {
area = "";
}
return page.setRecords(paperMapper.findList(subjectId , courseId , paperType , year , area , page));
// 通过 PaperMapper 的 findList 方法,传入学科 ID、处理后的课程 ID、试卷类型、年份、地区以及分页对象page这些参数
// 从数据库中获取符合条件的试卷信息列表,并将结果设置到 Page 对象中(通过 page.setRecords 方法),最后返回这个包含查询结果的 Page 对象,
// 方便后续进行分页相关的业务操作,如在前端展示分页后的试卷列表等。
return page.setRecords(paperMapper.findList(subjectId, courseId, paperType, year, area, page));
}
/**
* find
* IPaperService find paperId
// 直接调用了 PaperMapper 的 selectById 方法,传入试卷 UID 参数paperId由 PaperMapper 具体实现与数据库的交互查询操作,返回查询到的试卷实体对象。
*/
@Override
public PaperEntity find(String paperId) {
return paperMapper.selectById(paperId);
}
/**
* findPaperByAreaId
* IPaperService findPaperByAreaId ID
// 根据传入的 type 参数值判断不同的情况,如果 type 参数值为 "n",则调用本类中的 findHotPaper 方法(传入 areaId 参数)获取热门试卷信息列表并返回;
// 如果不是 "n",则创建一个 Page 对象用于分页查询(设置当前页码为 1每页显示记录数为 8然后通过 PaperMapper 的 findPaperByAreaId 方法,传入地区 ID、试卷类型以及分页对象
// 从数据库中获取符合条件的试卷信息列表并返回,具体的查询逻辑由 PaperMapper 中对应的方法实现来确定。
*/
@Override
public List<PaperEntity> findPaperByAreaId(String areaId , String type) {
if("n".equals(type)){
public List<PaperEntity> findPaperByAreaId(String areaId, String type) {
if ("n".equals(type)) {
return this.findHotPaper(areaId);
}
Page<PaperEntity> page = new Page<>(1 , 8);
return paperMapper.findPaperByAreaId(areaId , type , page);
Page<PaperEntity> page = new Page<>(1, 8);
return paperMapper.findPaperByAreaId(areaId, type, page);
}
/**
* getPaperTotal
* IPaperService getPaperTotal
// 直接调用了 PaperMapper 的 getPaperTotal 方法,由 PaperMapper 具体实现查询数据库中试卷总记录数的逻辑,返回查询到的试卷总数量(类型为 Long
*/
@Override
public Long getPaperTotal() {
return paperMapper.getPaperTotal();
}
/**
* findByCreaterId
* IPaperService findByCreaterId createrId
// 直接调用了 PaperMapper 的 findByCreaterId 方法,传入创建者 ID 参数createrId由 PaperMapper 具体实现与数据库的交互查询操作,返回查询到的试卷列表。
*/
@Override
public List<PaperEntity> findByCreaterId(String createrId) {
return paperMapper.findByCreaterId(createrId);
}
@Transactional(readOnly=false)
/**
* updatePaperName
* IPaperService updatePaperName
// 首先通过 PaperMapper 的 selectById 方法根据传入的试卷 IDpaperId从数据库中查询获取对应的试卷实体
// 然后将试卷实体的名称属性name设置为传入的新名称name最后通过 PaperMapper 的 updateById 方法将更新后的试卷实体信息更新到数据库中,完成试卷名称的修改操作。
*/
@Transactional(readOnly = false)
@Override
public void updatePaperName(String paperId, String name) {
PaperEntity paper = paperMapper.selectById(paperId);
@ -120,122 +215,15 @@ public class PaperService extends ServiceImpl<PaperMapper, PaperEntity> implemen
paperMapper.updateById(paper);
}
/**
* deletePaper
* IPaperService deletePaper @Transactional(readOnly = false)
// 因为涉及到对数据库中试卷和试题数据的删除操作,需要保证事务的完整性,要么全部操作成功,要么全部回滚。
*/
@SuppressWarnings("unchecked")
@Override
public Result deletePaper(String paperId) {
// 首先通过 PaperMapper 的 selectById 方法根据传入的试卷 IDpaperId从数据库中查询获取对应的试卷实体
PaperEntity paper = paperMapper.selectById(paperId);
if(!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
return Result.result(501, null , "不能删除其他人的试卷!");
}
paperMapper.deleteById(paperId);
// 删除试题
questionMapper.delete(Condition.create().eq("paper_id", paperId));
return Result.result(Result.SUCCESS_CODE, null , "删除成功!");
}
@Transactional(readOnly=false)
@Override
public void addPaperQuestionInfo(String paperId, String title,
String name, String type) {
PaperEntity paper = paperMapper.selectById(paperId);
String questionInfo = paper.getQuestionInfo();
JSONArray qList = JSONArray.parseArray(questionInfo);
JSONObject entity = new JSONObject();
entity.put("name", name);
entity.put("title", title);
entity.put("type", type);
entity.put("uid", UUID.randomUUID().toString());
qList.add(entity);
paper.setQuestionInfo(qList.toString());
paperMapper.updateById(paper);
}
@Transactional(readOnly=false)
@Override
public void updatePaperQuestionInfo(String paperId, String title,
String name, String type , String uid) {
PaperEntity paper = paperMapper.selectById(paperId);
JSONArray qList = JSONArray.parseArray(paper.getQuestionInfo());
for(int i =0 ; i<qList.size() ; i++){
JSONObject q = qList.getJSONObject(i);
if(q.getString("uid").equals(uid)){
q.put("name", name);
q.put("title", title);
q.put("type", type);
}
}
paper.setQuestionInfo(qList.toString());
paperMapper.updateById(paper);
}
@Override
public Result deletePaperQuestionInfoBtn(String paperId, String uid) {
PaperEntity paper = paperMapper.selectById(paperId);
if(!paper.getCreaterId().equals(ShiroUtils.getMember().getUid())) {
return Result.failResult("试卷属于当前用户,不能修改!");
}
JSONArray qList = JSONArray.parseArray(paper.getQuestionInfo());
for(int i =0 ; i<qList.size() ; i++){
JSONObject q = qList.getJSONObject(i);
if(q.getString("uid").equals(uid)){
qList.remove(i);
}
}
paper.setQuestionInfo(qList.toString());
paperMapper.updateById(paper);
return Result.result(Result.SUCCESS_CODE, null, "删除子卷成功");
}
@Override
public Page<PaperEntity> memberPaperList(String name , String memberId , Page<PaperEntity> page) {
if(!StringUtils.isEmpty(name)){
name = "%" + name + "%";
}
return page.setRecords(paperMapper.queryPageByNameAndCreatorId(name , memberId , page));
}
@Transactional(readOnly=false)
@Override
public void addPaper(PaperEntity paper) {
paper.setDownHits(0);
paper.setOpenHits(0);
paper.setQuestionInfo("[]");
// 写入seo信息
paper.setSeoTitle(paper.getName());
paper.setSeoKeywords(paper.getName());
paper.setSeoDescription(paper.getName());
paperMapper.insert(paper);
}
@Transactional(readOnly=true)
@Override
public List<PaperEntity> featuredPaper(String type, String subjectId) {
Page<PaperEntity> page = new Page<>(1,8);
return paperMapper.featuredPaper(type , subjectId , page);
}
@Transactional(readOnly=true)
@Override
public List<PaperEntity> findHotPaper(String subjectId, String courseId) {
Page<PaperEntity> page = new Page<>(1,5);
return paperMapper.findHotPaper(subjectId , courseId , page);
}
@Transactional(readOnly=false)
@Override
public Result updatePaper(PaperEntity paper) {
PaperEntity entity = paperMapper.selectById(paper.getUid());
if(!entity.getCreaterId().equals(ShiroUtils.getMember().getUid())) {
return Result.failResult("试卷属于当前用户,不能修改!");
}
paper.setCreaterId(ShiroUtils.getUserId());
paperMapper.updateById(paper);
return Result.result(Result.SUCCESS_CODE, paper, "修改成功");
}
}
// 通过 ShiroUtils 获取当前用户的 ID并与试卷的创建者 ID 进行比对,如果当前用户不是试卷的创建者,
//

@ -9,6 +9,7 @@ import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.tamguo.dao.PaperMapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.tamguo.dao.QuestionMapper;
import com.tamguo.model.PaperEntity;
import com.tamguo.model.QuestionEntity;
@ -16,88 +17,176 @@ import com.tamguo.service.IQuestionService;
import com.tamguo.util.Result;
import com.tamguo.util.ShiroUtils;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件意味着它主要负责处理与试题Question相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循了Spring的分层架构设计规范。
@Service
public class QuestionService extends ServiceImpl<QuestionMapper, QuestionEntity> implements IQuestionService{
public class QuestionService extends ServiceImpl<QuestionMapper, QuestionEntity> implements IQuestionService {
// 通过Spring的依赖注入机制自动注入 QuestionMapper 接口的实现类实例,
// QuestionMapper 是用于定义与试题实体QuestionEntity相关的数据库操作方法的接口例如查询、插入、更新、删除等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取试题相关的数据信息。
@Autowired
private QuestionMapper questionMapper;
// 同样通过依赖注入注入 PaperMapper 接口的实现类实例PaperMapper 用于定义与试卷实体PaperEntity相关的数据库操作方法
// 在涉及试题与试卷关联相关的业务逻辑(如判断试题所属试卷的创建者等情况)时,会调用它的方法来获取试卷相关信息,辅助试题业务的处理。
@Autowired
private PaperMapper paperMapper;
/**
* findByChapterId
* IQuestionService findByChapterId chapterId
* Page 便
*/
@Override
public Page<QuestionEntity> findByChapterId(String chapterId , Page<QuestionEntity> page) {
return page.setRecords(questionMapper.findByChapterId(chapterId , page));
public Page<QuestionEntity> findByChapterId(String chapterId, Page<QuestionEntity> page) {
// 通过 QuestionMapper 的 findByChapterId 方法,传入章节 IDchapterId和分页对象page
// 从数据库中获取该章节下的试题信息列表,并将结果设置到传入的 Page 对象中(通过 page.setRecords 方法),
// 最后返回这个包含查询结果的 Page 对象,以便后续进行分页相关的业务操作,比如在前端展示该章节下分页后的试题列表。
return page.setRecords(questionMapper.findByChapterId(chapterId, page));
}
@Transactional(readOnly=true)
/**
* findNormalQuestion
* IQuestionService findNormalQuestion @Transactional(readOnly = true)
// 因为只是进行查询操作,不会对数据库中的数据进行修改,使用只读事务可以提高查询性能,同时避免不必要的数据库锁等问题,
// 直接调用了 QuestionMapper 的 findNormalQuestion 方法传入试题的唯一标识符uid由 QuestionMapper 具体实现与数据库的交互查询操作,返回查询到的试题实体对象。
*/
@Transactional(readOnly = true)
@Override
public QuestionEntity findNormalQuestion(String uid) {
return questionMapper.findNormalQuestion(uid);
}
/**
* findPaperQuestion
* IQuestionService findPaperQuestion
// 直接调用了 QuestionMapper 的 findByPaperId 方法传入试卷的唯一标识符paperId由 QuestionMapper 具体实现与数据库的交互查询操作,返回查询到的该试卷包含的试题列表。
*/
@Override
public List<QuestionEntity> findPaperQuestion(String paperId) {
return questionMapper.findByPaperId(paperId);
}
/**
* select
* IQuestionService select questionId
// 直接调用了 QuestionMapper 的 selectByUid 方法,传入试题 UID 参数questionId由 QuestionMapper 具体实现与数据库的交互查询操作,返回查询到的试题实体对象。
*/
@Override
public QuestionEntity select(String questionId) {
return questionMapper.selectByUid(questionId);
}
@Transactional(readOnly=false)
/**
* addQuestion
* IQuestionService addQuestion @Transactional(readOnly = false)
// 因为涉及到向数据库插入试题数据的操作,需要保证事务的完整性,确保插入操作要么全部成功,要么全部回滚,避免出现数据不一致的情况。
*/
@Transactional(readOnly = false)
@Override
public Result addQuestion(QuestionEntity question) {
// 首先通过 PaperMapper 的 selectById 方法,根据传入的试题所属试卷的 IDquestion.getPaperId().toString())从数据库中查询获取对应的试卷实体,
// 这是为了后续判断当前用户是否有权限向该试卷添加试题,需要确认试卷的创建者信息。
PaperEntity paper = paperMapper.selectById(question.getPaperId().toString());
if(!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
// 通过 ShiroUtils 获取当前用户的 ID并与试卷的创建者 ID 进行比对,如果当前用户不是试卷的创建者,
// 则返回相应的错误提示结果(表示该试卷不属于当前用户,无权添加试题),将错误提示信息封装在 Result 对象中返回给调用者。
if (!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
return Result.result(501, null, "改试卷不属于您!");
}
// 如果当前用户是试卷的创建者,将试题的课程 ID 设置为试卷的课程 ID确保试题与所属试卷在课程方面的关联性
question.setCourseId(paper.getCourseId());
// 通过 QuestionMapper 的 insert 方法将试题实体信息插入到数据库中,完成试题添加操作,
// 最后返回添加成功的提示结果,将成功信息封装在 Result 对象中返回给调用者,表示试题添加成功。
questionMapper.insert(question);
return Result.result(0, null, "添加成功!");
}
@Transactional(readOnly=true)
/**
* queryQuestionList
* IQuestionService queryQuestionList ID
* Page
*/
@Transactional(readOnly = true)
@Override
public Page<QuestionEntity> queryQuestionList(String questionType , String uid , String content , String paperId ,
Page<QuestionEntity> page) {
if(!ShiroUtils.getUserId().equals(paperMapper.selectById(paperId).getCreaterId())) {
public Page<QuestionEntity> queryQuestionList(String questionType, String uid, String content, String paperId,
Page<QuestionEntity> page) {
// 通过 PaperMapper 的 selectById 方法,根据传入的试卷 IDpaperId从数据库中查询获取对应的试卷实体
// 然后获取试卷的创建者 ID并与当前用户的 ID通过 ShiroUtils 获取)进行比对,如果当前用户不是试卷的创建者,
// 则直接返回一个空的记录列表(通过 page.setRecords(null) 设置 Page 对象的记录列表为空),表示无权查询该试卷下的试题信息。
if (!ShiroUtils.getUserId().equals(paperMapper.selectById(paperId).getCreaterId())) {
return page.setRecords(null);
}
if(!StringUtils.isEmpty(content)){
// 判断传入的试题内容content是否为空字符串如果不为空则在内容前后添加 "%" 符号,用于构建模糊查询的条件,
// 可能在数据库查询中会根据这个模糊条件查找包含指定内容的试题信息,具体取决于数据库查询语句的实现逻辑。
if (!StringUtils.isEmpty(content)) {
content = "%" + content + "%";
}
return page.setRecords(questionMapper.queryQuestionList(questionType, uid , content , paperId , page));
// 通过 QuestionMapper 的 queryQuestionList 方法,传入试题类型、试题唯一标识符、处理后的试题内容、试卷 ID 以及分页对象page这些参数
// 从数据库中获取符合条件的试题信息列表,并将结果设置到 Page 对象中(通过 page.setRecords 方法),
// 最后返回这个包含查询结果的 Page 对象,方便后续进行分页相关的业务操作,如在前端展示分页后的试题列表等。
return page.setRecords(questionMapper.queryQuestionList(questionType, uid, content, paperId, page));
}
@Transactional(readOnly=false)
/**
* updateQuestion
* IQuestionService updateQuestion @Transactional(readOnly = false)
// 因为涉及到对数据库中试题数据的更新操作,需要保证事务的完整性,确保更新操作要么全部成功,要么全部回滚,避免出现数据不一致的情况。
*/
@Transactional(readOnly = false)
@Override
public Result updateQuestion(QuestionEntity question) {
// 首先通过 PaperMapper 的 selectById 方法,根据传入的试题所属试卷的 IDquestion.getPaperId().toString())从数据库中查询获取对应的试卷实体,
// 这是为了后续判断当前用户是否有权限更新该试卷下的试题信息,需要确认试卷的创建者信息。
PaperEntity paper = paperMapper.selectById(question.getPaperId().toString());
if(!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
// 通过 ShiroUtils 获取当前用户的 ID并与试卷的创建者 ID 进行比对,如果当前用户不是试卷的创建者,
// 则返回相应的错误提示结果(表示该试卷不属于当前用户,无权更新试题),将错误提示信息封装在 Result 对象中返回给调用者。
if (!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
return Result.result(501, null, "改试卷不属于您!");
}
// 如果当前用户是试卷的创建者,通过 QuestionMapper 的 updateById 方法,将传入的更新后的试题实体信息更新到数据库中,完成试题更新操作,
// 最后返回更新成功的提示结果,将成功信息封装在 Result 对象中返回给调用者,表示试题更新成功。
questionMapper.updateById(question);
return Result.result(0, null, "修改成功!");
}
@Transactional(readOnly=false)
/**
* delete
* IQuestionService delete @Transactional(readOnly = false)
// 因为涉及到对数据库中试题数据的删除操作,需要保证事务的完整性,确保删除操作要么全部成功,要么全部回滚,避免出现数据不一致的情况。
*/
@Transactional(readOnly = false)
@Override
public Result delete(String uid) {
// 首先通过 QuestionMapper 的 selectById 方法根据传入的试题的唯一标识符uid从数据库中查询获取对应的试题实体
QuestionEntity question = questionMapper.selectById(uid);
// 接着通过 PaperMapper 的 selectById 方法,根据试题所属试卷的 IDquestion.getPaperId().toString())从数据库中查询获取对应的试卷实体,
// 这是为了判断当前用户是否有权限删除该试卷下的试题,需要确认试卷的创建者信息。
PaperEntity paper = paperMapper.selectById(question.getPaperId().toString());
if(!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
// 通过 ShiroUtils 获取当前用户的 ID并与试卷的创建者 ID 进行比对,如果当前用户不是试卷的创建者,
// 则返回相应的错误提示结果(表示该试卷不属于当前用户,无权删除试题),将错误提示信息封装在 Result 对象中返回给调用者。
if (!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
return Result.result(501, null, "改试卷不属于您!");
}
// 如果当前用户是试卷的创建者,通过 QuestionMapper 的 deleteById 方法根据试题的唯一标识符uid从数据库中删除对应的试题信息完成试题删除操作
// 最后返回删除成功的提示结果,将成功信息封装在 Result 对象中返回给调用者,表示试题删除成功。
questionMapper.deleteById(uid);
return Result.result(0, null, "删除成功!");
}
@Transactional(readOnly=true)
/**
* featuredQuestion
* IQuestionService featuredQuestion @Transactional(readOnly = true)
// 因为只是进行查询操作,不会对数据库中的数据进行修改,使用只读事务可以提高查询性能,同时避免不必要的数据库锁等问题。
*/
@Transactional(readOnly = true)
@Override
public List<QuestionEntity> featuredQuestion(String subjectId, String courseId) {
Page<QuestionEntity> page = new Page<>(1 , 5);
return questionMapper.featuredQuestion(subjectId , courseId , page);
// 创建一个 Page 对象用于分页查询,设置当前页码为 1每页显示记录数为 5这里的分页参数可以根据实际业务需求调整
Page<QuestionEntity> page = new Page<>(1, 5);
// 通过 QuestionMapper 的 featuredQuestion 方法,传入学科 IDsubjectId和课程 IDcourseId以及分页对象page
// 从数据库中获取符合条件的特色试题信息列表,具体的特色试题判定以及查询逻辑由 QuestionMapper 中对应的方法实现来确定,
// 最后返回查询到的特色试题列表,方便在业务中进行展示、推荐等相关操作。
return questionMapper.featuredQuestion(subjectId, courseId, page);
}
}
}

@ -19,54 +19,105 @@ import com.tamguo.model.SchoolEntity;
import com.tamguo.service.ISchoolService;
import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它主要负责处理与学校School相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循Spring的分层架构设计。
@Service
public class SchoolService extends ServiceImpl<SchoolMapper, SchoolEntity> implements ISchoolService {
// 通过Spring的依赖注入机制自动注入 SchoolMapper 接口的实现类实例,
// SchoolMapper 是用于定义与学校实体SchoolEntity相关的数据库操作方法的接口例如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取学校相关的数据信息。
@Autowired
private SchoolMapper schoolMapper;
// 同样通过依赖注入注入 PaperMapper 接口的实现类实例PaperMapper 用于定义与试卷实体PaperEntity相关的数据库操作方法
// 在涉及学校与试卷关联相关的业务逻辑(如获取某个学校的试卷列表等情况)时,会调用它的方法来获取试卷相关信息,辅助学校业务的处理。
@Autowired
private PaperMapper paperMapper;
// 通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在学校相关业务中用于缓存不同类型的学校列表数据以及关联的试卷数据等,
// 以减少频繁访问数据库获取学校及相关信息的开销,提高系统性能,优化学校相关业务操作的响应速度。
@Autowired
private CacheService cacheService;
/**
* findEliteSchoolPaper
* ISchoolService findEliteSchoolPaper
*/
@SuppressWarnings("unchecked")
@Override
public List<SchoolEntity> findEliteSchoolPaper(String shcoolId) {
// 首先尝试从缓存中获取名为 TamguoConstant.ELITE_SCHOOL_PAPER 的缓存对象,并将其强制转换为 List<SchoolEntity> 类型,
// 期望从缓存中获取到之前存储的名校试卷相关的学校信息列表,如果缓存命中,就可以直接返回这个缓存中的学校列表数据,减少数据库查询操作。
List<SchoolEntity> schoolList = (List<SchoolEntity>) cacheService.getObject(TamguoConstant.ELITE_SCHOOL_PAPER);
// 获取名校试卷
if(schoolList == null || schoolList.isEmpty()){
// 判断从缓存中获取到的学校列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的学校信息),
// 如果满足这个条件,就需要从数据库中重新获取名校试卷相关的学校信息列表,并构建学校与试卷的关联信息,然后将其存入缓存中供后续使用。
if (schoolList == null || schoolList.isEmpty()) {
// 创建一个 Page 对象用于分页查询学校信息,设置当前页码为 1每页显示的记录数为 3这里的分页参数可以根据实际业务需求调整
Page<SchoolEntity> page = new Page<>();
page.setCurrent(1);
page.setSize(3);
schoolList = schoolMapper.findByAreaId(shcoolId , page);
for(SchoolEntity school : schoolList){
// 通过注入的 SchoolMapper 的 findByAreaId 方法,传入学校 IDshcoolId和分页对象page
// 从数据库中获取符合条件的学校信息列表,这里的具体查询逻辑由 SchoolMapper 中对应的方法实现来确定,返回查询到的学校列表赋值给 schoolList 变量。
schoolList = schoolMapper.findByAreaId(shcoolId, page);
// 遍历获取到的学校列表对于每个学校school需要获取其对应的试卷列表信息并进行关联设置。
for (SchoolEntity school : schoolList) {
// 创建一个新的 Page 对象用于分页查询试卷信息,设置当前页码为 1每页显示记录数为 3同样可根据实际情况调整分页参数。
Page<PaperEntity> p = new Page<>();
p.setCurrent(1);
p.setSize(3);
List<PaperEntity> paperList = paperMapper.findBySchoolId(school.getUid() , p);
// 通过 PaperMapper 的 findBySchoolId 方法传入当前学校的唯一标识符school.getUid()和分页对象p
// 从数据库中获取该学校对应的试卷信息列表,具体的查询逻辑由 PaperMapper 中对应方法实现来确定,将查询到的试卷列表赋值给 school 的 paperList 属性,
// 这样就构建了学校与试卷的关联关系,每个学校实体中包含了其对应的试卷列表信息。
List<PaperEntity> paperList = paperMapper.findBySchoolId(school.getUid(), p);
school.setPaperList(paperList);
}
cacheService.setObject(TamguoConstant.ELITE_SCHOOL_PAPER, schoolList , 2 * 60 * 60);
// 将构建好的包含学校与试卷关联信息的学校列表存入缓存中,缓存的键名为 TamguoConstant.ELITE_SCHOOL_PAPER同时设置了缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findEliteSchoolPaper 方法,就可以直接从缓存中获取名校试卷相关的学校数据,而不用再次查询数据库了。
cacheService.setObject(TamguoConstant.ELITE_SCHOOL_PAPER, schoolList, 2 * 60 * 60);
}
// 最后返回获取到的学校信息列表,这个列表可能是从缓存中直接获取的,也可能是从数据库中查询后存入缓存再返回的,
// 取决于缓存中是否存在有效数据以及之前的逻辑执行情况,每个学校实体中包含了对应的试卷列表信息,方便在业务中进行展示、操作等处理。
return schoolList;
}
/**
* findEliteSchool
* ISchoolService findEliteSchool 访
*/
@SuppressWarnings("unchecked")
@Override
public List<SchoolEntity> findEliteSchool() {
// 先从缓存中获取名为 TamguoConstant.ELITE_PAPER 的缓存对象,并强制转换为 List<SchoolEntity> 类型,尝试获取之前缓存的名校信息列表,
// 如果缓存中有数据,就直接返回该缓存中的学校列表数据,避免再次查询数据库。
List<SchoolEntity> schoolList = (List<SchoolEntity>) cacheService.getObject(TamguoConstant.ELITE_PAPER);
if(schoolList == null || schoolList.isEmpty()){
RowBounds row = new RowBounds(1 , 6);
// 判断从缓存中获取到的学校列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取名校信息列表并缓存起来供后续使用。
if (schoolList == null || schoolList.isEmpty()) {
// 创建一个 RowBounds 对象用于设置分页参数,这里设置偏移量为 1表示从第 1 条记录开始获取,通常第 1 条记录的索引是 0所以实际是获取第 2 条开始的数据,不过具体含义可能根据数据库实现有差异),
// 每页获取的记录数为 6可根据实际业务需求调整用于在数据库查询中进行分页控制。
RowBounds row = new RowBounds(1, 6);
// 通过 SchoolMapper 的 selectPage 方法,传入 RowBounds 对象row和 Condition.EMPTY表示使用默认的查询条件通常是查询所有符合条件的记录
// 从数据库中获取符合条件的名校信息列表,具体的名校判定以及查询逻辑由 SchoolMapper 中对应的方法实现来确定,将查询到的名校列表赋值给 schoolList 变量。
schoolList = schoolMapper.selectPage(row, Condition.EMPTY);
cacheService.setObject(TamguoConstant.ELITE_PAPER, schoolList , 2 * 60 * 60);
// 将获取到的名校列表存入缓存中,缓存键名为 TamguoConstant.ELITE_PAPER缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 以便后续在有效期内再次调用 findEliteSchool 方法时,能直接从缓存获取名校数据,提高获取数据的效率。
cacheService.setObject(TamguoConstant.ELITE_PAPER, schoolList, 2 * 60 * 60);
}
// 最后返回获取到的名校信息列表,其来源取决于缓存是否命中以及之前的逻辑执行情况,可能是缓存数据也可能是新从数据库获取后存入缓存再返回的数据。
return schoolList;
}
@Transactional(readOnly=true)
/**
* findSchoolByAreaId
* ISchoolService findSchoolByAreaId ID ID
// 该方法被标记为只读事务(通过 @Transactional(readOnly = true) 注解指定),因为只是进行查询操作,不会对数据库中的数据进行修改,使用只读事务可以提高查询性能,同时避免不必要的数据库锁等问题。
*/
@Transactional(readOnly = true)
@Override
public List<SchoolEntity> findSchoolByAreaId(String areaId) {
// 将传入的以逗号分隔的地区 ID 字符串进行分割,得到一个包含各个地区 ID 的字符串数组,
// 然后通过 Arrays.asList 方法将其转换为 List<String> 类型,以便后续作为参数传递给 SchoolMapper 的查询方法,用于根据地区 ID 列表查询学校信息。
return schoolMapper.findByAreaIds(Arrays.asList(areaId.split(",")));
}
}
}

@ -14,76 +14,132 @@ import com.tamguo.model.CourseEntity;
import com.tamguo.model.SubjectEntity;
import com.tamguo.service.ISubjectService;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件意味着它主要负责处理与学科Subject相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循Spring的分层架构设计。
@Service
public class SubjectService extends ServiceImpl<SubjectMapper, SubjectEntity> implements ISubjectService{
public class SubjectService extends ServiceImpl<SubjectMapper, SubjectEntity> implements ISubjectService {
// 通过Spring的依赖注入机制自动注入 SubjectMapper 接口的实现类实例,
// SubjectMapper 是用于定义与学科实体SubjectEntity相关的数据库操作方法的接口比如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取学科相关的数据信息。
@Autowired
private SubjectMapper subjectMapper;
// 同样通过依赖注入注入 CourseMapper 接口的实现类实例CourseMapper 用于定义与课程CourseEntity相关的数据库操作方法
// 在涉及学科与课程关联相关的业务逻辑(如获取某个学科下的课程列表等情况)时,会调用它的方法来获取课程相关信息,辅助学科业务的处理。
@Autowired
private CourseMapper courseMapper;
/**
* find
* ISubjectService find uid
*/
@Override
public SubjectEntity find(String uid) {
// 通过 SubjectMapper 的 selectById 方法,根据传入的学科 IDuid从数据库中查询获取对应的学科实体对象这一步获取到了基本的学科信息。
SubjectEntity subject = subjectMapper.selectById(uid);
// 通过 CourseMapper 的 findBySubjectId 方法,传入学科 IDuid从数据库中查询获取该学科下的课程列表信息得到一个包含课程实体对象的列表。
List<CourseEntity> courseList = courseMapper.findBySubjectId(uid);
// 将查询到的课程列表信息设置到学科实体对象的 courseList 属性中,这样返回的学科对象就包含了其关联的课程信息,方便后续在业务中进行统一处理和展示等操作。
subject.setCourseList(courseList);
return subject;
}
/**
* getCourseTree
* JSONArray
*/
@Override
public JSONArray getCourseTree() {
// 创建一个空的 JSONArray 对象,用于存储最终构建好的课程树形结构数据,后续会不断向其中添加节点信息来构建完整的树形结构。
JSONArray courseTree = new JSONArray();
// 通过 SubjectMapper 的 selectList 方法,传入 Condition.EMPTY表示使用默认的查询条件通常意味着查询所有符合条件的记录在这里就是所有学科记录
// 从数据库中获取所有的学科信息列表,得到一个包含所有学科实体对象的列表,用于后续构建树形结构时作为顶层节点(父节点)使用。
@SuppressWarnings("unchecked")
List<SubjectEntity> subjectList = subjectMapper.selectList(Condition.EMPTY);
for(int i=0 ; i<subjectList.size() ; i++){
// 遍历获取到的学科列表对于每个学科subject都要在树形结构中创建对应的节点信息并添加到 courseTree 中,同时还要添加其下的课程节点信息作为子节点。
for (int i = 0; i < subjectList.size(); i++) {
SubjectEntity subject = subjectList.get(i);
// 创建一个新的 JSONObject 对象,用于表示当前学科在树形结构中的节点信息,后续会向其中设置各种属性,如唯一标识符、名称、父节点 ID 等。
JSONObject node = new JSONObject();
// 设置当前学科节点的唯一标识符这里在学科原有的唯一标识符subject.getUid())前添加了 "s",可能是为了在整个树形结构中区分学科节点和课程节点,或者遵循特定的业务标识规则。
node.put("uid", "s" + subject.getUid());
// 设置当前学科节点的名称直接从学科实体对象中获取其名称属性subject.getName())并设置到节点信息中,方便在前端等展示时呈现相应的学科名称。
node.put("name", subject.getName());
// 设置当前学科节点的父节点 ID这里设置为 "-1",表示它是顶层节点(没有父节点),符合树形结构中根节点的特征,当然具体的父节点标识规则可以根据业务实际情况调整。
node.put("parentId", "-1");
// 将构建好的学科节点信息添加到 courseTree 中,这样就完成了一个学科节点在树形结构中的添加操作,后续还需要添加该学科下的课程节点作为其子节点。
courseTree.add(node);
// 通过 CourseMapper 的 findBySubjectId 方法传入当前学科的唯一标识符subject.getUid()),从数据库中查询获取该学科下的课程列表信息,得到一个包含课程实体对象的列表,用于添加课程节点作为当前学科节点的子节点。
List<CourseEntity> courseList = courseMapper.findBySubjectId(subject.getUid());
for(int k=0 ; k<courseList.size() ; k++){
// 遍历获取到的课程列表对于每个课程course同样要在树形结构中创建对应的节点信息并添加到 courseTree 中,作为当前学科节点的子节点。
for (int k = 0; k < courseList.size(); k++) {
CourseEntity course = courseList.get(k);
// 重新创建一个新的 JSONObject 对象,用于表示当前课程在树形结构中的节点信息,同样后续会设置其相关属性,如唯一标识符、名称、父节点 ID 等。
node = new JSONObject();
// 设置当前课程节点的唯一标识符直接使用课程实体对象的唯一标识符course.getUid()),使其在整个树形结构中有唯一标识,方便后续操作和查找等。
node.put("uid", course.getUid());
// 设置当前课程节点的名称从课程实体对象中获取其名称属性course.getName())并设置到节点信息中,以便在前端展示相应的课程名称。
node.put("name", course.getName());
// 设置当前课程节点的父节点 ID这里设置为 "s" 加上当前学科的唯一标识符subject.getUid()),表明该课程节点的父节点是对应的学科节点,建立起学科与课程之间的层级关联关系。
node.put("parentId", "s" + subject.getUid());
// 将构建好的课程节点信息添加到 courseTree 中,这样就完成了一个课程节点在树形结构中的添加操作,作为对应学科节点的子节点,不断重复这个过程,就构建出了完整的课程树形结构。
courseTree.add(node);
}
}
return courseTree;
}
/**
* getCourseCascaderTree
* Cascader使 JSONArray 便
*/
@Override
public JSONArray getCourseCascaderTree() {
// 创建一个空的 JSONArray 对象,用于存储最终构建好的适合级联选择器的课程树形结构数据,后续会不断向其中添加节点信息来构建完整的树形结构。
JSONArray courseTree = new JSONArray();
// 通过 SubjectMapper 的 selectList 方法,传入 Condition.EMPTY表示使用默认的查询条件通常意味着查询所有符合条件的记录在这里就是所有学科记录
// 从数据库中获取所有的学科信息列表,得到一个包含所有学科实体对象的列表,用于后续构建树形结构时作为顶层节点(父节点)使用,与 getCourseTree 方法中的这一步类似,都是先获取学科基础信息。
@SuppressWarnings("unchecked")
List<SubjectEntity> subjectList = subjectMapper.selectList(Condition.EMPTY);
for(int i=0 ; i<subjectList.size() ; i++){
// 遍历获取到的学科列表对于每个学科subject都要在树形结构中创建对应的节点信息并添加到 courseTree 中,同时还要添加其下的课程节点信息作为子节点,整体构建思路与 getCourseTree 方法类似,但节点的属性设置等细节稍有不同,以符合级联选择器的数据格式要求。
for (int i = 0; i < subjectList.size(); i++) {
SubjectEntity subject = subjectList.get(i);
// 创建一个新的 JSONObject 对象用于表示当前学科在树形结构中的节点信息后续会向其中设置各种属性如值value、标签label以及子节点children这些属性符合级联选择器对节点数据格式的要求。
JSONObject node = new JSONObject();
// 设置当前学科节点的值value直接使用学科实体对象的唯一标识符subject.getUid()),这个值在级联选择器中用于唯一标识该节点,方便后续的选中、传递等操作,具体使用方式根据前端级联选择器的实现而定。
node.put("value", subject.getUid());
// 设置当前学科节点的标签label从学科实体对象中获取其名称属性subject.getName())并设置到节点信息中,这样在前端级联选择器中展示时会呈现相应的学科名称,方便用户识别和选择。
node.put("label", subject.getName());
// 创建一个空的 JSONArray 对象,用于存储当前学科节点下的课程子节点信息,后续会遍历该学科下的课程列表,将课程节点信息添加到这个数组中,再设置到学科节点的 children 属性中,形成层级结构。
JSONArray children = new JSONArray();
// 通过 CourseMapper 的 findBySubjectId 方法传入当前学科的唯一标识符subject.getUid()),从数据库中查询获取该学科下的课程列表信息,得到一个包含课程实体对象的列表,用于添加课程节点作为当前学科节点的子节点,与 getCourseTree 方法中的这一步操作相同,都是获取课程信息用于构建树形结构。
List<CourseEntity> courseList = courseMapper.findBySubjectId(subject.getUid());
for(int k=0 ; k<courseList.size() ; k++){
// 遍历获取到的课程列表对于每个课程course同样要在树形结构中创建对应的节点信息并添加到 children 数组中,作为当前学科节点的子节点,构建出完整的学科 - 课程层级结构。
for (int k = 0; k < courseList.size(); k++) {
CourseEntity course = courseList.get(k);
// 创建一个新的 JSONObject 对象用于表示当前课程在树形结构中的节点信息同样后续会设置其相关属性如值value、标签label符合级联选择器对节点数据格式的要求。
JSONObject no = new JSONObject();
// 设置当前课程节点的值value直接使用课程实体对象的唯一标识符course.getUid()),使其在整个树形结构中有唯一标识,方便在级联选择器中进行选择和传递等操作。
no.put("value", course.getUid());
// 设置当前课程节点的标签label从课程实体对象中获取其名称属性course.getName())并设置到节点信息中,以便在前端级联选择器中展示相应的课程名称,方便用户识别和选择。
no.put("label", course.getName());
// 将构建好的课程节点信息添加到 children 数组中,这样就完成了一个课程节点在树形结构中的添加操作,作为对应学科节点的子节点,不断重复这个过程,就构建出了当前学科下包含课程子节点的层级结构。
children.add(no);
}
// 将包含课程子节点信息的 children 数组设置到当前学科节点的 children 属性中,这样就完整地构建好了一个学科节点及其下课程子节点的树形结构信息,符合级联选择器对数据格式的要求。
node.put("children", children);
// 将构建好的学科节点信息添加到 courseTree 中,这样就完成了一个学科节点及其子节点在树形结构中的添加操作,不断重复这个过程,就构建出了整个适合级联选择器使用的课程树形结构。
courseTree.add(node);
}
return courseTree;
}
}
}
Loading…
Cancel
Save