readme 修改

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

@ -19,48 +19,90 @@ import com.google.code.kaptcha.util.Config;
import com.tamguo.interceptor.MemberInterceptor;
import com.tamguo.interceptor.MenuInterceptor;
// 这个类使用了Spring的 @Configuration 注解表示它是一个配置类用于配置Spring Web相关的一些功能和组件
// Spring在启动时会自动扫描并加载该类中的配置信息将其中定义的Bean注册到Spring容器中。
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
// 通过 @Value 注解从配置文件(例如 application.properties 或 application.yml中读取名为 "file.storage.path" 的属性值,
// 并注入到这个变量中,该路径可能用于指定文件存储的位置,后续在资源处理器配置中会用到这个路径来映射资源访问路径。
@Value("${file.storage.path}")
private String fileStoragePath;
// 通过Spring的依赖注入机制自动注入 MenuInterceptor 实例MenuInterceptor 应该是自定义的拦截器,
// 用于在请求处理过程中对菜单相关的逻辑进行拦截和处理,比如权限校验、菜单数据准备等操作(具体功能取决于拦截器的实现代码)。
@Autowired
private MenuInterceptor menuInterceptor;
// 同样通过依赖注入机制注入 MemberInterceptor 实例MemberInterceptor 也是自定义拦截器,
// 从命名可以推测它可能用于对会员相关的请求路径进行拦截,执行如会员权限验证、会员数据处理等相关操作。
@Autowired
private MemberInterceptor memberInterceptor;
/**
*
* WebMvcConfigurerAdapter addInterceptors Spring
* Web
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 将 menuInterceptor 注册到拦截器注册表中,并配置其拦截的路径模式为 "/**",表示对所有的请求路径都会进行拦截,
// 具体在 MenuInterceptor 内部会根据业务逻辑判断是否需要进行处理以及如何处理这些请求。
registry.addInterceptor(menuInterceptor).addPathPatterns("/**");
// 将 memberInterceptor 注册到拦截器注册表中,配置其拦截的路径模式为 "/member/**",意味着对以 "/member/" 开头的请求路径进行拦截,
// 这通常用于对会员模块相关的请求进行特定的前置处理,比如验证会员登录状态、权限等情况。
registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**");
}
/**
*
* addResourceHandlers Spring Web
* "/files/**" fileStoragePath
* "/files/" Spring
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**").addResourceLocations("file:" + fileStoragePath);
super.addResourceHandlers(registry);
}
/**
* Kaptcha
* "producer" Bean DefaultKaptcha DefaultKaptcha
* Properties
* DefaultKaptcha 便使
*/
@Bean(name = "producer")
public DefaultKaptcha getKaptchaBean() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// properties.setProperty("kaptcha.border.color", "105,179,90");
// 设置验证码图片是否显示边框,这里配置为 "no",即不显示边框。
properties.setProperty("kaptcha.border", "no");
// 设置验证码图片的宽度为 125 像素。
properties.setProperty("kaptcha.image.width", "125");
// 设置验证码图片的高度为 45 像素。
properties.setProperty("kaptcha.image.height", "45");
// 设置在Session中存储验证码文本的键名这里设置为 "code"方便后续在验证时从Session中获取对应的验证码文本进行比对验证。
properties.setProperty("kaptcha.session.key", "code");
// 设置验证码文本的字符长度为 4 个字符,即生成的验证码文本将包含 4 个字符。
properties.setProperty("kaptcha.textproducer.char.length", "4");
// 设置验证码文本生成时使用的字体,可以指定多个字体,用逗号隔开,这里指定了宋体、楷体、微软雅黑等常见字体,
// 在生成验证码时会随机从这些字体中选择一种来绘制验证码文本。
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
/**
* Servlet
* "containerCustomizer" Bean EmbeddedServletContainerCustomizer
* ServletSpring Boot使Tomcat
* HttpStatus.NOT_FOUND404 "/404.html"
* HttpStatus.INTERNAL_SERVER_ERROR500 "/500.html"
*
*/
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@ -71,5 +113,4 @@ public class WebConfig extends WebMvcConfigurerAdapter {
}
};
}
}

@ -21,20 +21,28 @@ import com.tamguo.service.IMemberService;
/**
*
*
* `MemberRealm` `AuthorizingRealm` Apache Shiro
*
*/
public class MemberRealm extends AuthorizingRealm {
// 通过Spring的依赖注入机制自动注入 `IMemberService` 接口的实现类实例,
// 用于后续在认证和授权过程中与数据库等数据源交互,获取用户相关信息(如用户实体、登录失败次数等)。
@Autowired
private IMemberService iMemberService;
/**
* ()
* `AuthorizingRealm` `doGetAuthorizationInfo`
* Shiro `PrincipalCollection`
* `SimpleAuthorizationInfo`
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 初始化一个用于存储权限字符串的集合,当前示例中暂未填充具体的权限数据,实际业务中需要从数据库等数据源获取用户对应的权限并添加到这个集合中。
Set<String> permsSet = null;
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 将权限集合设置到 `SimpleAuthorizationInfo` 对象中,用于后续权限验证时判断用户是否具有特定权限。
info.setStringPermissions(permsSet);
return info;
@ -42,27 +50,46 @@ public class MemberRealm extends AuthorizingRealm {
/**
* ()
* `AuthorizingRealm` `doGetAuthenticationInfo`
* Shiro
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// 从 `AuthenticationToken` 中获取用户输入的用户名,在 Shiro 中,`AuthenticationToken` 通常是在登录时传递用户名和密码等登录凭证的对象,
// 这里将获取到的用户名强制转换为 `String` 类型,因为假设登录时用户名是以字符串形式传递的(实际应用中需根据具体的 `AuthenticationToken` 实现来确定类型转换是否正确)。
String username = (String) token.getPrincipal();
// 从 `AuthenticationToken` 中获取用户输入的密码,同样需要进行类型转换,将其转换为 `String` 类型,这里将 `char[]` 类型的密码转换为 `String`
// 注意这种方式在实际应用中可能存在安全风险(比如密码在内存中会以明文形式存在一段时间),更安全的做法是使用合适的加密算法立即对密码进行处理,避免明文暴露。
String password = new String((char[]) token.getCredentials());
// 通过注入的 `IMemberService` 调用 `findByUsername` 方法,根据用户名从数据库或其他数据源查找对应的 `MemberEntity`(用户实体对象),
// 如果返回 `null`,说明用户名不存在,此时抛出 `UnknownAccountException`,表示用户名或密码有误(在实际应用中可以更细化错误提示,区分是用户名不存在还是密码错误等情况)。
MemberEntity member = iMemberService.findByUsername(username);
if (member == null) {
throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码");
}
// 通过 `IMemberService` 调用 `getLoginFailureCount` 方法获取当前用户的登录失败次数,
// 如果登录失败次数大于 10 次,说明账号可能存在异常风险,抛出 `LockedAccountException`,表示账号被锁定,禁止登录。
Integer loginFailureCount = iMemberService.getLoginFailureCount(member);
if (loginFailureCount > 10) {
throw new LockedAccountException("账号被锁定");
}
// 使用 `Sha256Hash` 对用户输入的密码进行哈希处理(将密码转换为不可逆的哈希值),并与数据库中存储的用户密码(假设数据库中存储的也是经过相同哈希算法处理后的哈希值)进行比较,
// 如果不相等,说明密码错误,此时增加登录失败次数,并通过 `IMemberService` 的 `updateLoginFailureCount` 方法更新数据库中的登录失败次数记录,
// 然后抛出 `IncorrectCredentialsException`,提示用户名或密码有误,请重新输入或找回密码。
if (!new Sha256Hash(password).toHex().equals(member.getPassword())) {
loginFailureCount++;
iMemberService.updateLoginFailureCount(member, loginFailureCount);
throw new IncorrectCredentialsException("用户名或密码有误,请重新输入或找回密码");
}
// 如果密码正确,调用 `IMemberService` 的 `updateLastLoginTime` 方法更新用户的最后登录时间,记录此次登录操作,
// 然后创建一个 `SimpleAuthenticationInfo` 对象,将用户实体对象(`member`)、密码(`password`)以及当前 `Realm` 的名称(通过 `getName()` 获取)传递进去,
// 这个对象会被 Shiro 框架用于后续的认证相关流程,比如在会话中保存认证信息等操作。
// 更新登录时间
iMemberService.updateLastLoginTime(member.getUid().toString());

@ -14,15 +14,26 @@ import org.springframework.context.annotation.Configuration;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
// 这个类使用了Spring的 @Configuration 注解表明它是一个配置类用于配置Shiro框架相关的各种组件和功能
// Spring在启动时会自动扫描并加载这个类中的配置信息将其中定义的Bean注册到Spring容器中。
@Configuration
public class ShiroConfiguration {
// 创建一个LinkedHashMap用于定义Shiro的过滤链规则键是URL路径的匹配模式值是对应的权限过滤器名称
// 后续会将这个映射关系配置到ShiroFilterFactoryBean中用于控制不同URL的访问权限。
private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 定义一个名为 "shiroRealm" 的Bean这个Bean返回的是自定义的MemberRealm实例
// MemberRealm是继承自Shiro的AuthorizingRealm用于实现具体的认证登录验证和授权权限验证逻辑
// 将其注册为Spring容器中的Bean方便其他Shiro相关组件引用比如在SecurityManager中设置使用这个Realm进行验证操作。
@Bean(name = "shiroRealm")
public MemberRealm getShiroRealm() {
return new MemberRealm();
}
// 定义一个名为 "shiroEhcacheManager" 的Bean用于创建和配置EhCacheManager实例
// EhCacheManager在Shiro框架中用于缓存相关的数据比如用户的授权信息等提高系统性能减少重复查询授权等操作的开销。
// 通过设置cacheManagerConfigFile属性指定了EhCache的配置文件路径这里是类路径下的ehcache-shiro.xml文件
// 该配置文件会定义缓存的具体策略、缓存区域等相关设置。
@Bean(name = "shiroEhcacheManager")
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
@ -30,11 +41,17 @@ public class ShiroConfiguration {
return em;
}
// 定义一个名为 "lifecycleBeanPostProcessor" 的Bean创建LifecycleBeanPostProcessor实例
// LifecycleBeanPostProcessor是Shiro提供的一个用于管理Shiro相关Bean生命周期的后置处理器
// 它能够确保Shiro的一些组件如Realm等在Spring容器中正确地初始化、销毁等按照其生命周期规范执行相应操作。
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
// 定义一个名为 "defaultAdvisorAutoProxyCreator" 的Bean创建DefaultAdvisorAutoProxyCreator实例
// 它是Spring AOP中的一个自动代理创建器用于在Spring容器中自动创建基于Advisor切面顾问的代理对象
// 在Shiro与Spring集成时帮助实现基于AOP的权限控制拦截等功能通过设置proxyTargetClass为true表示使用基于类的代理方式而不是基于接口的代理
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
@ -42,6 +59,10 @@ public class ShiroConfiguration {
return daap;
}
// 定义一个名为 "securityManager" 的Bean创建DefaultWebSecurityManager实例
// DefaultWebSecurityManager是Shiro在Web应用中用于管理安全相关操作的核心组件
// 在这里设置了它所使用的Realm通过调用getShiroRealm()方法获取前面定义的MemberRealm实例以及缓存管理器通过getEhCacheManager()获取EhCacheManager实例
// 这样SecurityManager在进行认证和授权操作时就会使用指定的Realm进行验证并利用缓存管理器来缓存相关信息提高效率。
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
@ -50,6 +71,10 @@ public class ShiroConfiguration {
return dwsm;
}
// 定义一个名为 "authorizationAttributeSourceAdvisor" 的Bean创建AuthorizationAttributeSourceAdvisor实例
// AuthorizationAttributeSourceAdvisor是Shiro与Spring AOP集成的一个关键组件用于在方法级别进行权限控制
// 它通过设置securityManager调用getDefaultWebSecurityManager()获取配置好的SecurityManager来关联整个权限管理体系
// 使得在Spring应用中可以基于注解等方式方便地进行权限控制比如在方法上添加Shiro的权限注解来限制哪些用户可以访问该方法。
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
@ -59,13 +84,23 @@ public class ShiroConfiguration {
/**
* ShiroDialectthymeleaf使shirobean
* @return
* "shiroDialect" BeanShiroDialect
* ShiroDialectThymeleaf使Shiro
* 使ThymeleafShiro
* BeanSpringThymeleafShiro
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
// 定义一个名为 "shiroFilter" 的Bean创建ShiroFilterFactoryBean实例
// ShiroFilterFactoryBean是Shiro在Web应用中用于配置URL级别的访问控制过滤器链的核心组件
// 它通过设置securityManager关联前面配置好的DefaultWebSecurityManager来建立整个权限过滤的基础框架
// 同时设置了登录页面的URL通过setLoginUrl方法设置为 "/login",当用户未登录访问受保护资源时会重定向到这个登录页面)、
// 登录成功后的默认跳转页面URL通过setSuccessUrl方法设置为 "/index"
// 并且将前面定义的filterChainDefinitionMap包含了URL路径和对应权限过滤器的映射关系设置进去
// 这样Shiro就能根据这些配置对不同的URL请求进行权限验证和相应的处理比如哪些URL需要认证才能访问哪些URL可以匿名访问等。
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

@ -6,12 +6,22 @@ import org.apache.ibatis.annotations.Param;
import com.tamguo.config.dao.SuperMapper;
import com.tamguo.model.ChapterEntity;
// 定义了一个名为ChapterMapper的接口该接口继承自SuperMapper<ChapterEntity>
// 通常继承自自定义的通用Mapper接口这里的SuperMapper大概率是用于简化MyBatis常见CRUD操作的基础Mapper接口
// 这样可以复用一些通用的数据库操作方法比如增删改查等基本功能同时也可以定义自己特定的查询方法用于与数据库中的章节Chapter相关表进行交互。
public interface ChapterMapper extends SuperMapper<ChapterEntity> {
// 定义了一个查询方法findByBookId用于根据书籍的唯一标识符bookId从数据库中查找对应的章节信息列表。
// 使用了@Param注解来为方法参数指定一个名称这样在对应的SQL语句中就可以通过这个指定的名称来引用参数
// 在这里参数名为"bookId"表示传入的是书籍的ID方法返回值是一个ChapterEntity类型的列表即查询到的对应章节实体的集合。
List<ChapterEntity> findByBookId(@Param(value = "bookId") String bookId);
// 定义另一个查询方法findByParentId目的是根据章节的父级章节的唯一标识符parentId来查找对应的子章节信息列表。
// 同样使用了@Param注解为参数命名为"parentId"方便在SQL语句中引用返回值也是ChapterEntity类型的列表包含了满足条件的子章节实体集合。
List<ChapterEntity> findByParentId(@Param(value = "parentId") String parentId);
// 定义了一个查找下一个知识点这里推测ChapterEntity可能代表知识点相关实体具体取决于业务逻辑的方法findNextPoint。
// 该方法接收两个参数一个是知识点的唯一标识符uid另一个是顺序号orders通过这两个参数从数据库中查找符合条件的下一个知识点实体。
// 使用@Param注解分别为两个参数命名为"uid"和"orders"方便在对应的SQL语句中准确引用参数返回值是一个ChapterEntity类型的对象代表找到的下一个知识点实体。
ChapterEntity findNextPoint(@Param(value = "uid") String uid, @Param(value = "orders") Integer orders);
}

@ -7,27 +7,54 @@ import com.baomidou.mybatisplus.plugins.pagination.Pagination;
import com.tamguo.config.dao.SuperMapper;
import com.tamguo.model.PaperEntity;
// 定义了名为PaperMapper的接口它继承自SuperMapper<PaperEntity>
// 与之前类似SuperMapper大概率是一个自定义的通用Mapper接口用于继承通用的数据库操作方法如基本的增删改查等
// 在此基础上PaperMapper接口可以定义针对试卷PaperEntity从命名推测其代表试卷相关实体相关的特定数据库查询方法。
public interface PaperMapper extends SuperMapper<PaperEntity> {
// 定义一个查询方法findByTypeAndAreaId用于根据试卷类型type和地区IDareaId进行分页查询试卷信息列表。
// 使用了@Param注解为方法的前两个参数分别命名为"type"和"areaId"方便在对应的SQL语句中准确引用参数
// 第三个参数是Pagination类型的page这是MyBatis Plus提供的用于分页的对象通过它可以指定分页相关的参数如页码、每页数量等
// 方法返回值是一个PaperEntity类型的列表即符合条件的试卷实体集合。
List<PaperEntity> findByTypeAndAreaId(@Param(value = "type") String type, @Param(value = "areaId") String areaId, Pagination page);
// 定义查询方法findByAreaId根据地区IDareaId进行分页查询试卷信息列表。
// 使用@Param注解为参数"areaId"命名便于SQL语句引用同样传入Pagination类型的page对象用于分页
// 返回值为PaperEntity类型的列表包含了该地区下对应的试卷实体集合。
List<PaperEntity> findByAreaId(@Param(value = "areaId") String areaId, Pagination page);
// 定义查询方法findBySchoolId依据学校IDschoolId进行分页查询试卷信息列表。
// 通过@Param注解将参数命名为"schoolId"结合分页对象page来实现分页查询功能返回符合条件的试卷实体列表。
List<PaperEntity> findBySchoolId(@Param(value = "schoolId") String schoolId, Pagination page);
List<PaperEntity> findList(@Param(value="subjectId")String subjectId, @Param(value="courseId")String courseId, @Param(value="paperType")String paperType,
// 定义了一个较为复杂的查询方法findList用于根据多个条件进行分页查询试卷信息列表这些条件包括学科IDsubjectId、课程IDcourseId
// 试卷类型paperType、年份year以及地区area通过@Param注解分别为这些参数命名方便在SQL语句中准确引用
// 传入Pagination类型的page对象用于分页操作返回值是满足所有条件的PaperEntity类型的试卷实体列表。
List<PaperEntity> findList(@Param(value = "subjectId") String subjectId, @Param(value = "courseId") String courseId,
@Param(value = "paperType") String paperType,
@Param(value = "year") String year, @Param(value = "area") String area, Pagination page);
// 定义查询方法findPaperByAreaId根据地区IDareaId和试卷类型type进行分页查询试卷信息列表。
// 利用@Param注解对两个参数"areaId"和"type"进行命名结合分页对象page实现分页查询返回符合条件的试卷实体集合。
List<PaperEntity> findPaperByAreaId(@Param(value = "areaId") String areaId, @Param(value = "type") String type, Pagination page);
// 定义了一个查询方法getPaperTotal用于获取试卷的总数量。
// 该方法没有传入分页相关的参数返回值是Long类型表示符合某种未明确限定可能是所有记录也可能是满足特定默认条件的记录取决于具体业务逻辑和SQL实现的试卷总条数。
Long getPaperTotal();
// 定义查询方法findByCreaterId根据创建者的IDcreaterId来查找对应的试卷信息列表。
// 使用@Param注解将参数命名为"createrId"方便在SQL语句中引用返回值为PaperEntity类型的列表即该创建者创建的试卷实体集合。
List<PaperEntity> findByCreaterId(@Param(value = "createrId") String createrId);
// 定义查询方法queryPageByNameAndCreatorId用于根据试卷名称name和创建者IDmemberId进行分页查询试卷信息列表。
// 通过@Param注解分别为两个参数命名传入Pagination类型的page对象用于分页返回值是满足条件的试卷实体集合。
List<PaperEntity> queryPageByNameAndCreatorId(@Param(value = "name") String name, @Param(value = "memberId") String memberId, Pagination page);
// 定义查询方法featuredPaper根据试卷类型type和学科IDsubjectId进行分页查询特色试卷信息列表。
// 使用@Param注解为参数命名结合分页对象page实现分页查询功能返回值是符合特色试卷相关条件的PaperEntity类型的列表。
List<PaperEntity> featuredPaper(@Param(value = "type") String type, @Param(value = "subjectId") String subjectId, Pagination page);
// 定义查询方法findHotPaper根据学科IDsubjectId和课程IDcourseId进行分页查询热门试卷信息列表。
// 利用@Param注解对两个参数命名结合分页对象page进行分页查询操作返回值是满足热门试卷相关条件的PaperEntity类型的列表。
List<PaperEntity> findHotPaper(@Param(value = "subjectId") String subjectId, @Param(value = "courseId") String courseId, Pagination page);
}

@ -6,6 +6,8 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
// 使用@Component注解将这个类标记为Spring组件这样Spring在扫描时能够发现并将其纳入到Spring容器管理中
// 便于在其他地方进行依赖注入或者配置使用。
@Component
public class MemberInterceptor extends HandlerInterceptorAdapter {
@ -20,36 +22,47 @@ public class MemberInterceptor extends HandlerInterceptorAdapter{
/**
*
* HandlerInterceptorAdapterpreHandleController
*
*
* @param request
* HttpServletRequest
* @param response
* HttpServletResponse
* @param handler
*
* @return
* @param request HttpServletRequestHTTP
* @param response HttpServletResponse
* @param handler Controller
* @return trueControllerfalse
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从当前的HTTP请求对象中获取会话Session里名为"currMember"的属性值,通常这里的"currMember"可能是存储了当前已登录会员的相关信息的对象,
// 如果获取到的值不为null说明当前用户已经登录那么允许请求继续执行直接返回true。
Object currMember = request.getSession().getAttribute("currMember");
if (currMember!= null) {
return true;
} else {
// 获取请求头中名为"X-Requested-With"的值这个请求头常用于判断请求是否是通过AjaxXMLHttpRequest发起的
// 在一些Web应用中对于Ajax请求和普通页面请求的处理方式可能会有所不同比如返回的错误提示格式等方面。
String requestType = request.getHeader("X-Requested-With");
if (requestType!= null && requestType.equalsIgnoreCase("XMLHttpRequest")) {
// 如果请求是Ajax请求"X-Requested-With"请求头的值为"XMLHttpRequest"),则向响应头中添加一个名为"loginStatus"的自定义头信息,
// 设置其值为"accessDenied"表示登录状态为拒绝访问也就是未登录情况下的Ajax请求被拦截了
// 然后设置响应的状态码为HttpServletResponse.SC_FORBIDDEN403表示禁止访问最后返回false中断请求处理流程不让请求继续执行。
response.addHeader("loginStatus", "accessDenied");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
} else {
// 如果不是Ajax请求再判断请求的方法是否是GET方法对于不同的HTTP请求方法重定向的逻辑可能稍有不同。
if (request.getMethod().equalsIgnoreCase("GET")) {
// 如果是GET请求构建一个重定向的URL将当前请求的完整路径包括请求的URI和查询字符串如果有的话进行编码后作为参数添加到登录URL后面
// 具体做法是先判断当前请求是否有查询字符串通过request.getQueryString()判断如果有则将请求的URI和查询字符串拼接起来
// 如果没有查询字符串就只使用请求的URI然后使用URLEncoder将这个重定向的URL按照UTF-8编码格式进行编码避免出现中文等特殊字符乱码问题
// 最后通过response.sendRedirect方法将客户端重定向到登录页面并带上这个重定向URL参数以便在登录成功后可以根据这个参数跳回原来请求的页面。
String redirectUrl = request.getQueryString()!= null? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI();
response.sendRedirect(request.getContextPath() + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + URLEncoder.encode(redirectUrl, "UTF-8"));
} else {
// 如果不是GET请求比如POST等其他请求方法直接重定向到登录页面不携带额外的重定向URL参数因为对于非GET请求通常不需要记录原请求的详细信息来进行后续跳转。
response.sendRedirect(request.getContextPath() + loginUrl);
}
return false;
}
}
}
}

@ -9,38 +9,55 @@ import com.tamguo.config.dao.SuperEntity;
/**
* The persistent class for the tiku_ad database table.
*
* AdEntitytiku_ad广Ad
* SuperEntity<AdEntity>SerializableSuperEntitySuperEntity
* Serializable
*/
@TableName(value = "tiku_ad")
public class AdEntity extends SuperEntity<AdEntity> implements Serializable {
private static final long serialVersionUID = 1L;
// 用于存储业务相关的键值具体含义可能与广告业务中的某种标识或者关联信息有关其值由外部设置并获取通过对应的getter和setter方法操作。
private String businessKey;
// 用于存储广告的名称方便在业务中对不同广告进行区分和识别同样通过getter和setter方法进行访问和修改。
private String name;
// 用于存储广告的详细信息,可能是包含了广告内容、展示规则等各种相关数据的字符串表示形式(具体格式取决于业务设计),
// 通过getter和setter方法来操作该属性值。
private String adInfo;
// getName方法用于获取广告名称属性的值供外部代码调用遵循JavaBean规范的getter方法定义。
public String getName() {
return name;
}
// setName方法用于设置广告名称属性的值外部代码可以传入一个字符串参数来更新广告名称遵循JavaBean规范的setter方法定义。
public void setName(String name) {
this.name = name;
}
// getSerialversionuid方法返回类的序列化版本UID这个UID在序列化和反序列化过程中用于验证类的版本一致性
// 这里直接返回了定义好的静态常量serialVersionUID的值一般不需要手动修改这个方法的实现它是按照Java序列化机制要求定义的。
public static long getSerialversionuid() {
return serialVersionUID;
}
// getAdInfo方法用于获取广告详细信息adInfo属性的值外部代码可以通过调用这个方法获取存储的广告相关信息字符串。
public String getAdInfo() {
return adInfo;
}
// setAdInfo方法用于设置广告详细信息属性的值外部代码可以传入一个字符串参数来更新广告详细信息的内容。
public void setAdInfo(String adInfo) {
this.adInfo = adInfo;
}
/**
* getAdsadInfoJSONArray便JSON广
* adInfoStringUtils.isEmptynull广
* 使FastJSONJSONArray.parseArrayJSONArray便
*/
public JSONArray getAds() {
if (StringUtils.isEmpty(getAdInfo())) {
return null;
@ -48,12 +65,13 @@ public class AdEntity extends SuperEntity<AdEntity> implements Serializable {
return JSONArray.parseArray(getAdInfo());
}
// getBusinessKey方法用于获取业务键值businessKey属性的值供外部代码获取相关的业务标识或关联信息。
public String getBusinessKey() {
return businessKey;
}
// setBusinessKey方法用于设置业务键值属性的值外部代码可以传入一个字符串参数来更新该属性所代表的业务相关标识信息。
public void setBusinessKey(String businessKey) {
this.businessKey = businessKey;
}
}

@ -6,89 +6,141 @@ import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
// 这个类BookEntity定义了与数据库中名为“tiku_book”表对应的实体对象从命名来看它可能是用于表示书籍相关信息的数据实体类。
// 该类继承自MyBatis Plus的Model<BookEntity>类继承Model类可以方便地使用MyBatis Plus提供的一些便捷的数据库操作方法比如CRUD操作等
// 同时还实现了Serializable接口使得该类的对象能够在诸如网络传输、持久化存储等场景下进行序列化和反序列化操作保证对象状态可以被正确保存和恢复。
@TableName(value = "tiku_book")
public class BookEntity extends Model<BookEntity> {
private static final long serialVersionUID = 1L;
// 使用@TableId注解标记这个字段表明它是数据库表“tiku_book”表中的主键字段在这里字段名为“uid”
// 具体的主键生成策略等相关设置如果有的话通常会根据MyBatis Plus的默认配置或者项目中额外的配置来确定
// 通过对应的getter和setter方法来操作这个属性的值外部代码可以获取或设置书籍的唯一标识符uid
@TableId
private String uid;
// 用于存储学科的唯一标识符通过这个字段可以关联到对应的学科信息其值由外部设置并获取使用对应的getter和setter方法进行操作
// 方便在业务逻辑中确定书籍所属的学科分类等情况。
private String subjectId;
// 用于存储课程的唯一标识符类似学科标识符的作用可用于关联到具体的课程信息外部代码可以通过getter和setter方法对其值进行操作
// 帮助在业务中明确书籍与具体课程之间的关联关系。
private String courseId;
// 用于存储书籍的名称通过getter和setter方法getName和setName外部代码能够获取或更新书籍的名称信息便于在业务中展示、查找等操作时使用。
private String name;
// 用于存储书籍的出版社信息外部代码可利用相应的getter和setter方法获取或修改该属性值在涉及书籍来源、版权等业务场景下会用到这个信息。
private String publishingHouse;
// 用于存储书籍中包含的题目数量相关信息可能是一个数字字符串形式具体取决于业务存储格式通过对应的getter和setter方法进行操作
// 在统计、查询等业务操作中可以依据这个属性了解书籍的题目规模情况。
private String questionNum;
// 用于存储书籍中所涵盖的知识点数量相关信息同样通过getter和setter方法来操作其值方便在业务逻辑中对书籍涵盖的知识点情况进行把握和处理。
private String pointNum;
// 用于存储书籍的排序序号相关信息可能用于在展示、排序等场景下确定书籍的顺序通过对应的getter和setter方法进行操作
// 外部代码可以获取或修改这个序号值来调整书籍的排列顺序等情况。
private Integer orders;
public String getUid() {
return uid;
// getName方法是遵循JavaBean规范的getter方法用于获取书籍名称属性name的值外部代码可以调用这个方法获取当前BookEntity对象所代表书籍的名称。
public String getName() {
return name;
}
public void setUid(String uid) {
this.uid = uid;
// setName方法是遵循JavaBean规范的setter方法用于设置书籍名称属性的值外部代码可以传入一个字符串参数来更新当前BookEntity对象所代表书籍的名称。
public void setName(String name) {
this.name = name;
}
// getSubjectId方法用于获取学科标识符subjectId属性的值供外部代码获取书籍所属的学科相关信息便于进行关联查询、分类统计等操作。
public String getSubjectId() {
return subjectId;
}
// setSubjectId方法用于设置学科标识符属性的值外部代码可以传入一个字符串参数来更新书籍所属的学科信息在业务逻辑中调整书籍的学科分类归属等情况时会用到。
public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
}
// getCourseId方法用于获取课程标识符courseId属性的值方便外部代码知晓书籍与具体课程的关联情况在课程相关的业务操作中起到关联作用。
public String getCourseId() {
return courseId;
}
// setCourseId方法用于设置课程标识符属性的值外部代码可以传入一个字符串参数来更新书籍对应的课程信息用于在业务中重新确定书籍与课程的关联关系。
public void setCourseId(String courseId) {
this.courseId = courseId;
}
public String getName() {
return name;
// getUid方法用于获取书籍的唯一标识符uid属性的值这个值作为主键在数据库操作以及对象标识等方面有着重要作用
// 外部代码可以通过调用这个方法获取当前BookEntity对象对应的书籍在数据库中的唯一标识。
public String getUid() {
return uid;
}
public void setName(String name) {
this.name = name;
// setUid方法用于设置书籍的唯一标识符属性的值外部代码可以传入一个字符串参数来更新这个唯一标识信息不过在实际应用中要谨慎操作
// 因为主键通常具有唯一性且关联着数据库中的重要数据记录,随意修改可能导致数据不一致等问题。
public void setUid(String uid) {
this.uid = uid;
}
// getPublishingHouse方法用于获取书籍出版社publishingHouse属性的值外部代码可以通过调用这个方法获取当前BookEntity对象所代表书籍的出版社信息。
public String getPublishingHouse() {
return publishingHouse;
}
// setPublishingHouse方法用于设置书籍出版社属性的值外部代码可以传入一个字符串参数来更新书籍的出版社信息比如在书籍信息更新、录入新书籍等场景下使用。
public void setPublishingHouse(String publishingHouse) {
this.publishingHouse = publishingHouse;
}
// getQuestionNum方法用于获取书籍题目数量questionNum属性的值外部代码可以获取这个属性值来了解书籍包含的题目规模情况
// 在统计分析、展示等业务操作中会用到这个信息。
public String getQuestionNum() {
return questionNum;
}
// setQuestionNum方法用于设置书籍题目数量属性的值外部代码可以传入一个字符串参数来更新书籍所包含的题目数量信息
// 例如在题目数量发生变化(新增、删除题目等情况)时对该属性进行相应的更新操作。
public void setQuestionNum(String questionNum) {
this.questionNum = questionNum;
}
// getPointNum方法用于获取书籍知识点数量pointNum属性的值外部代码可以通过调用这个方法获取当前BookEntity对象所代表书籍涵盖的知识点数量情况
// 在知识点相关的业务处理、统计等场景下会用到这个属性值。
public String getPointNum() {
return pointNum;
}
// setPointNum方法用于设置书籍知识点数量属性的值外部代码可以传入一个字符串参数来更新书籍涵盖的知识点数量信息
// 比如在对书籍内容进行知识点梳理、更新后对该属性进行相应的调整操作。
public void setPointNum(String pointNum) {
this.pointNum = pointNum;
}
// getOrders方法用于获取书籍排序序号orders属性的值外部代码可以获取这个序号值来了解书籍在相关展示、排序场景下的顺序位置情况
// 并且可以通过对应的setOrders方法来修改这个序号从而调整书籍的排列顺序等。
public Integer getOrders() {
return orders;
}
// setOrders方法用于设置书籍排序序号属性的值外部代码可以传入一个整数参数来更新书籍的排序序号用于在业务中改变书籍的排列顺序
// 例如在列表展示中调整书籍的先后位置等情况。
public void setOrders(Integer orders) {
this.orders = orders;
}
// getSerialversionuid方法返回类的序列化版本UID这个UID在序列化和反序列化过程中用于验证类的版本一致性
// 这里直接返回了定义好的静态常量serialVersionUID的值一般不需要手动修改这个方法的实现它是按照Java序列化机制要求定义的。
public static long getSerialversionuid() {
return serialVersionUID;
}
// 重写了父类Model的pkVal方法用于指定当前实体类的主键值在这里返回了getUid方法获取到的书籍唯一标识符uid
// 这样MyBatis Plus在进行一些基于主键的数据库操作如根据主键查询、更新等时就能准确知道当前实体对应的主键是什么从而正确执行相关操作。
@Override
protected Serializable pkVal() {
return getUid();

@ -13,24 +13,49 @@ import com.tamguo.model.AdEntity;
import com.tamguo.service.IAdService;
import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它是用于处理业务逻辑的类Spring会自动扫描并将其纳入到Spring容器管理中
// 方便在其他地方进行依赖注入等操作同时也遵循了Spring的分层架构规范使得代码结构更清晰。
@Service
public class AdService extends ServiceImpl<AdMapper, AdEntity> implements IAdService {
// 通过Spring的依赖注入机制自动注入 AdMapper 接口的实现类实例,
// AdMapper 应该是用于定义与广告AdEntity相关的数据库操作方法的接口比如查询、插入、更新等操作
// 在这个服务类中会调用它的方法来与数据库进行交互,获取广告相关的数据。
@Autowired
AdMapper adMapper;
// 同样通过依赖注入机制注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存等功能,在这里主要用于缓存广告数据,减少频繁访问数据库的开销,提高系统性能。
@Autowired
CacheService cacheService;
/**
* findAll
* IAdService findAll
* 广
*/
@SuppressWarnings("unchecked")
@Override
public List<AdEntity> findAll() {
// 首先尝试从缓存中获取名为 TamguoConstant.ALL_AD 的缓存对象,这里将获取到的对象强制转换为 List<AdEntity> 类型,
// 期望从缓存中获取到之前存储的广告信息列表,如果缓存命中,就可以直接返回这个缓存中的广告列表数据,减少数据库查询操作。
List<AdEntity> adList = (List<AdEntity>) cacheService.getObject(TamguoConstant.ALL_AD);
// 此处将 adList 赋值为 null这看起来有点奇怪可能是代码有误或者有特定的后续逻辑比如要重新获取最新数据等情况
// 正常情况下如果是想清空缓存数据重新获取,应该先删除缓存中的对应数据等操作,而不是简单赋值为 null不过先按照现有逻辑继续分析。
adList = null;
// 判断从缓存中获取到的广告列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的广告信息),
// 如果满足这个条件,就需要从数据库中重新获取广告信息列表。
if (adList == null || adList.isEmpty()) {
// 通过注入的 AdMapper 调用其 selectList 方法,传入 Condition.EMPTY 参数,这表示使用默认的查询条件(通常是查询所有记录),
// 从数据库中获取所有的广告实体信息,将获取到的结果赋值给 adList 变量,用于后续操作。
adList = adMapper.selectList(Condition.EMPTY);
// 调用注入的 CacheService 的 setObject 方法将刚刚从数据库中获取到的广告列表数据adList存入缓存中
// 缓存的键名为 TamguoConstant.ALL_AD同时设置了缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findAll 方法,就可以直接从缓存中获取广告数据,而不用再次查询数据库了。
cacheService.setObject(TamguoConstant.ALL_AD, adList, 2 * 60 * 60);
}
// 最后返回获取到的广告信息列表,这个列表可能是从缓存中直接获取的,也可能是从数据库中查询后存入缓存再返回的,
// 取决于缓存中是否存在有效数据以及之前的逻辑执行情况。
return adList;
}
}

@ -15,39 +15,77 @@ import com.tamguo.service.IAreaService;
import com.tamguo.util.Result;
import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件意味着它主要负责处理业务逻辑相关的操作
// Spring在扫描时会自动识别并将其纳入到Spring容器中进行管理方便在其他地方进行依赖注入等操作符合Spring的分层架构设计。
@Service
public class AreaService extends ServiceImpl<AreaMapper, AreaEntity> implements IAreaService {
// 通过Spring的依赖注入机制自动注入 AreaMapper 接口的实现类实例,
// AreaMapper 应该是用于定义与地区AreaEntity相关的数据库操作方法的接口比如查询、插入、更新等操作
// 在本服务类中会调用它的方法来和数据库进行交互,获取地区相关的数据信息。
@Autowired
private AreaMapper areaMapper;
// 同样通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在这个类中主要用于缓存地区相关的数据,以优化数据获取性能,减少数据库访问次数。
@Autowired
private CacheService cacheService;
/**
* findAll
* IAreaService findAll
* AreaMapper selectList Condition.EMPTY
* 使
*/
@SuppressWarnings("unchecked")
@Override
public List<AreaEntity> findAll() {
return areaMapper.selectList(Condition.EMPTY);
}
/**
* findRootArea
* IAreaService findRootArea @Transactional(readOnly = true)
*
* AreaMapper findRootArea AreaMapper
// 返回查询到的根地区信息列表。
*/
@Transactional(readOnly = true)
@Override
public List<AreaEntity> findRootArea() {
return areaMapper.findRootArea();
}
/**
* findAreaTree
* IAreaService findAreaTree
* TamguoConstant.AREA_ALL_TREE
* Result.successResult
*
*/
@Transactional(readOnly = true)
@Override
public Result findAreaTree() {
if (cacheService.isExist(TamguoConstant.AREA_ALL_TREE)) {
return Result.successResult(cacheService.getObject(TamguoConstant.AREA_ALL_TREE));
}
// 如果缓存中不存在相应数据,先从数据库中获取根地区信息列表,调用 AreaMapper 的 findRootArea 方法来获取,
// 这些根地区将作为地区树结构的顶层节点,后续会基于这些节点逐步构建完整的地区树。
List<AreaEntity> areaList = areaMapper.findRootArea();
// 遍历根地区列表,对于每个根地区,查找其下一级子地区信息,通过调用 AreaMapper 的 findByParent 方法传入当前根地区的唯一标识符area.getUid())作为参数,
// 获取该根地区对应的子地区列表,然后判断子地区列表是否为空,如果不为空,将子地区列表设置为当前根地区的子节点(通过 area.setChildren(childend) 方法设置),
// 这样就构建了地区树结构的第一层父子关系。
for (AreaEntity area : areaList) {
List<AreaEntity> childend = areaMapper.findByParent(area.getUid());
if (!CollectionUtils.isEmpty(childend)) {
area.setChildren(childend);
}
// 对于每个子地区,再进一步查找它的下一级子地区(也就是孙子地区等更深层次的子节点),同样通过调用 AreaMapper 的 findByParent 方法,
// 传入子地区的唯一标识符a.getUid())获取其对应的子地区列表,若列表不为空,将这些子地区设置为当前子地区的子节点(通过 a.setChildren(ceList) 方法设置),
// 以此类推,通过循环嵌套的方式逐步构建出完整的地区树结构,包含多层级的父子关系。
for (AreaEntity a : childend) {
List<AreaEntity> ceList = areaMapper.findByParent(a.getUid());
if (!CollectionUtils.isEmpty(ceList)) {
@ -55,8 +93,13 @@ public class AreaService extends ServiceImpl<AreaMapper, AreaEntity> implements
}
}
}
// 将构建好的地区树结构数据存入缓存中,缓存的键名为 TamguoConstant.AREA_ALL_TREE同时设置缓存的有效时间为 60 * 60 * 2 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findAreaTree 方法,就可以直接从缓存中获取地区树数据,而不用重新构建,提高了数据获取的性能和效率。
cacheService.setObject(TamguoConstant.AREA_ALL_TREE, areaList, 60 * 60 * 2);
// 将构建好的地区树结构数据作为成功结果返回(通过 Result.successResult 方法进行包装),方便在调用处统一处理返回结果,
// 并且符合项目中对返回结果进行统一格式封装的规范要求。
return Result.successResult(areaList);
}
}
Loading…
Cancel
Save