diff --git a/tamguo/src/main/java/com/tamguo/service/impl/ChapterService.java b/tamguo/src/main/java/com/tamguo/service/impl/ChapterService.java index 6bd85c1..32bbf1d 100644 --- a/tamguo/src/main/java/com/tamguo/service/impl/ChapterService.java +++ b/tamguo/src/main/java/com/tamguo/service/impl/ChapterService.java @@ -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 implements IChapterService{ - +public class ChapterService extends ServiceImpl implements IChapterService { + + // 通过Spring的依赖注入机制,自动注入 ChapterMapper 接口的实现类实例, + // ChapterMapper 是用于定义与章节实体(ChapterEntity)相关的数据库操作方法的接口,例如查询、插入、更新等操作, + // 在本服务类中会调用它的方法来与数据库进行交互,获取章节相关的数据信息。 @Autowired private ChapterMapper chapterMapper; + /** + * findCourseChapter 方法 + * 实现了 IChapterService 接口中的 findCourseChapter 方法,用于获取某课程书籍(通过 bookId 参数指定)下的章节信息,并构建章节的树形结构数据。 + */ @Override public List findCourseChapter(String bookId) { + // 首先调用 ChapterMapper 的 findByBookId 方法,根据传入的书籍 ID(bookId)从数据库中获取该书籍对应的所有章节信息列表, + // 这些章节信息将作为后续构建章节树形结构的基础数据。 List chapterList = chapterMapper.findByBookId(bookId); - - // 获取根chapter UID + + // 初始化用于存储根章节 UID 的变量,初始值设为空字符串,后续会在章节列表中查找真正的根章节 UID 并赋值。 String rootUid = StringUtils.EMPTY; - for(int i=0 ; i entitys = new ArrayList<>(); - for(int i=0 ; i childs = new ArrayList<>(); - for(int k=0 ; k childs = entitys.get(i).getChildChapterList(); - for(int k=0 ; k tmpChilds = new ArrayList<>(); - for(int n=0 ; n 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 list = chapterMapper.findByBookId(courseId); - if(CollectionUtils.isEmpty(list)) { + if (CollectionUtils.isEmpty(list)) { return rootChapterNode(); } + + // 如果获取到了有效的章节信息列表,就直接返回这个列表,该列表后续可用于构建具体的章节树形结构或者直接在业务中进行展示、处理等操作,具体取决于业务逻辑要求。 return list; } - private List rootChapterNode(){ + /** + * rootChapterNode 方法 + * 这是一个私有方法,用于创建并返回一个默认的根章节节点列表,构建了一个包含单个 ChapterEntity 实例的列表, + * 该实例模拟了一个根章节的基本信息,如设置了课程 ID、顺序号、知识点数量、题目数量、章节 UID、章节名称以及父章节 ID 等属性值, + * 返回的这个默认根章节节点列表可以在没有具体课程章节数据或者作为章节树的顶级节点等情况下使用,确保章节树结构有一个初始的根节点展示。 + */ + private List rootChapterNode() { ChapterEntity chapter = new ChapterEntity(); chapter.setCourseId(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID); chapter.setOrders(0); @@ -104,5 +160,4 @@ public class ChapterService extends ServiceImpl im chapter.setParentId(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID); return Arrays.asList(chapter); } - -} +} \ No newline at end of file diff --git a/tamguo/src/main/java/com/tamguo/service/impl/MemberService.java b/tamguo/src/main/java/com/tamguo/service/impl/MemberService.java index c3c1250..ecc153d 100644 --- a/tamguo/src/main/java/com/tamguo/service/impl/MemberService.java +++ b/tamguo/src/main/java/com/tamguo/service/impl/MemberService.java @@ -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 implements IMemberService{ - +public class MemberService extends ServiceImpl 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 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 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 \ No newline at end of file diff --git a/tamguo/src/main/java/com/tamguo/service/impl/MenuService.java b/tamguo/src/main/java/com/tamguo/service/impl/MenuService.java index 51f90d8..97505ae 100644 --- a/tamguo/src/main/java/com/tamguo/service/impl/MenuService.java +++ b/tamguo/src/main/java/com/tamguo/service/impl/MenuService.java @@ -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 implements IMenuService{ - +public class MenuService extends ServiceImpl implements IMenuService { + + // 通过Spring的依赖注入机制,自动注入 MenuMapper 接口的实现类实例, + // MenuMapper 是用于定义与菜单实体(MenuEntity)相关的数据库操作方法的接口,例如查询、插入、更新等操作, + // 在本服务类中会调用它的方法来与数据库进行交互,获取菜单相关的数据信息。 @Autowired private MenuMapper menuMapper; + + // 同样通过依赖注入注入 CacheService 实例,CacheService 大概率是用于处理缓存相关操作的服务类, + // 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在这个类中主要用于缓存不同类型的菜单数据, + // 以减少频繁访问数据库获取菜单信息的开销,提高系统性能,优化菜单相关业务操作的响应速度。 @Autowired private CacheService cacheService; + /** + * findMenus 方法 + * 实现了 IMenuService 接口中的 findMenus 方法,用于获取首页菜单信息列表,并加入了缓存相关逻辑来优化数据获取性能。 + */ @SuppressWarnings("unchecked") @Override public List findMenus() { + // 首先尝试从缓存中获取名为 TamguoConstant.INDEX_MENU 的缓存对象,并将其强制转换为 List 类型, + // 期望从缓存中获取到之前存储的首页菜单信息列表,如果缓存命中,就可以直接返回这个缓存中的菜单列表数据,避免重复查询数据库。 List menuList = ((List) 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 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 findAllMenus() { + // 首先尝试从缓存中获取名为 TamguoConstant.ALL_INDEX_MENU 的缓存对象,并将其强制转换为 List 类型, + // 期望获取到之前存储的所有菜单信息列表,如果缓存命中,直接返回该缓存中的菜单列表数据即可。 List allMenuList = ((List) 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 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 findLeftMenus() { + // 先从缓存中获取名为 TamguoConstant.LEFT_INDEX_MENU 的缓存对象,并强制转换为 List 类型,尝试获取之前缓存的左侧菜单信息列表, + // 如果缓存中有数据,就直接返回该缓存中的菜单列表数据,避免再次查询数据库。 List leftMenuList = ((List) 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 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 findChapterMenus() { + // 首先从缓存中获取名为 TamguoConstant.CHAPTER_INDEX_MENU 的缓存对象,并强制转换为 List 类型,尝试获取之前缓存的章节相关菜单信息列表, + // 如果缓存中有有效数据,就直接返回该缓存中的菜单列表数据,无需再次查询数据库。 List chapterMenuList = ((List) 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 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 findFooterMenus() { + // 先从缓存中获取名为 TamguoConstant.FOOTER_INDEX_MENU 的缓存对象,并强制转换为 List 类型,尝试获取之前缓存的底部菜单信息列表, + // 这里有个特殊处理,先将获取到的列表赋值为 null,不过从常规逻辑来看,这样可能不太合适,一般如果要清空缓存重新获取数据,会有更合理的删除缓存等操作方式, + // 暂时按照现有代码逻辑继续分析,后续可能需要确认此处的真实意图是否合理。 List footerMenuList = ((List) 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 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 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); } - -} +} \ No newline at end of file diff --git a/tamguo/src/main/java/com/tamguo/service/impl/PaperService.java b/tamguo/src/main/java/com/tamguo/service/impl/PaperService.java index c82c531..67c14f6 100644 --- a/tamguo/src/main/java/com/tamguo/service/impl/PaperService.java +++ b/tamguo/src/main/java/com/tamguo/service/impl/PaperService.java @@ -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 implements IPaperService{ - +public class PaperService extends ServiceImpl 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 findHistoryPaper() { + // 首先尝试从缓存中获取名为 TamguoConstant.HISTORY_PAPER 的缓存对象,并将其强制转换为 List 类型, + // 期望从缓存中获取到之前存储的历史试卷信息列表,如果缓存命中,就可以直接返回这个缓存中的试卷列表数据,减少数据库查询操作。 List paperList = (List) cacheService.getObject(TamguoConstant.HISTORY_PAPER); - if(paperList == null || paperList.isEmpty()){ - Page 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 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 findSimulationPaper() { + // 先从缓存中获取名为 TamguoConstant.SIMULATION_PAPER 的缓存对象,并强制转换为 List 类型,尝试获取之前缓存的模拟试卷信息列表, + // 如果缓存中有数据,就直接返回该缓存中的试卷列表数据,避免再次查询数据库。 List paperList = (List) cacheService.getObject(TamguoConstant.SIMULATION_PAPER); - if(paperList == null || paperList.isEmpty()){ - Page 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 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 findHotPaper(String areaId) { + // 首先从缓存中获取名为 TamguoConstant.HOT_PAPER 的缓存对象,并强制转换为 List 类型,尝试获取之前缓存的热门试卷信息列表, + // 这里有个特殊处理,先将获取到的列表赋值为 null,不过通常来说这样的操作不太符合常规逻辑,可能需要确认此处的真实意图是否合理, + // 暂时按照现有代码逻辑继续分析,后续可能需要优化此处代码。 List paperList = (List) cacheService.getObject(TamguoConstant.HOT_PAPER); paperList = null; - if(paperList == null || paperList.isEmpty()){ - Page 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 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 findList(String subjectId , String courseId, - String paperType, String year, String area , Integer pageNum) { - Page page = new Page<>(pageNum , TamguoConstant.DEFAULT_PAGE_SIZE); - if("0".equals(courseId)) { + public Page findList(String subjectId, String courseId, + String paperType, String year, String area, Integer pageNum) { + // 创建一个 Page 对象用于分页查询,设置当前页码为传入的 pageNum 参数值,每页显示的记录数为 TamguoConstant.DEFAULT_PAGE_SIZE(这应该是预定义的每页默认显示记录数常量), + Page 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 findPaperByAreaId(String areaId , String type) { - if("n".equals(type)){ + public List findPaperByAreaId(String areaId, String type) { + if ("n".equals(type)) { return this.findHotPaper(areaId); } - Page page = new Page<>(1 , 8); - return paperMapper.findPaperByAreaId(areaId , type , page); + Page 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 findByCreaterId(String createrId) { return paperMapper.findByCreaterId(createrId); } - @Transactional(readOnly=false) + /** + * updatePaperName 方法 + * 实现了 IPaperService 接口中的 updatePaperName 方法,用于更新试卷的名称, + // 首先通过 PaperMapper 的 selectById 方法根据传入的试卷 ID(paperId)从数据库中查询获取对应的试卷实体, + // 然后将试卷实体的名称属性(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 implemen paperMapper.updateById(paper); } + /** + * deletePaper 方法 + * 实现了 IPaperService 接口中的 deletePaper 方法,用于删除试卷以及与之相关的试题信息,被标记为可读写事务(通过 @Transactional(readOnly = false) 注解指定), + // 因为涉及到对数据库中试卷和试题数据的删除操作,需要保证事务的完整性,要么全部操作成功,要么全部回滚。 + */ @SuppressWarnings("unchecked") @Override public Result deletePaper(String paperId) { + // 首先通过 PaperMapper 的 selectById 方法根据传入的试卷 ID(paperId)从数据库中查询获取对应的试卷实体, 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 memberPaperList(String name , String memberId , Page 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 featuredPaper(String type, String subjectId) { - Page page = new Page<>(1,8); - return paperMapper.featuredPaper(type , subjectId , page); - } - - @Transactional(readOnly=true) - @Override - public List findHotPaper(String subjectId, String courseId) { - Page 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 进行比对,如果当前用户不是试卷的创建者, + // \ No newline at end of file diff --git a/tamguo/src/main/java/com/tamguo/service/impl/QuestionService.java b/tamguo/src/main/java/com/tamguo/service/impl/QuestionService.java index 89f84a4..00c2e5a 100644 --- a/tamguo/src/main/java/com/tamguo/service/impl/QuestionService.java +++ b/tamguo/src/main/java/com/tamguo/service/impl/QuestionService.java @@ -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 implements IQuestionService{ - +public class QuestionService extends ServiceImpl 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 findByChapterId(String chapterId , Page page) { - return page.setRecords(questionMapper.findByChapterId(chapterId , page)); + public Page findByChapterId(String chapterId, Page page) { + // 通过 QuestionMapper 的 findByChapterId 方法,传入章节 ID(chapterId)和分页对象(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 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 方法,根据传入的试题所属试卷的 ID(question.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 queryQuestionList(String questionType , String uid , String content , String paperId , - Page page) { - if(!ShiroUtils.getUserId().equals(paperMapper.selectById(paperId).getCreaterId())) { + public Page queryQuestionList(String questionType, String uid, String content, String paperId, + Page page) { + // 通过 PaperMapper 的 selectById 方法,根据传入的试卷 ID(paperId)从数据库中查询获取对应的试卷实体, + // 然后获取试卷的创建者 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 方法,根据传入的试题所属试卷的 ID(question.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 方法,根据试题所属试卷的 ID(question.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 featuredQuestion(String subjectId, String courseId) { - Page page = new Page<>(1 , 5); - return questionMapper.featuredQuestion(subjectId , courseId , page); + // 创建一个 Page 对象用于分页查询,设置当前页码为 1,每页显示记录数为 5(这里的分页参数可以根据实际业务需求调整), + Page page = new Page<>(1, 5); + // 通过 QuestionMapper 的 featuredQuestion 方法,传入学科 ID(subjectId)和课程 ID(courseId)以及分页对象(page), + // 从数据库中获取符合条件的特色试题信息列表,具体的特色试题判定以及查询逻辑由 QuestionMapper 中对应的方法实现来确定, + // 最后返回查询到的特色试题列表,方便在业务中进行展示、推荐等相关操作。 + return questionMapper.featuredQuestion(subjectId, courseId, page); } - -} +} \ No newline at end of file diff --git a/tamguo/src/main/java/com/tamguo/service/impl/SchoolService.java b/tamguo/src/main/java/com/tamguo/service/impl/SchoolService.java index 2a2a285..8fdf9a6 100644 --- a/tamguo/src/main/java/com/tamguo/service/impl/SchoolService.java +++ b/tamguo/src/main/java/com/tamguo/service/impl/SchoolService.java @@ -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 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 findEliteSchoolPaper(String shcoolId) { + // 首先尝试从缓存中获取名为 TamguoConstant.ELITE_SCHOOL_PAPER 的缓存对象,并将其强制转换为 List 类型, + // 期望从缓存中获取到之前存储的名校试卷相关的学校信息列表,如果缓存命中,就可以直接返回这个缓存中的学校列表数据,减少数据库查询操作。 List schoolList = (List) cacheService.getObject(TamguoConstant.ELITE_SCHOOL_PAPER); - // 获取名校试卷 - if(schoolList == null || schoolList.isEmpty()){ + // 判断从缓存中获取到的学校列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的学校信息), + // 如果满足这个条件,就需要从数据库中重新获取名校试卷相关的学校信息列表,并构建学校与试卷的关联信息,然后将其存入缓存中供后续使用。 + if (schoolList == null || schoolList.isEmpty()) { + // 创建一个 Page 对象用于分页查询学校信息,设置当前页码为 1,每页显示的记录数为 3(这里的分页参数可以根据实际业务需求调整), Page page = new Page<>(); page.setCurrent(1); page.setSize(3); - schoolList = schoolMapper.findByAreaId(shcoolId , page); - for(SchoolEntity school : schoolList){ + // 通过注入的 SchoolMapper 的 findByAreaId 方法,传入学校 ID(shcoolId)和分页对象(page), + // 从数据库中获取符合条件的学校信息列表,这里的具体查询逻辑由 SchoolMapper 中对应的方法实现来确定,返回查询到的学校列表赋值给 schoolList 变量。 + schoolList = schoolMapper.findByAreaId(shcoolId, page); + // 遍历获取到的学校列表,对于每个学校(school),需要获取其对应的试卷列表信息并进行关联设置。 + for (SchoolEntity school : schoolList) { + // 创建一个新的 Page 对象用于分页查询试卷信息,设置当前页码为 1,每页显示记录数为 3,同样可根据实际情况调整分页参数。 Page p = new Page<>(); p.setCurrent(1); p.setSize(3); - List paperList = paperMapper.findBySchoolId(school.getUid() , p); + // 通过 PaperMapper 的 findBySchoolId 方法,传入当前学校的唯一标识符(school.getUid())和分页对象(p), + // 从数据库中获取该学校对应的试卷信息列表,具体的查询逻辑由 PaperMapper 中对应方法实现来确定,将查询到的试卷列表赋值给 school 的 paperList 属性, + // 这样就构建了学校与试卷的关联关系,每个学校实体中包含了其对应的试卷列表信息。 + List 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 findEliteSchool() { + // 先从缓存中获取名为 TamguoConstant.ELITE_PAPER 的缓存对象,并强制转换为 List 类型,尝试获取之前缓存的名校信息列表, + // 如果缓存中有数据,就直接返回该缓存中的学校列表数据,避免再次查询数据库。 List schoolList = (List) 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 findSchoolByAreaId(String areaId) { + // 将传入的以逗号分隔的地区 ID 字符串进行分割,得到一个包含各个地区 ID 的字符串数组, + // 然后通过 Arrays.asList 方法将其转换为 List 类型,以便后续作为参数传递给 SchoolMapper 的查询方法,用于根据地区 ID 列表查询学校信息。 return schoolMapper.findByAreaIds(Arrays.asList(areaId.split(","))); } - -} +} \ No newline at end of file diff --git a/tamguo/src/main/java/com/tamguo/service/impl/SubjectService.java b/tamguo/src/main/java/com/tamguo/service/impl/SubjectService.java index 3659042..3e799fc 100644 --- a/tamguo/src/main/java/com/tamguo/service/impl/SubjectService.java +++ b/tamguo/src/main/java/com/tamguo/service/impl/SubjectService.java @@ -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 implements ISubjectService{ +public class SubjectService extends ServiceImpl 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 方法,根据传入的学科 ID(uid)从数据库中查询获取对应的学科实体对象,这一步获取到了基本的学科信息。 SubjectEntity subject = subjectMapper.selectById(uid); + // 通过 CourseMapper 的 findBySubjectId 方法,传入学科 ID(uid),从数据库中查询获取该学科下的课程列表信息,得到一个包含课程实体对象的列表。 List 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 subjectList = subjectMapper.selectList(Condition.EMPTY); - for(int i=0 ; i courseList = courseMapper.findBySubjectId(subject.getUid()); - for(int k=0 ; k subjectList = subjectMapper.selectList(Condition.EMPTY); - for(int i=0 ; i courseList = courseMapper.findBySubjectId(subject.getUid()); - for(int k=0 ; k