From ab369351a651c009b3f0c298d98252b99d383642 Mon Sep 17 00:00:00 2001 From: Dcx12138 <2320898596@qq.com> Date: Sun, 15 Dec 2024 17:30:20 +0800 Subject: [PATCH] =?UTF-8?q?readme=20=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SmileToCandy --- .../src/main/java/com/tamguo/Application.java | 40 +++++++- .../tamguo/config/dao/MybatisPlusConfig.java | 51 ++++++++-- .../com/tamguo/config/shiro/MemberRealm.java | 92 +++++++++++++------ .../config/shiro/ShiroConfiguration.java | 63 +++++++++++++ .../tamguo/config/web/ThymeleafConfig.java | 79 ++++++++++++---- .../java/com/tamguo/config/web/WebConfig.java | 72 +++++++++++---- .../tamguo/interceptor/MemberInterceptor.java | 65 +++++++++---- .../java/com/tamguo/utils/FileMd5Utils.java | 42 +++++++-- .../java/com/tamguo/utils/ShiroUtils.java | 66 ++++++++++++- 9 files changed, 466 insertions(+), 104 deletions(-) diff --git a/tamguo-bms/src/main/java/com/tamguo/Application.java b/tamguo-bms/src/main/java/com/tamguo/Application.java index d1d90b8..01e8688 100644 --- a/tamguo-bms/src/main/java/com/tamguo/Application.java +++ b/tamguo-bms/src/main/java/com/tamguo/Application.java @@ -10,27 +10,59 @@ import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +// 这是Spring Boot应用的主启动类,使用了 @SpringBootApplication 注解,该注解是一个组合注解,相当于同时使用了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 注解, +// 意味着它标记这个类是一个配置类,会自动配置Spring Boot应用的很多默认设置,并且会自动扫描和加载应用中的各个组件(如各种Bean、Controller、Service等)。 @SpringBootApplication +// 通过 @ComponentScan 注解指定要扫描的基础包路径,这里设置为 "com.tamguo",表示Spring会在该包及其子包下查找带有Spring相关注解(如 @Component、@Service、@Controller 等)的类,并将它们注册为Spring容器中的组件,方便后续进行依赖注入等操作。 @ComponentScan("com.tamguo") public class Application { + /** + * 应用的主入口方法,用于启动Spring Boot应用,通过创建 SpringApplicationBuilder 对象并传入当前的 Application 类作为配置类, + * 然后调用 run 方法并传入命令行参数(args)来启动整个Spring Boot应用,启动过程中会进行一系列的初始化操作,如加载配置文件、创建Spring容器、扫描组件、自动配置等,最终启动嵌入式的Web服务器(如果是Web应用的话)并监听相应的端口等待请求。 + * + * @param args 命令行参数,可用于传递一些外部配置信息给应用,例如指定运行的端口号、配置文件的路径等,在应用启动时可以根据这些参数进行相应的初始化配置调整,不过这里暂时未对这些参数进行详细的处理逻辑(可根据实际需求扩展)。 + */ public static void main(String[] args) { new SpringApplicationBuilder(Application.class).run(args); } - + /** * FastJson替代Jackson - * @return + * 该方法创建并配置了一个用于将对象转换为JSON格式数据的HttpMessageConverters(消息转换器)的Bean, + * 在Spring Boot应用中,默认使用Jackson作为JSON序列化和反序列化的工具,但在这里通过自定义配置,使用FastJson来替代Jackson进行JSON数据的处理, + * 可以按照项目的特定需求对JSON序列化的格式(如日期格式)以及一些特性(如禁用循环引用检测)进行配置,使得生成的JSON数据更符合项目要求,方便与前端等进行数据交互。 + * + * @return 返回配置好的HttpMessageConverters对象,Spring容器会管理该对象,并在处理HTTP请求和响应中涉及JSON数据转换时应用该消息转换器,将Java对象转换为JSON格式发送给客户端,或者将接收到的JSON数据转换为Java对象进行处理。 */ @Bean public HttpMessageConverters fastJsonHttpMessageConverters() { + // 创建一个FastJsonHttpMessageConverter实例,它是基于FastJson实现的HTTP消息转换器,用于将Java对象转换为FastJson格式的JSON字符串,或者将接收到的FastJson格式的JSON字符串转换为Java对象, + // 在后续配置中会设置它的相关属性,使其按照项目需求进行JSON数据的转换操作。 FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); + + // 创建一个FastJsonConfig实例,用于配置FastJson的相关参数,比如设置日期格式、序列化特性等,通过该配置对象可以定制化FastJson序列化和反序列化的行为,使其符合项目的具体要求。 FastJsonConfig fastJsonConfig = new FastJsonConfig(); + + // 设置FastJson序列化日期类型数据时的格式,这里设置为 "yyyy-MM-dd HH:mm:ss",使得在将包含日期属性的Java对象转换为JSON字符串时,日期字段会按照该格式进行序列化,方便前端进行统一的日期格式展示和处理, + // 例如数据库中的日期时间类型数据在转换为JSON发送给前端时,会以指定的这种常见的格式化字符串形式呈现,便于前端直接展示或者进行日期相关的操作(如格式化、比较等)。 fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); + + // 设置FastJson的序列化特性,这里通过SerializerFeature.DisableCircularReferenceDetect来禁用循环引用检测, + // 当Java对象之间存在复杂的关联关系,可能形成循环引用时(比如对象A包含对象B,对象B又包含对象A这样的情况),默认情况下FastJson会进行循环引用检测并做相应处理, + // 但有时候可能希望关闭这种检测(根据具体业务场景和数据结构确定),通过设置该特性可以改变FastJson在序列化时对循环引用的处理方式,避免可能出现的序列化异常或者生成不符合预期的JSON结构等问题。 fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); + + // 将配置好的FastJsonConfig对象设置到FastJsonHttpMessageConverter中,使得消息转换器在进行JSON数据转换时应用这些配置参数,按照设定的日期格式和序列化特性来处理Java对象与JSON字符串之间的转换操作。 fastConverter.setFastJsonConfig(fastJsonConfig); + + // 将配置好的FastJsonHttpMessageConverter赋值给converter变量(这里其实可以直接使用fastConverter,赋值这一步略显多余,但不影响功能实现,可能是代码风格或者后续潜在扩展的考虑), + // 后续会使用该变量来创建并返回HttpMessageConverters对象。 FastJsonHttpMessageConverter converter = fastConverter; + + // 创建并返回一个HttpMessageConverters对象,将配置好的FastJsonHttpMessageConverter作为参数传入,这样Spring容器就会识别并使用这个自定义的消息转换器来处理HTTP请求和响应中的JSON数据转换, + // 替代默认的Jackson相关的消息转换器,实现使用FastJson进行JSON数据处理的目的。 return new HttpMessageConverters(converter); } - -} + +} \ No newline at end of file diff --git a/tamguo-bms/src/main/java/com/tamguo/config/dao/MybatisPlusConfig.java b/tamguo-bms/src/main/java/com/tamguo/config/dao/MybatisPlusConfig.java index 47d0386..c5c3eeb 100644 --- a/tamguo-bms/src/main/java/com/tamguo/config/dao/MybatisPlusConfig.java +++ b/tamguo-bms/src/main/java/com/tamguo/config/dao/MybatisPlusConfig.java @@ -19,23 +19,40 @@ import com.baomidou.mybatisplus.plugins.parser.tenant.TenantSqlParser; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; +// 标识这是一个Spring的配置类,用于配置MyBatis Plus相关的功能和插件,通过定义多个Bean方法来创建并配置不同的MyBatis Plus组件, +// 例如性能分析拦截器、分页插件、元对象处理器以及SQL注入器等,这些组件将被Spring容器管理并应用到MyBatis Plus的相关操作中,影响数据库访问的行为和功能。 @Configuration +// 配置MyBatis的Mapper扫描路径,告诉MyBatis Plus去指定的包及其子包下扫描接口作为Mapper接口,这样MyBatis Plus就能自动为这些Mapper接口生成对应的实现类并注入到Spring容器中,方便进行数据库操作。 @MapperScan("com.tamguo.modules.*.dao*") public class MybatisPlusConfig { + /** + * 创建并配置PerformanceInterceptor(性能拦截器)的Bean,该拦截器用于在MyBatis Plus执行SQL语句时收集并输出相关性能统计信息, + * 例如SQL执行的耗时等情况,方便在开发和测试阶段对数据库操作的性能进行分析和优化,了解哪些SQL语句执行效率较低,以便针对性地进行改进。 + * + * @return 返回创建好的PerformanceInterceptor实例,Spring容器会管理该实例,并在合适的时机将其应用到MyBatis Plus的相关操作链路中进行性能监控。 + */ @Bean public PerformanceInterceptor performanceInterceptor() { return new PerformanceInterceptor(); } /** - * mybatis-plus分页插件
- * 文档:http://mp.baomidou.com
+ * 创建并配置PaginationInterceptor(分页插件)的Bean,用于在MyBatis Plus中实现分页功能,它能够自动拦截并处理带有分页参数的查询请求, + * 根据配置将原始查询转换为带有分页逻辑的数据库查询语句(例如添加LIMIT子句等,具体取决于数据库类型),使得开发人员可以方便地在代码中使用分页功能进行数据查询操作。 + * 同时,这里还配置了与多租户相关的SQL解析处理逻辑(虽然是示例性质的固定配置,实际可根据业务场景调整),用于处理多租户场景下的数据隔离等问题。 + *

+ * 文档参考:http://mp.baomidou.com + * + * @return 返回配置好的PaginationInterceptor实例,Spring容器会管理该实例,并在执行涉及分页的数据库查询操作时应用该插件进行分页处理以及相关的SQL解析操作。 */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); - paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持 + // 设置开启PageHelper的支持,这里的PageHelper可能是指与分页相关的某种兼容模式或者特定功能支持(具体依赖于MyBatis Plus内部实现以及与其他分页相关框架的整合情况), + // 通过开启此功能,可以更好地适配一些已有的分页相关的使用习惯或者与其他分页工具协同工作(具体根据实际业务场景确定)。 + paginationInterceptor.setLocalPage(true); + /* * 【测试多租户】 SQL 解析处理拦截器
* 这里固定写成住户 1 实际情况你可以从cookie读取,因此数据看不到 【 麻花藤 】 这条记录( 注意观察 SQL )
@@ -45,24 +62,28 @@ public class MybatisPlusConfig { tenantSqlParser.setTenantHandler(new TenantHandler() { @Override public Expression getTenantId() { + // 返回表示租户ID的表达式,这里固定返回一个值为1L的LongValue表达式,表示当前租户的ID为1,实际业务中通常需要从合适的地方(如请求头、Cookie等)动态获取租户ID。 return new LongValue(1L); } @Override public String getTenantIdColumn() { + // 返回用于标识租户ID的数据库表列名,这里指定为"course_id",意味着在多租户场景下,通过该列的值来区分不同租户的数据,即该列存储了租户的相关标识信息。 return "course_id"; } @Override public boolean doTableFilter(String tableName) { - // 这里可以判断是否过滤表 + // 这里可以判断是否过滤表,即根据表名决定是否应用多租户的过滤逻辑(例如某些公共表可能不需要进行租户隔离,可以返回true进行过滤,不应用租户相关的SQL解析处理), + // 当前直接返回true,表示默认对所有表应用过滤逻辑,实际业务中需要根据具体的表结构和业务需求来准确判断哪些表需要进行租户相关处理,哪些表不需要。 return true; } }); - + // 将配置好的TenantSqlParser添加到SQL解析器列表中,后续分页拦截器会遍历该列表中的解析器,对SQL语句依次进行解析处理,应用相应的逻辑(如多租户数据隔离等)。 sqlParserList.add(tenantSqlParser); paginationInterceptor.setSqlParserList(sqlParserList); + // 以下过滤方式与 @SqlParser(filter = true) 注解等效 // paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() { // @Override @@ -75,19 +96,31 @@ public class MybatisPlusConfig { // return false; // } // }); + return paginationInterceptor; } + /** + * 创建并配置MetaObjectHandler(元对象处理器)的Bean,该处理器用于在MyBatis Plus执行插入和更新操作时,自动填充一些公共字段的值, + * 例如创建时间、更新时间、创建人、更新人等字段,通过实现该处理器的相关方法,可以定义统一的字段填充逻辑,避免在每个Mapper方法中手动设置这些公共字段的值,提高代码的复用性和开发效率。 + * + * @return 返回创建好的MetaObjectHandler实例(这里返回的是自定义的MyMetaObjectHandler,其应该继承自MetaObjectHandler并实现了相应的填充逻辑,具体实现类的逻辑根据业务需求而定), + * Spring容器会管理该实例,并在执行插入和更新操作时调用其相关方法进行字段的自动填充操作。 + */ @Bean - public MetaObjectHandler metaObjectHandler(){ + public MetaObjectHandler metaObjectHandler() { return new MyMetaObjectHandler(); } /** - * 注入sql注入器 + * 创建并配置ISqlInjector(SQL注入器)的Bean,用于向MyBatis Plus注入一些自定义的SQL方法,这里注入的是LogicSqlInjector, + * 它通常用于实现逻辑删除等功能(例如在数据库表中通过一个字段标识数据是否被删除,而不是真正从数据库中删除数据,方便数据的恢复以及保留数据的历史记录等), + * 通过注入这个SQL注入器,可以在MyBatis Plus的Mapper接口中方便地使用逻辑删除相关的方法,而不需要手动编写复杂的SQL语句来实现逻辑删除逻辑。 + * + * @return 返回创建好的LogicSqlInjector实例作为SQL注入器,Spring容器会管理该实例,并使其生效,使得在MyBatis Plus的相关操作中可以使用其提供的自定义SQL功能(如逻辑删除功能)。 */ @Bean - public ISqlInjector sqlInjector(){ + public ISqlInjector sqlInjector() { return new LogicSqlInjector(); } -} +} \ No newline at end of file diff --git a/tamguo-bms/src/main/java/com/tamguo/config/shiro/MemberRealm.java b/tamguo-bms/src/main/java/com/tamguo/config/shiro/MemberRealm.java index 6a55f8d..d5112a6 100644 --- a/tamguo-bms/src/main/java/com/tamguo/config/shiro/MemberRealm.java +++ b/tamguo-bms/src/main/java/com/tamguo/config/shiro/MemberRealm.java @@ -1,6 +1,7 @@ package com.tamguo.config.shiro; import java.util.Set; + import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; @@ -20,53 +21,88 @@ import com.tamguo.modules.member.service.IMemberService; /** * 认证 - * + * 该类继承自Shiro框架中的AuthorizingRealm,用于实现自定义的认证(登录验证)和授权(权限验证)逻辑, + * 在Shiro的安全框架体系中,Realm是用于连接应用程序的安全数据(如用户信息、权限信息等)与Shiro框架的桥梁, + * 在这里通过重写相应的方法,结合业务层的服务(IMemberService)来实现基于数据库中会员信息的认证和授权操作,例如验证用户登录时输入的用户名和密码是否正确、判断账号是否被锁定、更新登录相关信息以及获取用户权限信息等功能。 */ public class MemberRealm extends AuthorizingRealm { - + + // 自动注入IMemberService,用于调用会员相关的业务逻辑方法,比如根据用户名查找会员信息、获取登录失败次数、更新登录失败次数、更新最后登录时间等操作, + // 这些操作都是在认证(验证用户登录合法性)和授权(后续可能基于会员角色等信息获取权限)过程中需要依赖的业务逻辑实现。 @Autowired private IMemberService iMemberService; - - /** - * 授权(验证权限时调用) - */ + + /** + * 授权(验证权限时调用) + * 该方法是Shiro框架要求重写的用于获取授权信息的方法,当需要进行权限验证(例如访问某个受权限控制的资源时),Shiro会调用此方法来获取当前用户对应的权限信息, + * 在这里目前只是简单地创建了一个空的权限集合,并将其设置到SimpleAuthorizationInfo对象中返回,实际业务中需要根据具体的业务逻辑从数据库等数据源获取用户所拥有的权限信息并进行相应设置, + * 例如根据用户所属角色、用户直接关联的权限等方式来确定权限集合内容,然后通过info对象返回给Shiro框架用于后续的权限判断操作。 + * + * @param principals 包含了当前用户主体信息的集合,通过该集合可以获取到登录成功后的用户相关信息(例如用户名、用户对象等,具体取决于登录认证时设置的主体信息内容), + * 通常可以基于这些信息去查询数据库获取对应的权限信息,但当前方法只是简单示例,暂未充分利用该参数进行实际的权限查询操作。 + * @return 返回包含权限信息的AuthorizationInfo对象(这里返回的是SimpleAuthorizationInfo实例),Shiro框架会根据该对象中的权限信息来判断当前用户是否具有访问特定资源的权限。 + */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { - Set permsSet = null; + // 目前只是初始化一个空的权限集合,实际业务中应从数据库等数据源查询并填充相应的权限信息,例如根据用户角色、用户直接关联的权限等获取权限列表。 + Set permsSet = null; SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + // 将权限集合设置到授权信息对象中,当前为空集合,表示没有赋予任何权限,实际情况需要根据业务逻辑查询并设置正确的权限信息。 info.setStringPermissions(permsSet); - + return info; } /** * 认证(登录时调用) + * 该方法是Shiro框架要求重写的用于进行用户认证(登录验证)的方法,当用户发起登录操作时,Shiro会调用此方法来验证用户输入的用户名和密码等凭证信息是否正确, + * 在这里通过获取登录凭证中的用户名和密码,调用会员服务查找对应的会员信息,然后进行一系列验证操作,如判断用户是否存在、账号是否被锁定、密码是否匹配等, + * 根据验证结果抛出相应的异常(由Shiro框架统一处理并返回给客户端合适的错误提示)或者返回认证成功的信息(包含用户对象和密码等信息的AuthenticationInfo对象)。 + * + * @param token 包含用户登录凭证信息的对象,例如用户名和密码等信息,Shiro框架会将前端传来的登录信息封装到该对象中传递给此方法进行验证, + * 在这里可以通过token.getPrincipal()获取用户名,通过token.getCredentials()获取密码(通常需要进行类型转换等处理)。 + * @return 返回包含认证成功信息的AuthenticationInfo对象(这里返回的是SimpleAuthenticationInfo实例),如果验证通过则将相关的用户信息、密码以及当前Realm的名称等信息设置到该对象中返回给Shiro框架, + * 表示登录认证成功;如果验证过程中出现异常情况(如用户不存在、密码错误、账号锁定等),则会抛出相应的AuthenticationException异常,由Shiro框架统一处理并返回错误提示给客户端。 + * @throws AuthenticationException 如果在认证过程中出现任何不符合验证规则的情况(如用户不存在、密码错误、账号锁定等),则抛出该异常,Shiro框架会根据异常类型进行相应的错误处理和提示返回。 */ @Override - protected AuthenticationInfo doGetAuthenticationInfo( - AuthenticationToken token) throws AuthenticationException { + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + // 从登录凭证对象中获取用户名信息,通常前端输入的用户名会被封装到token中作为主体信息,这里将其从token中取出并转换为字符串类型,用于后续查询数据库验证用户是否存在等操作。 String username = (String) token.getPrincipal(); - String password = new String((char[]) token.getCredentials()); - - MemberEntity member = iMemberService.findByUsername(username); - if(member == null) { - throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码"); - } - Integer loginFailureCount = iMemberService.getLoginFailureCount(member); - if(loginFailureCount > 10) { - throw new LockedAccountException("账号被锁定"); - } - - if(!new Sha256Hash(password).toHex().equals(member.getPassword())){ + // 从登录凭证对象中获取密码信息,token.getCredentials()返回的是字符数组类型,将其转换为字符串类型,用于后续与数据库中存储的用户密码进行比对验证操作。 + String password = new String((char[]) token.getCredentials()); + + // 通过会员服务,根据用户名去数据库中查找对应的会员实体信息,用于后续验证该用户是否存在以及密码是否匹配等操作,如果查找不到则表示用户不存在,需要抛出相应异常。 + MemberEntity member = iMemberService.findByUsername(username); + if (member == null) { + // 如果根据用户名未找到对应的会员信息,抛出UnknownAccountException异常,表示用户名不存在或者未找到对应的用户信息,Shiro框架会捕获该异常并返回相应的错误提示给客户端。 + throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码"); + } + + // 通过会员服务获取当前会员账号的登录失败次数,用于后续判断账号是否因为多次登录失败而被锁定,具体的锁定阈值(这里是10次)根据业务需求设定,可调整。 + Integer loginFailureCount = iMemberService.getLoginFailureCount(member); + if (loginFailureCount > 10) { + // 如果登录失败次数超过设定的阈值(10次),抛出LockedAccountException异常,表示账号被锁定,Shiro框架会捕获该异常并返回相应的错误提示给客户端。 + throw new LockedAccountException("账号被锁定"); + } + + // 将用户输入的密码进行哈希处理(使用Sha256Hash算法进行加密,这里将用户输入的密码转换为十六进制字符串形式,方便与数据库中存储的加密后的密码进行比对), + // 然后与数据库中存储的会员密码进行比对,如果不相等则表示密码错误,需要进行相应的错误处理操作(如记录登录失败次数、抛出异常等)。 + if (!new Sha256Hash(password).toHex().equals(member.getPassword())) { + // 如果密码不匹配,先将登录失败次数加1,表示又一次登录失败,然后调用会员服务更新数据库中该会员账号的登录失败次数记录。 loginFailureCount++; - iMemberService.updateLoginFailureCount(member , loginFailureCount); + iMemberService.updateLoginFailureCount(member, loginFailureCount); + // 抛出IncorrectCredentialsException异常,表示密码错误,Shiro框架会捕获该异常并返回相应的错误提示给客户端。 throw new IncorrectCredentialsException("用户名或密码有误,请重新输入或找回密码"); } - // 更新登录时间 + + // 如果密码验证通过,调用会员服务更新数据库中该会员账号的最后登录时间,记录用户此次登录的时间信息,方便后续进行一些统计分析或者基于登录时间的业务逻辑处理(如判断账号是否长时间未登录等情况)。 iMemberService.updateLastLoginTime(member.getId()); - - SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(member, password, getName()); - return info; + + // 创建一个SimpleAuthenticationInfo对象,用于返回认证成功的信息给Shiro框架,将找到的会员实体对象(member)作为主体信息、用户输入的密码(password)以及当前Realm的名称(通过getName()方法获取)设置到该对象中, + // 表示登录认证成功,Shiro框架后续会基于这些信息进行会话管理等相关操作,并且可以在后续的请求处理中通过Shiro的相关API获取当前登录用户的信息等内容。 + SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(member, password, getName()); + return info; } -} +} \ No newline at end of file diff --git a/tamguo-bms/src/main/java/com/tamguo/config/shiro/ShiroConfiguration.java b/tamguo-bms/src/main/java/com/tamguo/config/shiro/ShiroConfiguration.java index 96f9ab3..bf7aa6d 100644 --- a/tamguo-bms/src/main/java/com/tamguo/config/shiro/ShiroConfiguration.java +++ b/tamguo-bms/src/main/java/com/tamguo/config/shiro/ShiroConfiguration.java @@ -12,27 +12,59 @@ import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreato import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +// 标识这是一个Spring的配置类,用于配置Shiro框架相关的组件和功能,通过定义多个Bean方法来创建并配置Shiro中的核心组件, +// 如Realm(用于认证和授权的领域对象)、缓存管理器、安全管理器、过滤器工厂等,这些组件协同工作,为Web应用提供基于角色和权限的安全访问控制功能。 @Configuration public class ShiroConfiguration { + + // 定义一个静态的LinkedHashMap,用于存储URL路径与对应的Shiro过滤器链定义,其中键表示URL的匹配模式,值表示对应的过滤器链定义(例如需要进行何种认证、授权等操作), + // 后续会将该映射配置到ShiroFilterFactoryBean中,用于控制不同URL的访问权限和安全策略。 private static Map filterChainDefinitionMap = new LinkedHashMap(); + /** + * 创建并配置MemberRealm(自定义的Realm,用于实现具体的认证和授权逻辑,应该继承自Shiro的相关Realm类并实现了特定的业务逻辑)的Bean, + * 该Realm会被注入到安全管理器(SecurityManager)中,作为Shiro进行用户认证和授权操作的核心逻辑处理单元,根据业务需求从数据库等数据源获取用户信息、权限信息等进行相应验证。 + * + * @return 返回创建好的MemberRealm实例,Spring容器会管理该实例,并将其提供给其他Shiro组件(如安全管理器)使用,以实现认证和授权功能。 + */ @Bean(name = "shiroRealm") public MemberRealm getShiroRealm() { return new MemberRealm(); } + /** + * 创建并配置EhCacheManager(基于EhCache的缓存管理器)的Bean,用于管理Shiro框架中的缓存,例如缓存用户的认证信息、授权信息等, + * 可以提高系统性能,减少频繁查询数据库等操作,通过设置配置文件路径(classpath:ehcache-shiro.xml)来加载具体的缓存配置信息,确定缓存的策略、过期时间等相关设置。 + * + * @return 返回创建好的EhCacheManager实例,Spring容器会管理该实例,并将其注入到安全管理器等相关Shiro组件中,使其生效用于缓存管理。 + */ @Bean(name = "shiroEhcacheManager") public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); + // 设置EhCache的配置文件路径,该文件通常定义了缓存的名称、缓存的策略(如内存缓存、磁盘缓存等)、缓存的过期时间、最大缓存数量等相关配置信息, + // Shiro会根据该配置文件来初始化和管理缓存,这里使用classpath路径表示从类路径下查找该配置文件。 em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return em; } + /** + * 创建并配置LifecycleBeanPostProcessor(生命周期Bean后置处理器)的Bean,用于管理Shiro组件在Spring容器中的生命周期, + * 它可以自动调用Shiro组件实现了相关生命周期接口的方法(如初始化、销毁方法等),确保Shiro组件在正确的时机进行相应的初始化和资源释放操作,保证系统的稳定性和资源管理的合理性。 + * + * @return 返回创建好的LifecycleBeanPostProcessor实例,Spring容器会管理该实例,并使其作用于Shiro组件的生命周期管理过程中。 + */ @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } + /** + * 创建并配置DefaultAdvisorAutoProxyCreator(默认的顾问自动代理创建器)的Bean,用于在Spring AOP框架中创建基于顾问(Advisor)的自动代理, + * 在Shiro与Spring整合时,它可以帮助自动创建代理对象,使得Shiro的安全拦截等功能能够通过AOP机制应用到相应的方法或类上,实现权限控制等安全相关的切面逻辑, + * 通过设置proxyTargetClass为true,表示使用基于类的代理方式(而不是基于接口的代理方式,具体取决于业务场景和需求)。 + * + * @return 返回创建好的DefaultAdvisorAutoProxyCreator实例,Spring容器会管理该实例,并使其在Spring AOP和Shiro整合过程中发挥作用,创建合适的代理对象用于安全控制。 + */ @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); @@ -40,14 +72,30 @@ public class ShiroConfiguration { return daap; } + /** + * 创建并配置DefaultWebSecurityManager(默认的Web安全管理器)的Bean,它是Shiro框架在Web应用中进行安全管理的核心组件, + * 负责协调和管理其他安全相关的组件,如Realm(用于认证和授权的具体逻辑实现)、缓存管理器(用于缓存相关信息以提高性能)等, + * 通过将自定义的MemberRealm和EhCacheManager注入到其中,使其具备了根据业务逻辑进行用户认证、授权以及缓存管理的能力,从而实现整个Web应用的安全访问控制功能。 + * + * @return 返回创建好的DefaultWebSecurityManager实例,Spring容器会管理该实例,并将其作为核心的安全管理组件应用到Shiro的整个安全体系中,例如配置到ShiroFilterFactoryBean中用于处理请求的安全验证等操作。 + */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager() { DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); + // 设置自定义的Realm,将之前创建的MemberRealm实例注入到安全管理器中,使得安全管理器在进行认证和授权操作时能够调用MemberRealm中实现的具体业务逻辑。 dwsm.setRealm(getShiroRealm()); + // 设置缓存管理器,将之前创建的EhCacheManager实例注入到安全管理器中,使得安全管理器能够利用缓存来管理用户认证信息、授权信息等,提高系统性能和响应速度。 dwsm.setCacheManager(getEhCacheManager()); return dwsm; } + /** + * 创建并配置AuthorizationAttributeSourceAdvisor(授权属性源顾问)的Bean,它是Shiro与Spring AOP整合用于处理授权相关切面逻辑的组件, + * 通过设置安全管理器(将之前创建的DefaultWebSecurityManager实例注入其中),使得在进行方法调用等操作时,能够基于Shiro的授权机制对方法进行权限验证, + // 例如判断当前用户是否具有访问某个被@RequiresPermissions等授权注解标注的方法的权限,从而实现细粒度的权限控制功能。 + * + * @return 返回创建好的AuthorizationAttributeSourceAdvisor实例,Spring容器会管理该实例,并使其在Spring AOP和Shiro整合的授权控制过程中发挥作用,对方法进行权限验证等操作。 + */ @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); @@ -55,15 +103,30 @@ public class ShiroConfiguration { return new AuthorizationAttributeSourceAdvisor(); } + /** + * 创建并配置ShiroFilterFactoryBean(Shiro过滤器工厂Bean)的Bean,它是Shiro在Web应用中配置URL过滤器链的核心组件, + * 用于定义不同URL路径需要应用的Shiro过滤器(如认证过滤器、授权过滤器等),从而实现对Web资源的访问控制,通过设置安全管理器、登录页面URL、成功登录后的跳转URL以及具体的过滤器链定义映射等参数, + * 来定制整个Web应用的安全访问策略,例如哪些URL需要用户登录后才能访问,哪些URL可以匿名访问等情况。 + * + * @return 返回配置好的ShiroFilterFactoryBean实例,Spring容器会管理该实例,并根据其配置的过滤器链定义等信息,对Web请求进行相应的安全拦截和处理,实现Web应用的安全访问控制功能。 + */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean() { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); + // 设置安全管理器,将之前创建的DefaultWebSecurityManager实例注入到ShiroFilterFactoryBean中,使得过滤器工厂能够利用安全管理器进行认证、授权等安全相关的操作, + // 根据安全管理器中配置的Realm、缓存管理器等组件来判断请求是否合法、用户是否具有访问权限等情况。 shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager()); + // 设置登录页面的URL,当用户访问需要认证但未登录的资源时,Shiro会自动重定向到该URL,让用户进行登录操作,这里设置为"/login",表示应用中对应的登录页面路径。 shiroFilterFactoryBean.setLoginUrl("/login"); + // 设置用户成功登录后的跳转URL,当用户登录成功后,Shiro会自动重定向到该URL,这里设置为"/index",表示登录成功后默认跳转到应用中的首页路径(具体根据业务需求确定)。 shiroFilterFactoryBean.setSuccessUrl("/index"); + + // 将定义好的URL路径与对应的Shiro过滤器链定义的映射添加到ShiroFilterFactoryBean中,例如将"/member/**"路径设置为需要进行认证("authc"表示需要进行身份认证的过滤器), + // 将"/**"路径设置为可以匿名访问("anon"表示允许匿名访问的过滤器),通过这样的配置来精确控制不同URL资源的访问权限,实现灵活的安全访问策略。 filterChainDefinitionMap.put("/member/**", "authc"); filterChainDefinitionMap.put("/**", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + return shiroFilterFactoryBean; } } \ No newline at end of file diff --git a/tamguo-bms/src/main/java/com/tamguo/config/web/ThymeleafConfig.java b/tamguo-bms/src/main/java/com/tamguo/config/web/ThymeleafConfig.java index 7c7be0b..bf8d203 100644 --- a/tamguo-bms/src/main/java/com/tamguo/config/web/ThymeleafConfig.java +++ b/tamguo-bms/src/main/java/com/tamguo/config/web/ThymeleafConfig.java @@ -11,32 +11,79 @@ import org.thymeleaf.spring5.view.ThymeleafViewResolver; import com.tamguo.common.utils.SystemConstant; +// 标识这是一个Spring的组件类,用于配置Thymeleaf视图相关的一些静态变量,通过实现EnvironmentAware接口可以获取Spring的应用程序环境信息, +// 进而从环境配置中获取特定的属性值,并将这些属性值以及一些常量值设置到ThymeleafViewResolver的静态变量中,使得在Thymeleaf模板中可以方便地使用这些变量进行页面渲染等操作。 @Component -public class ThymeleafConfig implements EnvironmentAware{ +public class ThymeleafConfig implements EnvironmentAware { + // 通过@Resource注解注入Spring的Environment对象,用于获取应用程序的配置属性信息,例如从配置文件(如application.properties或application.yml等)中读取各种自定义的配置项值。 @Resource - private Environment env; + private Environment env; + /** + * 配置Thymeleaf视图解析器的静态变量的方法,通过该方法向ThymeleafViewResolver对象中设置一系列静态变量, + * 这些变量的值一部分来自于应用程序的配置属性(通过Environment对象获取),另一部分来自于自定义的系统常量(通过SystemConstant类获取), + * 设置后的静态变量可以在Thymeleaf模板文件中直接使用,方便在页面渲染时进行动态数据展示或者根据不同的变量值进行不同的页面逻辑处理(如根据不同的域名显示不同的链接等情况)。 + * 此方法通过方法参数注入的方式获取ThymeleafViewResolver对象,Spring会自动查找并注入对应的实例供该方法使用。 + * + * @param viewResolver 要配置静态变量的ThymeleafViewResolver对象,用于设置各种静态变量,使其在视图解析和页面渲染过程中能够被Thymeleaf模板访问和使用。 + */ @Resource private void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) { - if(viewResolver != null) { - Map vars = new HashMap<>(); - vars.put("domainName", env.getProperty("domain.name")); - vars.put("adminDomain", env.getProperty("admin.domain.name")); - vars.put("memberDomain", env.getProperty("member.domain.name")); - vars.put("tamguoDomain", env.getProperty("tamguo.domain.name")); - vars.put("PAPER_TYPE_ZHENTI", SystemConstant.ZHENGTI_PAPER_ID); - vars.put("PAPER_TYPE_MONI", SystemConstant.MONI_PAPER_ID); - vars.put("PAPER_TYPE_YATI", SystemConstant.YATI_PAPER_ID); - vars.put("PAPER_TYPE_MINGXIAO", SystemConstant.MINGXIAO_PAPER_ID); - vars.put("BEIJING_AREA_ID", SystemConstant.BEIJING_AREA_ID); - viewResolver.setStaticVariables(vars); - } + if (viewResolver!= null) { + // 创建一个HashMap用于存储要设置的静态变量,键为变量名,值为对应的变量值,后续会将这些变量设置到ThymeleafViewResolver中,以便在Thymeleaf模板中使用。 + Map vars = new HashMap<>(); + + // 从应用程序的配置属性中获取名为"domain.name"的属性值,并将其作为"domainName"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中生成完整的域名相关的链接、资源引用等情况,例如拼接完整的图片地址、外部接口地址等,具体根据业务需求确定。 + vars.put("domainName", env.getProperty("domain.name")); + + // 从应用程序的配置属性中获取名为"admin.domain.name"的属性值,并将其作为"adminDomain"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中针对管理员相关的功能模块生成特定域名的链接、资源引用等情况,例如管理员登录页面的域名、管理员后台接口的域名等,具体根据业务中管理员模块的需求确定。 + vars.put("adminDomain", env.getProperty("admin.domain.name")); + + // 从应用程序的配置属性中获取名为"member.domain.name"的属性值,并将其作为"memberDomain"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中针对会员相关的功能模块生成特定域名的链接、资源引用等情况,例如会员中心页面的域名、会员相关接口的域名等,具体根据业务中会员模块的需求确定。 + vars.put("memberDomain", env.getProperty("member.domain.name")); + + // 从应用程序的配置属性中获取名为"tamguo.domain.name"的属性值,并将其作为"tamguoDomain"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中针对特定业务域名(以tamguo相关的业务场景)生成链接、资源引用等情况,具体根据业务中该域名对应的功能模块需求确定。 + vars.put("tamguoDomain", env.getProperty("tamguo.domain.name")); + + // 从自定义的SystemConstant类中获取名为ZHENGTI_PAPER_ID的常量值,并将其作为"PAPER_TYPE_ZHENTI"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中区分不同类型的试卷相关逻辑处理,例如根据试卷类型显示不同的样式、进行不同的操作按钮显示等情况,具体根据业务中试卷模块的需求确定。 + vars.put("PAPER_TYPE_ZHENTI", SystemConstant.ZHENGTI_PAPER_ID); + + // 从自定义的SystemConstant类中获取名为MONI_PAPER_ID的常量值,并将其作为"PAPER_TYPE_MONI"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中区分不同类型的试卷相关逻辑处理,例如根据试卷类型显示不同的样式、进行不同的操作按钮显示等情况,具体根据业务中试卷模块的需求确定。 + vars.put("PAPER_TYPE_MONI", SystemConstant.MONI_PAPER_ID); + + // 从自定义的SystemConstant类中获取名为YATI_PAPER_ID的常量值,并将其作为"PAPER_TYPE_YATI"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中区分不同类型的试卷相关逻辑处理,例如根据试卷类型显示不同的样式、进行不同的操作按钮显示等情况,具体根据业务中试卷模块的需求确定。 + vars.put("PAPER_TYPE_YATI", SystemConstant.YATI_PAPER_ID); + + // 从自定义的SystemConstant类中获取名为MINGXIAO_PAPER_ID的常量值,并将其作为"PAPER_TYPE_MINGXIAO"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中区分不同类型的试卷相关逻辑处理,例如根据试卷类型显示不同的样式、进行不同的操作按钮显示等情况,具体根据业务中试卷模块的需求确定。 + vars.put("PAPER_TYPE_MINGXIAO", SystemConstant.MINGXIAO_PAPER_ID); + + // 从自定义的SystemConstant类中获取名为BEIJING_AREA_ID的常量值,并将其作为"BEIJING_AREA_ID"变量的值存储到vars映射中, + // 该变量可能用于在Thymeleaf模板中涉及北京地区相关的逻辑处理,例如根据地区ID进行特定地区的业务展示、数据筛选等情况,具体根据业务中地区相关模块的需求确定。 + vars.put("BEIJING_AREA_ID", SystemConstant.BEIJING_AREA_ID); + + // 将包含所有静态变量的映射设置到ThymeleafViewResolver对象中,使得这些变量可以在Thymeleaf模板中被访问和使用,实现配置的生效,方便页面渲染时的动态数据展示和逻辑处理。 + viewResolver.setStaticVariables(vars); + } } + /** + * 实现EnvironmentAware接口的方法,用于设置当前类中的Environment对象,Spring容器会在初始化该类时自动调用此方法, + * 将应用程序的环境对象传递进来,以便在类中可以通过该对象获取各种配置属性信息,在这里将传入的environment对象赋值给类中已声明的env对象,方便后续使用。 + * + * @param environment Spring的应用程序环境对象,包含了应用程序的配置属性、系统环境变量等信息,通过该对象可以获取到各种自定义的配置项值以及系统相关的环境变量值等内容。 + */ @Override public void setEnvironment(Environment environment) { env = environment; } -} +} \ No newline at end of file diff --git a/tamguo-bms/src/main/java/com/tamguo/config/web/WebConfig.java b/tamguo-bms/src/main/java/com/tamguo/config/web/WebConfig.java index 2b2faad..859b0a5 100644 --- a/tamguo-bms/src/main/java/com/tamguo/config/web/WebConfig.java +++ b/tamguo-bms/src/main/java/com/tamguo/config/web/WebConfig.java @@ -12,32 +12,70 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.tamguo.interceptor.MemberInterceptor; +// 标识这是一个Spring的配置类,用于配置Spring Web应用相关的功能,通过实现WebMvcConfigurer接口并重写其中的方法, +// 可以对资源处理器、拦截器以及Cookie序列化等方面进行定制化配置,以满足项目特定的业务需求,例如设置文件资源的访问路径、添加会员相关请求的拦截逻辑、配置Cookie的相关属性等。 @Configuration public class WebConfig implements WebMvcConfigurer { - - @Value("${file.storage.path}") - private String fileStoragePath; - @Value("${cookie.domian.name}") - private String cookieDomianName; - @Autowired - private MemberInterceptor memberInterceptor; - + + // 通过@Value注解从配置文件(如application.properties或application.yml等)中注入文件存储路径属性值, + // 该路径用于指定服务器上存储文件的实际位置,后续在配置文件资源处理器时会用到,以便正确地将特定URL路径映射到该文件存储位置,实现文件的访问。 + @Value("${file.storage.path}") + private String fileStoragePath; + + // 通过@Value注解从配置文件中注入Cookie的域名属性值,该域名用于设置Cookie的作用域,决定了Cookie在哪些域名下可以被发送和接收, + // 在配置Cookie序列化相关逻辑时会使用该值来准确设置Cookie的域名属性,确保会话相关的Cookie在正确的域名范围内有效。 + @Value("${cookie.domian.name}") + private String cookieDomianName; + + // 自动注入MemberInterceptor,这是一个自定义的拦截器,用于在特定的请求路径下执行一些前置处理逻辑,比如可能用于验证会员的登录状态、权限等情况, + // 在配置拦截器时会将该拦截器添加到相应的请求路径上,使其生效并发挥作用。 + @Autowired + private MemberInterceptor memberInterceptor; + + /** + * 重写WebMvcConfigurer接口中的addResourceHandlers方法,用于配置资源处理器,将特定的URL路径与服务器上的实际文件存储位置进行映射, + * 使得当客户端访问指定的URL路径时,能够正确地从服务器对应的文件存储位置获取并返回相应的文件资源,例如图片、文档等静态文件资源。 + * + * @param registry 资源处理器注册对象,通过该对象可以添加不同的资源处理器配置,将URL路径模式与实际的文件资源位置进行关联。 + */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/files/**").addResourceLocations("file:"+fileStoragePath); + // 将以"/files/"开头的所有URL路径(例如 "/files/image.jpg"、"/files/documents/report.pdf"等), + // 映射到服务器上以"file:"开头的实际文件存储路径(通过fileStoragePath变量获取),这样客户端访问 "/files/" 相关路径时,就能获取到存储在对应文件存储路径下的文件资源了。 + registry.addResourceHandler("/files/**").addResourceLocations("file:" + fileStoragePath); } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**"); - } - + + /** + * 重写WebMvcConfigurer接口中的addInterceptors方法,用于添加自定义的拦截器到指定的请求路径上, + * 使得在这些请求被处理之前,拦截器能够执行相应的前置逻辑,比如进行会员登录状态验证、权限检查等操作,根据验证结果决定是否允许请求继续执行或者进行相应的跳转、提示等处理。 + * + * @param registry 拦截器注册对象,通过该对象可以添加不同的拦截器,并指定拦截器应用的请求路径模式,决定拦截器在哪些请求上生效。 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 将自动注入的MemberInterceptor添加到以"/member/"开头的所有请求路径上(例如 "/member/profile"、"/member/orders"等), + // 意味着当客户端发起这些会员相关的请求时,MemberInterceptor中的逻辑会先被执行,进行相应的前置处理,例如检查当前用户是否已登录为会员,如果未登录则可能重定向到登录页面等操作。 + registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**"); + } + + /** + * 创建并配置CookieSerializer(Cookie序列化器)的Bean,这里创建的是DefaultCookieSerializer的实例, + * 用于对Spring Session相关的Cookie进行序列化和反序列化操作,同时设置了Cookie的一些关键属性,如Cookie名称、域名以及路径等, + * 这些属性配置决定了Cookie在客户端和服务器之间的传输、存储以及作用范围等情况,确保会话相关的Cookie能按照项目需求正确工作。 + * + * @return 返回配置好的CookieSerializer实例,Spring容器会管理该实例,并将其应用到Spring Session相关的Cookie处理过程中,使其配置生效。 + */ @Bean - public CookieSerializer defaultCookieSerializer(){ + public CookieSerializer defaultCookieSerializer() { DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer(); + // 设置Cookie的名称为"sessionId",这意味着在客户端和服务器之间传输的会话相关的Cookie名称将是"sessionId", + // 服务器通过该名称来识别和处理对应的会话信息,前端在发送请求时也会带上名为"sessionId"的Cookie来标识当前会话。 defaultCookieSerializer.setCookieName("sessionId"); + // 设置Cookie的域名,使用之前注入的cookieDomianName属性值,确定Cookie的作用域,使得Cookie只会在指定的域名下有效, + // 例如,如果域名设置为"example.com",那么只有访问该域名下的页面时,相关的Cookie才会被发送和接收,保证会话信息在正确的域名范围内传递。 defaultCookieSerializer.setDomainName(cookieDomianName); + // 设置Cookie的路径为"/",表示该Cookie在整个域名下的所有路径都有效,即无论访问域名下的哪个具体页面路径,都会带上这个Cookie,方便会话信息在整个应用的各个页面间共享和使用。 defaultCookieSerializer.setCookiePath("/"); return defaultCookieSerializer; } -} +} \ No newline at end of file diff --git a/tamguo-bms/src/main/java/com/tamguo/interceptor/MemberInterceptor.java b/tamguo-bms/src/main/java/com/tamguo/interceptor/MemberInterceptor.java index c26a535..1d52adc 100644 --- a/tamguo-bms/src/main/java/com/tamguo/interceptor/MemberInterceptor.java +++ b/tamguo-bms/src/main/java/com/tamguo/interceptor/MemberInterceptor.java @@ -8,48 +8,75 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; +// 标识这是一个Spring的组件类,用于拦截Web请求,它继承自HandlerInterceptorAdapter,通过重写其中的preHandle方法来实现自定义的请求拦截逻辑, +// 主要用于检查用户是否已登录(通过查看会话中是否存在特定的用户标识信息),根据不同的请求类型(如普通的GET请求、AJAX请求等)以及登录状态进行相应的处理, +// 例如未登录时对于AJAX请求返回特定的错误响应头和状态码,对于普通GET请求则重定向到登录页面并携带当前请求的URL作为重定向后的参数,对于其他非GET请求则直接重定向到登录页面。 @Component -public class MemberInterceptor extends HandlerInterceptorAdapter{ +public class MemberInterceptor extends HandlerInterceptorAdapter { - /** "重定向URL"参数名称 */ + /** + * "重定向URL"参数名称 + * 定义一个常量字符串,表示在重定向到登录页面时,用于传递当前请求的原始URL的参数名称,以便在登录成功后可以根据该参数值将用户重定向回原本想要访问的页面。 + */ private static final String REDIRECT_URL_PARAMETER_NAME = "redirectUrl"; - /** 默认登录URL */ + /** + * 默认登录URL + * 定义一个常量字符串,表示默认的登录页面的URL路径,当用户未登录且需要重定向到登录页面时,如果没有特别配置登录URL,则会使用这个默认的路径进行重定向操作,方便统一管理登录入口。 + */ private static final String DEFAULT_LOGIN_URL = "/login.html"; - /** 登录URL */ + /** + * 登录URL + * 用于存储实际的登录页面的URL路径,初始值为默认登录URL(DEFAULT_LOGIN_URL),可以通过配置文件注入等方式来修改这个值,使其指向项目中真正的登录页面路径,以适应不同的项目部署或配置需求。 + */ private String loginUrl = DEFAULT_LOGIN_URL; - + + // 通过@Value注解从配置文件(如application.properties或application.yml等)中注入名为"tamguo.domain.name"的属性值, + // 该域名通常用于构建完整的登录页面URL等情况,确保重定向到登录页面时使用的是正确的域名,使得在不同的部署环境下(域名可能不同)能准确访问到登录页面。 @Value("${tamguo.domain.name}") private String tamguoDomainName; - + /** * 请求前处理 - * - * @param request - * HttpServletRequest - * @param response - * HttpServletResponse - * @param handler - * 处理器 - * @return 是否继续执行 + * 重写HandlerInterceptorAdapter类中的preHandle方法,该方法会在请求被处理之前执行,用于实现自定义的请求拦截逻辑,在这里主要用于检查用户的登录状态, + * 根据登录状态以及请求的类型(如是否为AJAX请求、是否为GET请求等)来决定是允许请求继续执行还是进行相应的重定向、返回错误响应等操作,返回值决定了后续请求处理流程是否继续进行。 + * + * @param request 当前的HttpServletRequest对象,包含了请求的各种信息,如请求头、请求参数、会话信息等,可以通过它获取到当前请求的详细情况,用于判断登录状态以及构建重定向URL等操作。 + * @param response 当前的HttpServletResponse对象,用于向客户端发送响应,例如设置响应头、重定向页面、返回错误状态码等操作,根据不同的逻辑情况向客户端做出相应的响应。 + * @param handler 处理器对象,一般是被拦截的请求对应的处理器(如Controller中的方法等),在这里当前方法暂未对该参数做过多使用,但在一些复杂的拦截逻辑中可能会根据处理器的相关信息来做更精细的处理决定。 + * @return 返回一个布尔值,若返回true,则表示允许请求继续执行后续的处理流程(如调用对应的Controller方法等);若返回false,则表示拦截该请求,中断后续的请求处理流程,直接根据当前方法中的逻辑向客户端返回相应的响应。 + * @throws Exception 如果在执行拦截逻辑过程中出现异常(如获取会话属性异常、重定向操作异常等),则抛出该异常,可能会导致请求处理出现错误情况,一般需要根据实际业务进行合适的异常处理和日志记录等操作。 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - Object currMember = request.getSession().getAttribute("currMember"); - if (currMember != null) { + // 从当前请求的会话(Session)中获取名为"currMember"的属性值,该属性通常在用户登录成功后被设置,用于标识当前登录的用户信息, + // 如果能获取到该属性值(即不为null),表示用户已经登录,此时允许请求继续执行后续的处理流程,直接返回true。 + Object currMember = request.getSession().getAttribute("currMember"); + if (currMember!= null) { return true; } else { + // 获取请求头中名为"X-Requested-With"的字段值,该字段常用于判断请求是否为AJAX请求,不同的前端框架在发起AJAX请求时通常会设置这个请求头字段,方便后端进行区分处理。 String requestType = request.getHeader("X-Requested-With"); - if (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) { + if (requestType!= null && requestType.equalsIgnoreCase("XMLHttpRequest")) { + // 如果请求头中的"X-Requested-With"字段值表明这是一个AJAX请求,且用户未登录,那么向响应头中添加一个名为"loginStatus"的字段,值为"accessDenied", + // 用于告知前端该请求因为未登录被拒绝访问,然后通过response发送一个HTTP状态码为FORBIDDEN(403,表示禁止访问)的错误响应给客户端,最后返回false,拦截该请求,中断后续的请求处理流程。 response.addHeader("loginStatus", "accessDenied"); response.sendError(HttpServletResponse.SC_FORBIDDEN); return false; } else { + // 如果不是AJAX请求,则根据请求的方法(GET、POST等)进行不同的重定向处理。 if (request.getMethod().equalsIgnoreCase("GET")) { - String redirectUrl = request.getQueryString() != null ? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI(); + // 如果是GET请求,构建要重定向的URL参数部分,先判断请求是否带有查询字符串(即请求URL中是否包含 "?" 后面的参数部分), + // 如果有查询字符串,则将请求的URI(不包含域名和端口部分的请求路径)加上原有的查询字符串作为重定向的URL参数部分;如果没有查询字符串,则只使用请求的URI作为重定向的URL参数部分, + // 这样做是为了在用户登录成功后能根据这个参数值将用户重定向回原本想要访问的页面。 + String redirectUrl = request.getQueryString()!= null? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI(); + // 进行重定向操作,将用户重定向到登录页面,构建完整的登录页面URL,通过将配置的域名(tamguoDomainName)、登录页面的URL路径(loginUrl)以及重定向URL参数(使用URLEncoder进行UTF-8编码,确保特殊字符能正确传递)拼接起来, + // 形成最终的重定向地址发送给客户端,让用户先进行登录操作,然后返回false,拦截该请求,中断后续的请求处理流程。 response.sendRedirect(tamguoDomainName + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + URLEncoder.encode(redirectUrl, "UTF-8")); } else { + // 如果是除GET之外的其他请求方法(如POST等),直接将用户重定向到登录页面,构建完整的登录页面URL,通过将配置的域名(tamguoDomainName)和登录页面的URL路径(loginUrl)拼接起来, + // 形成最终的重定向地址发送给客户端,让用户先进行登录操作,然后返回false,拦截该请求,中断后续的请求处理流程。 response.sendRedirect(tamguoDomainName + loginUrl); } return false; @@ -57,4 +84,4 @@ public class MemberInterceptor extends HandlerInterceptorAdapter{ } } -} +} \ No newline at end of file diff --git a/tamguo-bms/src/main/java/com/tamguo/utils/FileMd5Utils.java b/tamguo-bms/src/main/java/com/tamguo/utils/FileMd5Utils.java index 539a8ba..e33b6c8 100644 --- a/tamguo-bms/src/main/java/com/tamguo/utils/FileMd5Utils.java +++ b/tamguo-bms/src/main/java/com/tamguo/utils/FileMd5Utils.java @@ -5,33 +5,61 @@ import java.io.IOException; import java.security.MessageDigest; import org.apache.commons.codec.binary.Hex; +// 该类是一个工具类,主要提供了计算文件MD5值的功能,通过读取文件内容并按照一定的算法逐步更新MD5摘要信息,最终生成文件的MD5值, +// 并且能够处理大文件(通过分块读取文件内容的方式避免一次性将整个大文件读入内存),同时对可能出现的异常进行了相应处理,并确保文件输入流资源在使用后能正确关闭。 public class FileMd5Utils { /** - * 获取一个文件的md5值(可处理大文件) - * @return md5 value + * 获取一个文件的md5值(可处理大文件) + * 该方法用于计算给定文件输入流对应的文件的MD5值,采用分块读取文件内容的方式,每次读取一定大小的数据块(8192字节), + * 将读取到的数据块更新到MD5摘要对象中,直到读取完整个文件内容,最后通过编码转换将生成的MD5摘要字节数组转换为十六进制字符串形式的MD5值返回, + * 如果在计算过程中出现异常,则会打印异常堆栈信息并返回null表示获取MD5值失败。 + * + * @param fileInputStream 要计算MD5值的文件对应的输入流对象,通过该输入流可以读取文件的内容,该方法会从该输入流中逐块读取数据进行MD5值的计算, + * 在调用该方法时,需要确保传入的文件输入流已正确打开且指向有效的文件,并且调用者有责任在合适的时候创建和关闭该输入流(本方法内部会在最后进行关闭操作)。 + * @return 返回文件的MD5值,以十六进制字符串形式表示,如果计算过程中出现异常则返回null,调用者可根据返回值判断是否成功获取到文件的MD5值。 */ public static String getMD5(FileInputStream fileInputStream) { try { + // 获取MD5算法的MessageDigest实例,用于计算文件内容的MD5摘要信息,MessageDigest类提供了按照特定算法(这里是MD5算法)计算数据摘要的功能, + // 通过调用getInstance方法并传入算法名称("MD5")来创建对应的实例,后续可以使用该实例不断更新要计算摘要的数据内容,最终生成完整的摘要结果(即MD5值对应的字节数组)。 MessageDigest MD5 = MessageDigest.getInstance("MD5"); + + // 创建一个字节数组缓冲区,用于每次从文件输入流中读取文件内容的数据块,这里设置缓冲区大小为8192字节,通过分块读取文件内容的方式, + // 可以避免一次性将整个大文件读入内存,从而能够处理大文件的MD5计算,提高内存使用效率以及避免内存溢出等问题,特别是对于大型文件的处理更为合理和可行。 byte[] buffer = new byte[8192]; + + // 用于记录每次从文件输入流中实际读取到的字节数,在循环读取文件内容过程中,每次读取操作都会更新该变量的值,通过判断其是否为 -1 来确定是否已经读取完整个文件内容, + // 如果读取到文件末尾则返回 -1,表示文件内容已全部读完,循环结束。 int length; - while ((length = fileInputStream.read(buffer)) != -1) { + + // 通过循环,不断从文件输入流中读取文件内容到缓冲区,每次读取的数据长度存储在length变量中,然后使用MD5摘要对象的update方法将读取到的数据块更新到摘要计算中, + // 这样不断更新摘要信息,直到读取完整个文件内容(即length为 -1 时结束循环),实现对整个文件内容的MD5摘要计算。 + while ((length = fileInputStream.read(buffer))!= -1) { MD5.update(buffer, 0, length); } + + // 通过Hex类(来自Apache Commons Codec库)的encodeHex方法将最终生成的MD5摘要字节数组转换为十六进制字符串形式, + // 这样得到的就是常见的以十六进制字符串表示的文件MD5值,方便后续存储、展示以及与其他MD5值进行比对等操作,最后将该十六进制字符串作为文件的MD5值返回。 return new String(Hex.encodeHex(MD5.digest())); } catch (Exception e) { + // 如果在获取MD5算法实例、读取文件内容、更新MD5摘要或者转换MD5摘要字节数组为十六进制字符串等过程中出现异常, + // 则会打印异常的堆栈信息(通过e.printStackTrace()方法),方便后续排查问题,同时返回null,表示获取文件MD5值失败,调用者可根据返回值进行相应的错误处理。 e.printStackTrace(); return null; } finally { try { - if (fileInputStream != null){ + // 在finally块中确保文件输入流资源能够正确关闭,避免因异常等情况导致资源未释放的问题,先判断文件输入流是否不为空, + // 如果不为空则调用其close方法关闭输入流,释放相关的文件资源,确保文件读取操作完成后正确关闭资源,避免资源泄漏等问题。 + if (fileInputStream!= null) { fileInputStream.close(); - } + } } catch (IOException e) { + // 如果在关闭文件输入流过程中出现异常(例如文件被其他程序占用无法正常关闭等情况),同样会打印异常的堆栈信息,方便后续查看问题原因, + // 虽然此处无法对关闭流失败进行更多的补救措施,但通过打印异常信息可以帮助了解资源释放出现的问题。 e.printStackTrace(); } } } - -} + +} \ No newline at end of file diff --git a/tamguo-bms/src/main/java/com/tamguo/utils/ShiroUtils.java b/tamguo-bms/src/main/java/com/tamguo/utils/ShiroUtils.java index a31888d..bb30c1c 100644 --- a/tamguo-bms/src/main/java/com/tamguo/utils/ShiroUtils.java +++ b/tamguo-bms/src/main/java/com/tamguo/utils/ShiroUtils.java @@ -6,38 +6,96 @@ import org.apache.shiro.subject.Subject; import com.tamguo.modules.member.model.MemberEntity; +// 该类是一个工具类,主要提供了一系列基于Shiro框架的便捷方法,用于获取Shiro相关的核心对象(如Subject、Session等)、获取当前登录用户的信息(如用户实体、用户ID等)、操作会话属性(设置和获取)以及判断用户是否登录、执行用户登出等功能, +// 在整个项目中方便其他地方复用这些与Shiro框架交互的常用操作逻辑,避免重复编写相似的代码来获取Shiro相关对象和信息。 public class ShiroUtils { + /** + * 获取Shiro的会话对象(Session)。 + * 通过调用Shiro框架提供的SecurityUtils工具类的getSubject方法获取当前的Subject对象,再从Subject对象中获取对应的会话对象, + * 该会话对象可用于存储和获取与当前用户会话相关的信息,例如在用户登录后的一段时间内保存用户的临时状态、缓存一些数据等,方便在不同请求处理过程中共享这些信息。 + * + * @return 返回当前用户对应的Shiro会话对象(Session),后续可基于该对象进行会话相关的属性操作(如设置、获取属性等)。 + */ public static Session getSession() { return SecurityUtils.getSubject().getSession(); } + /** + * 获取Shiro的Subject对象。 + * 在Shiro框架中,Subject代表了当前执行的用户主体,它是与当前用户交互的核心对象,可用于进行认证(登录验证)、授权(权限验证)以及获取用户相关信息、操作会话等诸多操作, + * 通过Shiro的SecurityUtils工具类的getSubject方法来获取,表示当前与系统交互的用户主体对象,其他很多Shiro相关的操作都依赖于该对象来发起。 + * + * @return 返回当前的Subject对象,用于后续基于该对象进行诸如登录、获取用户信息、判断权限等一系列与用户主体相关的操作。 + */ public static Subject getSubject() { return SecurityUtils.getSubject(); } + /** + * 获取当前登录用户对应的MemberEntity(会员实体对象)。 + * 通过Shiro的SecurityUtils工具类获取当前的Subject对象,再从Subject对象中获取其主体信息(Principal),这里假设主体信息就是存储的会员实体对象(MemberEntity),所以进行强制类型转换获取, + * 该会员实体对象包含了当前登录用户的详细信息(如用户名、用户ID、用户其他业务相关属性等),方便在业务逻辑中获取和使用当前登录用户的具体信息进行相应处理,例如根据用户信息展示个性化页面、进行用户相关的数据操作等。 + * + * @return 返回当前登录用户对应的MemberEntity对象,如果当前用户未登录,则可能会抛出类型转换异常(因为未登录时获取的主体信息可能为null),在实际使用中需要注意进行合适的异常处理(通常在调用处判断用户是否登录)。 + */ public static MemberEntity getMember() { - return (MemberEntity)SecurityUtils.getSubject().getPrincipal(); + return (MemberEntity) SecurityUtils.getSubject().getPrincipal(); } + /** + * 获取当前登录用户的ID。 + * 先调用getMember方法获取当前登录用户对应的MemberEntity对象,再从该对象中获取其ID属性值,用于在业务逻辑中方便地获取当前登录用户的唯一标识, + * 例如根据用户ID查询该用户相关的其他数据、进行权限判断与用户ID关联等操作,在很多业务场景下需要明确知道当前操作的用户的唯一标识信息,通过该方法可以便捷获取。 + * + * @return 返回当前登录用户的ID,以字符串形式表示,如果当前用户未登录,调用getMember方法时可能出现异常情况(因为未登录获取不到MemberEntity对象),实际使用中要注意处理这种边界情况。 + */ public static String getMemberId() { return getMember().getId(); } - + + /** + * 设置Shiro会话对象(Session)中的属性值。 + * 先调用getSession方法获取当前用户对应的会话对象,然后使用该会话对象的setAttribute方法将要存储的属性键(key)和属性值(value)设置到会话中, + * 这样在同一个用户会话的不同请求处理过程中,可以通过相同的键来获取该属性值,实现会话级别的数据共享,方便存储一些临时的、与当前用户相关的数据,例如缓存用户在某个页面的操作状态等。 + * + * @param key 要设置的会话属性的键,用于唯一标识该属性,后续可通过该键来获取对应的属性值,类型为Object,可以是任意符合Java对象规范的类型,根据业务需求确定具体的键和对应的值类型。 + * @param value 要设置的会话属性的值,与传入的键相对应,存储到会话中,方便后续在同一个用户会话内通过键来获取使用,类型同样为Object,可以根据实际业务场景存储各种类型的数据。 + */ public static void setSessionAttribute(Object key, Object value) { getSession().setAttribute(key, value); } + /** + * 获取Shiro会话对象(Session)中指定键对应的属性值。 + * 先调用getSession方法获取当前用户对应的会话对象,然后使用该会话对象的getAttribute方法根据传入的属性键(key)来获取对应的属性值, + * 如果会话中存在该键对应的属性,则返回相应的值,否则返回null,通过该方法可以在同一个用户会话的不同请求处理中获取之前设置的临时数据,便于业务逻辑中使用会话中存储的相关信息。 + * + * @param key 要获取的会话属性的键,用于在会话中查找对应的属性值,类型为Object,需与之前设置属性时使用的键类型一致,确保能准确获取到对应的属性值。 + * @return 返回会话中指定键对应的属性值,如果不存在该键对应的属性,则返回null,返回值类型为Object,在实际使用时通常需要根据业务预期的属性值类型进行相应的强制类型转换操作。 + */ public static Object getSessionAttribute(Object key) { return getSession().getAttribute(key); } + /** + * 判断当前用户是否已登录。 + * 通过Shiro的SecurityUtils工具类获取当前的Subject对象,再判断其主体信息(Principal)是否为null,如果不为null则表示当前用户已登录,否则表示未登录, + * 可用于在业务逻辑中根据用户的登录状态进行不同的处理,例如对于未登录的用户重定向到登录页面、对于已登录用户展示特定的功能页面等情况,方便进行登录状态的判断操作。 + * + * @return 返回一个布尔值,如果当前用户已登录(即Subject的Principal不为null)则返回true,否则返回false,表示当前用户未登录。 + */ public static boolean isLogin() { - return SecurityUtils.getSubject().getPrincipal() != null; + return SecurityUtils.getSubject().getPrincipal()!= null; } + /** + * 执行用户登出操作。 + * 通过Shiro的SecurityUtils工具类获取当前的Subject对象,然后调用其logout方法进行用户登出处理,该操作会清除当前用户的会话信息、相关的认证和授权状态等, + * 使得用户回到未登录的状态,常用于用户主动点击登出按钮或者系统根据某些业务规则(如长时间未操作等)强制用户登出的场景,确保用户登出功能的实现。 + */ public static void logout() { SecurityUtils.getSubject().logout(); } -} +} \ No newline at end of file