diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/SnailmallApiGatewayApplication.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/SnailmallApiGatewayApplication.java index 6a6fdfc..e84b276 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/SnailmallApiGatewayApplication.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/SnailmallApiGatewayApplication.java @@ -8,20 +8,38 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +// 这是一个Spring Boot应用的主启动类,使用了多个注解来配置应用的相关特性和功能。 @SpringBootApplication +// @SpringBootApplication注解是一个复合注解,它包含了多个用于开启Spring Boot自动配置、组件扫描等功能的注解, +// 表明这是一个Spring Boot应用的入口点,会自动配置很多默认的Spring组件和行为,方便快速搭建项目框架。 + @EnableDiscoveryClient +// @EnableDiscoveryClient注解用于启用服务发现功能,使得该应用能够注册到服务注册中心(如Eureka、Consul等), +// 并且可以从注册中心发现其他服务,方便在微服务架构中实现服务之间的相互调用和协作。 + @EnableZuulProxy -@PropertySource(value="classpath:parameter.properties") +// @EnableZuulProxy注解用于启用Zuul作为微服务架构中的API网关代理,Zuul可以对外部请求进行路由转发、过滤等操作, +// 例如根据不同的请求路径将请求转发到相应的后端微服务上,还能实现诸如限流、权限校验等功能。 + +@PropertySource(value = "classpath:parameter.properties") +// @PropertySource注解用于指定外部属性文件的位置,这里表示从类路径下的parameter.properties文件中加载配置属性, +// 可以在该文件中定义各种项目中需要用到的配置项,如数据库连接信息、服务端口等,Spring会读取并注入这些配置到相应的组件中使用。 public class SnailmallApiGatewayApplication { + // 应用的主方法,是Java应用程序的入口点,用于启动Spring Boot应用。 public static void main(String[] args) { + // 调用SpringApplication的静态run方法来启动Spring Boot应用,传入当前主启动类的Class对象以及命令行参数, + // Spring Boot会根据配置自动完成一系列的初始化、组件加载、配置注入等操作,然后启动应用并监听相应的端口,等待外部请求。 SpringApplication.run(SnailmallApiGatewayApplication.class, args); } + // 使用@Bean注解定义一个Spring Bean,该方法返回的对象会被Spring容器管理,可在其他组件中通过依赖注入的方式使用。 @Bean public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() { + // 创建并返回一个PropertySourcesPlaceholderConfigurer对象,它主要用于处理属性占位符的替换功能, + // 在Spring应用中,如果配置文件中的属性使用了占位符(例如${property.name}这种形式),该配置器会帮助将占位符替换为实际配置的值, + // 确保应用能够正确使用配置文件中的各项配置属性。 return new PropertySourcesPlaceholderConfigurer(); } -} - +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java index 385face..8a8f61f 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java @@ -12,43 +12,63 @@ import redis.clients.jedis.JedisPool; * @Date 2019/1/1 15:03 * @CONTACT 317758022@qq.com * @DESC + * 该类是一个通用的缓存工具类,用于与Redis进行交互,实现缓存数据的存储、获取、设置过期时间以及删除等操作。 + * 依赖Spring框架进行组件管理,并使用Jedis库来操作Redis缓存。 */ @Component @Slf4j public class CommonCacheUtil { + // 通过依赖注入获取JedisPoolWrapper实例,用于后续获取JedisPool来操作Redis @Autowired private JedisPoolWrapper jedisPoolWrapper; /** * 缓存永久key + * 此方法用于向Redis缓存中永久存储一个键值对,即将指定的key和value存入Redis中。 + * 如果在操作过程中出现异常,会记录错误日志并抛出自定义的SnailmallException异常。 + * + * @param key 要存入缓存的键 + * @param value 要存入缓存对应键的值 */ public void cache(String key, String value) { try { + // 通过JedisPoolWrapper获取JedisPool实例,JedisPool用于管理Jedis连接资源 JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { + // 从JedisPool中获取一个Jedis连接资源,使用try-with-resources语句确保资源能自动关闭 try (Jedis Jedis = pool.getResource()) { + // 选择Redis的数据库,这里选择索引为0的数据库(通常用于存放通用缓存数据等情况) Jedis.select(0); + // 将键值对存入Redis Jedis.set(key, value); } } } catch (Exception e) { + // 如果出现异常,记录详细的错误日志,日志内容为"redis存值失败"以及异常信息 log.error("redis存值失败", e); + // 抛出自定义的SnailmallException异常,异常信息为"redis报错" throw new SnailmallException("redis报错"); } } /** * 获取缓存key + * 该方法用于从Redis缓存中获取指定key对应的value值。 + * 若获取过程中出现异常,同样会记录错误日志并抛出自定义异常。 + * + * @param key 要获取值的缓存键 + * @return 返回从Redis中获取到的对应键的值,如果没获取到则返回null */ public String getCacheValue(String key) { String value = null; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis Jedis = pool.getResource()) { Jedis.select(0); + // 从Redis中获取指定键的值 value = Jedis.get(key); } } @@ -61,15 +81,24 @@ public class CommonCacheUtil { /** * 过期key + * 此方法实现了原子性地设置缓存值(当键不存在时才设置)并设置过期时间的功能。 + * 操作Redis过程中若出现异常,会记录日志并抛出自定义异常,最后返回setnx操作的结果。 + * + * @param key 要设置的缓存键 + * @param value 要设置的缓存值 + * @param expire 设置的过期时间(单位可能是秒,具体取决于Redis配置) + * @return 返回jedis.setnx操作的结果,若键不存在设置成功返回1,否则返回0 */ public long cacheNxExpire(String key, String value, int expire) { long result = 0; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); + // 原子性地设置缓存值(当键不存在时才设置),返回设置结果 result = jedis.setnx(key, value); + // 为该键设置过期时间 jedis.expire(key, expire); } } @@ -83,13 +112,18 @@ public class CommonCacheUtil { /** * 删除缓存key + * 用于从Redis中删除指定的key对应的缓存数据。 + * 如果在删除过程中出现异常,会记录错误日志并抛出自定义异常。 + * + * @param key 要删除的缓存键 */ public void delKey(String key) { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); try { + // 调用Jedis的del方法删除指定的键 jedis.del(key); } catch (Exception e) { log.error("从redis中删除失败", e); @@ -101,4 +135,4 @@ public class CommonCacheUtil { -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java index 52cd0f7..c7fc709 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java @@ -14,30 +14,53 @@ import javax.annotation.PostConstruct; * @Date 2019/1/1 15:00 * @CONTACT 317758022@qq.com * @DESC 只做了单个redis,但是课程中实现的redis客户端集群,要掌握一致性hash算法 + * 该类主要用于对JedisPool进行封装和初始化操作,目的是方便获取JedisPool实例来操作Redis。 + * 目前仅针对单个Redis进行配置,而课程中还涉及到了Redis客户端集群相关内容以及一致性hash算法,这里需要后续进一步掌握拓展相关知识。 */ @Component @Slf4j public class JedisPoolWrapper { + + // 通过Spring的依赖注入,获取Parameters对象,该对象应该包含了Redis相关的配置参数 @Autowired private Parameters parameters; + // 用于存放JedisPool实例,初始化为null,后续在初始化方法中进行实例化赋值 private JedisPool jedisPool = null; + /** + * @PostConstruct注解表示该方法会在对象实例化后、依赖注入完成后自动被调用, + * 用于完成一些初始化的操作,在这里主要是对JedisPool进行初始化配置并创建实例。 + */ @PostConstruct - public void init(){ + public void init() { try { + // 创建JedisPoolConfig对象,用于配置JedisPool的各项参数 JedisPoolConfig config = new JedisPoolConfig(); + // 设置JedisPool中最大连接数,参数值从Parameters对象中获取 config.setMaxTotal(parameters.getRedisMaxTotal()); + // 设置JedisPool中空闲连接的最大数量,同样从Parameters对象获取对应参数值 config.setMaxIdle(parameters.getRedisMaxIdle()); + // 设置获取连接时的最大等待时间(单位为毫秒),也是从Parameters对象获取对应参数值 config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis()); - jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx"); + // 创建JedisPool实例,传入配置对象、Redis服务器主机地址、端口号、超时时间以及密码等信息 + // 这里密码写为"xxx",实际应用中应替换为真实的Redis访问密码 + jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx"); + // 如果初始化成功,记录日志信息表示初始化Redis连接池成功 log.info("【初始化redis连接池成功】"); - }catch (Exception e){ - log.error("【初始化redis连接池失败】",e); + } catch (Exception e) { + // 如果在初始化过程中出现异常,记录详细的错误日志,日志内容为"【初始化redis连接池失败】"以及异常信息 + log.error("【初始化redis连接池失败】", e); } } + /** + * 该方法用于对外提供获取JedisPool实例的功能, + * 外部类可以通过调用此方法来获取已经初始化好的JedisPool对象,进而操作Redis。 + * + * @return 返回已经初始化或者创建好的JedisPool实例,如果初始化失败则可能返回null + */ public JedisPool getJedisPool() { return jedisPool; } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/common/Parameters.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/common/Parameters.java index f147b5c..c7c3db7 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/common/Parameters.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/common/Parameters.java @@ -11,23 +11,54 @@ import java.util.List; * @Date 2019/1/1 14:27 * @CONTACT 317758022@qq.com * @DESC + * 该类主要用于存储系统中一些配置参数,通过Spring的相关注解来实现配置值的注入, + * 并使用了Lombok的@Data注解来自动生成常见的Getter、Setter、ToString等方法,方便对参数的访问和操作。 */ @Component @Data public class Parameters { /*****redis config start*******/ + /** + * 通过@Value注解,从配置文件(通常是application.properties或application.yml等)中读取名为"redis.host"的配置项的值, + * 并将其注入到该成员变量中,用于表示Redis服务器的主机地址。 + */ @Value("${redis.host}") private String redisHost; + + /** + * 同样使用@Value注解,从配置文件中获取名为"redis.port"的配置项的值, + * 注入到该变量,用于表示Redis服务器的端口号,类型为整型。 + */ @Value("${redis.port}") private int redisPort; + + /** + * 此处原代码中变量名可能有误(从语义看应该是max-idle对应maxIdle,max-total对应maxTotal更合理), + * 不过按现有代码逻辑,它是从配置文件读取名为"redis.max-idle"的配置项的值,注入进来,用于表示某个与Redis相关的最大空闲数量相关的配置参数,类型为整型。 + */ @Value("${redis.max-idle}") private int redisMaxTotal; + + /** + * 类似地,通过@Value注解从配置文件获取"redis.max-total"配置项的值,注入到该变量, + * 按推测应该是用于表示Redis连接池的最大连接数之类的参数,类型为整型。 + */ @Value("${redis.max-total}") private int redisMaxIdle; + + /** + * 从配置文件读取"redis.max-wait-millis"配置项的值,注入到该变量, + * 用于表示获取Redis连接时的最大等待时间(单位为毫秒),类型为整型。 + */ @Value("${redis.max-wait-millis}") private int redisMaxWaitMillis; /*****redis config end*******/ + /** + * 使用@Value注解结合SpEL(Spring表达式语言),从配置文件中读取名为"security.noneSecurityAdminPaths"的配置项的值, + * 并将其按照逗号进行分割,最终注入到该List类型的变量中, + * 推测其可能用于存放一些不需要安全验证的管理员相关路径列表。 + */ @Value("#{'${security.noneSecurityAdminPaths}'.split(',')}") private List noneSecurityAdminPaths; -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/config/CorsConfig.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/config/CorsConfig.java index 0acfa03..67038c6 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/config/CorsConfig.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/config/CorsConfig.java @@ -11,22 +11,41 @@ import java.util.Arrays; /** * 跨域配置 允许所有的接口都可以跨域 * C - Cross O - Origin R - Resource S - Sharing + * 该类是Spring框架中的配置类,用于配置跨域相关设置,使得项目中的所有接口都能支持跨域访问, + * 通过定义相关的跨域规则来控制不同源之间的资源共享情况。 */ @Configuration public class CorsConfig { + /** + * @Bean注解用于将该方法返回的对象作为一个Bean注册到Spring容器中, + * 在这里返回的是一个CorsFilter类型的Bean,它是Spring处理跨域请求的核心过滤器, + * 通过配置该过滤器来实现具体的跨域策略。 + */ @Bean public CorsFilter corsFilter() { + // 创建一个基于URL的跨域配置源对象,用于根据不同的URL路径来应用不同的跨域配置规则 final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + // 创建一个CorsConfiguration对象,用于具体设置跨域相关的各种配置参数 final CorsConfiguration config = new CorsConfiguration(); + // 设置是否允许携带凭证(例如Cookie等)进行跨域请求,这里设置为true,表示允许 config.setAllowCredentials(true); - config.setAllowedOrigins(Arrays.asList("*")); //http:www.a.com + // 设置允许跨域访问的源(Origin)列表,这里使用Arrays.asList("*")表示允许所有来源的请求跨域访问, + // 在实际应用中,更安全的做法是明确指定具体的源地址,例如http://www.a.com这种形式 + config.setAllowedOrigins(Arrays.asList("*")); + // 设置允许跨域请求包含的头部信息列表,使用Arrays.asList("*")表示允许所有头部信息, + // 但在更严格的安全场景下,应该明确列出允许的头部字段 config.setAllowedHeaders(Arrays.asList("*")); + // 设置允许的跨域请求方法列表,如GET、POST等,这里使用Arrays.asList("*")表示允许所有请求方法跨域访问 config.setAllowedMethods(Arrays.asList("*")); + // 设置浏览器对预检请求(OPTIONS请求)的缓存时间(单位为秒),这里设置为300秒, + // 即在这个时间范围内,对于相同源的相同请求,浏览器无需再次发送预检请求 config.setMaxAge(300l); + // 将上述配置应用到所有路径("/**"表示匹配所有路径)上,这样所有接口都会应用此跨域配置 source.registerCorsConfiguration("/**", config); + // 返回创建好的CorsFilter对象,该过滤器会在Spring处理Web请求时根据配置拦截并处理跨域相关情况 return new CorsFilter(source); } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/constants/Constants.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/constants/Constants.java index f721714..5ce3e8c 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/constants/Constants.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/constants/Constants.java @@ -5,42 +5,72 @@ package com.njupt.swg.constants; * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com * @DESC + * 该类用于定义项目中的各种常量,将一些常用的、固定不变的值集中管理在此处,方便在整个项目中统一引用,提高代码的可维护性和可读性。 */ public class Constants { - /**自定义状态码 start**/ + + /** + * 自定义状态码 start + * 以下定义了一系列项目中自定义的状态码,用于在不同的业务场景下表示请求的处理结果状态,方便统一规范响应情况。 + */ + // 表示请求成功的状态码,对应HTTP状态码中的200,表示操作成功完成 public static final int RESP_STATUS_OK = 200; + // 表示未授权的状态码,对应HTTP状态码中的401,通常用于用户未通过认证或授权时返回的状态 public static final int RESP_STATUS_NOAUTH = 401; + // 表示服务器内部错误的状态码,对应HTTP状态码中的500,用于在服务器端出现异常等内部问题时返回该状态告知客户端 public static final int RESP_STATUS_INTERNAL_ERROR = 500; + // 表示请求参数错误的状态码,对应HTTP状态码中的400,当客户端发送的请求参数不符合要求时返回此状态 public static final int RESP_STATUS_BADREQUEST = 400; + /** + * 自定义状态码 end + */ - /**自定义状态码 end**/ - - /***redis user相关的key以这个打头**/ + /** + * *redis user相关的key以这个打头 + * 用于定义在Redis中存储用户相关数据时,键(key)的前缀,通过统一前缀方便对用户相关缓存数据进行区分和管理。 + */ public static final String TOKEN_PREFIX = "user_"; /** * 用户登陆redis的过期时间 + * 定义了一个内部接口用于存放用户登录信息在Redis中的过期时间相关常量,这样的接口形式便于在代码中清晰地表示这一组相关的常量定义。 */ public interface RedisCacheExtime{ - int REDIS_SESSION_EXTIME = 60* 30;//30分钟 + // 定义用户登录信息在Redis中的过期时间,以秒为单位,这里设置为30分钟(60秒 * 30),方便后续统一管理和修改该时间设置 + int REDIS_SESSION_EXTIME = 60 * 30; //30分钟 } - /** 用户注册判断重复的参数类型 start **/ + /** + * 用户注册判断重复的参数类型 start + * 以下定义了在用户注册过程中,用于判断是否重复的参数类型对应的常量字符串,方便在验证逻辑中统一使用和判断。 + */ + // 表示注册时判断邮箱是否重复的参数类型对应的常量字符串,用于标识邮箱字段 public static final String EMAIL = "email"; + // 表示注册时判断用户名是否重复的参数类型对应的常量字符串,用于标识用户名字段 public static final String USERNAME = "username"; - /** 用户注册判断重复的参数类型 end **/ + /** + * 用户注册判断重复的参数类型 end + */ - /** 用户角色 **/ + /** + * 用户角色 + * 定义了一个内部接口用于存放项目中不同用户角色对应的整数值常量,清晰地划分和表示不同用户角色,便于在权限等相关逻辑中使用。 + */ public interface Role{ - int ROLE_CUSTOME = 0;//普通用户 - int ROLE_ADMIN = 1;//管理员用户 + // 表示普通用户角色对应的整数值,赋值为0,方便在代码中通过该值来判断用户是否为普通用户角色 + int ROLE_CUSTOME = 0; //普通用户 + + // 表示管理员用户角色对应的整数值,赋值为1,用于判断用户是否为管理员角色 + int ROLE_ADMIN = 1; //管理员用户 } - /**用户注册分布式锁路径***/ + /** + * 用户注册分布式锁路径 + * 定义了在用户注册过程中使用分布式锁时对应的路径字符串常量,方便在分布式锁相关逻辑中准确指定操作路径。 + */ public static final String USER_REGISTER_DISTRIBUTE_LOCK_PATH = "/user_reg"; - -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/controller/ErrorHandleController.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/controller/ErrorHandleController.java index fdac6bd..c81d4a1 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/controller/ErrorHandleController.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/controller/ErrorHandleController.java @@ -11,17 +11,30 @@ import org.springframework.web.bind.annotation.RestController; * @Date 2019/1/2 21:21 * @CONTACT 317758022@qq.com * @DESC + * 该类是一个Spring MVC中的控制器类,主要用于处理系统中的错误情况,实现了Spring Boot提供的ErrorController接口, + * 目的是自定义当出现错误(比如未登录、权限不足等情况)时返回给客户端的响应内容。 */ @RestController public class ErrorHandleController implements ErrorController { + + /** + * 实现ErrorController接口的方法,用于指定处理错误的路径, + * 返回值"/error"表示当系统出现错误时,会将请求转发到"/error"这个路径进行后续处理。 + */ @Override public String getErrorPath() { return "/error"; } + /** + * 使用@RequestMapping注解将该方法映射到"/error"路径上, + * 也就是当请求到达"/error"路径时,会执行这个方法来生成并返回相应的错误响应信息给客户端。 + * 这里返回的是一个ServerResponse类型的响应对象,通过调用ServerResponse的静态方法创建, + * 携带了对应错误的状态码(从ResponseEnum中获取未登录或权限不足对应的状态码)以及相应的错误提示信息,告知客户端用户未登陆或者权限不足的情况。 + */ @RequestMapping("/error") public ServerResponse error() { - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆或者权限不足"); + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆或者权限不足"); } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/entity/User.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/entity/User.java index 4354b81..b36f1e0 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/entity/User.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/entity/User.java @@ -13,31 +13,45 @@ import java.util.Date; * @Date 2018/12/31 21:01 * @CONTACT 317758022@qq.com * @DESC 用户实体类 + * 该类用于定义用户相关的属性,代表了系统中用户的基本信息模型,通过使用Lombok的相关注解来简化代码编写, + * 避免手动编写一些常规的方法(如Getter、Setter、构造函数、ToString方法等),实现了Java对象与数据库等存储介质中用户数据的映射。 */ @Data +// 使用@Data注解,由Lombok自动为类中的所有非final字段生成Getter、Setter方法,同时还生成了equals、hashCode和toString方法,方便对对象属性的访问和操作。 @NoArgsConstructor +// 使用@NoArgsConstructor注解,由Lombok自动生成一个无参构造函数,方便在某些情况下(如通过反射等方式创建对象实例)对对象进行初始化。 @AllArgsConstructor +// 使用@AllArgsConstructor注解,由Lombok自动生成一个包含所有参数的构造函数,可用于在创建对象时一次性传入所有必要的属性值来初始化对象。 @ToString +// 使用@ToString注解,由Lombok自动为该类生成一个toString方法,便于在调试等场景下直观地查看对象的属性值信息。 public class User implements Serializable { + // 用户的唯一标识符,通常对应数据库表中的主键,用于区分不同的用户记录,类型为整数类型。 private Integer id; + // 用户的用户名,用于用户登录等场景下的身份标识,是一个字符串类型的属性。 private String username; + // 用户的登录密码,存储用户登录时需要验证的密码信息,为字符串类型,通常会进行加密存储以保证安全性。 private String password; + // 用户的电子邮箱地址,可用于找回密码、接收系统通知等功能,是字符串类型的属性。 private String email; + // 用户的电话号码,可能用于联系用户、短信验证等相关业务场景,同样为字符串类型。 private String phone; + // 用户设置的密保问题,用于在忘记密码等情况下辅助验证用户身份,属于字符串类型的属性。 private String question; + // 用户对密保问题设置的答案,与question属性对应,用于验证用户身份,也是字符串类型。 private String answer; - //角色0-管理员,1-普通用户 + // 用户角色,通过整数值来表示不同的角色类型,0代表管理员,1代表普通用户,用于权限控制等相关业务逻辑。 private Integer role; + // 用户账号创建的时间,记录用户首次注册创建账号的具体日期和时间,类型为Java中的Date类型。 private Date createTime; + // 用户信息最后更新的时间,每次用户修改自身信息等操作后会更新该时间,同样是Date类型的属性。 private Date updateTime; - } \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/exception/SnailmallException.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/exception/SnailmallException.java index af7918a..a2e718e 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/exception/SnailmallException.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/exception/SnailmallException.java @@ -8,18 +8,33 @@ import lombok.Getter; * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC + * 该类是一个自定义的异常类,继承自Java的RuntimeException,用于在项目中抛出特定的运行时异常情况, + * 通过添加自定义的属性和构造函数,可以携带更多与异常相关的信息,方便统一处理和向客户端返回合适的错误响应。 */ @Getter -public class SnailmallException extends RuntimeException{ +// 使用Lombok的@Getter注解,自动为类中的私有属性生成对应的Getter方法,使得外部可以获取这些属性的值。 +public class SnailmallException extends RuntimeException { + // 用于存储异常对应的状态码,初始化为ResponseEnum.ERROR.getCode(),即默认的错误状态码,后续可通过构造函数进行修改。 private int exceptionStatus = ResponseEnum.ERROR.getCode(); - public SnailmallException(String msg){ + /** + * 构造函数,接收一个字符串类型的参数msg,用于创建一个只携带错误消息的异常实例, + * 调用父类(RuntimeException)的构造函数将错误消息传递上去,以便在异常抛出时可以展示相应的错误提示信息。 + * @param msg 异常的错误消息内容,用于描述异常发生的原因等情况。 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 重载的构造函数,接收一个整型的状态码参数code和一个字符串类型的错误消息参数msg, + * 不仅会调用父类构造函数传递错误消息,还会将传入的状态码赋值给exceptionStatus属性, + * 这样就可以创建一个携带特定状态码和错误消息的异常实例,方便根据不同情况进行更细致的异常处理和响应。 + * @param code 异常对应的状态码,用于表示特定的错误类型等情况,可对应项目中定义的不同错误状态码枚举值。 + * @param msg 异常的错误消息内容,同上面构造函数中的msg参数含义。 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/AdminUserFilter.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/AdminUserFilter.java index 037213b..7bc36b1 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/AdminUserFilter.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/AdminUserFilter.java @@ -27,26 +27,43 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst * @Date 2019/1/3 10:21 * @CONTACT 317758022@qq.com * @DESC 关于后台管理系统,登陆不需要拦截,对于需要认证的富文本上传,需要特定的格式,所以,这里先对富文本放开,到controller里面进行特别处理 - * 前台,就不再在这里进行处理了,因为前台需要防止用户同级的攻击,所以需要userID,放到这里,controller那边不好处理 + * 前台,就不再在这里进行处理了,因为前台需要防止用户同级的攻击,所以需要userID,放到这里,controller那边不好处理 + * 该类是一个ZuulFilter的实现类,用于在Zuul网关中对请求进行过滤处理,主要针对后台管理系统的请求进行权限校验等相关操作, + * 根据不同的请求路径和用户身份等情况决定是否放行请求,或者返回相应的错误提示信息。 */ @Slf4j @Component public class AdminUserFilter extends ZuulFilter { + // 通过依赖注入获取CommonCacheUtil实例,用于后续从缓存(Redis)中获取用户相关信息 @Autowired private CommonCacheUtil commonCacheUtil; + // 通过依赖注入获取Parameters实例,该实例可能包含了一些系统配置参数,比如不需要安全校验的路径等信息 @Autowired private Parameters parameters; + /** + * 该方法用于指定过滤器的类型,返回值表明该过滤器属于前置过滤器(在请求路由之前执行), + * 对应的值取自Spring Cloud Zuul定义的常量PRE_TYPE,表示在请求被路由之前进行相关处理。 + */ @Override public String filterType() { return PRE_TYPE; } + /** + * 该方法用于指定过滤器的执行顺序,返回值表示该过滤器在前置过滤器中的执行顺序, + * 通过取PRE_DECORATION_FILTER_ORDER减1的值来确定其相对顺序,使得该过滤器能在特定的顺序下执行,先于某些其他前置过滤器操作。 + */ @Override public int filterOrder() { - return PRE_DECORATION_FILTER_ORDER-1; + return PRE_DECORATION_FILTER_ORDER - 1; } + /** + * 该方法用于判断当前请求是否需要经过该过滤器进行处理,根据请求的URL等条件来决定。 + * 首先获取当前请求的上下文以及对应的HttpServletRequest对象,然后获取请求的URL, + * 根据URL的内容判断是否需要进行权限校验,如果不符合校验条件则直接返回false表示不需要该过滤器处理,否则返回true。 + */ @Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); @@ -54,63 +71,68 @@ public class AdminUserFilter extends ZuulFilter { //获取当前请求的url String url = request.getRequestURI(); //前端的路径不在这里校验,直接放过 - if (!url.contains("manage")){ - log.info("【{}不需要进行权限校验】",url); + if (!url.contains("manage")) { + log.info("【{}不需要进行权限校验】", url); return false; } - if(url.contains("upload")){ - log.info("【{}不需要进行权限校验】",url); + if (url.contains("upload")) { + log.info("【{}不需要进行权限校验】", url); return false; } //从配置文件获取所有门户需要校验的路径 // String[] passUrls = parameters.getNoneSecurityAdminPaths().toArray(new String[parameters.getNoneSecurityAdminPaths().size()]); -// for(String str:passUrls){ +// for (String str : passUrls) { // //指定的路径比较特殊,也不在这里校验 -// if(url.contains("manage") && url.contains(str)){ -// log.info("【{}不需要进行权限校验】",url); +// if (url.contains("manage") && url.contains(str)) { +// log.info("【{}不需要进行权限校验】", url); // return false; // } // } - log.info("【{}----需要进行权限校验,必须是管理员身份才可以进入】",url); + log.info("【{}----需要进行权限校验,必须是管理员身份才可以进入】", url); return true; } + /** + * 该方法是过滤器的核心执行逻辑所在,当shouldFilter方法返回true时会执行该方法, + * 在这里主要进行用户登录状态以及管理员权限的校验等操作,如果校验不通过则进行相应的错误处理, + * 如阻止请求路由、设置错误响应状态码、返回错误提示信息等,若校验通过则允许请求继续路由转发。 + */ @Override public ServerResponse run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); //校验是否为管理员身份 String loginToken = CookieUtil.readLoginToken(request); - log.info("【获取cookie----{}】",loginToken); - if(StringUtils.isEmpty(loginToken)){ + log.info("【获取cookie----{}】", loginToken); + if (StringUtils.isEmpty(loginToken)) { // // 过滤该请求,不对其进行路由 // requestContext.setSendZuulResponse(false); // //返回错误代码 // requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH); // return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); this.returnMsg(requestContext); - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息"); //throw new SnailmallException("用户未登录,无法获取当前用户信息"); } //2.从redis中获取用户信息 String userStr = commonCacheUtil.getCacheValue(loginToken); - log.info("【从redis中获取用户信息:{}】",userStr); - if(userStr == null){ + log.info("【从redis中获取用户信息:{}】", userStr); + if (userStr == null) { // // 过滤该请求,不对其进行路由 // requestContext.setSendZuulResponse(false); // //返回错误代码 // requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH); // return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); //SnailmallException(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); this.returnMsg(requestContext); - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息"); } String url = request.getRequestURI(); - log.info("【获取当前url:{}】",url); - if(url.contains("manage")){ + log.info("【获取当前url:{}】", url); + if (url.contains("manage")) { log.info("【来到了管理后台,需要校验权限】"); - User currentUser = JsonUtil.Str2Obj(userStr,User.class); - log.info("【当前登陆的用户为:{}】",currentUser); - if(!currentUser.getRole().equals(Constants.Role.ROLE_ADMIN)){ + User currentUser = JsonUtil.Str2Obj(userStr, User.class); + log.info("【当前登陆的用户为:{}】", currentUser); + if (!currentUser.getRole().equals(Constants.Role.ROLE_ADMIN)) { //不是管理员报错 log.error("【当前登陆用户不是管理员身份】"); // 过滤该请求,不对其进行路由 @@ -119,7 +141,7 @@ public class AdminUserFilter extends ZuulFilter { // requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH); // return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); this.returnMsg(requestContext); - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息"); //throw new SnailmallException("用户权限不够"); } } @@ -127,10 +149,14 @@ public class AdminUserFilter extends ZuulFilter { } //返回没权限消息 - private void returnMsg(RequestContext ctx){ + private void returnMsg(RequestContext ctx) { + // 设置响应的内容类型为JSON格式,字符编码为utf-8,确保返回的错误信息能被正确解析 ctx.getResponse().setContentType("application/json; charset=utf-8"); - ctx.setSendZuulResponse(false); //令zuul过滤该请求,不对其进行路由 + // 令zuul过滤该请求,不对其进行路由,即阻止请求继续往后端服务转发 + ctx.setSendZuulResponse(false); + // 设置响应的状态码,这里设置为Constants.RESP_STATUS_OK,可能需要根据实际情况确认是否合适(一般权限相关错误可以考虑用对应的错误状态码) ctx.setResponseStatusCode(Constants.RESP_STATUS_OK); - ctx.setResponseBody(JsonUtil.obj2String(ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"))); + // 将错误提示信息转换为JSON字符串,并设置为响应体的内容,以便客户端能获取到具体的错误提示信息 + ctx.setResponseBody(JsonUtil.obj2String(ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息"))); } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/RateLimitFilter.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/RateLimitFilter.java index 4f1944b..3393ac6 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/RateLimitFilter.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/RateLimitFilter.java @@ -16,37 +16,64 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst * @Date 2019/1/3 11:21 * @CONTACT 317758022@qq.com * @DESC 令牌桶法限流 具体测试没做 + * 该类是一个ZuulFilter的实现类,用于在Zuul网关中实现基于令牌桶算法的限流功能, + * 通过控制请求获取令牌的情况来决定是否允许请求继续往后端服务转发,以此限制单位时间内的请求数量。 */ @Component public class RateLimitFilter extends ZuulFilter { //放100个令牌 + // 使用RateLimiter类(来自Google Guava库)创建一个令牌桶限流器,设置初始令牌数量为100个, + // 意味着令牌桶一开始有100个可用令牌,后续请求需要获取令牌才能通过该过滤器继续路由。 private static final RateLimiter RATE_LIMITER = RateLimiter.create(100); + /** + * 该方法用于指定过滤器的类型,返回值表明该过滤器属于前置过滤器(在请求路由之前执行), + * 对应的值取自Spring Cloud Zuul定义的常量PRE_TYPE,表示在请求被路由之前进行相关处理,这里在前置阶段进行限流判断。 + */ @Override public String filterType() { return PRE_TYPE; } + /** + * 该方法用于指定过滤器的执行顺序,返回值表示该过滤器在前置过滤器中的执行顺序, + * 通过取SERVLET_DETECTION_FILTER_ORDER减1的值来确定其相对顺序,使得该过滤器能在特定的顺序下执行,先于某些其他前置过滤器操作。 + */ @Override public int filterOrder() { return SERVLET_DETECTION_FILTER_ORDER - 1; } + /** + * 该方法用于判断当前请求是否需要经过该过滤器进行处理,这里直接返回true, + * 意味着所有的请求都会经过该过滤器进行限流相关的判断操作,不会有请求被直接跳过该限流判断环节。 + */ @Override public boolean shouldFilter() { return true; } + /** + * 该方法是过滤器的核心执行逻辑所在,当shouldFilter方法返回true时会执行该方法, + * 在这里主要进行令牌获取的操作,如果请求能够成功获取到一个令牌(即令牌桶中有可用令牌),则允许请求继续路由转发, + * 若没有取到令牌,则进行相应的错误处理,比如设置一些错误相关的信息到请求上下文中(这里简单示例了设置状态码和错误消息)。 + */ @Override public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); - if(!RATE_LIMITER.tryAcquire()){ + // 使用tryAcquire方法尝试从令牌桶中获取一个令牌,该方法会立即返回获取结果, + // 如果获取成功(即令牌桶中有可用令牌)则返回true,请求可以继续往后执行;如果获取失败(令牌桶中无可用令牌)则返回false。 + if (!RATE_LIMITER.tryAcquire()) { //没有取到一个令牌的话,可以这样返回信息给前端 - context.set("状态码",401); - context.set("error.message","用户没有获取到令牌"); + // 在请求上下文中设置一个名为"状态码"的属性,值为401,表示未授权状态(这里只是简单示例设置状态码,实际应用可能需遵循规范的状态码使用方式), + // 用于后续可能的错误处理或者向客户端返回相应的提示信息,告知客户端请求因未获取到令牌而被限制。 + context.set("状态码", 401); + // 在请求上下文中设置一个名为"error.message"的属性,值为"用户没有获取到令牌", + // 用于更详细地向客户端说明请求被限制的原因,方便客户端知晓具体的错误情况。 + context.set("error.message", "用户没有获取到令牌"); } return null; } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ResponseEnum.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ResponseEnum.java index 428850a..6e07b1c 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ResponseEnum.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ResponseEnum.java @@ -7,19 +7,34 @@ import lombok.Getter; * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com * @DESC 基本的返回状态描述 + * 该类是一个枚举类型,用于定义项目中基本的响应状态相关的常量值, + * 通过枚举的形式可以清晰地列举出不同的返回状态及其对应的代码和描述信息,方便在整个项目中统一使用和进行状态判断。 */ @Getter +// 使用Lombok的@Getter注解,会自动为枚举中的code和desc属性生成对应的Getter方法,便于外部获取这些属性的值。 public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + // 定义一个名为SUCCESS的枚举常量,表示操作成功的响应状态,其对应的状态码为0,描述信息为"SUCCESS", + // 可用于在返回响应给客户端等场景下表示请求处理成功的情况。 + SUCCESS(0, "SUCCESS"), + // 定义一个名为ERROR的枚举常量,表示操作出现错误的响应状态,状态码为1,描述信息为"ERROR", + // 当系统出现一般性错误时可使用该枚举值来构建相应的错误响应返回给客户端。 + ERROR(1, "ERROR"), + // 定义一个名为ILLEGAL_ARGUMENTS的枚举常量,用于表示请求参数不合法的响应状态,状态码为2,描述信息为"ILLEGAL_ARGUMENTS", + // 当客户端发送的请求参数不符合要求时,可以用此枚举值来构造响应告知客户端参数非法情况。 + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + // 定义一个名为NEED_LOGIN的枚举常量,用于表示需要用户登录的响应状态,状态码为10,描述信息为"NEED_LOGIN", + // 当客户端发起需要登录才能访问的请求但未登录时,可通过该枚举值构建响应提示客户端进行登录操作。 + NEED_LOGIN(10, "NEED_LOGIN"); + // 用于存储响应状态对应的代码,不同的枚举常量有不同的整数值,以此区分不同的响应情况。 private int code; + // 用于存储响应状态对应的描述信息,以字符串形式展示具体的状态含义,方便直观理解每个枚举常量所代表的情况。 private String desc; - ResponseEnum(int code,String desc){ + // 枚举类型的构造函数,用于初始化每个枚举常量对应的code和desc属性值, + // 在定义枚举常量时传入相应的代码和描述信息,以此来完成每个枚举常量的具体状态设置。 + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ServerResponse.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ServerResponse.java index 2dd46a2..bd9e87f 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ServerResponse.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ServerResponse.java @@ -11,65 +11,110 @@ import java.io.Serializable; * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com * @DESC 作为本项目的通用的返回封装类 + * 该类是项目中的一个通用返回值封装类,用于统一包装服务器端返回给客户端的响应信息, + * 包含了响应状态码、提示消息以及具体的数据内容等属性,通过提供一系列静态方法方便创建不同情况(成功、失败等)下的响应对象实例。 */ @Getter +// 使用Lombok的@Getter注解,自动为类中的私有属性(status、msg、data)生成对应的Getter方法,便于外部获取这些属性的值。 @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// 使用@JsonSerialize注解并指定Inclusion.NON_NULL配置,用于在将对象转换为JSON格式时,忽略值为null的属性, +// 这样在返回给客户端的JSON数据中就不会包含那些没有实际值的属性,减少数据传输量并使数据结构更简洁。 public class ServerResponse implements Serializable { + // 用于存储响应的状态码,通过不同的状态码值来表示请求处理的结果情况,如成功、失败、需要登录等不同状态。 private int status; + // 用于存储响应的提示消息,以字符串形式向客户端传达一些与响应相关的额外信息,比如成功时的提示、失败原因等。 private String msg; + // 用于存储响应中具体的数据内容,该数据类型通过泛型参数来灵活指定,根据不同的业务场景可以是各种类型的数据(如对象、列表等)。 private T data; - private ServerResponse(int status){ + // 私有构造函数,接收一个状态码参数,用于创建只包含状态码的ServerResponse对象实例, + // 通常在一些内部初始化或者特定场景下使用,其他更完整的构造函数会基于此进一步设置更多属性值。 + private ServerResponse(int status) { this.status = status; } - private ServerResponse(int status,String msg){ + + // 私有构造函数,接收状态码和提示消息两个参数,用于创建包含状态码和提示消息的ServerResponse对象实例, + // 可用于在需要返回带有特定提示信息的响应情况时进行对象初始化,比如返回错误提示消息等场景。 + private ServerResponse(int status, String msg) { this.status = status; this.msg = msg; } - private ServerResponse(int status,T data){ + + // 私有构造函数,接收状态码和具体数据两个参数,用于创建包含状态码和具体业务数据的ServerResponse对象实例, + // 当有需要返回成功处理后的数据给客户端时,可以使用此构造函数来初始化响应对象。 + private ServerResponse(int status, T data) { this.status = status; this.data = data; } - private ServerResponse(int status,String msg,T data){ + + // 私有构造函数,接收状态码、提示消息以及具体数据三个参数,用于创建一个完整包含状态码、提示消息和业务数据的ServerResponse对象实例, + // 可以在需要同时返回详细提示信息和数据的场景下使用此构造函数进行对象初始化。 + private ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + /** + * @JsonIgnore注解用于标记该方法,使得在将对象转换为JSON格式时,忽略此方法对应的属性(这里实际是方法返回值当作属性来看待), + * 该方法用于判断当前响应是否表示成功状态,通过比较响应状态码与表示成功的状态码(ResponseEnum.SUCCESS.getCode())是否相等来确定, + * 返回true表示成功,false表示失败或其他非成功状态。 + */ @JsonIgnore - public boolean isSuccess(){ + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } /** * 成功的方法 + * 以下是一组静态方法,用于创建表示成功情况的ServerResponse对象实例,方便在业务逻辑处理成功后返回相应的响应给客户端, + * 每个方法根据不同的参数需求返回不同内容设置的成功响应对象。 */ - public static ServerResponse createBySuccess(){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); + // 创建一个表示成功的ServerResponse对象实例,状态码使用ResponseEnum.SUCCESS中定义的成功状态码,提示消息也使用ResponseEnum.SUCCESS中定义的描述信息, + // 适用于简单的成功情况,不需要额外传递具体业务数据和自定义提示消息的场景。 + public static ServerResponse createBySuccess() { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc()); } - public static ServerResponse createBySuccessMessage(String message){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); + + // 创建一个表示成功的ServerResponse对象实例,状态码使用ResponseEnum.SUCCESS中定义的成功状态码,提示消息使用传入的自定义参数message, + // 适用于需要返回成功状态但想自定义提示消息给客户端的场景,比如提示操作成功的具体说明等情况。 + public static ServerResponse createBySuccessMessage(String message) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); + + // 创建一个表示成功的ServerResponse对象实例,状态码使用ResponseEnum.SUCCESS中定义的成功状态码,具体业务数据使用传入的参数data, + // 适用于有具体业务数据需要返回给客户端以表示操作成功且携带对应数据的场景,比如查询数据成功后返回查询到的数据列表等情况。 + public static ServerResponse createBySuccess(T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data); } - public static ServerResponse createBySuccess(String message,T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); + + // 创建一个表示成功的ServerResponse对象实例,状态码使用ResponseEnum.SUCCESS中定义的成功状态码,提示消息使用传入的参数message,具体业务数据使用传入的参数data, + // 适用于既需要返回自定义的提示消息又要携带具体业务数据来表示成功情况的场景,更加灵活地满足不同业务需求下成功响应的构建。 + public static ServerResponse createBySuccess(String message, T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } /** * 失败的方法 + * 以下是一组静态方法,用于创建表示失败情况的ServerResponse对象实例,方便在业务逻辑处理出现错误等情况时返回相应的失败响应给客户端, + * 同样每个方法根据不同的参数需求返回不同内容设置的失败响应对象。 */ - public static ServerResponse createByError(){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); - } - public static ServerResponse createByErrorMessage(String msg){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); - } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); + // 创建一个表示失败的ServerResponse对象实例,状态码使用ResponseEnum.ERROR中定义的表示错误的状态码,提示消息也使用ResponseEnum.ERROR中定义的描述信息, + // 适用于一般性的错误情况,返回默认的错误提示给客户端表示操作出现问题的场景。 + public static ServerResponse createByError() { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); } + // 创建一个表示失败的ServerResponse对象实例,状态码使用ResponseEnum.ERROR中定义的表示错误的状态码,提示消息使用传入的自定义参数msg, + // 适用于需要返回特定错误提示消息给客户端以说明具体错误原因的场景,比如参数错误、业务规则不满足等各种导致失败的具体情况说明。 + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } + // 创建一个表示失败的ServerResponse对象实例,状态码使用传入的自定义参数code(通常是根据具体错误类型定义的不同状态码),提示消息使用传入的参数msg, + // 适用于需要根据不同错误类型返回对应状态码和具体错误提示消息的场景,更加灵活准确地向客户端反馈错误情况,比如不同业务模块下的不同错误状态反馈等。 + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + return new ServerResponse<>(code, msg); + } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/CookieUtil.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/CookieUtil.java index ffab240..782bc41 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/CookieUtil.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/CookieUtil.java @@ -9,40 +9,59 @@ import javax.servlet.http.HttpServletResponse; /** * cookie读写 + * 该类提供了与Cookie操作相关的实用方法,主要用于在Web应用中进行登录相关Cookie的写入、读取以及注销时的删除操作, + * 通过封装这些操作逻辑,方便在项目的不同业务场景中统一处理Cookie相关事宜,确保用户登录等状态信息的正确维护。 */ @Slf4j public class CookieUtil { + // 定义Cookie的域名,用于指定该Cookie在哪个域名下有效,这里设置为"oursnail.cn",表示该Cookie在该域名及其子域名下都可以被识别和使用。 private final static String COOKIE_DOMAIN = "oursnail.cn"; + // 定义Cookie的名称,用于唯一标识该Cookie,这里设置为"snailmall_login_token",通常用于存储用户登录相关的标识信息(如登录令牌等)。 private final static String COOKIE_NAME = "snailmall_login_token"; /** * 登陆的时候写入cookie - * @param response - * @param token + * 该方法用于在用户登录时向客户端写入一个Cookie,将登录相关的标识信息(如登录令牌)存储到客户端,方便后续识别用户登录状态等操作。 + * @param response 用于向客户端发送响应的HttpServletResponse对象,通过它来添加要写入客户端的Cookie信息。 + * @param token 要写入Cookie的值,通常是代表用户登录状态的令牌等关键信息,会被存储在名为COOKIE_NAME的Cookie中。 */ - public static void writeLoginToken(HttpServletResponse response,String token){ - Cookie ck = new Cookie(COOKIE_NAME,token); + public static void writeLoginToken(HttpServletResponse response, String token) { + // 创建一个新的Cookie对象,指定Cookie的名称(使用预先定义的COOKIE_NAME)和要存储的值(传入的token参数)。 + Cookie ck = new Cookie(COOKIE_NAME, token); + // 设置Cookie的域名,使其在指定的域名(COOKIE_DOMAIN)及其子域名下有效,确保在相应的网站范围内能正确识别该Cookie。 ck.setDomain(COOKIE_DOMAIN); - ck.setPath("/");//设值在根目录 - ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击 - ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒,maxage不设置的话,cookie就不会写入硬盘,只会写在内存,只在当前页面有效 - log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); + // 设置Cookie的路径为根目录("/"),表示该Cookie在整个网站的所有页面路径下都可以被访问到,即全站有效。 + ck.setPath("/"); //设值在根目录 + // 设置Cookie为HttpOnly属性为true,这样可以防止通过客户端脚本(如JavaScript)访问该Cookie,有效避免脚本攻击,提高安全性。 + ck.setHttpOnly(true); //不允许通过脚本访问cookie,避免脚本攻击 + // 设置Cookie的最大存活时间,这里设置为一年(60秒 * 60分钟 * 24小时 * 365天),单位是秒, + // 如果设置为-1则表示永久有效,若不设置该属性(maxage不设置的话),Cookie就不会写入硬盘,只会写在内存,只在当前页面有效。 + ck.setMaxAge(60 * 60 * 24 * 365); //一年,-1表示永久,单位是秒,maxage不设置的话,cookie就不会写入硬盘,只会写在内存,只在当前页面有效 + // 记录日志,输出要写入的Cookie的名称和值,方便在调试等场景下查看具体的Cookie写入情况。 + log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue()); + // 将创建并配置好的Cookie添加到响应中,这样客户端收到响应后就会将该Cookie保存下来,以便后续请求时携带该Cookie信息。 response.addCookie(ck); } /** * 读取登陆的cookie - * @param request - * @return + * 该方法用于从客户端发送的请求中读取之前写入的登录相关的Cookie信息(名称为COOKIE_NAME的Cookie), + * 如果能找到则返回其存储的值(通常是登录令牌等),若没找到则返回null。 + * @param request 用于接收客户端请求的HttpServletRequest对象,通过它来获取客户端携带的Cookie信息。 + * @return 返回从请求中读取到的登录相关Cookie的值(如果存在),若不存在则返回null。 */ - public static String readLoginToken(HttpServletRequest request){ + public static String readLoginToken(HttpServletRequest request) { + // 从请求中获取所有的Cookie数组,客户端发送请求时会携带之前服务器写入的Cookie信息,这里获取到这些Cookie以便后续查找特定的Cookie。 Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks){ - log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ - log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + if (cks!= null) { + for (Cookie ck : cks) { + // 记录每个Cookie的名称和值的日志信息,方便在调试等场景下查看请求中携带的Cookie情况。 + log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 通过比较Cookie的名称是否与预先定义的登录相关Cookie名称(COOKIE_NAME)相等,来判断是否是我们要找的登录Cookie。 + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + // 如果找到对应的登录Cookie,记录其名称和值的日志信息,并返回该Cookie存储的值(通常是登录令牌等关键信息)。 + log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); return ck.getValue(); } } @@ -52,18 +71,27 @@ public class CookieUtil { /** * 注销的时候进行删除 - * @param request - * @param response + * 该方法用于在用户注销登录时,从客户端删除之前写入的登录相关的Cookie,使得客户端后续请求不再携带该Cookie, + * 以此来清除用户登录状态相关的标识信息,确保用户处于未登录状态。 + * @param request 用于接收客户端请求的HttpServletRequest对象,通过它获取客户端当前携带的Cookie信息,以便查找并删除登录相关的Cookie。 + * @param response 用于向客户端发送响应的HttpServletResponse对象,通过它来设置要删除的Cookie的相关属性并发送给客户端,使客户端执行删除操作。 */ - public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ + public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) { + // 从请求中获取客户端携带的所有Cookie数组,用于查找要删除的登录相关Cookie。 Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks) { - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ + if (cks!= null) { + for (Cookie ck : cks) { + // 通过比较Cookie的名称是否与预先定义的登录相关Cookie名称(COOKIE_NAME)相等,来确定是否是要删除的Cookie。 + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + // 设置要删除的Cookie的域名,确保与之前写入时的域名一致,以便准确删除对应的Cookie。 ck.setDomain(COOKIE_DOMAIN); + // 设置Cookie的路径为根目录("/"),与之前写入时的路径设置保持一致,保证删除的是对应路径下的该Cookie。 ck.setPath("/"); - ck.setMaxAge(0);//0表示消除此cookie - log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + // 设置Cookie的最大存活时间为0,表示立即删除该Cookie,客户端收到响应后会将该Cookie从本地移除。 + ck.setMaxAge(0); //0表示消除此cookie + // 记录要删除的Cookie的名称和值的日志信息,方便在调试等场景下查看具体的删除操作情况。 + log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 将配置好的(即将要删除的)Cookie添加到响应中,客户端收到响应后会根据设置删除对应的Cookie。 response.addCookie(ck); return; } @@ -71,4 +99,4 @@ public class CookieUtil { } } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/DateTimeUtil.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/DateTimeUtil.java index 7b8fed0..90ad040 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/DateTimeUtil.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/DateTimeUtil.java @@ -4,65 +4,117 @@ import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * @DESC 时间转换的工具类 + * 该类提供了一系列用于时间相关转换的实用方法,主要借助Joda-Time库以及Java原生的日期时间处理类, + * 实现字符串与日期对象之间的相互转换,以及日期对象与时间戳之间的转换功能,方便在项目中统一处理时间相关的格式化操作。 */ public class DateTimeUtil { //joda-time + // 用于说明该工具类在处理时间相关操作时使用了Joda-Time库,它是一个功能强大的日期时间处理库,能更方便地进行各种日期时间的操作和格式化等工作。 //str->Date //Date->str - public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; - + // 注释说明该工具类提供了将字符串转换为日期对象以及将日期对象转换为字符串这两种方向的时间转换功能,方便在不同业务场景下对时间数据进行处理。 + public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + // 定义了一个标准的日期时间格式字符串常量,用于在没有指定特定格式时,作为默认的日期时间格式化模板,符合常见的年-月-日 时:分:秒的格式表示。 - public static Date strToDate(String dateTimeStr, String formatStr){ + /** + * 将指定格式的字符串转换为日期对象的方法。 + * 它使用Joda-Time库的DateTimeFormatter根据传入的格式字符串创建格式化器,然后解析输入的日期时间字符串, + * 最后将解析得到的DateTime对象转换为Java原生的Date对象并返回。 + * + * @param dateTimeStr 要转换的日期时间字符串,其格式需要与传入的formatStr参数相匹配。 + * @param formatStr 用于指定日期时间字符串的格式模板,例如"yyyy-MM-dd HH:mm:ss"等格式定义字符串。 + * @return 转换后的Java原生Date对象,如果解析过程出现问题可能会抛出异常(由Joda-Time库内部处理或者向上传播)。 + */ + public static Date strToDate(String dateTimeStr, String formatStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); return dateTime.toDate(); } - public static String dateToStr(Date date,String formatStr){ - if(date == null){ + /** + * 将日期对象转换为指定格式的字符串的方法。 + * 首先判断传入的日期对象是否为null,如果是null则返回一个空字符串, + * 若不为null,则使用Joda-Time库创建对应日期的DateTime对象,再按照传入的格式字符串将其转换为相应格式的字符串并返回。 + * + * @param date 要转换的Java原生Date对象,代表一个具体的日期时间点。 + * @param formatStr 用于指定转换后的字符串格式模板,决定了最终返回的日期时间字符串的格式样式。 + * @return 转换后的日期时间字符串,如果传入的日期对象为null则返回空字符串,否则返回按照指定格式转换后的字符串。 + */ + public static String dateToStr(Date date, String formatStr) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); return dateTime.toString(formatStr); } - //固定好格式 - public static Date strToDate(String dateTimeStr){ + /** + * 将符合标准格式("yyyy-MM-dd HH:mm:ss")的字符串转换为日期对象的方法。 + * 它使用预先定义的标准格式字符串常量(STANDARD_FORMAT)创建DateTimeFormatter格式化器, + * 然后解析输入的日期时间字符串,最后将解析得到的DateTime对象转换为Java原生的Date对象并返回。 + * + * @param dateTimeStr 要转换的日期时间字符串,其格式需要符合"yyyy-MM-dd HH:mm:ss"标准格式要求。 + * @return 转换后的Java原生Date对象,如果解析过程出现问题可能会抛出异常(由Joda-Time库内部处理或者向上传播)。 + */ + public static Date strToDate(String dateTimeStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); return dateTime.toDate(); } - public static String dateToStr(Date date){ - if(date == null){ + /** + * 将日期对象转换为标准格式("yyyy-MM-dd HH:mm:ss")的字符串的方法。 + * 首先判断传入的日期对象是否为null,如果是null则返回一个空字符串, + * 若不为null,则使用Joda-Time库创建对应日期的DateTime对象,再按照标准格式将其转换为相应格式的字符串并返回。 + * + * @param date 要转换的Java原生Date对象,代表一个具体的日期时间点。 + * @return 转换后的日期时间字符串,如果传入的日期对象为null则返回空字符串,否则返回按照标准格式"yyyy-MM-dd HH:mm:ss"转换后的字符串。 + */ + public static String dateToStr(Date date) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); return dateTime.toString(STANDARD_FORMAT); } - //Date -> 时间戳 + /** + * 将日期对象转换为时间戳(从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数)的方法。 + * 首先判断传入的日期对象是否为null,如果是null则返回null, + * 若不为null,则使用Java原生的SimpleDateFormat按照指定的标准格式("yyyy-MM-dd HH:mm:ss")创建格式化器, + * 先将日期对象转换为字符串再解析为Date对象,最后获取其对应的时间戳(以毫秒为单位)并返回。 + * + * @param date 要转换的Java原生Date对象,代表一个具体的日期时间点。 + * @return 转换后的时间戳(Long类型,表示从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数),如果传入的日期对象为null则返回null, + * 若在转换过程中出现解析等问题则会抛出ParseException异常(需要调用者处理)。 + */ public static Long dateToChuo(Date date) throws ParseException { - if(date == null){ + if (date == null) { return null; } - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.parse(String.valueOf(date)).getTime(); } + /** + * 主方法,用于简单测试该工具类中的时间转换功能,这里以将一个指定的日期时间字符串解析为Date对象,然后获取其时间戳并打印输出为例, + * 可以在开发过程中直接运行该类来验证部分功能是否符合预期,但实际应用中可能需要更完善的单元测试等方式来全面测试功能的正确性。 + * + * @param args 命令行参数,在这个简单测试场景下未使用到。 + * @throws ParseException 如果在解析日期时间字符串为Date对象过程中出现格式不匹配等问题,则会抛出该异常,由Java虚拟机向上传播处理。 + */ public static void main(String[] args) throws ParseException { - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); - String time="1970-01-06 11:45:55"; + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String time = "1970-01-06 11:45:55"; Date date = format.parse(time); - System.out.print("Format To times:"+date.getTime()); + System.out.print("Format To times:" + date.getTime()); } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/JsonUtil.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/JsonUtil.java index 135096d..30b873a 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/JsonUtil.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/JsonUtil.java @@ -8,119 +8,154 @@ import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.annotate.JsonSerialize; import org.codehaus.jackson.type.JavaType; import org.codehaus.jackson.type.TypeReference; - import java.io.IOException; import java.text.SimpleDateFormat; /** * jackson的序列化和反序列化 + * 该类是一个工具类,主要基于Jackson库提供了一系列用于对象序列化(将对象转换为JSON字符串)和反序列化(将JSON字符串转换为对象)的方法, + * 同时在类的初始化阶段对Jackson的ObjectMapper进行了一些配置,以满足项目中特定的JSON处理需求,例如统一日期格式、忽略某些转换错误等。 */ @Slf4j public class JsonUtil { + // 创建一个ObjectMapper对象,它是Jackson库中用于进行JSON序列化和反序列化操作的核心类,后续的各种序列化和反序列化操作都依赖它来实现。 private static ObjectMapper objectMapper = new ObjectMapper(); static { //所有字段都列入进行转换 + // 设置ObjectMapper在序列化时的包含规则,这里设置为JsonSerialize.Inclusion.ALWAYS,表示所有的字段都会被包含进序列化的结果中, + // 即使字段的值为null也会进行序列化处理,确保完整的对象结构能在JSON字符串中体现出来。 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); //取消默认转换timestamp形式 - objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); + // 配置ObjectMapper在序列化日期类型字段时,取消将日期转换为时间戳的默认行为,这样在序列化后的JSON字符串中日期会以指定的格式呈现,而不是时间戳形式。 + objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); //忽略空bean转json的错误 - objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); + // 配置ObjectMapper在尝试将一个空的Java对象(没有任何属性值的对象)转换为JSON字符串时,忽略可能出现的错误, + // 使得即使对象为空也能正常进行序列化操作,避免抛出异常导致程序中断。 + objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false); //统一时间的格式 + // 设置ObjectMapper在处理日期类型数据时使用的统一日期格式,通过传入一个SimpleDateFormat对象,指定了格式为DateTimeUtil.STANDARD_FORMAT(在其他地方定义的标准格式), + // 保证序列化和反序列化过程中日期格式的一致性和规范性。 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); //忽略json存在属性,但是java对象不存在属性的错误 - objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); + // 配置ObjectMapper在进行反序列化时,忽略JSON字符串中存在但对应的Java对象中不存在的属性所引发的错误, + // 这样在JSON数据结构可能发生变化或者不完全匹配Java对象结构时,依然能够正常进行反序列化操作,提高了程序的兼容性和容错性。 + objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** * 序列化方法,将对象转为字符串 - * @param obj - * @param - * @return + * 该方法用于将一个Java对象转换为JSON字符串表示形式,如果对象为null则直接返回null, + * 在转换过程中若出现IO异常(例如对象的某些属性无法正常序列化等情况),会记录警告日志并返回null。 + * + * @param obj 要进行序列化的Java对象,可以是任意类型的对象,由泛型参数表示其通用性。 + * @param 泛型参数,用于表示传入对象的类型,确保返回的JSON字符串能正确对应相应类型的对象序列化结果。 + * @return 返回转换后的JSON字符串,如果对象为null或者序列化过程出现异常则返回null。 */ - public static String obj2String(T obj){ - if(obj == null){ + public static String obj2String(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); + // 判断传入的对象是否本身就是字符串类型,如果是则直接返回该字符串,否则使用ObjectMapper将对象转换为JSON字符串并返回。 + return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** * 序列化方法,同上,只是输出的格式是美化的,便于测试 - * @param obj - * @param - * @return + * 该方法与obj2String方法功能类似,也是将Java对象转换为JSON字符串,不同之处在于它会使用美化的格式输出JSON字符串, + * 使得生成的JSON字符串更易于阅读和查看,方便在测试等场景下查看对象序列化后的具体结构,同样在出现异常时会记录警告日志并返回null。 + * + * @param obj 要进行序列化的Java对象,可以是任意类型的对象,由泛型参数表示其通用性。 + * @param 泛型参数,用于表示传入对象的类型,确保返回的JSON字符串能正确对应相应类型的对象序列化结果。 + * @return 返回转换后的JSON字符串,如果对象为null或者序列化过程出现异常则返回null,正常情况下返回美化格式的JSON字符串。 */ - public static String obj2StringPretty(T obj){ - if(obj == null){ + public static String obj2StringPretty(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + // 使用ObjectMapper的writerWithDefaultPrettyPrinter方法获取一个能够生成美化格式JSON字符串的写入器, + // 然后通过该写入器将对象转换为美化后的JSON字符串并返回。 + return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** * 比较简单的反序列化的方法,将字符串转为单个对象 - * @param str - * @param clazz - * @param - * @return + * 该方法用于将一个JSON字符串反序列化为指定类型的Java对象,在进行反序列化前会先判断字符串是否为空以及指定的目标类是否为null, + * 如果有任意一个为空则直接返回null,若反序列化过程中出现IO异常(例如JSON格式不匹配目标对象结构等情况),会记录警告日志并返回null。 + * + * @param str 要进行反序列化的JSON字符串,包含了要转换为Java对象的数据内容。 + * @param clazz 目标Java对象的类型,通过传入的Class参数明确指定要将JSON字符串反序列化为哪种类型的对象,确保类型的准确性。 + * @param 泛型参数,用于表示要反序列化得到的对象类型,与传入的clazz参数类型相对应。 + * @return 返回反序列化后的Java对象,如果字符串为空、目标类为null或者反序列化过程出现异常则返回null。 */ - public static T String2Obj(String str,Class clazz){ - if(StringUtils.isEmpty(str) || clazz == null){ + public static T String2Obj(String str, Class clazz) { + if (StringUtils.isEmpty(str) || clazz == null) { return null; } try { - return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz); + // 判断传入的目标类是否就是字符串类型的Class对象,如果是则直接将传入的字符串强制转换并返回(因为本身就是字符串无需真正的反序列化操作), + // 否则使用ObjectMapper将JSON字符串按照指定的目标类进行反序列化并返回对应的对象。 + return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** * 复杂对象的反序列化(通用) - * @param str - * @param typeReference - * @param - * @return + * 该方法用于处理更复杂的反序列化场景,能够将JSON字符串反序列化为复杂类型的对象(例如包含泛型的集合类型等), + * 通过传入TypeReference类型的参数来指定复杂对象的具体类型信息,同样在字符串为空或者TypeReference为null时会直接返回null, + * 若反序列化出现异常会记录警告日志并返回null。 + * + * @param str 要进行反序列化的JSON字符串,包含了要转换为复杂Java对象的数据内容。 + * @param typeReference 用于指定复杂对象类型的TypeReference对象,通过它可以准确描述包含泛型等复杂结构的对象类型信息,确保反序列化的准确性。 + * @param 泛型参数,用于表示要反序列化得到的复杂对象类型,与传入的typeReference所描述的类型相对应。 + * @return 返回反序列化后的复杂Java对象,如果字符串为空、typeReference为null或者反序列化过程出现异常则返回null。 */ - public static T Str2Obj(String str, TypeReference typeReference){ - if(StringUtils.isEmpty(str) || typeReference == null){ + public static T Str2Obj(String str, TypeReference typeReference) { + if (StringUtils.isEmpty(str) || typeReference == null) { return null; } try { - return (T) (typeReference.getType().equals(String.class)?str:objectMapper.readValue(str,typeReference)); + // 判断传入的TypeReference所表示的类型是否就是字符串类型,如果是则直接返回传入的字符串(无需反序列化操作), + // 否则使用ObjectMapper按照TypeReference指定的复杂类型信息将JSON字符串进行反序列化并返回对应的对象。 + return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference)); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** * 第二种方式实现复杂对象的反序列化 - * @param str - * @param collectionClass - * @param elementClasses - * @param - * @return + * 该方法是另一种实现复杂对象反序列化的方式,通过传入集合类型的Class对象以及元素类型的Class对象(可以多个,用于表示集合中元素的类型信息), + * 利用ObjectMapper的类型工厂构建出JavaType对象来准确描述复杂对象的类型结构,然后进行反序列化操作, + * 在出现异常时同样会记录警告日志并返回null。 + * + * @param str 要进行反序列化的JSON字符串,包含了要转换为复杂Java对象的数据内容。 + * @param collectionClass 表示集合类型的Class对象,用于指定要反序列化得到的对象是哪种集合类型(如List、Set等)。 + * @param elementClasses 可变参数,用于指定集合中元素的类型Class对象,可以传入多个,按照顺序依次表示集合中各元素的类型信息。 + * @param 泛型参数,用于表示要反序列化得到的复杂对象类型,与传入的collectionClass和elementClasses所构建的类型结构相对应。 + * @return 返回反序列化后的复杂Java对象,如果反序列化过程出现异常则返回null。 */ - public static T Str2Obj(String str,Class collectionClass,Class... elementClasses){ - JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); + public static T Str2Obj(String str, Class collectionClass, Class... elementClasses) { + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); try { - return objectMapper.readValue(str,javaType); + return objectMapper.readValue(str, javaType); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/SnailmallCategoryServiceApplication.java b/snailmall-category-service/src/main/java/com/njupt/swg/SnailmallCategoryServiceApplication.java index 0fe5c53..e034576 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/SnailmallCategoryServiceApplication.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/SnailmallCategoryServiceApplication.java @@ -4,13 +4,28 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +/** + * 该类SnailmallCategoryServiceApplication是Spring Boot应用的启动类,它是整个应用程序的入口点。 + * 通过运行这个类的main方法,将会启动Spring Boot应用,加载相关的配置、初始化各种组件,并启动内置的Web服务器(如Tomcat等,根据配置而定),开始对外提供服务。 + * 它使用了Spring Boot相关的注解来简化配置以及集成Spring Cloud相关功能。 + */ @SpringBootApplication +// 这是一个组合注解,相当于同时使用了@Configuration、@EnableAutoConfiguration和@ComponentScan三个注解。 +// @Configuration表示该类是一个配置类,可用于定义一些Spring的Bean或者配置相关的属性等; +// @EnableAutoConfiguration用于启用Spring Boot的自动配置功能,会根据项目依赖自动配置各种组件,比如数据源、Web相关配置等; +// @ComponentScan用于指定Spring扫描组件(如带有@Component、@Service、@Controller等注解的类)的基础包路径,默认会扫描当前类所在的包及其子包下的组件。 @EnableDiscoveryClient +// 该注解用于开启服务发现客户端功能,使得应用可以注册到服务注册中心(比如Eureka、Consul等),并且可以从注册中心发现其他服务,方便实现微服务之间的调用和协作。 public class SnailmallCategoryServiceApplication { + /** + * main方法是Java应用程序的入口方法,在这里通过调用SpringApplication的run方法来启动Spring Boot应用。 + * 传入当前启动类的Class对象(SnailmallCategoryServiceApplication.class)以及命令行参数(args), + * Spring Boot会根据配置和依赖进行一系列的初始化和启动操作,最终启动应用并使其处于可运行状态,对外提供服务。 + * @param args 命令行参数,可用于在启动应用时传入一些自定义的配置信息等,比如指定应用运行的端口号、配置文件路径等(具体取决于应用的实现和配置)。 + */ public static void main(String[] args) { SpringApplication.run(SnailmallCategoryServiceApplication.class, args); } -} - +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/constants/Constants.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/constants/Constants.java index 91ab476..234f77b 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/constants/Constants.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/constants/Constants.java @@ -1,22 +1,37 @@ package com.njupt.swg.common.constants; /** + * 该类Constants用于定义一些常用的常量,主要是自定义的状态码相关常量。 * @Author swg. * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com * @DESC */ public class Constants { - /**自定义状态码 start**/ + /** + * 自定义状态码开始标识,以下几个常量用于定义不同含义的状态码。 + */ + /** + * 表示请求成功的状态码,对应HTTP状态码中的200,表示操作顺利完成,请求被正常处理。 + */ public static final int RESP_STATUS_OK = 200; + /** + * 表示未授权的状态码,对应HTTP状态码中的401,意味着客户端请求没有经过授权,通常需要进行身份验证等操作来获取权限。 + */ public static final int RESP_STATUS_NOAUTH = 401; + /** + * 表示服务器内部错误的状态码,对应HTTP状态码中的500,说明服务器在处理请求时发生了内部错误,无法正确完成请求的处理。 + */ public static final int RESP_STATUS_INTERNAL_ERROR = 500; + /** + * 表示请求参数错误的状态码,对应HTTP状态码中的400,意味着客户端发送的请求参数不符合要求,例如格式错误、缺少必要参数等情况。 + */ public static final int RESP_STATUS_BADREQUEST = 400; - /**自定义状态码 end**/ - - -} + /** + * 自定义状态码结束标识 + */ +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java index cef87ac..c614c83 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java @@ -1,6 +1,5 @@ package com.njupt.swg.common.exception; - import com.njupt.swg.common.constants.Constants; import com.njupt.swg.common.resp.ServerResponse; import lombok.extern.slf4j.Slf4j; @@ -9,25 +8,41 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** + * 该类ExceptionHandlerAdvice用于进行全局异常处理,它可以捕获不同类型的异常,并返回相应的错误响应信息给客户端。 * @Author swg. * @Date 2019/1/1 13:21 * @CONTACT 317758022@qq.com * @DESC 全局异常处理 */ @ControllerAdvice +// 表示该类中处理异常的方法返回的结果会直接作为响应体返回给客户端,而不是进行视图解析等操作。 @ResponseBody +// 使用Slf4j注解,方便进行日志记录,简化日志输出的代码编写。 @Slf4j public class ExceptionHandlerAdvice { + /** + * 该方法用于处理所有类型为Exception(即最通用的异常类型)的异常情况。 + * 当捕获到这类异常时,会先将异常信息记录到日志中(方便后续排查问题),然后返回一个表示服务器内部错误的响应信息给客户端, + * 告知客户端系统出现异常,让其稍后再试。响应中使用了在Constants类中定义的表示服务器内部错误的状态码。 + * @param e 捕获到的Exception类型的异常对象 + * @return 返回一个ServerResponse类型的对象,包含错误状态码和对应的提示信息 + */ @ExceptionHandler(Exception.class) - public ServerResponse handleException(Exception e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); + public ServerResponse handleException(Exception e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试"); } + /** + * 该方法用于专门处理SnailmallException类型的异常情况。 + * 同样会先将异常信息记录到日志中,然后返回一个包含该异常对象中定义的异常状态码以及异常消息的响应信息给客户端。 + * 这样针对特定业务的异常可以更精准地反馈给客户端相应的错误情况。 + * @param e 捕获到的SnailmallException类型的异常对象 + * @return 返回一个ServerResponse类型的对象,包含对应异常的状态码和消息 + */ @ExceptionHandler(SnailmallException.class) - public ServerResponse handleException(SnailmallException e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); + public ServerResponse handleException(SnailmallException e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage()); } - -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java index 363f19d..0b4c007 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java @@ -4,22 +4,38 @@ import com.njupt.swg.common.resp.ResponseEnum; import lombok.Getter; /** + * 该类SnailmallException继承自RuntimeException,用于定义特定于业务(可能是名为Snailmall相关业务)的异常情况。 + * 通过继承RuntimeException,可以在代码中方便地抛出这种自定义异常,并且无需在方法声明中显式地抛出(因为是运行时异常)。 * @Author swg. * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC */ @Getter -public class SnailmallException extends RuntimeException{ +// 使用lombok的@Getter注解,自动生成获取exceptionStatus属性值的get方法,方便外部获取该异常状态码。 +public class SnailmallException extends RuntimeException { + // 定义一个表示异常状态码的属性,初始值设置为从ResponseEnum.ERROR中获取的状态码,通常用于表示默认的错误状态码情况。 private int exceptionStatus = ResponseEnum.ERROR.getCode(); - public SnailmallException(String msg){ + /** + * 构造方法,接收一个字符串类型的参数msg,用于创建一个SnailmallException实例。 + * 调用父类(RuntimeException)的构造方法,将传入的msg作为异常消息传递给父类,此时异常状态码使用默认值(即ResponseEnum.ERROR.getCode())。 + * 这种情况适用于只需要传递异常消息,而使用默认错误状态码的场景。 + * @param msg 异常消息内容 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 另一个构造方法,接收两个参数,一个整数类型的code表示自定义的异常状态码,一个字符串类型的msg表示异常消息。 + * 先调用父类(RuntimeException)的构造方法,将msg作为异常消息传递给父类,然后将传入的code赋值给exceptionStatus属性, + * 这样就可以根据具体业务需求自定义异常状态码以及异常消息,更灵活地表示不同的业务异常情况。 + * @param code 自定义的异常状态码 + * @param msg 异常消息内容 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java index e4e59c7..ee20c07 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java @@ -3,23 +3,36 @@ package com.njupt.swg.common.resp; import lombok.Getter; /** + * 该类ResponseEnum是一个枚举类型,用于定义基本的返回状态描述。 + * 它列举了一些常见的业务返回状态情况,每个枚举常量对应一种特定的状态,包含了状态码和对应的描述信息,方便在整个项目中统一使用这些返回状态标识。 * @Author swg. * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com * @DESC 基本的返回状态描述 */ @Getter +// 使用lombok的@Getter注解,自动为枚举中的code和desc属性生成对应的获取方法,方便外部获取这些属性值。 public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + // 表示成功的返回状态,状态码为0,对应的描述信息为"SUCCESS",用于标识业务操作成功完成的情况。 + SUCCESS(0, "SUCCESS"), + // 表示出现错误的返回状态,状态码为1,对应的描述信息为"ERROR",可用于一般性的错误情况,较为笼统地表示操作未成功。 + ERROR(1, "ERROR"), + // 表示参数非法的返回状态,状态码为2,对应的描述信息为"ILLEGAL_ARGUMENTS",用于客户端传入的参数不符合要求等情况。 + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + // 表示需要登录的返回状态,状态码为10,对应的描述信息为"NEED_LOGIN",用于提示客户端当前操作需要先进行登录认证的场景。 + NEED_LOGIN(10, "NEED_LOGIN"); private int code; private String desc; - ResponseEnum(int code,String desc){ + /** + * 枚举的构造方法,用于初始化每个枚举常量对应的状态码和描述信息。 + * 在定义每个枚举常量(如SUCCESS、ERROR等)时会调用该构造方法来传入相应的code和desc值,以此来构建完整的返回状态描述。 + * @param code 状态码,用于在程序中区分不同的返回状态情况。 + * @param desc 状态描述信息,通常用于向客户端展示具体的状态含义,便于理解。 + */ + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java index 53f3a65..0c4467c 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java @@ -4,75 +4,168 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Getter; import lombok.NoArgsConstructor; - import java.io.Serializable; /** + * 该类ServerResponse是本项目的通用的返回封装类,用于统一封装服务端返回给客户端的响应信息, + * 包含了状态码、提示消息以及具体的数据内容(数据部分可以是各种类型,通过泛型来表示其通用性),方便客户端解析和处理返回结果。 * @Author swg. * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com * @DESC 作为本项目的通用的返回封装类 */ @Getter +// 使用lombok的@Getter注解,自动生成获取status、msg、data属性值的方法,方便外部获取这些成员变量的值。 @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// 通过该注解配置Jackson序列化时的行为,这里表示只序列化非空的属性,避免返回包含大量空值的JSON数据给客户端,节省网络传输和客户端解析成本。 public class ServerResponse implements Serializable { + // 表示响应的状态码,用于客户端判断请求的处理结果状态,通常会对应一些预定义的状态码值,比如成功、失败等情况。 private int status; + // 表示响应的提示消息,用于向客户端传达一些关于请求处理结果的文字描述信息,便于客户端展示给用户知晓具体情况。 private String msg; + // 泛型成员变量,用于承载具体的业务数据,其类型根据实际业务场景而定,可以是对象、集合等各种数据结构类型。 private T data; - public ServerResponse(){} + public ServerResponse() { + } - public ServerResponse(int status){ + /** + * 构造方法,只接收状态码参数,用于创建一个ServerResponse实例,此时提示消息和数据部分为空, + * 可用于只需要传递状态码来表示一种简单响应情况的场景,比如某些只关注操作是否成功的情况(成功或失败状态码)。 + * @param status 响应状态码 + */ + public ServerResponse(int status) { this.status = status; } - public ServerResponse(int status,String msg){ + + /** + * 构造方法,接收状态码和提示消息参数,用于创建一个带有指定状态码和提示消息的ServerResponse实例, + * 数据部分为空,适用于需要向客户端传达具体的状态信息以及对应的文字描述情况,比如操作失败时告知具体失败原因等场景。 + * @param status 响应状态码 + * @param msg 响应提示消息 + */ + public ServerResponse(int status, String msg) { this.status = status; this.msg = msg; } - public ServerResponse(int status,T data){ + + /** + * 构造方法,接收状态码和数据参数,用于创建一个带有指定状态码和具体业务数据的ServerResponse实例, + * 提示消息为空,适用于操作成功且需要返回具体业务数据给客户端的场景,比如查询操作成功后返回查询到的数据集合等情况。 + * @param status 响应状态码 + * @param data 具体业务数据 + */ + public ServerResponse(int status, T data) { this.status = status; this.data = data; } - public ServerResponse(int status,String msg,T data){ + + /** + * 构造方法,接收状态码、提示消息和数据参数,用于创建一个完整的带有状态码、提示消息以及具体业务数据的ServerResponse实例, + * 适用于各种需要全面传递响应信息给客户端的复杂场景,综合体现了请求处理的结果情况。 + * @param status 响应状态码 + * @param msg 响应提示消息 + * @param data 具体业务数据 + */ + public ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + /** + * 通过该注解标记该方法在JSON序列化时被忽略,也就是不会将该方法的返回结果作为JSON数据的一部分返回给客户端。 + * 该方法用于判断当前响应是否表示成功,通过对比响应状态码和预定义的表示成功的状态码(从ResponseEnum.SUCCESS中获取)来确定, + * 方便客户端快速判断请求是否成功处理,而不需要关注具体的状态码数值细节。 + * @return 返回一个布尔值,true表示成功(状态码匹配成功状态码),false表示失败。 + */ @JsonIgnore - public boolean isSuccess(){ + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } /** - * 成功的方法 + * 以下是一系列静态方法,用于方便地创建表示成功的ServerResponse实例,按照不同的需求场景提供了多种创建方式。 */ - public static ServerResponse createBySuccess(){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); - } - public static ServerResponse createBySuccessMessage(String message){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); - } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); - } - public static ServerResponse createBySuccess(String message,T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); + + /** + * 创建一个表示成功的ServerResponse实例,使用预定义的成功状态码和对应的成功描述信息(从ResponseEnum.SUCCESS中获取), + * 数据部分为空,适用于只需要简单告知客户端操作成功的基本情况,不需要额外返回具体业务数据和自定义提示消息的场景。 + * @param 泛型标识,确保返回的ServerResponse类型与调用处期望的类型一致(虽然此处数据部分为空,但保持泛型通用性)。 + * @return 返回一个表示成功的ServerResponse实例。 + */ + public static ServerResponse createBySuccess() { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc()); } /** - * 失败的方法 + * 创建一个表示成功的ServerResponse实例,使用预定义的成功状态码,同时可以自定义提示消息,数据部分为空, + * 适用于操作成功但需要向客户端传达一些额外的自定义提示内容的场景,比如成功后提示一些引导性的话语等情况。 + * @param 泛型标识,确保返回的ServerResponse类型与调用处期望的类型一致(虽然此处数据部分为空,但保持泛型通用性)。 + * @param message 自定义的提示消息内容。 + * @return 返回一个表示成功的ServerResponse实例。 */ - public static ServerResponse createByError(){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); + public static ServerResponse createBySuccessMessage(String message) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createByErrorMessage(String msg){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); + + /** + * 创建一个表示成功的ServerResponse实例,使用预定义的成功状态码,同时携带具体业务数据,提示消息为默认的成功描述信息(从ResponseEnum.SUCCESS中获取), + * 适用于操作成功且需要返回具体业务数据给客户端的场景,比如查询到了相关数据并返回给客户端展示等情况。 + * @param 泛型标识,确保返回的ServerResponse类型与调用处期望的类型一致(根据传入的数据类型来确定具体的泛型类型)。 + * @param data 具体业务数据。 + * @return 返回一个表示成功的ServerResponse实例。 + */ + public static ServerResponse createBySuccess(T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data); } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); + + /** + * 创建一个表示成功的ServerResponse实例,使用预定义的成功状态码,同时携带具体业务数据以及自定义提示消息, + * 适用于操作成功且需要向客户端返回具体业务数据和一些额外自定义提示内容的复杂场景,综合向客户端传达完整的成功响应信息。 + * @param 泛型标识,确保返回的ServerResponse类型与调用处期望的类型一致(根据传入的数据类型来确定具体的泛型类型)。 + * @param message 自定义的提示消息内容。 + * @param data 具体业务数据。 + * @return 返回一个表示成功的ServerResponse实例。 + */ + public static ServerResponse createBySuccess(String message, T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } + /** + * 以下是一系列静态方法,用于方便地创建表示失败的ServerResponse实例,按照不同的需求场景提供了多种创建方式。 + */ + /** + * 创建一个表示失败的ServerResponse实例,使用预定义的表示失败的状态码和对应的失败描述信息(从ResponseEnum.ERROR中获取), + * 适用于一般性的失败情况,不需要额外自定义提示消息和返回具体业务数据,简单告知客户端操作失败的基本情况。 + * @param 泛型标识,确保返回的ServerResponse类型与调用处期望的类型一致(虽然此处数据部分为空,但保持泛型通用性)。 + * @return 返回一个表示失败的ServerResponse实例。 + */ + public static ServerResponse createByError() { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); + } -} + /** + * 创建一个表示失败的ServerResponse实例,使用预定义的表示失败的状态码,同时可以自定义提示消息, + * 适用于操作失败且需要向客户端传达具体的失败原因等自定义提示内容的场景,更详细地告知客户端操作失败的情况。 + * @param 泛型标识,确保返回的ServerResponse类型与调用处期望的类型一致(虽然此处数据部分为空,但保持泛型通用性)。 + * @param msg 自定义的提示消息内容。 + * @return 返回一个表示失败的ServerResponse实例。 + */ + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } + + /** + * 创建一个表示失败的ServerResponse实例,可自定义失败状态码和提示消息, + * 适用于需要根据具体业务逻辑来设定特定的失败状态码以及传达对应的失败原因等情况,更加灵活地表示各种失败场景。 + * @param 泛型标识,确保返回的ServerResponse类型与调用处期望的类型一致(虽然此处数据部分为空,但保持泛型通用性)。 + * @param code 自定义的失败状态码。 + * @param msg 自定义的提示消息内容。 + * @return 返回一个表示失败的ServerResponse实例。 + */ + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + return new ServerResponse<>(code, msg); + } +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/controller/CategoryController.java b/snailmall-category-service/src/main/java/com/njupt/swg/controller/CategoryController.java index 1eb7330..d75a274 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/controller/CategoryController.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/controller/CategoryController.java @@ -7,8 +7,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - /** + * 该类CategoryController是一个Spring的RestController,用于处理与品类相关的业务接口请求。 + * 这些接口主要是供后端管理系统人员操作使用的,所以通常需要管理员权限来访问(当前代码中鉴权相关有待后续完善,如TODO注释所述)。 * @Author swg. * @Date 2019/1/2 12:57 * @CONTACT 317758022@qq.com @@ -18,55 +19,69 @@ import org.springframework.web.bind.annotation.RestController; //TODO 这里首先实现业务 关于这里重复的鉴权,后面将会移植到网关中统一去做 //TODO 先开放GET请求 @RestController +// 表明该类是一个Spring RESTful风格的控制器,其内部的方法返回值会直接序列化为JSON格式并返回给客户端,无需额外配置视图解析等。 @RequestMapping("/manage/category/") +// 定义该控制器处理请求的基础路径,所有该控制器下的接口请求路径都以此为前缀。 public class CategoryController { @Autowired + // 使用Spring的依赖注入功能,自动注入ICategoryService的实现类实例,以便在控制器方法中调用对应的业务逻辑方法。 private ICategoryService categoryService; /** - * 获取品类子节点(平级) + * 获取品类子节点(平级)的接口方法。 + * 接收一个名为categoryId的请求参数(类型为Integer,默认值为0),用于指定获取哪个品类的子节点信息。 + * 通过调用categoryService的getCategory方法来获取相应的品类子节点信息,并将其返回给客户端,返回的数据格式为ServerResponse类型, + * 该类型是项目自定义的通用响应封装类,包含了状态码、提示消息以及具体的数据内容等信息。 */ @RequestMapping("get_category.do") - public ServerResponse getCategory(@RequestParam(value = "categoryId",defaultValue = "0") Integer categoryId){ + public ServerResponse getCategory(@RequestParam(value = "categoryId", defaultValue = "0") Integer categoryId) { ServerResponse response = categoryService.getCategory(categoryId); return response; } /** - * 增加节点 + * 增加品类节点的接口方法。 + * 接收两个请求参数,一个是categoryName(类型为String)表示要增加的品类节点的名称,另一个是parentId(类型为int,默认值为0), + * 用于指定新增节点的父节点ID。通过调用categoryService的addCategory方法来执行增加品类节点的业务逻辑, + * 并将执行结果(以ServerResponse类型封装)返回给客户端,告知客户端操作是否成功以及相关信息。 */ @RequestMapping("add_category.do") - public ServerResponse addCategory(String categoryName, @RequestParam(value = "parentId",defaultValue = "0")int parentId){ - ServerResponse response = categoryService.addCategory(categoryName,parentId); + public ServerResponse addCategory(String categoryName, @RequestParam(value = "parentId", defaultValue = "0") int parentId) { + ServerResponse response = categoryService.addCategory(categoryName, parentId); return response; } /** - * 修改品类名称 + * 修改品类名称的接口方法。 + * 接收两个请求参数,categoryName(类型为String)表示要修改后的品类名称,categoryId(类型为Integer)用于指定要修改名称的品类的ID。 + * 直接调用categoryService的updateCategoryName方法来执行修改品类名称的业务逻辑,并将该方法返回的ServerResponse类型的结果直接返回给客户端, + * 这里返回的泛型可能表示返回一些与修改操作相关的提示消息等字符串内容。 */ @RequestMapping("set_category_name.do") - public ServerResponse set_category_name(String categoryName,Integer categoryId){ - return categoryService.updateCategoryName(categoryName,categoryId); + public ServerResponse set_category_name(String categoryName, Integer categoryId) { + return categoryService.updateCategoryName(categoryName, categoryId); } /** - * 递归获取自身和所有的子节点 + * 递归获取自身和所有的子节点的接口方法。 + * 接收一个名为categoryId的请求参数(类型为Integer,默认值为0),用于指定从哪个品类节点开始递归获取其自身及所有子节点信息。 + * 通过调用categoryService的selectCategoryAndDeepChildrenById方法来执行递归获取的业务逻辑,并将结果(以ServerResponse类型封装)返回给客户端, + * 方便客户端获取完整的品类树相关信息。 */ @RequestMapping("get_deep_category.do") - public ServerResponse get_deep_category(@RequestParam(value = "categoryId",defaultValue = "0") Integer categoryId){ + public ServerResponse get_deep_category(@RequestParam(value = "categoryId", defaultValue = "0") Integer categoryId) { return categoryService.selectCategoryAndDeepChildrenById(categoryId); } /** - * 这是为了给其他服务调用而新增的接口 + * 这是为了给其他服务调用而新增的接口。 + * 接收一个名为categoryId的请求参数(类型为Integer),用于指定要获取详细信息的品类ID。 + * 通过调用categoryService的getCategoryDetail方法来获取相应品类的详细信息,并将结果(以ServerResponse类型封装)返回给客户端, + * 方便其他服务使用该接口获取所需的品类详情数据。 */ @RequestMapping("get_category_detail.do") - public ServerResponse get_category_detail(Integer categoryId){ + public ServerResponse get_category_detail(Integer categoryId) { return categoryService.getCategoryDetail(categoryId); } - - - - -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/dao/CategoryMapper.java b/snailmall-category-service/src/main/java/com/njupt/swg/dao/CategoryMapper.java index be3f08a..2ffbeb0 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/dao/CategoryMapper.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/dao/CategoryMapper.java @@ -1,24 +1,79 @@ package com.njupt.swg.dao; - import com.njupt.swg.entity.Category; import org.apache.ibatis.annotations.Mapper; - import java.util.List; +/** + * 该接口CategoryMapper是MyBatis框架中用于定义与数据库交互操作的Mapper接口, + * 它定义了一系列针对Category实体相关的数据库操作方法,这些方法会与对应的MyBatis映射文件(XML配置文件)中的SQL语句进行关联, + * 实现对数据库中相关表的数据操作功能。 + * @Mapper注解用于标记该接口是一个MyBatis的Mapper接口,在Spring框架集成MyBatis时,会自动扫描并创建该接口的代理实现类, + * 方便在其他业务层代码中进行注入和调用这些数据库操作方法。 + */ @Mapper public interface CategoryMapper { + /** + * 根据主键(id)删除对应的记录的方法,接收一个Integer类型的id参数, + * 用于指定要删除记录的主键值,返回一个整数类型结果,表示受影响的行数(通常用于判断删除操作是否成功,例如返回1表示成功删除了1条记录)。 + * 该方法会关联到MyBatis映射文件中对应的标签定义的SQL语句来执行实际的数据库删除操作。 + * @param id 要删除记录的主键值(对应数据库表中的唯一标识字段) + * @return 受影响的行数 + */ int deleteByPrimaryKey(Integer id); + /** + * 插入一条完整的Category实体记录到数据库的方法,接收一个Category类型的record参数, + * 该参数包含了要插入到数据库表中的各个字段对应的值,返回一个整数类型结果,表示受影响的行数(例如返回1表示成功插入了1条记录)。 + * 此方法会对应MyBatis映射文件中定义的插入相关的SQL语句(如标签定义的语句)来执行实际的插入操作。 + * @param record 要插入的Category实体对象,包含了插入到数据库表所需的各个字段值 + * @return 受影响的行数 + */ int insert(Category record); + /** + * 有选择性地插入Category实体记录到数据库的方法,同样接收一个Category类型的record参数。 + * 与insert方法不同的是,它会根据record对象中各个属性是否为null来决定是否插入对应的列数据到数据库表中,更加灵活,避免插入不必要的null值。 + * 返回一个整数类型结果,表示受影响的行数,关联到MyBatis映射文件中对应的动态插入SQL语句(如标签定义的语句)来执行操作。 + * @param record 要插入的Category实体对象,其属性的null情况会决定实际插入的列数据 + * @return 受影响的行数 + */ int insertSelective(Category record); + /** + * 根据主键(id)查询对应的Category实体记录的方法,接收一个Integer类型的id参数,用于指定要查询记录的主键值。 + * 返回一个Category类型的结果,即查询到的对应的实体对象,如果没有查询到则返回null。 + * 该方法会关联到MyBatis映射文件中对应的查询SQL语句(如 - select + select from mmall_category where id = #{id,jdbcType=INTEGER} + + delete from mmall_category where id = #{id,jdbcType=INTEGER} + + - insert into mmall_category (id, parent_id, name, - status, sort_order, create_time, - update_time) - values (#{id,jdbcType=INTEGER}, #{parentId,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, - #{status,jdbcType=BIT}, #{sortOrder,jdbcType=INTEGER}, now(), - now()) + insert into mmall_category (id, parent_id, name, + status, sort_order, create_time, + update_time) + values (#{id,jdbcType=INTEGER}, #{parentId,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, + #{status,jdbcType=BIT}, #{sortOrder,jdbcType=INTEGER}, now(), + now()) + + insert into mmall_category - + id, - + parent_id, - + name, - + status, - + sort_order, - + create_time, - + update_time, - + #{id,jdbcType=INTEGER}, - + #{parentId,jdbcType=INTEGER}, - + #{name,jdbcType=VARCHAR}, - + #{status,jdbcType=BIT}, - + #{sortOrder,jdbcType=INTEGER}, - + now(), - + now(), + + update mmall_category - + parent_id = #{parentId,jdbcType=INTEGER}, - + name = #{name,jdbcType=VARCHAR}, - + status = #{status,jdbcType=BIT}, - + sort_order = #{sortOrder,jdbcType=INTEGER}, - + create_time = #{createTime,jdbcType=TIMESTAMP}, - + update_time = now(), where id = #{id,jdbcType=INTEGER} + + update mmall_category set parent_id = #{parentId,jdbcType=INTEGER}, - name = #{name,jdbcType=VARCHAR}, - status = #{status,jdbcType=BIT}, - sort_order = #{sortOrder,jdbcType=INTEGER}, - create_time = #{createTime,jdbcType=TIMESTAMP}, - update_time = now() + name = #{name,jdbcType=VARCHAR}, + status = #{status,jdbcType=BIT}, + sort_order = #{sortOrder,jdbcType=INTEGER}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + update_time = now() where id = #{id,jdbcType=INTEGER} + diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/entity/Category.java b/snailmall-category-service/src/main/java/com/njupt/swg/entity/Category.java index 7138ccc..d26ccc9 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/entity/Category.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/entity/Category.java @@ -3,24 +3,32 @@ package com.njupt.swg.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; - import java.util.Date; +/** + * 该类Category是一个实体类,用于映射数据库中对应的表结构,代表了品类相关的业务对象。 + * 它通过使用Lombok提供的注解来简化代码编写,自动生成了一些常用的方法,如构造函数、Getter/Setter方法等。 + * 此类中的各个属性对应数据库表中的相应字段,用于在Java程序与数据库之间传递和存储数据。 + */ @Data +// 使用Lombok的@Data注解,会自动为类中的所有非静态、非final属性生成Getter、Setter方法,同时还会生成toString、equals、hashCode等方法,方便对象的操作和比较等。 @AllArgsConstructor +// 使用Lombok的@AllArgsConstructor注解,会自动生成一个包含所有属性的构造函数,方便在创建对象时一次性传入所有属性值进行初始化。 @NoArgsConstructor +// 使用Lombok的@NoArgsConstructor注解,会自动生成一个无参构造函数,在一些框架(如MyBatis在实例化对象等场景)可能需要使用无参构造函数的情况下很有用。 public class Category { + // 对应数据库表中品类记录的唯一标识字段,用于区分不同的品类,类型为Integer。 private Integer id; - + // 表示品类的父节点ID,用于构建品类之间的层级关系,类型为Integer,通过该字段可以知道某个品类所属的上级品类,若为顶级品类则可能为特定的默认值(如0等表示无父节点)。 private Integer parentId; - + // 品类的名称,类型为String,用于直观地标识和区分不同的品类,例如“电子产品”“服装”等具体的品类名称。 private String name; - + // 表示品类的状态,类型为Boolean,可用于标记品类是否有效、是否启用等业务状态,例如true表示该品类正常可用,false表示已停用等情况。 private Boolean status; - + // 用于指定品类在展示、排序等场景下的顺序,类型为Integer,数值越小可能表示在排序中越靠前等,方便对多个品类进行顺序排列。 private Integer sortOrder; - + // 记录品类创建的时间,类型为Date,用于记录该品类首次被添加到系统中的时间点,方便后续进行时间相关的查询、统计等操作。 private Date createTime; - + // 记录品类最后一次更新的时间,类型为Date,每次对品类的相关信息(如名称、状态等)进行修改操作后,会更新该时间字段,便于追踪品类信息的变更情况。 private Date updateTime; } \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/entity/User.java b/snailmall-category-service/src/main/java/com/njupt/swg/entity/User.java index 4354b81..9545e70 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/entity/User.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/entity/User.java @@ -4,40 +4,45 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; - import java.io.Serializable; import java.util.Date; /** + * 该类User是用户实体类,用于在Java程序中表示用户相关的业务对象,其各个属性对应了用户在系统中的各项信息。 + * 通过使用Lombok提供的注解来简化代码编写,自动生成了诸如构造函数、Getter/Setter方法、toString方法等常用的代码结构。 + * 此类实现了Serializable接口,以便能够在网络传输、对象持久化等场景下进行序列化和反序列化操作,保证对象状态的正确保存和恢复。 * @Author swg. * @Date 2018/12/31 21:01 * @CONTACT 317758022@qq.com * @DESC 用户实体类 */ @Data +// 使用Lombok的@Data注解,会自动为类中的所有非静态、非final属性生成Getter、Setter方法,同时还会生成toString、equals、hashCode等方法,方便对用户对象进行操作、比较以及以字符串形式展示等操作。 @NoArgsConstructor +// 使用Lombok的@NoArgsConstructor注解,会自动生成一个无参构造函数,在一些框架初始化对象或者通过反射创建对象等场景中可能会需要使用无参构造函数。 @AllArgsConstructor +// 使用Lombok的@AllArgsConstructor注解,会自动生成一个包含所有属性的构造函数,方便在创建用户对象时一次性传入所有属性值来进行初始化操作。 @ToString +// 使用Lombok的@ToString注解,会自动重写toString方法,使得在调用该方法时能够以一种比较友好的格式输出用户对象的各个属性值,便于调试和查看对象内容。 public class User implements Serializable { + // 用户在系统中的唯一标识,通常由数据库自动生成或者按照一定规则分配,类型为Integer,用于区分不同的用户个体。 private Integer id; - + // 用户登录系统时使用的用户名,类型为String,要求具有唯一性(一般情况下),方便用户进行身份识别和登录操作。 private String username; - + // 用户登录系统时使用的密码,类型为String,通常会经过加密处理后存储,以保障用户账户的安全性。 private String password; - + // 用户的电子邮箱地址,类型为String,可用于接收系统发送的通知、验证邮件等,也是一种常用的用户联系方式和身份验证方式之一。 private String email; - + // 用户的手机号码,类型为String,同样可用于接收短信通知、作为登录验证的一种方式等,是重要的用户联系方式。 private String phone; - + // 用户设置的密保问题,类型为String,用于在用户忘记密码等情况下进行身份验证,辅助找回密码等操作。 private String question; - + // 用户针对密保问题设置的答案,类型为String,必须与密保问题相对应,用于验证用户身份的合法性。 private String answer; - - //角色0-管理员,1-普通用户 + // 用于标识用户在系统中的角色,类型为Integer,0表示管理员角色,拥有较高的系统操作权限;1表示普通用户角色,权限相对受限,只能进行一些常规的业务操作。 private Integer role; - + // 记录用户账号创建的时间,类型为Date,便于后续查询用户注册时间、统计不同时间段的注册人数等与时间相关的业务操作。 private Date createTime; - + // 记录用户账号信息最后一次更新的时间,类型为Date,比如用户修改密码、更新联系方式等操作后,会更新该时间字段,方便追踪用户信息的变更情况。 private Date updateTime; - } \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/service/CategoryServiceImpl.java b/snailmall-category-service/src/main/java/com/njupt/swg/service/CategoryServiceImpl.java index 8549a28..977c45b 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/service/CategoryServiceImpl.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/service/CategoryServiceImpl.java @@ -16,115 +16,165 @@ import java.util.List; import java.util.Set; /** + * 该类CategoryServiceImpl是实现了ICategoryService接口的服务层实现类,主要用于处理与品类相关的业务逻辑。 + * 它依赖于CategoryMapper来与数据库进行交互,获取或操作品类相关的数据,并通过抛出SnailmallException异常来处理业务中的错误情况, + * 使用ServerResponse来封装业务操作结果返回给上层调用者,同时利用日志记录相关信息辅助排查问题等。 * @Author swg. * @Date 2019/1/2 12:54 * @CONTACT 317758022@qq.com * @DESC */ @Service +// 该注解表明这个类是Spring框架中的一个服务层组件,Spring会自动扫描并管理这个类,使其可以被注入到其他需要使用的地方。 @Slf4j -public class CategoryServiceImpl implements ICategoryService{ +// 使用lombok的@Slf4j注解,自动生成一个名为log的日志记录对象,方便在类中记录日志信息,便于后续调试和查看业务执行情况。 +public class CategoryServiceImpl implements ICategoryService { @Autowired + // 使用Spring的依赖注入功能,自动注入CategoryMapper的实例,以便调用其定义的数据库操作方法,实现与数据库的交互。 private CategoryMapper categoryMapper; - + /** + * 获取品类子节点(平级)的业务方法,根据传入的品类ID获取其下一级的所有子品类信息。 + * + * @param categoryId 要获取子节点的品类的ID,若为null则表示参数不合法。 + * @return ServerResponse类型的结果,若获取成功则返回包含子品类列表的成功响应,若没有子节点则返回相应提示信息的成功响应,若参数不合法则抛出异常。 + */ @Override public ServerResponse getCategory(Integer categoryId) { - //1.校验参数 - if(categoryId == null){ + // 1.校验参数 + if (categoryId == null) { + // 如果品类ID为null,说明参数不符合要求,抛出SnailmallException异常,提示“未找到该品类”,由全局异常处理机制进行处理。 throw new SnailmallException("未找到该品类"); } - //2.根据父亲id获取这个父亲下一级所有子ID + // 2.根据父亲id获取这个父亲下一级所有子ID List categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); - if(CollectionUtils.isEmpty(categoryList)){ + if (CollectionUtils.isEmpty(categoryList)) { + // 如果获取到的子品类列表为空,说明该节点下没有任何子节点,记录相应的日志信息供后续查看。 log.info("该节点下没有任何子节点"); } return ServerResponse.createBySuccess(categoryList); } + /** + * 增加品类节点的业务方法,根据传入的品类名称和父节点ID创建并添加一个新的品类到数据库中。 + * + * @param categoryName 要添加的品类的名称,不能为空字符串。 + * @param parentId 新添加品类的父节点的ID,默认值为0等情况可表示无父节点(根据具体业务规则)。 + * @return ServerResponse类型的结果,若添加成功则返回包含成功提示消息的成功响应,若添加失败则返回包含失败提示消息的响应。 + */ @Override public ServerResponse addCategory(String categoryName, int parentId) { - //1.校验参数 - if(StringUtils.isBlank(categoryName)){ + // 1.校验参数 + if (StringUtils.isBlank(categoryName)) { + // 如果品类名称为空字符串,说明参数不符合要求,抛出SnailmallException异常,提示“品类名字不能为空”,由全局异常处理机制进行处理。 throw new SnailmallException("品类名字不能为空"); } - //2.创建类目 + // 2.创建类目 Category category = new Category(); category.setName(categoryName); category.setParentId(parentId); category.setStatus(true); int resultCount = categoryMapper.insert(category); - if(resultCount > 0){ + if (resultCount > 0) { + // 如果数据库插入操作影响的行数大于0,说明插入成功,返回包含“添加品类成功”提示消息的成功响应。 return ServerResponse.createBySuccessMessage("添加品类成功"); } return ServerResponse.createByErrorMessage("添加品类失败"); } + /** + * 修改品类名称的业务方法,根据传入的新的品类名称和品类ID来更新数据库中对应品类的名称信息。 + * + * @param categoryName 要修改成的新的品类名称,不能为空字符串。 + * @param categoryId 要修改名称的品类的ID,用于定位数据库中的对应记录。 + * @return ServerResponse类型的结果,若更新成功则返回包含成功提示消息的成功响应,若更新失败则返回包含失败提示消息的响应, + * 这里返回的泛型可能表示返回一些与修改操作相关的提示消息等字符串内容。 + */ @Override public ServerResponse updateCategoryName(String categoryName, Integer categoryId) { - //1.校验参数 - if(StringUtils.isBlank(categoryName)){ + // 1.校验参数 + if (StringUtils.isBlank(categoryName)) { + // 如果品类名称为空字符串,说明参数不符合要求,抛出SnailmallException异常,提示“品类名字不能为空”,由全局异常处理机制进行处理。 throw new SnailmallException("品类名字不能为空"); } - //2.根据id获取品类 + // 2.根据id获取品类 Category tmpCat = categoryMapper.selectByPrimaryKey(categoryId); - if(tmpCat == null){ + if (tmpCat == null) { + // 如果根据ID没有查询到对应的品类,说明品类不存在,抛出SnailmallException异常,提示“品类不存在”,由全局异常处理机制进行处理。 throw new SnailmallException("品类不存在"); } - //3.更新品类名称 + // 3.更新品类名称 Category category = new Category(); category.setId(categoryId); category.setName(categoryName); int resultCount = categoryMapper.updateByPrimaryKeySelective(category); - if(resultCount > 0){ + if (resultCount > 0) { + // 如果数据库更新操作影响的行数大于0,说明更新成功,返回包含“更新品类名称成功”提示消息的成功响应。 return ServerResponse.createBySuccessMessage("更新品类名称成功"); } return ServerResponse.createByErrorMessage("更新品类名称失败"); } + /** + * 递归获取自身和所有的子节点的业务方法,根据传入的品类ID,递归地获取该品类及其所有子品类的ID信息,并返回。 + * + * @param categoryId 要开始递归获取子节点的品类的ID,可以为null(根据业务逻辑进行相应处理)。 + * @return ServerResponse类型的结果,若获取成功则返回包含品类ID列表的成功响应,其中包含了自身及所有子品类的ID信息。 + */ @Override public ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId) { - //1、创建一个空Set用来存放不重复的品类对象--去重 + // 1、创建一个空Set用来存放不重复的品类对象--去重 Set categorySet = Sets.newHashSet(); - //2、递归获取所有的子节点(儿子、孙子、等等),包括自己也添加进去 - findChildCategory(categorySet,categoryId); - //3、将递归获取到的品类id取出来放进list中 + // 2、递归获取所有的子节点(儿子、孙子、等等),包括自己也添加进去 + findChildCategory(categorySet, categoryId); + // 3、将递归获取到的品类id取出来放进list中 List categoryIdList = new ArrayList<>(); - if(categoryId != null){ - for(Category category:categorySet){ + if (categoryId!= null) { + for (Category category : categorySet) { categoryIdList.add(category.getId()); } } return ServerResponse.createBySuccess(categoryIdList); } - private Set findChildCategory(Set categorySet,Integer categoryId){ - //4、如果自己不为空的话,首先把自己添加进去;如果自己为空,这个递归分支就结束,所以也是一个停止条件 + /** + * 递归查找子品类的私有方法,用于在selectCategoryAndDeepChildrenById方法中递归地查找给定品类及其所有子品类,并添加到传入的Set集合中进行去重存储。 + * + * @param categorySet 用于存储品类对象的Set集合,通过引用传递,在递归过程中不断添加品类对象进去,实现去重和收集所有相关品类的功能。 + * @param categoryId 当前要查找子品类的品类的ID,若为null则该递归分支结束(作为停止条件之一)。 + * @return 返回最终收集了自身及所有子品类对象的Set集合,方便外层方法进一步处理。 + */ + private Set findChildCategory(Set categorySet, Integer categoryId) { + // 4、如果自己不为空的话,首先把自己添加进去;如果自己为空,这个递归分支就结束,所以也是一个停止条件 Category category = categoryMapper.selectByPrimaryKey(categoryId); - if(category != null){ + if (category!= null) { categorySet.add(category); } - //5、根据父亲id获取下一级所有品类(即先获取儿子们) + // 5、根据父亲id获取下一级所有品类(即先获取儿子们) List categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); - //6、根据每一个儿子再获取儿子的儿子们,递归下去 - for(Category categoryItem:categoryList){ - findChildCategory(categorySet,categoryItem.getId()); + // 6、根据每一个儿子再获取儿子的儿子们,递归下去 + for (Category categoryItem : categoryList) { + findChildCategory(categorySet, categoryItem.getId()); } return categorySet; } - + /** + * 获取品类详细信息的业务方法,根据传入的品类ID获取对应品类的详细信息。 + * + * @param categoryId 要获取详细信息的品类的ID,若为null则表示参数不合法。 + * @return ServerResponse类型的结果,若获取成功则返回包含品类对象的成功响应,若品类不存在则返回包含相应提示消息的失败响应,若参数不合法则返回包含相应提示消息的失败响应。 + */ @Override public ServerResponse getCategoryDetail(Integer categoryId) { - if(categoryId == null){ + if (categoryId == null) { return ServerResponse.createByErrorMessage("参数不能为空"); } Category category = categoryMapper.selectByPrimaryKey(categoryId); - if(category == null){ + if (category == null) { return ServerResponse.createByErrorMessage("品类不存在"); } return ServerResponse.createBySuccess(category); } - -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/service/ICategoryService.java b/snailmall-category-service/src/main/java/com/njupt/swg/service/ICategoryService.java index 8d0d964..8f9b1d9 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/service/ICategoryService.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/service/ICategoryService.java @@ -4,6 +4,9 @@ import com.njupt.swg.common.resp.ServerResponse; import com.njupt.swg.entity.Category; /** + * 该接口ICategoryService定义了与品类相关的一系列业务操作方法,作为服务层的接口,它规定了品类业务逻辑的抽象方法签名, + * 具体的业务实现将由实现该接口的类(如CategoryServiceImpl)来完成。这样的设计符合面向接口编程的原则,便于代码的解耦和替换实现类等操作, + * 不同的实现类可以根据具体需求(例如不同的数据库存储方式、业务规则变化等)来实现这些方法,而对于调用者来说只需要依赖这个接口即可。 * @Author swg. * @Date 2019/1/2 12:54 * @CONTACT 317758022@qq.com @@ -11,19 +14,50 @@ import com.njupt.swg.entity.Category; */ public interface ICategoryService { - /** 根据类目id获取其下面所有的一级子类目 **/ + /** + * 根据类目id获取其下面所有的一级子类目方法声明。 + * 通过传入的品类ID,查询并返回该品类下的所有一级子品类信息,返回结果使用ServerResponse进行封装,方便统一处理响应状态、提示消息以及实际数据内容。 + * @param categoryId 用于指定要获取子品类的那个品类的唯一标识,即类目ID。 + * @return 返回一个ServerResponse对象,若查询成功,其内部包含查询到的一级子品类相关数据(例如子品类列表等),若出现异常等情况则包含相应的错误提示信息等内容。 + */ ServerResponse getCategory(Integer categoryId); - /** 新建一个商品类目 **/ + /** + * 新建一个商品类目方法声明。 + * 根据传入的品类名称和父品类的ID,创建一个新的商品类目信息并保存到相关的数据存储中(通常是数据库),返回结果使用ServerResponse进行封装, + * 以告知调用者操作是否成功以及相关的提示消息等情况。 + * @param categoryName 要创建的商品类目的名称,用于标识该新品类的名字信息。 + * @param parentId 要创建的商品类目的父品类的ID,用于确定新品类在品类层级结构中的所属位置,若为特定值(如0等)可能表示顶级品类无父品类等情况。 + * @return 返回一个ServerResponse对象,若创建成功,其内部包含表示成功的提示消息等内容,若创建失败则包含相应的失败提示消息等情况。 + */ ServerResponse addCategory(String categoryName, int parentId); - /** 更新品类名称 **/ + /** + * 更新品类名称方法声明。 + * 通过传入的新的品类名称和要更新的品类的ID,更新对应品类在数据存储(如数据库)中的名称信息,返回结果使用ServerResponse进行封装, + * 这里的泛型可能表示返回一些与更新操作相关的额外字符串信息(比如更新成功的详细说明、失败原因等,具体由实现类决定),同时也包含操作是否成功的状态等信息。 + * @param categoryName 要更新成的新的品类名称,是更新操作的关键数据,决定了品类最终呈现的名称内容。 + * @param categoryId 要进行名称更新的品类的唯一标识,用于定位到具体要修改的品类记录。 + * @return 返回一个ServerResponse对象,若更新成功,其内部包含表示成功的提示消息等内容,若更新失败则包含相应的失败提示消息等情况,同时可能包含相关的额外字符串信息。 + */ ServerResponse updateCategoryName(String categoryName, Integer categoryId); - /** 递归查询出所有品类 **/ + /** + * 递归查询出所有品类方法声明。 + * 根据传入的品类ID,递归地查询出该品类及其所有层级的子品类信息,返回结果使用ServerResponse进行封装,方便调用者获取和处理查询到的品类相关数据, + * 以及知晓操作是否成功等整体情况。 + * @param categoryId 用于指定从哪个品类开始进行递归查询,即起始品类的唯一标识,若为特定值(如null等,具体由实现类约定)可能有不同的处理逻辑等情况。 + * @return 返回一个ServerResponse对象,若查询成功,其内部包含查询到的品类相关数据(例如品类ID列表等,具体由实现类决定返回的数据内容),若出现异常等情况则包含相应的错误提示信息等内容。 + */ ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId); - /** 被其他服务调用的接口 **/ + /** + * 被其他服务调用的接口方法声明。 + * 通过传入的品类ID,获取该品类的详细信息,返回结果使用ServerResponse进行封装,便于其他服务获取品类详细情况以及判断操作是否成功等, + * 该接口主要用于对外提供品类详细信息的获取功能,供其他相关服务使用。 + * @param categoryId 用于指定要获取详细信息的品类的唯一标识,通过该ID定位到具体的品类记录获取详细内容。 + * @return 返回一个ServerResponse对象,若获取成功,其内部包含查询到的品类详细数据(例如品类对象等,具体由实现类决定返回的数据内容),若出现异常等情况则包含相应的错误提示信息等内容。 + */ ServerResponse getCategoryDetail(Integer categoryId); -} +} \ No newline at end of file diff --git a/snailmall-user-service/pom.xml b/snailmall-user-service/pom.xml index 24a242b..49b6b36 100644 --- a/snailmall-user-service/pom.xml +++ b/snailmall-user-service/pom.xml @@ -104,6 +104,11 @@ io.springfox springfox-swagger-ui + + backport-util-concurrent + backport-util-concurrent + 3.1 + diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java b/snailmall-user-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java index 66c6499..81aaadd 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java @@ -8,73 +8,96 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** + * 该类CommonCacheUtil是一个工具类,用于与Redis缓存进行交互操作,提供了如缓存数据、获取缓存数据、设置带过期时间的缓存以及删除缓存等常用功能。 + * 它依赖于JedisPoolWrapper来获取JedisPool对象,进而操作Redis缓存,并且在操作过程中如果出现异常会抛出SnailmallException异常,同时记录相关的日志信息用于排查问题。 + * 此类被标记为Spring的@Component组件,方便在Spring容器中进行管理和注入使用。 * @Author swg. * @Date 2019/1/1 15:03 * @CONTACT 317758022@qq.com * @DESC */ @Component +// 该注解表明这个类是Spring框架中的一个组件,Spring会自动扫描并将其纳入到容器管理中,使其可以被其他需要使用的类进行依赖注入。 @Slf4j +// 使用lombok的@Slf4j注解,自动生成一个名为log的日志记录对象,方便在类中记录日志信息,便于后续调试和查看与Redis交互过程中出现的问题情况。 public class CommonCacheUtil { @Autowired + // 使用Spring的依赖注入功能,自动注入JedisPoolWrapper的实例,通过它来获取JedisPool,从而能够获取Jedis客户端连接到Redis服务器进行操作。 private JedisPoolWrapper jedisPoolWrapper; - /** - * 缓存永久key + * 缓存永久key的方法,用于将指定的键值对存储到Redis缓存中,且该缓存数据没有设置过期时间(永久有效,除非手动删除)。 + * + * @param key 要存储到Redis中的键,用于唯一标识缓存数据,后续可以通过该键来获取对应的缓存值。 + * @param value 要存储到Redis中的值,对应键所关联的数据内容,可以是各种序列化后的字符串形式的数据(如JSON字符串等)。 */ public void cache(String key, String value) { try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { - try (Jedis Jedis = pool.getResource()) { - Jedis.select(0); - Jedis.set(key, value); + if (pool!= null) { + try (Jedis jedis = pool.getResource()) { + jedis.select(0); + // 使用Jedis客户端将指定的键值对存储到Redis中,默认存储到Redis的第0个数据库(Redis可以有多个数据库,通过select方法选择)。 + jedis.set(key, value); } } } catch (Exception e) { log.error("redis存值失败", e); + // 如果在存储过程中出现异常,记录错误日志,并抛出SnailmallException异常,提示“redis报错”,由全局异常处理机制进行处理。 throw new SnailmallException("redis报错"); } } /** - * 获取缓存key + * 获取缓存key对应的缓存值的方法,用于从Redis缓存中根据指定的键获取对应的值。 + * + * @param key 要从Redis中获取值的键,通过该键来查找之前存储在Redis中的对应缓存数据。 + * @return 返回从Redis中获取到的对应键的值,如果键不存在则返回null,若获取过程中出现异常会抛出相应异常。 */ public String getCacheValue(String key) { String value = null; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { - try (Jedis Jedis = pool.getResource()) { - Jedis.select(0); - value = Jedis.get(key); + if (pool!= null) { + try (Jedis jedis = pool.getResource()) { + jedis.select(0); + // 使用Jedis客户端根据指定的键从Redis中获取对应的值,默认从Redis的第0个数据库中查找。 + value = jedis.get(key); } } } catch (Exception e) { log.error("redis获取指失败", e); + // 如果在获取过程中出现异常,记录错误日志,并抛出SnailmallException异常,提示“redis报错”,由全局异常处理机制进行处理。 throw new SnailmallException("redis报错"); } return value; } /** - * 过期key + * 过期key的方法,用于将指定的键值对存储到Redis缓存中,并同时设置该缓存数据的过期时间,且只有在键不存在时才设置成功(原子操作)。 + * + * @param key 要存储到Redis中的键,用于唯一标识缓存数据,后续可以通过该键来获取对应的缓存值以及判断其是否过期等情况。 + * @param value 要存储到Redis中的值,对应键所关联的数据内容,可以是各种序列化后的字符串形式的数据(如JSON字符串等)。 + * @param expire 要设置的缓存数据的过期时间,单位为秒,表示从存储该缓存数据开始,经过指定的秒数后,该缓存数据将自动从Redis中删除。 + * @return 返回一个长整型结果,表示设置操作是否成功,若返回1表示设置成功(键不存在,成功设置了键值对并设置了过期时间),若返回0表示设置失败(键已存在)。 */ public long cacheNxExpire(String key, String value, int expire) { long result = 0; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); + // 先使用Jedis的setnx方法尝试设置键值对,只有在键不存在时才会设置成功,返回1;若键已存在则设置失败,返回0。这是一个原子操作。 result = jedis.setnx(key, value); + // 如果设置键值对成功(即setnx返回1),再使用expire方法设置该键对应的缓存数据的过期时间,单位为秒。 jedis.expire(key, expire); } } } catch (Exception e) { log.error("redis塞值和设置缓存时间失败", e); + // 如果在设置过程中出现异常,记录错误日志,并抛出SnailmallException异常,提示“redis报错”,由全局异常处理机制进行处理。 throw new SnailmallException("redis报错"); } @@ -82,23 +105,24 @@ public class CommonCacheUtil { } /** - * 删除缓存key + * 删除缓存key的方法,用于从Redis缓存中删除指定的键及其对应的数据。 + * + * @param key 要从Redis中删除的键,通过该键来定位并删除对应的缓存数据。 */ public void delKey(String key) { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); try { + // 使用Jedis客户端根据指定的键从Redis中删除对应的缓存数据,默认从Redis的第0个数据库中删除。 jedis.del(key); } catch (Exception e) { log.error("从redis中删除失败", e); + // 如果在删除过程中出现异常,记录错误日志,并抛出SnailmallException异常,提示“redis报错”,由全局异常处理机制进行处理。 throw new SnailmallException("redis报错"); } } } } - - - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java b/snailmall-user-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java index addfe24..67f4d82 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java @@ -5,38 +5,57 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; - import javax.annotation.PostConstruct; /** + * 该类JedisPoolWrapper是对JedisPool的一个包装类,主要用于初始化和管理JedisPool对象,以便在项目中方便地获取JedisPool来连接Redis服务器。 + * 它通过依赖注入Parameters对象获取相关的Redis配置参数,在类初始化时(通过@PostConstruct注解标记的方法)构建JedisPool,并且提供了获取JedisPool的方法供其他类使用。 + * 虽然当前代码只实现了单个Redis的连接池配置,但注释中提到课程中有涉及Redis客户端集群以及一致性哈希算法相关内容需要掌握,意味着后续可能有更复杂的Redis使用场景拓展。 * @Author swg. * @Date 2019/1/1 15:00 * @CONTACT 317758022@qq.com * @DESC 只做了单个redis,但是课程中实现的redis客户端集群,要掌握一致性hash算法 */ @Component +// 该注解表明这个类是Spring框架中的一个组件,Spring会自动扫描并将其纳入到容器管理中,使其可以被其他需要使用的类进行依赖注入。 @Slf4j +// 使用lombok的@Slf4j注解,自动生成一个名为log的日志记录对象,方便在类中记录日志信息,便于后续调试和查看与Redis连接池初始化等相关的情况。 public class JedisPoolWrapper { @Autowired + // 使用Spring的依赖注入功能,自动注入Parameters对象,该对象应该包含了如Redis的最大连接数、最大空闲连接数、最大等待时间等配置参数相关信息,用于构建JedisPool。 private Parameters parameters; private JedisPool jedisPool = null; + /** + * @PostConstruct注解标记的方法会在类实例化之后、依赖注入完成之后自动执行,用于进行一些初始化操作。 + * 在这里该方法主要用于初始化JedisPool对象,通过获取Parameters中的配置参数来构建JedisPool,使得后续可以通过该连接池获取Jedis客户端连接到Redis服务器。 + */ @PostConstruct - public void init(){ + public void init() { try { JedisPoolConfig config = new JedisPoolConfig(); + // 设置JedisPool中最大的连接总数,从Parameters对象中获取对应的配置参数,用于控制同时可以从连接池获取的Jedis客户端的最大数量,避免过多连接导致资源耗尽等问题。 config.setMaxTotal(parameters.getRedisMaxTotal()); + // 设置JedisPool中最大的空闲连接数,从Parameters对象中获取对应的配置参数,空闲连接是指在连接池中暂时未被使用的连接,合理设置可提高连接复用效率。 config.setMaxIdle(parameters.getRedisMaxIdle()); + // 设置获取连接时的最大等待时间(单位为毫秒),从Parameters对象中获取对应的配置参数,当连接池中的连接都被占用时,新的获取连接请求会等待,超过该时间则抛出异常,避免长时间阻塞。 config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis()); - jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx"); + // 创建JedisPool对象,传入配置信息、Redis服务器的主机地址、端口号以及连接超时时间(这里设置为2000毫秒)和密码(示例中密码为"xxx",实际中应替换为真实密码)等参数, + // 这样就构建好了一个可以连接到指定Redis服务器的连接池对象,后续可以通过它获取Jedis客户端进行Redis操作。 + jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx"); log.info("【初始化redis连接池成功】"); - }catch (Exception e){ - log.error("【初始化redis连接池失败】",e); + } catch (Exception e) { + log.error("【初始化redis连接池失败】", e); } } + /** + * 该方法用于对外提供获取JedisPool对象的功能,使得其他类可以通过该方法获取到已经初始化好的JedisPool, + * 进而能够从连接池中获取Jedis客户端来进行与Redis服务器的交互操作,比如执行缓存数据的读写、删除等操作。 + * @return 返回已经初始化好的JedisPool对象,如果初始化失败(即init方法执行出现异常导致jedisPool仍为null)则返回null。 + */ public JedisPool getJedisPool() { return jedisPool; } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/cache/Parameters.java b/snailmall-user-service/src/main/java/com/njupt/swg/cache/Parameters.java index 9121848..9d35806 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/cache/Parameters.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/cache/Parameters.java @@ -5,29 +5,43 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** + * 该类Parameters是一个用于存储配置参数的组件类,它通过使用Spring的@Value注解从配置文件(如application.properties或application.yml等)中读取相应的配置值, + * 并将这些值注入到对应的属性中,方便在整个项目中使用这些配置参数。例如,它获取了Redis相关的配置参数(如主机地址、端口号、连接池相关参数等)以及Zookeeper相关的配置参数(主机地址等), + * 同时使用了Lombok的@Data注解来自动生成Getter、Setter等方法,简化代码编写,便于对这些配置参数进行访问和操作。 * @Author swg. * @Date 2019/1/1 14:27 * @CONTACT 317758022@qq.com * @DESC */ @Component +// 该注解表明这个类是Spring框架中的一个组件,Spring会自动扫描并将其纳入到容器管理中,使其可以被其他需要使用的类进行依赖注入,方便获取配置参数。 @Data +// 使用Lombok的@Data注解,会自动为类中的所有非静态、非final属性生成Getter、Setter方法,同时还会生成toString、equals、hashCode等方法,方便对配置参数对象进行操作和使用。 public class Parameters { /*****redis config start*******/ + // 使用Spring的@Value注解,从配置文件中读取名为"redis.host"的配置项的值,并将其注入到redisHost属性中,该属性用于存储Redis服务器的主机地址,方便后续连接Redis时使用。 @Value("${redis.host}") private String redisHost; + // 使用Spring的@Value注解,从配置文件中读取名为"redis.port"的配置项的值,并将其注入到redisPort属性中,该属性用于存储Redis服务器的端口号,以便在创建连接时指定正确的端口进行通信。 @Value("${redis.port}") private int redisPort; + // 此处可能存在属性名的配置错误(通常max-idle应该对应MaxIdle,max-total对应MaxTotal),但按照代码逻辑,该属性用于从配置文件中读取名为"redis.max-idle"的配置项的值, + // 并注入到redisMaxTotal属性中,本意可能是存储Redis连接池的最大空闲连接数相关参数(需确认配置项与属性的正确对应关系)。 @Value("${redis.max-idle}") private int redisMaxTotal; + // 此处同样可能存在属性名的配置错误(通常max-total应该对应MaxTotal,max-idle对应MaxIdle),按照代码逻辑,该属性用于从配置文件中读取名为"redis.max-total"的配置项的值, + // 并注入到redisMaxIdle属性中,本意可能是存储Redis连接池的最大连接总数相关参数(需确认配置项与属性的正确对应关系)。 @Value("${redis.max-total}") private int redisMaxIdle; + // 使用Spring的@Value注解,从配置文件中读取名为"redis.max-wait-millis"的配置项的值,并将其注入到redisMaxWaitMillis属性中,该属性用于存储获取Redis连接时的最大等待时间(单位为毫秒), + // 控制当连接池无可用连接时的等待时长,避免长时间阻塞。 @Value("${redis.max-wait-millis}") private int redisMaxWaitMillis; /*****redis config end*******/ /*****curator config start*******/ + // 使用Spring的@Value注解,从配置文件中读取名为"zk.host"的配置项的值,并将其注入到zkHost属性中,该属性用于存储Zookeeper服务器的主机地址,方便后续与Zookeeper进行交互时使用。 @Value("${zk.host}") private String zkHost; /*****curator config end*******/ -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/constants/Constants.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/constants/Constants.java index a510c62..7a38c7a 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/constants/Constants.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/constants/Constants.java @@ -1,46 +1,89 @@ package com.njupt.swg.common.constants; /** + * 该类Constants用于定义项目中常用的各种常量,这些常量涵盖了不同业务场景下的固定值,例如自定义的状态码、与Redis缓存相关的用户信息前缀、用户登录缓存过期时间、 + * 用户注册重复判断的参数类型、用户角色对应的数值以及用户注册分布式锁路径等,通过将这些常量集中定义在此类中,方便在整个项目中统一使用,提高代码的可维护性和可读性。 * @Author swg. * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com * @DESC */ public class Constants { - /**自定义状态码 start**/ + /** + * 自定义状态码相关的常量定义开始标识,以下几个常量用于定义不同含义的自定义状态码,方便在接口响应等场景中统一使用,用于表示请求处理的不同结果状态。 + */ + /** + * 表示请求成功的状态码,对应HTTP状态码中的200,意味着客户端发起的请求被服务器成功处理,操作顺利完成,常用于返回正确的业务响应时使用该状态码。 + */ public static final int RESP_STATUS_OK = 200; + /** + * 表示未授权的状态码,对应HTTP状态码中的401,说明客户端发起的请求没有经过授权,通常在需要用户登录或者具备特定权限才能访问的接口,而用户未满足这些条件时返回该状态码,提示客户端需要进行身份验证等操作来获取权限。 + */ public static final int RESP_STATUS_NOAUTH = 401; + /** + * 表示服务器内部错误的状态码,对应HTTP状态码中的500,意味着服务器在处理请求的过程中发生了内部错误,无法正确完成请求的处理,一般用于捕获到未预期的异常等情况时返回给客户端,告知客户端服务器出现问题,需要稍后再试等。 + */ public static final int RESP_STATUS_INTERNAL_ERROR = 500; + /** + * 表示请求参数错误的状态码,对应HTTP状态码中的400,用于客户端发送的请求参数不符合要求的场景,例如参数格式错误、缺少必要参数等情况,服务器接收到这类不符合要求的请求时,返回该状态码提示客户端检查并修正请求参数。 + */ public static final int RESP_STATUS_BADREQUEST = 400; - /**自定义状态码 end**/ + /** + * 自定义状态码相关的常量定义结束标识 + */ - /***redis user相关的key以这个打头**/ + /** + * 定义了一个字符串常量,用于表示在Redis中存储用户相关信息时的键(key)的前缀,后续存储用户相关数据(如用户登录的token等)在Redis中的键都会以这个前缀开头,方便进行统一管理和区分不同类型的数据。 + */ public static final String TOKEN_PREFIX = "user_"; /** - * 用户登陆redis的过期时间 + * 定义了一个内部接口RedisCacheExtime,用于封装与Redis缓存中用户登录相关的过期时间常量,通过接口的形式可以将相关的缓存时间配置集中管理,并且可以在代码中以一种语义化的方式来引用。 */ - public interface RedisCacheExtime{ - int REDIS_SESSION_EXTIME = 60 * 60 * 10;//30分钟 + public interface RedisCacheExtime { + /** + * 定义了用户登录信息在Redis中的过期时间,单位为秒,这里设置为60 * 60 * 10,即10个小时(原注释写的30分钟有误,实际计算应为10小时), + * 表示用户登录成功后,其相关登录信息(如登录凭证等存储在Redis中的数据)在缓存中保留的时长,超过这个时间,缓存数据将自动失效,需要重新进行登录等操作来更新缓存。 + */ + int REDIS_SESSION_EXTIME = 60 * 60 * 10; } - /** 用户注册判断重复的参数类型 start **/ + /** + * 用户注册判断重复的参数类型相关的常量定义开始标识,以下几个常量用于定义在用户注册过程中,判断哪些参数是否重复时使用的标识类型,方便统一进行参数重复校验逻辑的处理。 + */ + /** + * 表示用于判断用户注册时电子邮箱是否重复的参数类型标识,在进行用户注册相关的业务逻辑中,通过该标识来区分当前是针对电子邮箱进行重复性检查,例如检查数据库中是否已经存在相同的电子邮箱地址。 + */ public static final String EMAIL = "email"; + /** + * 表示用于判断用户注册时用户名是否重复的参数类型标识,在用户注册流程里,可依据该标识确定是对用户名进行唯一性验证,查看数据库中是否已有相同的用户名被注册使用。 + */ public static final String USERNAME = "username"; - /** 用户注册判断重复的参数类型 end **/ + /** + * 用户注册判断重复的参数类型相关的常量定义结束标识 + */ - /** 用户角色 **/ - public interface Role{ - int ROLE_CUSTOME = 0;//普通用户 - int ROLE_ADMIN = 1;//管理员用户 + /** + * 定义了一个内部接口Role,用于封装用户角色相关的常量数值,以一种更清晰、语义化的方式来表示不同的用户角色,便于在权限判断、业务逻辑处理等场景中根据角色进行不同的操作。 + */ + public interface Role { + /** + * 表示普通用户角色对应的数值,在项目中可以通过该数值来标识用户为普通用户身份,例如在权限分配、功能访问限制等业务逻辑中,根据这个数值来判断用户是否具有相应的操作权限,普通用户通常权限相对受限。 + */ + int ROLE_CUSTOME = 0; + /** + * 表示管理员用户角色对应的数值,通过该数值来区分用户为管理员身份,管理员在系统中一般拥有较高的操作权限,能够进行更多的系统管理相关操作,比如管理用户、配置系统参数等。 + */ + int ROLE_ADMIN = 1; } - /**用户注册分布式锁路径***/ + /** + * 定义了一个字符串常量,用于表示用户注册分布式锁的路径,在涉及到分布式环境下处理用户注册操作时,通过使用分布式锁来保证同一时间只有一个线程或节点可以进行用户注册相关的关键操作(如写入数据库等),避免并发冲突,该路径用于标识这个分布式锁在相应的分布式协调服务(如Zookeeper等)中的具体位置。 + */ public static final String USER_REGISTER_DISTRIBUTE_LOCK_PATH = "/user_reg"; - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java index cef87ac..6dacb3a 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java @@ -1,6 +1,5 @@ package com.njupt.swg.common.exception; - import com.njupt.swg.common.constants.Constants; import com.njupt.swg.common.resp.ServerResponse; import lombok.extern.slf4j.Slf4j; @@ -9,25 +8,50 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** + * 该类 `ExceptionHandlerAdvice` 是一个全局异常处理类,用于在 Spring 框架下统一捕获并处理应用程序中出现的异常情况。 + * 它通过使用 `@ControllerAdvice`、`@ResponseBody` 和 `@Slf4j` 等注解,结合 `@ExceptionHandler` 注解定义的异常处理方法, + * 能够将异常信息记录到日志中,并根据不同类型的异常返回相应合适的错误响应给客户端,提高应用程序的健壮性和用户体验。 + * * @Author swg. * @Date 2019/1/1 13:21 * @CONTACT 317758022@qq.com * @DESC 全局异常处理 */ @ControllerAdvice +// `@ControllerAdvice` 注解表明这个类是一个全局的异常处理组件,它可以对整个应用程序中的控制器(Controller)层抛出的异常进行统一处理。 @ResponseBody +// `@ResponseBody` 注解表示该类中处理异常的方法返回的结果会直接作为响应体返回给客户端,而不是进行视图解析等操作,通常用于返回 JSON 格式等数据给客户端。 @Slf4j +// `@Slf4j` 注解使用了 Lombok 提供的日志功能,会自动生成一个名为 `log` 的日志记录对象,方便在类中记录异常相关的日志信息,便于后续排查问题。 public class ExceptionHandlerAdvice { + /** + * 该方法使用 `@ExceptionHandler(Exception.class)` 注解进行标注,用于处理所有类型为 `Exception`(即最通用的异常类型)的异常情况。 + * 当应用程序中任何地方抛出 `Exception` 及其子类异常时,都会被这个方法捕获。 + * 它首先会将异常信息记录到日志中(方便后续开发人员查看具体的异常原因和堆栈信息进行问题排查), + * 然后返回一个表示服务器内部错误的响应信息给客户端,告知客户端系统出现异常,让其稍后再试。 + * 响应中使用了在 `Constants` 类中定义的表示服务器内部错误的状态码(`RESP_STATUS_INTERNAL_ERROR`)。 + * + * @param e 捕获到的 `Exception` 类型的异常对象,包含了具体的异常信息和堆栈跟踪等内容。 + * @return 返回一个 `ServerResponse` 类型的对象,该对象是项目自定义的通用响应封装类,包含了错误状态码和对应的提示信息,用于告知客户端异常情况。 + */ @ExceptionHandler(Exception.class) - public ServerResponse handleException(Exception e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); + public ServerResponse handleException(Exception e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试"); } + /** + * 该方法使用 `@ExceptionHandler(SnailmallException.class)` 注解进行标注,用于专门处理 `SnailmallException` 类型的异常情况。 + * `SnailmallException` 应该是项目自定义的特定业务异常类型,当业务逻辑中抛出这种异常时,会被此方法捕获。 + * 同样,它会先将异常信息记录到日志中,然后返回一个包含该异常对象中定义的异常状态码(通过 `e.getExceptionStatus()` 获取)以及异常消息(`e.getMessage()`)的响应信息给客户端, + * 这样针对特定业务的异常可以更精准地反馈给客户端相应的错误情况,而不是统一返回通用的服务器内部错误提示。 + * + * @param e 捕获到的 `SnailmallException` 类型的异常对象,包含了业务相关的异常信息、状态码以及堆栈跟踪等内容。 + * @return 返回一个 `ServerResponse` 类型的对象,包含对应异常的状态码和消息,用于向客户端准确传达业务异常的具体情况。 + */ @ExceptionHandler(SnailmallException.class) - public ServerResponse handleException(SnailmallException e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); + public ServerResponse handleException(SnailmallException e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage()); } - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java index 363f19d..5401810 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java @@ -4,22 +4,43 @@ import com.njupt.swg.common.resp.ResponseEnum; import lombok.Getter; /** + * 该类 `SnailmallException` 是自定义的运行时异常类,继承自 `java.lang.RuntimeException`。 + * 主要用于在项目的业务逻辑处理过程中,当出现特定的、需要向上层抛出的异常情况时使用,它能够携带自定义的异常状态码以及异常消息,方便进行更精准的异常处理和反馈。 + * * @Author swg. * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC */ @Getter -public class SnailmallException extends RuntimeException{ +// 使用 Lombok 的 `@Getter` 注解,会自动为类中的 `exceptionStatus` 属性生成对应的 `get` 方法,方便外部获取该异常状态码的值,遵循了 Java 的封装原则,同时简化了代码编写。 +public class SnailmallException extends RuntimeException { + // 定义一个用于存储异常状态码的属性,初始值设置为从 `ResponseEnum.ERROR` 中获取的状态码, + // `ResponseEnum` 应该是一个枚举类,用于定义各种响应状态的枚举值,这里默认使用 `ERROR` 对应的状态码作为异常状态码的初始值, + // 意味着在没有显式指定异常状态码时,将采用这个默认值来表示异常情况。 private int exceptionStatus = ResponseEnum.ERROR.getCode(); - public SnailmallException(String msg){ + /** + * 构造方法,用于创建一个 `SnailmallException` 实例,接收一个字符串类型的参数 `msg`,该参数表示异常消息内容。 + * 调用父类(`RuntimeException`)的构造方法,将传入的 `msg` 作为异常消息传递给父类,此时异常状态码使用默认值(即 `ResponseEnum.ERROR.getCode()`)。 + * 适用于只需要传递异常消息,而使用默认错误状态码来表示异常的场景,例如在业务逻辑中某些通用的、不太需要细分状态码的错误情况时可以使用此构造方法抛出异常。 + * + * @param msg 异常消息内容,用于描述异常发生的具体情况,方便后续查看日志或者反馈给客户端知晓异常原因。 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 另一个构造方法,接收两个参数,一个整数类型的 `code` 表示自定义的异常状态码,一个字符串类型的 `msg` 表示异常消息。 + * 先调用父类(`RuntimeException`)的构造方法,将 `msg` 作为异常消息传递给父类,然后将传入的 `code` 赋值给 `exceptionStatus` 属性, + * 这样就可以根据具体业务需求灵活自定义异常状态码以及异常消息,更精准地表示不同的业务异常情况,例如针对不同业务模块、不同类型的错误可以分别设置不同的状态码和消息进行区分。 + * + * @param code 自定义的异常状态码,用于在项目中更细致地区分不同的异常类型或者异常严重程度等情况,方便进行针对性的异常处理和响应。 + * @param msg 异常消息内容,同上面构造方法中的 `msg` 参数作用一样,用于描述异常发生的具体情况。 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java index e4e59c7..8d6a794 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java @@ -3,23 +3,44 @@ package com.njupt.swg.common.resp; import lombok.Getter; /** + * 该枚举类 `ResponseEnum` 用于定义项目中基本的返回状态描述,它列举了几种常见的业务操作返回状态情况, + * 通过不同的枚举常量来表示,每个枚举常量都包含了对应的状态码和状态描述信息,方便在整个项目中统一使用这些标准化的返回状态标识, + * 使得不同模块之间在处理和传递操作结果时更加清晰、规范。 + * * @Author swg. * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com * @DESC 基本的返回状态描述 */ @Getter +// 使用 Lombok 的 `@Getter` 注解,会自动为枚举中的 `code` 和 `desc` 属性生成对应的获取方法,方便外部代码获取这些属性值, +// 这样其他类在需要使用返回状态的状态码或者描述信息时,可以直接调用相应的获取方法,遵循了 Java 的封装原则,同时减少了代码冗余。 public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + // 表示业务操作成功的枚举常量,状态码为 `0`,对应的描述信息为 `"SUCCESS"`,通常用于标识业务操作顺利完成的情况, + // 例如查询操作成功获取到了期望的数据、新增数据成功插入到数据库等场景下,可以使用该枚举常量来表示操作成功的返回状态。 + SUCCESS(0, "SUCCESS"), + // 表示业务操作出现错误的枚举常量,状态码为 `1`,对应的描述信息为 `"ERROR"`,这是一个比较笼统的错误标识, + // 可用于一般性的业务操作失败情况,比如在执行某个业务逻辑时发生了未预期的异常、或者满足了某种错误条件但不需要细分具体错误类型时,可以使用该枚举常量返回给调用者表示操作失败。 + ERROR(1, "ERROR"), + // 表示参数非法的枚举常量,状态码为 `2`,对应的描述信息为 `"ILLEGAL_ARGUMENTS"`,主要用于客户端传入的参数不符合要求的场景, + // 例如参数格式错误、缺少必要参数、参数取值超出合法范围等情况,服务端在进行参数校验后发现参数不合法时,就可以返回该枚举常量对应的状态来告知调用者参数存在问题。 + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + // 表示需要登录的枚举常量,状态码为 `10`,对应的描述信息为 `"NEED_LOGIN"`,用于提示客户端当前操作需要先进行登录认证的场景, + // 比如客户端尝试访问某些需要登录权限才能操作的接口,但却未提供有效的登录凭证时,服务端就可以返回该枚举常量对应的状态,告知客户端需要先登录再进行后续操作。 + NEED_LOGIN(10, "NEED_LOGIN"); private int code; private String desc; - ResponseEnum(int code,String desc){ + /** + * 枚举的构造方法,用于初始化每个枚举常量对应的状态码和描述信息。 + * 在定义每个枚举常量(如 `SUCCESS`、`ERROR` 等)时会调用该构造方法来传入相应的 `code` 和 `desc` 值,以此来构建完整的返回状态描述。 + * + * @param code 状态码,用于在程序中区分不同的返回状态情况,不同的业务场景对应不同的状态码值,方便调用者根据状态码来判断具体的返回结果类型。 + * @param desc 状态描述信息,通常用于向客户端展示具体的状态含义,便于客户端理解操作结果,一般是一些具有明确语义的字符串描述。 + */ + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java index fd098b8..3df7f89 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java @@ -5,75 +5,167 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; - import java.io.Serializable; /** + * 该类 `ServerResponse` 是本项目的通用的返回封装类,用于将服务端的操作结果统一进行封装后返回给客户端。 + * 它可以包含操作的状态码、提示消息以及具体的数据内容(泛型 `T` 表示具体的数据类型),并且通过使用一些 Jackson 相关的注解以及定义的多个静态方法, + * 方便创建不同状态(成功或失败)下的响应实例,同时实现了 `Serializable` 接口,使得该类的对象可以在网络传输、对象持久化等场景下进行序列化和反序列化操作。 + * * @Author swg. * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com * @DESC 作为本项目的通用的返回封装类 */ @Getter +// 使用 Lombok 的 `@Getter` 注解,会自动为类中的 `status`、`msg` 和 `data` 属性生成对应的 `get` 方法,方便外部获取这些属性的值,遵循了 Java 的封装原则,简化了代码编写。 @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// 使用 Jackson 的 `@JsonSerialize` 注解,配置序列化时的行为,这里指定了只包含非 `null` 的属性进行序列化, +// 也就是说在将 `ServerResponse` 对象转换为 JSON 格式(例如返回给客户端时),如果 `msg` 或者 `data` 等属性为 `null`,则不会包含在序列化后的 JSON 数据中,减少不必要的数据传输。 public class ServerResponse implements Serializable { + // 用于存储操作的状态码,通过与 `ResponseEnum` 枚举类中的状态码进行对比,可以判断操作是成功还是失败以及具体的状态类型,例如 `0` 表示成功等情况。 private int status; + // 用于存储提示消息,一般是对操作结果的文字描述,比如操作成功时可以是 "操作成功",失败时可以是具体的错误原因等,方便客户端理解服务端返回的结果含义。 private String msg; + // 泛型属性,用于存储具体的业务数据内容,类型根据实际业务场景而定,可以是单个对象、对象列表或者其他复杂的数据结构等,比如查询用户信息时可以是 `User` 对象,查询用户列表时可以是 `List` 类型的数据。 private T data; - public ServerResponse(){} + public ServerResponse() {} - private ServerResponse(int status){ + // 私有构造方法,接收一个状态码参数,用于创建只包含状态码的 `ServerResponse` 对象实例,通常在一些内部初始化或者特定场景下使用, + // 外部一般通过静态方法来创建完整的响应对象,该构造方法主要是为了代码复用和方便在类内部构建不同情况的对象。 + private ServerResponse(int status) { this.status = status; } - private ServerResponse(int status,String msg){ + + // 私有构造方法,接收状态码和提示消息两个参数,用于创建包含状态码和提示消息的 `ServerResponse` 对象实例, + // 方便在已知操作状态码和对应的提示消息时构建响应对象,比如在出现错误时,传入错误状态码和具体的错误消息来创建相应的返回对象。 + private ServerResponse(int status, String msg) { this.status = status; this.msg = msg; } - private ServerResponse(int status,T data){ + + // 私有构造方法,接收状态码和业务数据两个参数,用于创建包含状态码和具体数据的 `ServerResponse` 对象实例, + // 常用于操作成功且有具体数据需要返回给客户端的场景,例如查询操作成功获取到了数据,就可以通过传入成功状态码和查询到的数据来构建响应对象。 + private ServerResponse(int status, T data) { this.status = status; this.data = data; } - private ServerResponse(int status,String msg,T data){ + + // 私有构造方法,接收状态码、提示消息和业务数据三个参数,用于创建包含完整信息(状态码、提示消息、业务数据)的 `ServerResponse` 对象实例, + // 适用于需要详细反馈操作结果,既包含状态、提示又有具体数据的各种复杂场景,比如部分更新操作,成功时可以传入成功状态码、更新成功的提示以及更新后的数据等情况来构建返回对象。 + private ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + /** + * 使用 `@JsonIgnore` 注解标记该方法,意味着在将 `ServerResponse` 对象进行序列化(如转换为 JSON 格式)时,这个方法不会被包含在内, + * 它用于判断当前响应对象是否表示操作成功,通过对比状态码和 `ResponseEnum.SUCCESS` 枚举常量对应的状态码(通常 `0` 表示成功)来返回布尔值结果, + * 方便外部代码快速判断操作是否成功,无需再去获取状态码后手动对比,提高了代码的可读性和便捷性。 + * + * @return 返回 `true` 表示操作成功(状态码与成功状态码一致),返回 `false` 表示操作失败。 + */ @JsonIgnore - public boolean isSuccess(){ + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } /** - * 成功的方法 + * 以下是一系列静态方法,用于创建表示操作成功的 `ServerResponse` 对象实例,方便在不同的成功场景下快速构建合适的返回对象。 */ - public static ServerResponse createBySuccess(){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); - } - public static ServerResponse createBySuccessMessage(String message){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); - } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); - } - public static ServerResponse createBySuccess(String message,T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); + + /** + * 创建一个表示操作成功且只包含默认成功提示消息(从 `ResponseEnum.SUCCESS.getDesc()` 获取)的 `ServerResponse` 对象实例, + * 适用于不需要额外提供详细提示消息,仅告知客户端操作成功的简单场景,例如简单的查询操作成功,无其他特殊说明时可以使用该方法创建返回对象。 + * + * @param 泛型类型参数,由调用者指定具体的数据类型,使得该方法可以灵活适应不同业务数据类型的返回情况。 + * @return 返回创建好的 `ServerResponse` 对象实例,状态码为成功状态码,提示消息为默认的成功描述信息。 + */ + public static ServerResponse createBySuccess() { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc()); } /** - * 失败的方法 + * 创建一个表示操作成功且包含自定义提示消息的 `ServerResponse` 对象实例, + * 通过传入自定义的 `message` 参数来指定具体的提示消息内容,适用于需要向客户端传达更详细的成功相关信息的场景, + * 比如新增数据成功后,可以传入 "数据新增成功,已保存到数据库" 这样的提示消息,让客户端更清楚具体的操作结果情况。 + * + * @param 泛型类型参数,由调用者指定具体的数据类型,与其他静态方法一样,可灵活适配不同业务数据返回情况。 + * @param message 自定义的提示消息内容,用于替换默认的成功提示消息,更精准地告知客户端操作成功的相关细节。 + * @return 返回创建好的 `ServerResponse` 对象实例,状态码为成功状态码,提示消息为传入的自定义消息。 */ - public static ServerResponse createByError(){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); + public static ServerResponse createBySuccessMessage(String message) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createByErrorMessage(String msg){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); + + /** + * 创建一个表示操作成功且包含具体业务数据的 `ServerResponse` 对象实例, + * 通过传入具体的数据对象 `data`(类型为泛型 `T`)来将业务数据封装到返回对象中,适用于查询等操作成功并获取到具体数据需要返回给客户端的场景, + * 例如查询用户信息成功后,将查询到的 `User` 对象作为参数传入,构建包含用户信息数据的返回对象传递给客户端。 + * + * @param 泛型类型参数,由调用者指定具体的数据类型,对应要返回的业务数据的实际类型。 + * @param data 具体的业务数据对象,将被封装到 `ServerResponse` 对象的 `data` 属性中,作为操作成功的结果数据返回给客户端。 + * @return 返回创建好的 `ServerResponse` 对象实例,状态码为成功状态码,数据为传入的具体业务数据。 + */ + public static ServerResponse createBySuccess(T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data); } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); + + /** + * 创建一个表示操作成功且包含自定义提示消息和具体业务数据的 `ServerResponse` 对象实例, + * 同时传入 `message` 和 `data` 参数,分别用于指定详细的提示消息和具体的业务数据内容,适用于既需要告知客户端操作成功的详细情况,又有具体数据需要返回的复杂场景, + * 比如更新用户信息成功后,可以传入 "用户信息更新成功" 这样的提示消息以及更新后的 `User` 对象数据,构建完整的返回对象反馈给客户端。 + * + * @param 泛型类型参数,由调用者指定具体的数据类型,对应实际业务数据的类型以及返回对象中 `data` 属性的类型。 + * @param message 自定义的提示消息内容,用于丰富操作成功的提示信息,让客户端更全面地了解操作结果情况。 + * @param data 具体的业务数据对象,将被封装到 `ServerResponse` 对象的 `data` 属性中,作为操作成功的业务数据结果返回给客户端。 + * @return 返回创建好的 `ServerResponse` 对象实例,状态码为成功状态码,包含传入的自定义提示消息和具体业务数据。 + */ + public static ServerResponse createBySuccess(String message, T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } + /** + * 以下是一系列静态方法,用于创建表示操作失败的 `ServerResponse` 对象实例,方便在不同的失败场景下快速构建合适的返回对象,向客户端反馈操作失败的情况及原因。 + */ + /** + * 创建一个表示操作失败且只包含默认错误提示消息(从 `ResponseEnum.ERROR.getDesc()` 获取)的 `ServerResponse` 对象实例, + * 适用于一般性的操作失败情况,不需要详细说明具体错误原因,仅告知客户端操作失败时可以使用该方法创建返回对象,例如在出现未预期的异常等通用错误场景下使用。 + * + * @param 泛型类型参数,由调用者指定具体的数据类型,虽然操作失败时可能不一定有具体业务数据返回,但保持与其他方法一致的泛型定义,以保证方法通用性和灵活性。 + * @return 返回创建好的 `ServerResponse` 对象实例,状态码为错误状态码,提示消息为默认的错误描述信息。 + */ + public static ServerResponse createByError() { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); + } -} + /** + * 创建一个表示操作失败且包含自定义错误提示消息的 `ServerResponse` 对象实例, + * 通过传入自定义的 `msg` 参数来指定具体的错误消息内容,适用于需要向客户端明确传达具体失败原因的场景, + * 比如参数校验不通过时,可以传入 "参数不符合要求,请检查输入参数" 这样的提示消息,让客户端清楚操作失败的具体缘由。 + * + * @param 泛型类型参数,由调用者指定具体的数据类型,同样是为了保证方法通用性,适配不同业务场景下的返回情况。 + * @param msg 自定义的错误提示消息内容,用于替换默认的错误提示消息,更精准地告知客户端操作失败的原因所在。 + * @return 返回创建好的 `ServerResponse` 对象实例,状态码为错误状态码,提示消息为传入的自定义消息。 + */ + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } + + /** + * 创建一个表示操作失败且包含自定义状态码和错误提示消息的 `ServerResponse` 对象实例, + * 通过传入 `code` 和 `msg` 参数分别指定具体的状态码和错误提示消息内容,适用于需要更精细地表示不同类型的失败情况, + * 例如根据不同业务模块或者不同错误类型设置不同的状态码,并搭配相应的错误消息,让客户端能更准确地区分和理解操作失败的具体情况。 + * + * @param 泛型类型参数,由调用者指定具体的数据类型,保持方法通用性,可用于各种需要自定义状态码和消息返回的失败场景。 + * @param code 自定义的状态码,用于更细致地标识不同的失败情况,与项目中定义的各种状态码规范相匹配,方便客户端根据状态码进行针对性处理。 + * @param msg 自定义的错误提示消息内容,详细说明操作失败的原因,辅助客户端理解具体的问题所在。 + * @return 返回创建好的 `ServerResponse` 对象实例,状态码为传入的自定义状态码,提示消息为传入的自定义消息。 + */ + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + return new ServerResponse<>(code, msg); + } +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java index 0233e8d..17e0940 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java @@ -2,47 +2,63 @@ package com.njupt.swg.common.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; - import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * cookie读写 + * 该类 `CookieUtil` 主要用于处理与 `Cookie` 相关的操作,包括在用户登录时写入 `Cookie`、读取登录相关的 `Cookie` 值以及在用户注销时删除对应的 `Cookie`。 + * 通过设置 `Cookie` 的各种属性(如域名、路径、有效期等)来控制其作用范围和生命周期,同时借助日志记录相关操作信息,方便排查问题以及了解 `Cookie` 的使用情况。 + * + * @Slf4j 注解用于生成一个名为 `log` 的日志记录对象,便于在类中记录操作日志,尤其是在处理 `Cookie` 过程中方便查看关键信息,比如 `Cookie` 的名称、值以及操作结果等情况。 */ @Slf4j public class CookieUtil { + // 定义了一个静态的常量字符串,表示 `Cookie` 所属的域名,用于设置 `Cookie` 的作用域名范围,这里固定为 "oursnail.cn",意味着只有在该域名下的页面请求才会携带和使用此 `Cookie`。 private final static String COOKIE_DOMAIN = "oursnail.cn"; + // 定义了一个静态的常量字符串,用于表示存储登录相关信息的 `Cookie` 的名称,后续通过这个名称来识别和操作特定的登录 `Cookie`,固定为 "snailmall_login_token"。 private final static String COOKIE_NAME = "snailmall_login_token"; - /** - * 登陆的时候写入cookie - * @param response - * @param token + * 该方法用于在用户登录时向客户端写入登录相关的 `Cookie`,将登录凭证(以 `token` 的形式)存储到 `Cookie` 中并发送给客户端浏览器保存。 + * + * @param response 作为 `HttpServletResponse` 类型的参数,用于向客户端发送响应信息,包括设置和添加 `Cookie` 到响应头中,使得客户端浏览器能够接收到并保存该 `Cookie`。 + * @param token 表示登录的凭证信息(通常是经过加密等处理后的字符串),会被存储到名为 `COOKIE_NAME` 的 `Cookie` 中,作为后续验证用户登录状态等用途的标识。 */ - public static void writeLoginToken(HttpServletResponse response,String token){ - Cookie ck = new Cookie(COOKIE_NAME,token); + public static void writeLoginToken(HttpServletResponse response, String token) { + // 创建一个新的 `Cookie` 对象,传入 `COOKIE_NAME` 作为 `Cookie` 的名称,`token` 作为 `Cookie` 的值,以此来构建用于存储登录凭证的 `Cookie`。 + Cookie ck = new Cookie(COOKIE_NAME, token); + // 设置 `Cookie` 的域名属性,使其作用范围限定在指定的域名 "oursnail.cn" 下,只有访问该域名下的页面时,浏览器才会自动携带这个 `Cookie` 发送请求。 ck.setDomain(COOKIE_DOMAIN); - ck.setPath("/");//设值在根目录 - ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击 - ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒,maxage不设置的话,cookie就不会写入硬盘,只会写在内存,只在当前页面有效 - log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); + // 设置 `Cookie` 的路径属性为根目录 "/",意味着在该域名下的所有页面请求都会携带这个 `Cookie`,如果设置为其他具体路径,则只有访问该路径及其子路径下的页面时才会携带。 + ck.setPath("/"); + // 设置 `Cookie` 的 `HttpOnly` 属性为 `true`,这是一种安全机制,使得客户端脚本(如 JavaScript)无法访问该 `Cookie`,可以有效避免跨站脚本攻击(XSS)等安全问题,提高系统的安全性。 + ck.setHttpOnly(true); + // 设置 `Cookie` 的最大存活时间,这里设置为 `60 * 60 * 24 * 365` 秒,即一年,表示该 `Cookie` 在客户端浏览器保存的有效期为一年,超过这个时间后,浏览器会自动删除该 `Cookie`。 + // 如果设置为 `-1`,则表示永久有效(不过不同浏览器对于永久有效的处理方式可能略有差异),若不设置该属性(`maxAge` 为默认值),则 `Cookie` 不会写入硬盘,只会保存在内存中,仅在当前页面会话有效。 + ck.setMaxAge(60 * 60 * 24 * 365); + log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue()); + // 将构建好并设置好属性的 `Cookie` 添加到 `HttpServletResponse` 的响应头中,发送给客户端浏览器,使得浏览器能够保存这个 `Cookie` 用于后续请求携带。 response.addCookie(ck); } /** - * 读取登陆的cookie - * @param request - * @return + * 该方法用于从客户端的请求中读取登录相关的 `Cookie` 值,通过遍历请求中携带的所有 `Cookie`,查找名称为 `COOKIE_NAME` 的 `Cookie`,并返回其值。 + * 如果找到了对应的 `Cookie`,则返回其存储的登录凭证(`token`)值,若没有找到则返回 `null`,表示未获取到登录相关的 `Cookie`。 + * + * @param request 作为 `HttpServletRequest` 类型的参数,用于获取客户端发送过来的请求信息,包括请求中携带的 `Cookie` 数据,通过它可以获取到所有的 `Cookie` 数组进行遍历查找。 + * @return 返回 `String` 类型的结果,如果找到了名称为 `COOKIE_NAME` 的 `Cookie`,则返回其对应的值(即登录凭证 `token`),若未找到则返回 `null`。 */ - public static String readLoginToken(HttpServletRequest request){ + public static String readLoginToken(HttpServletRequest request) { + // 通过 `HttpServletRequest` 的 `getCookies` 方法获取客户端发送过来的所有 `Cookie`,以数组形式返回,如果没有 `Cookie` 则返回 `null`。 Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks){ - log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ - log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + if (cks!= null) { + for (Cookie ck : cks) { + log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 使用 `StringUtils` 的 `equals` 方法(该方法可以避免空指针异常等情况,进行安全的字符串比较)比较当前遍历到的 `Cookie` 的名称与预定义的 `COOKIE_NAME` 是否相等, + // 如果相等,则说明找到了存储登录凭证的 `Cookie`,返回其值(即登录 `token`)。 + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); return ck.getValue(); } } @@ -51,24 +67,29 @@ public class CookieUtil { } /** - * 注销的时候进行删除 - * @param request - * @param response + * 该方法用于在用户注销时,从客户端删除对应的登录相关的 `Cookie`,通过遍历请求中携带的所有 `Cookie`,找到名称为 `COOKIE_NAME` 的 `Cookie`, + * 然后将其有效期设置为 `0`,并重新添加到响应中发送给客户端,通知客户端删除该 `Cookie`,以此实现注销时清除登录凭证的功能。 + * + * @param request 作为 `HttpServletRequest` 类型的参数,用于获取客户端当前发送过来的请求信息,从中获取携带的 `Cookie` 数组,以便查找要删除的登录 `Cookie`。 + * @param response 作为 `HttpServletResponse` 类型的参数,用于向客户端发送响应信息,将设置好有效期为 `0` 的 `Cookie` 重新添加到响应头中,通知客户端删除对应的 `Cookie`。 */ - public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ + public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) { Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks) { - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ + if (cks!= null) { + for (Cookie ck : cks) { + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + // 找到名称为 `COOKIE_NAME` 的 `Cookie` 后,先设置其域名属性为预定义的 `COOKIE_DOMAIN`,确保域名范围的一致性,与写入 `Cookie` 时的设置相匹配。 ck.setDomain(COOKIE_DOMAIN); + // 设置 `Cookie` 的路径属性为根目录 "/",同样与写入 `Cookie` 时的路径设置保持一致,确保能够正确地删除对应的 `Cookie`。 ck.setPath("/"); - ck.setMaxAge(0);//0表示消除此cookie - log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + // 设置 `Cookie` 的最大存活时间为 `0`,这表示通知客户端浏览器立即删除该 `Cookie`,使其失效,不再携带和使用该 `Cookie` 进行后续请求。 + ck.setMaxAge(0); + log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 将设置好属性的 `Cookie`(有效期为 `0`)重新添加到 `HttpServletResponse` 的响应头中,发送给客户端,触发客户端执行删除该 `Cookie` 的操作。 response.addCookie(ck); return; } } } } - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java index 8655b2a..a88e1e1 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java @@ -4,65 +4,116 @@ import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** + * 该类 `DateTimeUtil` 是一个时间转换的工具类,主要用于处理日期(`Date`)与字符串(`String`)之间的相互转换,以及将日期转换为时间戳的操作。 + * 它借助了 `Joda-Time` 库来进行日期相关的格式化和解析工作,同时提供了不同形式的方法重载,方便在不同场景下按照指定格式或者默认格式进行时间相关的转换操作, + * 并在代码中对一些可能出现的空值情况做了相应处理,提高了工具类的健壮性。另外还包含了一个简单的 `main` 方法用于测试时间转换功能(虽然这种测试方式相对简单,在实际项目中可能会有更完善的单元测试等手段)。 + * * @DESC 时间转换的工具类 */ public class DateTimeUtil { - //joda-time - - //str->Date - //Date->str + // 使用 `Joda-Time` 库来处理时间相关操作,以下定义的是一个常量字符串,表示时间格式的标准模板,用于在没有显式指定格式时作为默认的日期时间字符串格式,方便统一格式化规则。 public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; - - - public static Date strToDate(String dateTimeStr, String formatStr){ + /** + * 将指定格式的日期时间字符串转换为 `Date` 类型的方法,通过传入日期时间字符串和对应的格式字符串,利用 `Joda-Time` 库进行解析并转换为 `Date` 对象。 + * + * @param dateTimeStr 表示要转换的日期时间字符串,例如 "2024-12-17 10:30:00",其格式需要与传入的 `formatStr` 参数指定的格式一致,否则解析会失败。 + * @param formatStr 用于指定 `dateTimeStr` 的格式,例如 "yyyy-MM-dd HH:mm:ss" 等常见的日期时间格式模板,通过这个格式来解析输入的字符串,将其转换为对应的 `Date` 对象。 + * @return 返回解析后的 `Date` 对象,如果解析成功则返回对应的日期时间对应的 `Date` 实例,若解析失败(比如字符串格式不符合要求等情况)会抛出异常(由 `Joda-Time` 库内部处理机制决定)。 + */ + public static Date strToDate(String dateTimeStr, String formatStr) { + // 根据传入的格式字符串创建 `DateTimeFormatter` 对象,它用于定义如何解析日期时间字符串,按照指定的格式模板来解析输入的字符串内容。 DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr); + // 使用创建好的 `DateTimeFormatter` 对输入的日期时间字符串进行解析,得到 `DateTime` 对象,`DateTime` 是 `Joda-Time` 库中用于表示日期时间的核心类,提供了丰富的日期时间操作方法。 DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); + // 将 `DateTime` 对象转换为 `Java` 标准库中的 `Date` 对象并返回,以便在基于 `Java` 标准库的其他代码中使用该日期时间数据。 return dateTime.toDate(); } - public static String dateToStr(Date date,String formatStr){ - if(date == null){ + /** + * 将 `Date` 类型的日期时间对象转换为指定格式的字符串的方法,先判断传入的 `Date` 对象是否为 `null`,如果是则返回空字符串,避免空指针异常等问题, + * 若不为 `null`,则利用 `Joda-Time` 库将其转换为对应的字符串表示形式,按照传入的格式字符串进行格式化输出。 + * + * @param date 要转换的 `Date` 类型的日期时间对象,若为 `null`,则直接返回空字符串,若不为 `null`,则进行格式转换操作。 + * @param formatStr 用于指定转换后的字符串的格式,例如 "yyyy-MM-dd HH:mm:ss" 等,按照该格式将 `Date` 对象转换为相应格式的字符串返回。 + * @return 返回格式化后的日期时间字符串,如果传入的 `Date` 对象为 `null`,则返回 `StringUtils.EMPTY`(即空字符串),若不为 `null`,则返回按照指定格式转换后的字符串内容。 + */ + public static String dateToStr(Date date, String formatStr) { + if (date == null) { return StringUtils.EMPTY; } + // 根据传入的 `Date` 对象创建 `DateTime` 对象,以便使用 `Joda-Time` 库的功能进行后续的格式化操作,`DateTime` 类提供了方便的方法将日期时间转换为不同格式的字符串。 DateTime dateTime = new DateTime(date); return dateTime.toString(formatStr); } - //固定好格式 - public static Date strToDate(String dateTimeStr){ + /** + * 将日期时间字符串按照默认的标准格式(`STANDARD_FORMAT`,即 "yyyy-MM-dd HH:mm:ss")转换为 `Date` 类型的方法,简化了调用过程, + * 当日期时间字符串的格式符合默认标准格式时,可以直接使用该方法进行转换,无需再传入格式字符串参数。 + * + * @param dateTimeStr 要转换的日期时间字符串,其格式需要符合默认的 "yyyy-MM-dd HH:mm:ss" 格式,否则解析会失败。 + * @return 返回解析后的 `Date` 对象,如果解析成功则返回对应的日期时间对应的 `Date` 实例,若解析失败(比如字符串格式不符合要求等情况)会抛出异常(由 `Joda-Time` 库内部处理机制决定)。 + */ + public static Date strToDate(String dateTimeStr) { + // 根据默认的标准格式创建 `DateTimeFormatter` 对象,用于解析输入的日期时间字符串。 DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT); + // 使用创建好的 `DateTimeFormatter` 对输入的日期时间字符串进行解析,得到 `DateTime` 对象。 DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); + // 将 `DateTime` 对象转换为 `Java` 标准库中的 `Date` 对象并返回。 return dateTime.toDate(); } - public static String dateToStr(Date date){ - if(date == null){ + /** + * 将 `Date` 类型的日期时间对象按照默认的标准格式(`STANDARD_FORMAT`,即 "yyyy-MM-dd HH:mm:ss")转换为字符串的方法,同样简化了调用过程, + * 当需要将 `Date` 对象转换为符合默认格式的字符串时,可以直接使用该方法,无需再传入格式字符串参数,并且对传入的 `Date` 对象是否为 `null` 做了判断处理。 + * + * @param date 要转换的 `Date` 类型的日期时间对象,若为 `null`,则直接返回空字符串,若不为 `null`,则进行格式转换操作。 + * @return 返回格式化后的日期时间字符串,如果传入的 `Date` 对象为 `null`,则返回 `StringUtils.EMPTY`(即空字符串),若不为 `null`,则返回按照默认格式 "yyyy-MM-dd HH:mm:ss" 转换后的字符串内容。 + */ + public static String dateToStr(Date date) { + if (date == null) { return StringUtils.EMPTY; } + // 根据传入的 `Date` 对象创建 `DateTime` 对象,以便使用 `Joda-Time` 库的功能将其转换为默认格式的字符串。 DateTime dateTime = new DateTime(date); return dateTime.toString(STANDARD_FORMAT); } - //Date -> 时间戳 + /** + * 将 `Date` 类型的日期时间对象转换为时间戳(从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数)的方法,先判断传入的 `Date` 对象是否为 `null`, + * 如果是则返回 `null`,若不为 `null`,则通过 `Java` 标准库中的 `SimpleDateFormat` 按照指定格式(同样是默认的 "yyyy-MM-dd HH:mm:ss")将 `Date` 对象转换为字符串, + * 再解析该字符串为 `Date` 对象(这里解析操作看似重复,但目的是为了获取对应格式下准确的时间戳,`SimpleDateFormat` 的 `parse` 方法会根据格式解析并返回对应时间的 `Date` 对象), + * 最后获取其时间戳并返回。不过此方法在解析字符串为 `Date` 对象时可能会抛出 `ParseException` 异常,调用者需要进行相应的异常处理。 + * + * @param date 要转换为时间戳的 `Date` 类型的日期时间对象,若为 `null`,则返回 `null`,若不为 `null`,则进行时间戳转换操作。 + * @return 返回对应的时间戳(以毫秒为单位的长整型数值),如果传入的 `Date` 对象为 `null`,则返回 `null`,若不为 `null`,则返回从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数。 + * @throws ParseException 该方法在使用 `SimpleDateFormat` 解析字符串为 `Date` 对象过程中,如果字符串格式不符合要求等情况可能会抛出此异常,需要调用者进行捕获处理。 + */ public static Long dateToChuo(Date date) throws ParseException { - if(date == null){ + if (date == null) { return null; } - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.parse(String.valueOf(date)).getTime(); } + /** + * `main` 方法是 `Java` 程序的入口点,在这里主要用于简单测试 `dateToChuo` 方法的功能,通过创建 `SimpleDateFormat` 对象按照指定格式解析一个硬编码的日期时间字符串为 `Date` 对象, + * 然后调用 `dateToChuo` 方法(虽然这里没有捕获异常,实际使用中应该捕获可能抛出的 `ParseException` 异常)获取时间戳,并打印输出。 + * 这只是一个简单的测试示例,在实际项目中通常会使用更专业的单元测试框架(如 `JUnit`、`TestNG` 等)来进行更全面、严谨的功能测试。 + * + * @param args 命令行参数数组,在这里并没有实际使用到,一般用于在命令行启动程序时传入外部参数,改变程序的运行行为等情况。 + * @throws ParseException 由于 `main` 方法中调用了 `dateToChuo` 方法,该方法可能抛出 `ParseException` 异常,所以这里需要声明抛出该异常,不过实际中应该进行异常捕获处理以保证程序的稳定性。 + */ public static void main(String[] args) throws ParseException { - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); - String time="1970-01-06 11:45:55"; + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String time = "1970-01-06 11:45:55"; Date date = format.parse(time); - System.out.print("Format To times:"+date.getTime()); + System.out.print("Format To times:" + date.getTime()); } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java index 12daca4..3ffdca5 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java @@ -8,119 +8,153 @@ import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.annotate.JsonSerialize; import org.codehaus.jackson.type.JavaType; import org.codehaus.jackson.type.TypeReference; - import java.io.IOException; import java.text.SimpleDateFormat; /** - * jackson的序列化和反序列化 + * 该类 `JsonUtil` 是用于处理 `Jackson` 库的序列化和反序列化操作的工具类。 + * 它通过配置 `ObjectMapper` 对象的各种属性,来定制序列化和反序列化的行为,例如控制哪些字段参与序列化、如何处理日期格式、如何处理未知属性等情况, + * 同时提供了多个重载的方法,分别用于将对象序列化为字符串(普通格式和美化格式)以及将字符串反序列化为不同类型的对象(简单对象、复杂对象等),方便在项目中统一进行 `JSON` 数据与 `Java` 对象之间的转换操作, + * 并且在方法中对可能出现的异常情况进行了日志记录,并返回合适的默认值(如 `null`),以增强工具类的健壮性。 + * + * @Slf4j 注解用于生成一个名为 `log` 的日志记录对象,便于在类中记录操作相关的日志信息,特别是在序列化和反序列化出现问题(如 `IOException` 异常抛出时)记录错误情况,方便后续排查问题。 */ @Slf4j public class JsonUtil { + // 创建一个静态的 `ObjectMapper` 对象,它是 `Jackson` 库中用于进行序列化和反序列化操作的核心类,后续所有的配置和转换操作都基于这个对象来进行。 private static ObjectMapper objectMapper = new ObjectMapper(); static { - //所有字段都列入进行转换 + // 以下是对 `ObjectMapper` 对象进行一系列的配置操作,通过静态代码块在类加载时执行这些配置,确保整个应用程序中使用的 `ObjectMapper` 具有统一的序列化和反序列化行为。 + + // 设置序列化时包含所有字段,即无论字段是否为 `null`,都将其列入进行转换,通过指定 `JsonSerialize.Inclusion.ALWAYS`,使得在将 `Java` 对象转换为 `JSON` 字符串时,所有的对象属性都会参与序列化过程。 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); - //取消默认转换timestamp形式 - objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); - //忽略空bean转json的错误 - objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); - //统一时间的格式 + + // 取消默认将日期转换为时间戳形式的行为,配置 `SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS` 为 `false`, + // 这样在序列化包含日期类型的对象时,会按照后续配置的日期格式(如通过 `setDateFormat` 方法指定的格式)进行序列化,而不是转换为时间戳数值。 + objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); + + // 忽略空 `bean`(即没有属性值的 `Java` 对象)转 `JSON` 的错误,配置 `SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS` 为 `false`, + // 当尝试序列化一个没有属性值的对象时,不会抛出异常,而是正常返回相应的 `JSON` 表示(可能为空对象的 `JSON` 格式),提高了序列化操作的容错性。 + objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false); + + // 统一时间的格式,通过创建 `SimpleDateFormat` 对象并传入 `DateTimeUtil.STANDARD_FORMAT`(这应该是项目中定义的统一的日期时间格式字符串,例如 "yyyy-MM-dd HH:mm:ss"), + // 然后使用 `setDateFormat` 方法设置到 `ObjectMapper` 中,使得在序列化和反序列化日期类型数据时,都按照这个统一格式进行处理,保证日期格式的一致性。 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); - //忽略json存在属性,但是java对象不存在属性的错误 - objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); + + // 忽略 `JSON` 中存在属性,但对应的 `Java` 对象不存在该属性的错误,配置 `DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES` 为 `false`, + // 在将 `JSON` 字符串反序列化为 `Java` 对象时,如果 `JSON` 字符串中包含了 `Java` 对象中不存在的属性,不会抛出异常,只会忽略这些未知属性进行反序列化操作,增强了反序列化操作的兼容性。 + objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** - * 序列化方法,将对象转为字符串 - * @param obj - * @param - * @return + * 该方法是序列化方法,用于将 `Java` 对象转换为 `JSON` 字符串格式。 + * 首先判断传入的对象是否为 `null`,如果是则直接返回 `null`,若不为 `null`,则尝试使用配置好的 `ObjectMapper` 对象进行序列化操作, + * 如果对象本身就是 `String` 类型,则直接返回该对象(因为本身已经是字符串形式,无需再次进行序列化转换),否则调用 `writeValueAsString` 方法将对象转换为 `JSON` 字符串并返回, + * 在转换过程中如果出现 `IOException` 异常,则记录警告日志,并返回 `null` 表示转换失败。 + * + * @param obj 要进行序列化的 `Java` 对象,类型为泛型 `T`,可以是任意的 `Java` 类的实例,比如自定义的 `POJO` 类、集合类等,只要其能被 `Jackson` 库正常序列化即可。 + * @param 泛型类型参数,用于表示传入对象的实际类型,使得方法可以适应不同类型对象的序列化需求,在返回结果时也能保持类型的一致性。 + * @return 返回序列化后的 `JSON` 字符串,如果传入的对象为 `null` 或者序列化过程出现 `IOException` 异常,则返回 `null`,若序列化成功则返回对应的 `JSON` 格式的字符串表示。 */ - public static String obj2String(T obj){ - if(obj == null){ + public static String obj2String(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); + return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** - * 序列化方法,同上,只是输出的格式是美化的,便于测试 - * @param obj - * @param - * @return + * 该方法也是序列化方法,功能与 `obj2String` 方法类似,同样是将 `Java` 对象转换为 `JSON` 字符串格式,但输出的格式是美化后的,便于阅读和测试。 + * 它先判断传入的对象是否为 `null`,若是则返回 `null`,不为 `null` 时,若对象本身是 `String` 类型则直接返回该对象,否则通过调用 `writerWithDefaultPrettyPrinter` 方法获取美化后的写入器, + * 再使用该写入器的 `writeValueAsString` 方法将对象转换为格式化后的(美化的)`JSON` 字符串并返回,若出现 `IOException` 异常同样记录警告日志并返回 `null`。 + * + * @param obj 要进行序列化的 `Java` 对象,类型为泛型 `T`,与 `obj2String` 方法中参数的含义和作用相同,可接受各种能被 `Jackson` 库序列化的对象类型。 + * @param 泛型类型参数,用于表示传入对象的实际类型,确保方法的通用性以及返回结果类型的适配性,和 `obj2String` 方法中的泛型作用一致。 + * @return 返回美化后的序列化后的 `JSON` 字符串,如果传入的对象为 `null` 或者序列化过程出现 `IOException` 异常,则返回 `null`,若序列化成功则返回对应的格式化后的 `JSON` 字符串内容,方便查看和测试。 */ - public static String obj2StringPretty(T obj){ - if(obj == null){ + public static String obj2StringPretty(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** - * 比较简单的反序列化的方法,将字符串转为单个对象 - * @param str - * @param clazz - * @param - * @return + * 该方法是相对简单的反序列化方法,用于将 `JSON` 字符串转换为单个 `Java` 对象。 + * 首先判断传入的 `JSON` 字符串是否为空(通过 `StringUtils.isEmpty` 方法判断,可避免空指针异常等情况)以及要转换的目标 `Java` 类是否为 `null`,若有一个条件满足则返回 `null`, + * 若都不满足,则判断如果目标类就是 `String` 类,则直接将传入的字符串强制转换并返回(因为本身就是期望转换为字符串类型,无需进行复杂的反序列化操作), + * 否则使用配置好的 `ObjectMapper` 对象的 `readValue` 方法将 `JSON` 字符串反序列化为指定类型的 `Java` 对象并返回,若在反序列化过程中出现 `IOException` 异常,记录警告日志并返回 `null`。 + * + * @param str 要进行反序列化的 `JSON` 字符串,其格式需要符合 `Jackson` 库的要求以及与要转换的目标 `Java` 类的结构相匹配,否则可能会出现反序列化失败的情况。 + * @param clazz 目标 `Java` 类的类型,通过传入 `Class` 类型的参数来指定要将 `JSON` 字符串反序列化为哪种类型的对象,例如 `User.class` 表示要转换为 `User` 类型的对象,用于指导 `Jackson` 库进行类型转换。 + * @param 泛型类型参数,用于表示要反序列化得到的目标对象的实际类型,与传入的 `clazz` 参数的类型保持一致,确保方法的类型安全性以及返回结果的正确性。 + * @return 返回反序列化后的 `Java` 对象,如果传入的 `JSON` 字符串为空或者目标类为 `null`,或者反序列化过程出现 `IOException` 异常,则返回 `null`,若反序列化成功则返回对应的目标类型的 `Java` 对象实例。 */ - public static T String2Obj(String str,Class clazz){ - if(StringUtils.isEmpty(str) || clazz == null){ + public static T String2Obj(String str, Class clazz) { + if (StringUtils.isEmpty(str) || clazz == null) { return null; } try { - return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz); + return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** - * 复杂对象的反序列化(通用) - * @param str - * @param typeReference - * @param - * @return + * 该方法是用于复杂对象的反序列化的通用方法,通过 `TypeReference` 类型来指定复杂的类型信息,实现将 `JSON` 字符串转换为复杂结构的 `Java` 对象。 + * 先判断传入的 `JSON` 字符串是否为空以及 `TypeReference` 是否为 `null`,若满足其中一个条件则返回 `null`,若都不满足,则判断如果 `TypeReference` 所表示的类型就是 `String` 类型, + * 则直接返回传入的字符串(因为本身就是期望转换为字符串类型,无需进行反序列化操作),否则使用配置好的 `ObjectMapper` 对象的 `readValue` 方法按照 `TypeReference` 指定的复杂类型信息将 `JSON` 字符串反序列化为相应的 `Java` 对象并返回, + * 在反序列化出现 `IOException` 异常时,记录警告日志并返回 `null`。 + * + * @param str 要进行反序列化的 `JSON` 字符串,同样其格式需要符合 `Jackson` 库要求以及与 `TypeReference` 指定的复杂类型结构相匹配,以保证反序列化能够成功进行。 + * @param typeReference `TypeReference` 类型的参数,用于详细指定复杂的目标对象类型信息,例如可以表示包含泛型的集合类型(如 `List`)等复杂结构,通过它可以准确地告诉 `Jackson` 库如何进行反序列化操作。 + * @param 泛型类型参数,用于表示要反序列化得到的目标复杂对象的实际类型,和 `TypeReference` 所指向的类型保持一致,确保方法的类型准确性以及返回结果的正确性。 + * @return 返回反序列化后的复杂结构的 `Java` 对象,如果传入的 `JSON` 字符串为空或者 `TypeReference` 为 `null`,或者反序列化过程出现 `IOException` 异常,则返回 `null`,若反序列化成功则返回对应的符合复杂类型要求的 `Java` 对象实例。 */ - public static T Str2Obj(String str, TypeReference typeReference){ - if(StringUtils.isEmpty(str) || typeReference == null){ + public static T Str2Obj(String str, TypeReference typeReference) { + if (StringUtils.isEmpty(str) || typeReference == null) { return null; } try { - return (T) (typeReference.getType().equals(String.class)?str:objectMapper.readValue(str,typeReference)); + return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference)); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** - * 第二种方式实现复杂对象的反序列化 - * @param str - * @param collectionClass - * @param elementClasses - * @param - * @return + * 该方法是实现复杂对象反序列化的另一种方式,通过传入集合类类型以及元素类类型等参数,构造 `JavaType` 来指定复杂的类型信息,进而将 `JSON` 字符串转换为相应的复杂结构的 `Java` 对象。 + * 首先使用 `objectMapper.getTypeFactory().constructParametricType` 方法根据传入的集合类和元素类等信息构造出 `JavaType`,用于表示复杂的目标对象类型结构, + * 然后使用配置好的 `ObjectMapper` 对象的 `readValue` 方法按照构造好的 `JavaType` 将 `JSON` 字符串反序列化为相应的 `Java` 对象并返回,若在反序列化过程中出现 `IOException` 异常,记录警告日志并返回 `null`。 + * 这种方式适用于当需要反序列化的对象类型是集合类型且集合中的元素类型也需要明确指定的复杂情况,例如将 `JSON` 字符串转换为 `List` 类型的对象时可以使用该方法。 + * + * @param str 要进行反序列化的 `JSON` 字符串,其格式需要符合 `Jackson` 库要求以及与构造的 `JavaType` 所表示的复杂类型结构相匹配,确保反序列化能够正确执行。 + * @param collectionClass 表示集合类的类型,例如 `List.class`、`Set.class` 等,用于指定反序列化后的对象是哪种集合类型,作为复杂类型结构的外层容器类型信息传递给 `Jackson` 库。 + * @param elementClasses 可变参数,表示集合中元素的类型,例如 `User.class` 等,按照顺序依次指定集合中元素的具体类型,用于准确构建复杂的目标对象类型结构,指导 `Jackson` 库进行反序列化操作。 + * @param 泛型类型参数,用于表示要反序列化得到的目标复杂对象的实际类型,与构造的 `JavaType` 所指向的最终类型保持一致,保证方法的类型安全性以及返回结果的正确性。 + * @return 返回反序列化后的复杂结构的 `Java` 对象,如果在反序列化过程中出现 `IOException` 异常,则返回 `null`,若反序列化成功则返回对应的符合复杂类型要求的 `Java` 对象实例,例如成功将 `JSON` 字符串转换为 `List` 类型的对象等情况。 */ - public static T Str2Obj(String str,Class collectionClass,Class... elementClasses){ - JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); + public static T Str2Obj(String str, Class collectionClass, Class... elementClasses) { + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); try { - return objectMapper.readValue(str,javaType); + return objectMapper.readValue(str, javaType); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java index e6e5c8a..9f49cb3 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java @@ -3,9 +3,19 @@ package com.njupt.swg.common.utils; import java.security.MessageDigest; /** - * MD5加密工具类 + * 该类 `MD5Util` 是一个用于进行 `MD5` 加密的工具类,它提供了将输入的字符串按照 `MD5` 算法进行加密并返回加密结果的功能。 + * 内部包含了几个辅助方法用于字节数组与十六进制字符串之间的转换以及实际执行 `MD5` 加密操作的方法,同时还提供了一个方便的对外公开方法,按照 `UTF-8` 编码进行 `MD5` 加密, + * 并且在代码中预留了加盐(增加额外的字符串来提高加密安全性,虽然当前示例中未体现具体加盐操作)的位置,此外还有一个简单的 `main` 方法用于测试 `MD5` 加密功能(实际项目中通常会有更完善的测试手段)。 */ public class MD5Util { + /** + * 该私有方法用于将字节数组转换为十六进制字符串表示形式。 + * 它通过遍历字节数组中的每个字节,调用 `byteToHexString` 方法将每个字节转换为对应的十六进制字符串,然后将这些十六进制字符串依次拼接起来,最终返回拼接后的十六进制字符串结果。 + * 这个方法主要是作为中间转换步骤,用于后续在 `MD5` 加密结果(以字节数组形式返回)转换为十六进制字符串表示时使用。 + * + * @param b 要转换的字节数组,通常是 `MD5` 加密后得到的字节数组形式的结果,需要将其转换为便于查看和使用的十六进制字符串形式。 + * @return 返回转换后的十六进制字符串,即将字节数组中的每个字节都转换为十六进制表示后拼接而成的字符串。 + */ private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) @@ -14,6 +24,15 @@ public class MD5Util { return resultSb.toString(); } + /** + * 该私有方法用于将单个字节转换为十六进制字符串表示形式。 + * 先对传入的字节进行处理,如果字节值小于 `0`,则将其加上 `256`(因为在 `Java` 中字节是以有符号数表示,范围是 `-128` 到 `127`,转换为十六进制时需要将其转换为无符号数表示,范围是 `0` 到 `255`), + * 然后将处理后的字节值分别计算出高四位和低四位对应的十六进制数字(通过除以 `16` 和取模 `16` 的操作),再从预定义的十六进制数字字符数组 `hexDigits` 中获取对应的字符进行拼接,最终返回该字节对应的十六进制字符串表示。 + * 这个方法主要是配合 `byteArrayToHexString` 方法,用于将字节逐个转换为十六进制字符串,进而实现字节数组整体的十六进制字符串转换。 + * + * @param b 要转换的单个字节,通常是字节数组中的一个元素,将其转换为十六进制字符串表示形式,以便后续拼接成完整的十六进制字符串结果。 + * @return 返回转换后的十六进制字符串,即该字节对应的十六进制表示形式,长度为 `2` 个字符,例如 `0f`、`1a` 等。 + */ private static String byteToHexString(byte b) { int n = b; if (n < 0) @@ -24,11 +43,15 @@ public class MD5Util { } /** - * 返回大写MD5 + * 该私有方法是实际执行 `MD5` 加密操作并将结果转换为十六进制字符串的核心方法,根据是否指定字符编码来处理输入的字符串并返回加密后的十六进制字符串结果(大写形式)。 + * 首先将传入的原始字符串赋值给 `resultString`,然后通过 `MessageDigest.getInstance` 方法获取 `MD5` 算法对应的 `MessageDigest` 实例(用于执行加密操作), + * 如果传入的字符编码为空或者为空字符串,则直接使用默认编码将 `resultString` 转换为字节数组后进行 `MD5` 加密,并将加密后的字节数组通过 `byteArrayToHexString` 方法转换为十六进制字符串赋值给 `resultString`, + * 若指定了字符编码,则按照指定的字符编码(如 `UTF-8`)将 `resultString` 转换为字节数组后进行 `MD5` 加密,同样将加密后的字节数组转换为十六进制字符串赋值给 `resultString`,最后返回 `resultString` 并转换为大写形式(常见的 `MD5` 结果展示形式)。 + * 需要注意的是,当前方法在捕获异常时没有进行详细的处理(只是空的 `catch` 块),实际应用中可能需要根据具体情况进行合适的异常处理,比如记录日志、返回特定的默认值等。 * - * @param origin - * @param charsetname - * @return + * @param origin 要进行 `MD5` 加密的原始字符串,例如用户输入的密码等需要加密存储的数据,作为加密的输入内容。 + * @param charsetname 用于指定字符编码的字符串,例如 "utf-8"、"GBK" 等,若为 `null` 或空字符串,则使用默认编码进行字符串到字节数组的转换,若指定了具体编码,则按照该编码进行转换后再加密。 + * @return 返回经过 `MD5` 加密并转换为十六进制字符串表示的结果(大写形式),如果加密过程出现异常(虽然当前异常处理不完善),则可能返回 `null`。 */ private static String MD5Encode(String origin, String charsetname) { String resultString = null; @@ -44,16 +67,33 @@ public class MD5Util { return resultString.toUpperCase(); } + /** + * 该公开的静态方法是对外提供的按照 `UTF-8` 编码进行 `MD5` 加密的便捷方法,它调用了 `MD5Encode` 私有方法,并传入要加密的字符串和 "utf-8" 字符编码参数, + * 实现将输入的字符串按照 `UTF-8` 编码进行 `MD5` 加密并返回加密后的十六进制字符串结果(大写形式)。 + * 同时在方法注释中提到这里可以加盐,意味着可以在将字符串传入 `MD5Encode` 方法之前,添加额外的固定字符串(盐值)与原始字符串拼接后再进行加密,以此提高加密的安全性,不过当前代码中未体现具体的加盐操作实现。 + * + * @param origin 要进行 `MD5` 加密的原始字符串,同样可以是密码等需要加密的数据,会按照 `UTF-8` 编码进行加密处理。 + * @return 返回按照 `UTF-8` 编码进行 `MD5` 加密并转换为十六进制字符串表示的结果(大写形式),若加密过程出现异常(由 `MD5Encode` 方法内部的异常处理决定)则可能返回 `null`。 + */ public static String MD5EncodeUtf8(String origin) { //这里可以加盐 return MD5Encode(origin, "utf-8"); } + /** + * `main` 方法是 `Java` 程序的入口点,在这里主要用于简单测试 `MD5EncodeUtf8` 方法的功能,通过传入硬编码的字符串 "123456" 调用 `MD5EncodeUtf8` 方法进行 `MD5` 加密, + * 并将加密结果打印输出。这只是一个非常简单的测试示例,在实际项目中通常会使用更专业的单元测试框架(如 `JUnit`、`TestNG` 等)来进行更全面、严谨的功能测试,包括对各种边界情况、异常情况等的测试。 + * + * @param args 命令行参数数组,在这里并没有实际使用到,一般用于在命令行启动程序时传入外部参数,改变程序的运行行为等情况。 + */ public static void main(String[] args) { System.out.println(MD5EncodeUtf8("123456")); } - + /** + * 定义了一个私有的静态字符串数组 `hexDigits`,用于存储十六进制数字对应的字符表示,即 `0` 到 `9` 和 `a` 到 `f`, + * 主要是在 `byteToHexString` 方法中通过索引获取对应的字符来构建字节的十六进制字符串表示形式,作为转换过程中的一个基础数据结构。 + */ private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/ZkClient.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/ZkClient.java index d44bcf2..be22dd0 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/ZkClient.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/ZkClient.java @@ -9,26 +9,46 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /** + * 该类 `ZkClient` 主要用于创建和配置与 `Zookeeper` 服务器进行交互的 `Curator` 客户端。 + * 它通过依赖注入获取项目配置参数(`Parameters` 类中存储的相关配置信息),利用 `CuratorFrameworkFactory` 构建 `CuratorFramework` 实例, + * 并设置连接字符串、连接超时时间以及重试策略等重要参数,最后启动客户端使其处于可用状态,通过 `@Bean` 注解将创建好的客户端实例交给 `Spring` 容器管理,方便在项目的其他地方进行注入使用, + * 实现了在 `Spring` 项目中方便地与 `Zookeeper` 进行交互的功能,例如可以用于分布式锁、服务发现等基于 `Zookeeper` 的应用场景。 + * * @Author swg. * @Date 2019/1/1 17:57 * @CONTACT 317758022@qq.com * @DESC zk的curator客户端 */ @Component +// 该注解表明这个类是 `Spring` 框架中的一个组件,`Spring` 会自动扫描并将其纳入到容器管理中,使得这个类可以参与依赖注入等 `Spring` 容器相关的操作。 public class ZkClient { @Autowired + // 使用 `Spring` 的依赖注入功能,自动注入 `Parameters` 类的实例,通过 `Parameters` 可以获取到项目中关于 `Zookeeper` 的配置参数,比如 `Zookeeper` 服务器的主机地址等信息,用于后续构建 `Curator` 客户端。 private Parameters parameters; + /** + * `@Bean` 注解标记的方法,用于在 `Spring` 容器中创建并注册一个 `Bean`(在这里就是 `CuratorFramework` 类型的 `Zookeeper` 客户端实例)。 + * 该方法会被 `Spring` 容器调用,创建好的 `CuratorFramework` 实例会被 `Spring` 容器管理,其他需要使用 `Zookeeper` 客户端的地方可以通过依赖注入的方式获取到这个实例。 + * + * @return 返回创建并配置好的 `CuratorFramework` 实例,它是与 `Zookeeper` 服务器进行交互的客户端对象,已经设置好了连接相关的参数并且已经启动,处于可使用状态。 + */ @Bean - public CuratorFramework getZkClient(){ - - CuratorFrameworkFactory.Builder builder= CuratorFrameworkFactory.builder() + public CuratorFramework getZkClient() { + // 使用 `CuratorFrameworkFactory` 的 `builder` 方法创建一个构建器对象,通过这个构建器可以方便地设置各种创建 `CuratorFramework` 客户端所需的配置参数,逐步构建出符合需求的客户端实例。 + CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder() + // 设置要连接的 `Zookeeper` 服务器的连接字符串,通过从注入的 `Parameters` 实例中获取 `zkHost` 属性来获取 `Zookeeper` 服务器的主机地址信息, + // 连接字符串的格式通常类似于 "ip1:port1,ip2:port2,...",用于指定要连接的 `Zookeeper` 集群中的服务器节点地址列表。 .connectString(parameters.getZkHost()) + // 设置连接超时时间,单位为毫秒,这里设置为 `3000` 毫秒,表示客户端尝试连接 `Zookeeper` 服务器时,如果在 `3000` 毫秒内还未成功建立连接,就认为连接超时,触发相应的超时处理逻辑。 .connectionTimeoutMs(3000) + // 设置重试策略,这里使用 `RetryNTimes` 策略,表示当连接 `Zookeeper` 出现问题时,会进行重试操作,最多重试 `5` 次,每次重试间隔 `10` 毫秒, + // 通过这种重试机制来增加客户端连接成功的概率,提高客户端在面对网络抖动等临时问题时的健壮性。 .retryPolicy(new RetryNTimes(5, 10)); + // 通过构建器对象的 `build` 方法创建出最终的 `CuratorFramework` 客户端实例,此时实例已经包含了前面设置的连接字符串、连接超时时间以及重试策略等配置信息。 CuratorFramework framework = builder.build(); + // 启动 `CuratorFramework` 客户端,使其开始尝试连接 `Zookeeper` 服务器,在启动后客户端会根据配置的参数进行连接操作以及后续的相关初始化工作,进入可使用状态, + // 可以通过这个客户端实例执行诸如创建节点、获取节点数据、设置分布式锁等各种与 `Zookeeper` 交互的操作。 framework.start(); return framework; - } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/controller/UserController.java b/snailmall-user-service/src/main/java/com/njupt/swg/controller/UserController.java index 2079642..e79077e 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/controller/UserController.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/controller/UserController.java @@ -21,182 +21,222 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -/** - * @Author swg. - * @Date 2019/1/1 13:12 - * @CONTACT 317758022@qq.com - * @DESC 用户模块,POSTMAN简单测试通过 - */ - //TODO 先全部开放GET请求 +// 为该控制器类下的所有请求路径设置统一的前缀为 "user",方便对用户模块相关的接口进行统一管理和组织。 @RequestMapping("user") +// 表明这个类是一个RESTful风格的控制器,其方法返回的数据会直接作为响应体返回(通常以JSON等格式),而不是进行视图解析等操作。 @RestController +// 使用Slf4j注解生成一个名为log的日志记录对象,方便在类中记录与用户操作相关的日志信息。 @Slf4j -// 表示标识这个类是swagger的资源 +// 表示标识这个类是swagger的资源,用于生成接口文档,通过value属性指定接口文档中的名称,tags属性用于对接口进行分类标记。 @Api(value = "UserController", tags = {"用户服务接口"}) public class UserController { + // 通过Spring的依赖注入功能,自动注入IUserService接口的实现类实例,用于调用用户相关的业务逻辑方法。 @Autowired private IUserService userService; + // 注入CommonCacheUtil实例,用于操作缓存,如在用户登录等操作中与缓存进行交互。 @Autowired private CommonCacheUtil commonCacheUtil; /** - * 用户登陆:验证参数、登陆、写到cookie中并且写到redis中 - * 用户登陆以后,点击其他需要登陆才能看的页面时,先判断是否前端是否有这个key,没有则提示需要登陆 + * 用户登录方法,处理用户登录的业务逻辑。 + * 1. 接收用户名和密码,记录用户开始登录的日志信息。 + * 2. 调用userService的login方法进行登录验证,该方法会根据用户名和密码在数据库等地方进行验证,并返回包含验证结果和用户信息(如果登录成功)的ServerResponse对象。 + * 3. 如果登录成功,将代表用户的sessionId写到前端浏览器的cookie中,方便后续识别用户登录状态。 + * 4. 同时将用户信息序列化后存入redis缓存,设置过期时间为30分钟,以便后续快速获取用户信息。 + * 5. 最后返回登录操作的响应结果给客户端,包含登录是否成功以及相关用户信息(如果成功)。 */ - @ApiOperation(value="用户登陆", notes="输入用户名,密码,不能为空") + @ApiOperation(value = "用户登陆", notes = "输入用户名,密码,不能为空") @ApiImplicitParams({ @ApiImplicitParam(name = "username", value = "用户名", required = true, dataType = "String"), @ApiImplicitParam(name = "password", value = "用户密码", required = true, dataType = "String") }) @RequestMapping("/login.do") - public ServerResponse login(HttpSession session, HttpServletResponse response, String username, String password){ - log.info("【用户{}开始登陆】",username); - ServerResponse userVOServerResponse = userService.login(username,password); - if(userVOServerResponse.isSuccess()){ - //登陆成功,那么需要在redis中存储,并且将代表用户的sessionId写到前端浏览器的cookie中 - log.info("【用户{}cookie开始写入】",username); - CookieUtil.writeLoginToken(response,session.getId()); - //写到redis中,将用户信息序列化,设置过期时间为30分钟 - log.info("【用户{}redis开始写入】",username); + public ServerResponse login(HttpSession session, HttpServletResponse response, String username, String password) { + // 记录用户开始登录的日志,方便后续排查问题,如查看登录失败的原因等。 + log.info("【用户{}开始登陆】", username); + ServerResponse userVOServerResponse = userService.login(username, password); + if (userVOServerResponse.isSuccess()) { + // 记录开始写入cookie的日志,用于跟踪cookie写入操作是否正常。 + log.info("【用户{} cookie开始写入】", username); + // 将用户的sessionId写入cookie,使浏览器保存该cookie,用于后续请求识别用户登录状态。 + CookieUtil.writeLoginToken(response, session.getId()); + // 记录开始写入redis的日志,便于监控redis写入操作的情况。 + log.info("【用户{} redis开始写入】", username); + // 将用户信息序列化后存入redis缓存,设置过期时间为30分钟,提高后续获取用户信息的效率。 commonCacheUtil.cacheNxExpire(session.getId(), JsonUtil.obj2String(userVOServerResponse.getData()), Constants.RedisCacheExtime.REDIS_SESSION_EXTIME); } - log.info("【用户{}登陆成功】",username); + // 记录用户登录成功的日志,完整记录登录操作过程。 + log.info("【用户{}登陆成功】", username); return userVOServerResponse; } - /** - * 用户注册,要判断用户名和邮箱是否重复,这里用了分布式锁来防止用户名和邮箱可能出现重复 + * 用户注册方法,处理用户注册的业务逻辑。 + * 1. 接收用户对象,记录开始注册的日志信息。 + * 2. 调用userService的register方法进行用户注册,该方法内部会判断用户名和邮箱是否重复(使用分布式锁保证并发安全),并将用户信息保存到数据库等操作。 + * 3. 最后返回注册操作的响应结果给客户端,包含注册是否成功以及相关提示信息。 */ - @ApiOperation(value="创建用户", notes="根据User对象创建用户") + @ApiOperation(value = "创建用户", notes = "根据User对象创建用户") @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") @RequestMapping("/register.do") - public ServerResponse register(User user){ + public ServerResponse register(User user) { + // 记录开始注册的日志,有助于排查注册过程中出现的问题,如用户名或邮箱重复等情况。 log.info("【开始注册】"); - //这里模拟高并发的注册场景,防止用户名字注册重复,所以需要加上分布式锁 + // 这里模拟高并发的注册场景,防止用户名字注册重复,所以需要加上分布式锁,调用userService的register方法进行实际注册操作。 ServerResponse response = userService.register(user); + // 记录用户注册成功的日志,方便监控注册操作的整体情况。 log.info("【用户注册成功】"); return response; } /** - * 判断用户名和邮箱是否重复 + * 验证用户名和邮箱是否重复方法,处理验证用户名和邮箱唯一性的业务逻辑。 + * 1. 接收要验证的字符串和类型参数,记录开始验证的日志信息。 + * 2. 调用userService的checkValid方法进行验证,该方法会根据类型去数据库等地方查询判断传入的用户名或邮箱是否已存在。 + * 3. 最后返回验证操作的响应结果给客户端,包含验证是否通过以及相关提示信息。 */ - @ApiOperation(value="验证用户名和邮箱是否重复", notes="用户名和邮箱都不能用已经存在的") + @ApiOperation(value = "验证用户名和邮箱是否重复", notes = "用户名和邮箱都不能用已经存在的") @ApiImplicitParams({ @ApiImplicitParam(name = "str", value = "输入参数", required = true, dataType = "String"), @ApiImplicitParam(name = "type", value = "参数类型", required = true, dataType = "String") }) @RequestMapping("/check_valid.do") public ServerResponse checkValid(@RequestParam("str") String str, - @RequestParam("type") String type){ + @RequestParam("type") String type) { + // 记录开始验证用户名和邮箱是否重复的日志,方便后续查看验证操作的执行情况。 log.info("【开始验证用户名和邮箱是否重复】"); - ServerResponse response = userService.checkValid(str,type); + ServerResponse response = userService.checkValid(str, type); return response; } /** - * 获取登陆状态用户信息 - * 本地测试的时候,由于cookie是写到oursnai.cn域名下面的,所以需要在hosts文件中添加127.0.0.1 oursnail.cn这个解析 - * 在浏览器中测试的时候,将login方法暂时开放为GET请求,然后请求路径为:http://oursnail.cn:8081/user/login.do?username=admin&password=123456 - * 同样地,在测试获取登陆用户信息接口,也要按照域名来请求,否则拿不到token:http://oursnail.cn:8081/user/get_user_info.do + * 获取登录状态用户信息方法,处理获取已登录用户详细信息的业务逻辑。 + * 1. 从请求中读取登录token,若不存在则认为用户未登录,返回相应错误信息。 + * 2. 若token存在,从缓存中获取用户信息字符串,若为空也认为用户未登录并返回错误。 + * 3. 若获取到用户信息字符串,将其反序列化为User对象,再调用userService的getUserInfoFromDB方法从数据库获取更详细的用户信息。 + * 4. 最后返回获取用户信息的响应结果给客户端,包含获取是否成功以及用户详细信息(如果成功)。 */ - @ApiOperation(value="获取用户个人信息", notes="登陆状态下获取") + @ApiOperation(value = "获取用户个人信息", notes = "登陆状态下获取") @RequestMapping("/get_user_info.do") - public ServerResponse getUserInfo(HttpServletRequest request){ + public ServerResponse getUserInfo(HttpServletRequest request) { + // 从请求中读取登录token,用于判断用户是否登录。 String loginToken = CookieUtil.readLoginToken(request); - if(StringUtils.isEmpty(loginToken)){ + if (StringUtils.isEmpty(loginToken)) { + // 记录用户未登录的日志,方便排查问题,如用户未登录却尝试获取信息的情况。 log.info("【用户未登录,无法获取当前用户信息】"); return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); } + // 从缓存中获取用户信息字符串,若为空则表示用户未登录或缓存信息丢失等情况。 String userStr = commonCacheUtil.getCacheValue(loginToken); - if(userStr == null){ + if (userStr == null) { + // 记录用户未登录的日志,可能是缓存问题导致无法获取用户信息。 log.info("【用户未登录,无法获取当前用户信息】"); return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); } - User currentUser = JsonUtil.Str2Obj(userStr,User.class); + // 将用户信息字符串反序列化为User对象,以便后续获取详细信息。 + User currentUser = JsonUtil.Str2Obj(userStr, User.class); UserResVO userResVO = userService.getUserInfoFromDB(currentUser.getId()); - return ServerResponse.createBySuccess("登陆用户获取自身信息成功",userResVO); + return ServerResponse.createBySuccess("登陆用户获取自身信息成功", userResVO); } - /** - * 根据用户名去拿到对应的问题 + * 根据用户名获取对应的问题方法,用于处理忘记密码时获取问题的业务逻辑。 + * 1. 接收用户名作为参数,记录用户忘记密码并输入用户名获取问题的日志信息。 + * 2. 调用userService的getQuestionByUsername方法,根据传入的用户名去查找对应的问题(可能是找回密码时设置的安全问题等)。 + * 3. 最后返回获取问题操作的响应结果给客户端,包含是否获取到问题以及相关提示信息(如果获取成功则包含问题内容等)。 */ - @ApiOperation(value="根据用户名去拿到对应的问题", notes="忘记密码时首先根据用户名去获取设置的问题") + @ApiOperation(value = "根据用户名去拿到对应的问题", notes = "忘记密码时首先根据用户名去获取设置的问题") @ApiImplicitParam(name = "username", value = "用户名", required = true, dataType = "String") @RequestMapping("/forget_get_question.do") - public ServerResponse forgetGetQuestion(String username){ - log.info("【用户{}忘记密码,点击忘记密码输入用户名】",username); + public ServerResponse forgetGetQuestion(String username) { + // 记录用户忘记密码并进行获取问题操作的日志,方便后续排查忘记密码流程中的问题,如用户名不存在等情况。 + log.info("【用户{}忘记密码,点击忘记密码输入用户名】", username); ServerResponse response = userService.getQuestionByUsername(username); return response; } /** - * 校验答案是否正确 + * 校验答案是否正确方法,用于处理忘记密码时校验用户提交答案的业务逻辑。 + * 1. 接收用户名、问题和答案作为参数,记录用户忘记密码并提交问题答案的日志信息。 + * 2. 调用userService的checkAnswer方法,根据传入的用户名、问题和答案进行校验,判断答案是否正确(与之前设置的答案对比等)。 + * 3. 最后返回校验答案操作的响应结果给客户端,包含答案是否正确以及相关提示信息。 */ - @ApiOperation(value="校验答案是否正确", notes="忘记密码时输入正确的用户名之后就可以获取到问题,此时就可以输入答案") + @ApiOperation(value = "校验答案是否正确", notes = "忘记密码时输入正确的用户名之后就可以获取到问题,此时就可以输入答案") @ApiImplicitParams({ @ApiImplicitParam(name = "username", value = "用户名", required = true, dataType = "String"), @ApiImplicitParam(name = "question", value = "设置的问题", required = true, dataType = "String"), @ApiImplicitParam(name = "answer", value = "提交的答案", required = true, dataType = "String") }) @RequestMapping("/forget_check_answer.do") - public ServerResponse forgetCheckAnswer(String username,String question,String answer){ - log.info("【用户{}忘记密码,提交问题答案】",username); - ServerResponse response = userService.checkAnswer(username,question,answer); + public ServerResponse forgetCheckAnswer(String username, String question, String answer) { + // 记录用户忘记密码并提交答案进行校验的日志,有助于排查忘记密码答案校验过程中的问题,如答案错误等情况。 + log.info("【用户{}忘记密码,提交问题答案】", username); + ServerResponse response = userService.checkAnswer(username, question, answer); return response; } - /** - * 忘记密码的重置密码 + * 忘记密码的重置密码方法,用于处理忘记密码时重置密码的业务逻辑,需要进行token校验。 + * 1. 接收用户名、新密码和前端保存的token作为参数,记录用户忘记密码并输入新密码的日志信息。 + * 2. 调用userService的forgetResetPasswd方法,根据传入的用户名、新密码和token进行密码重置操作,token用于验证用户身份等(可能是之前获取问题或校验答案时生成的)。 + * 3. 最后返回重置密码操作的响应结果给客户端,包含密码是否重置成功以及相关提示信息。 */ - @ApiOperation(value="忘记密码的重置密码", notes="输入新的密码,要进行token的校验") + @ApiOperation(value = "忘记密码的重置密码", notes = "输入新的密码,要进行token的校验") @ApiImplicitParams({ @ApiImplicitParam(name = "username", value = "用户名", required = true, dataType = "String"), @ApiImplicitParam(name = "passwordNew", value = "新密码", required = true, dataType = "String"), @ApiImplicitParam(name = "forgetToken", value = "前端保存的token", required = true, dataType = "String") }) @RequestMapping("/forget_reset_password.do") - public ServerResponse forgetResetPasswd(String username,String passwordNew,String forgetToken){ - log.info("【用户{}忘记密码,输入新密码】",username); - ServerResponse response = userService.forgetResetPasswd(username,passwordNew,forgetToken); + public ServerResponse forgetResetPasswd(String username, String passwordNew, String forgetToken) { + // 记录用户忘记密码并进行重置密码操作的日志,方便后续排查忘记密码重置过程中的问题,如新密码不符合要求等情况。 + log.info("【用户{}忘记密码,输入新密码】", username); + ServerResponse response = userService.forgetResetPasswd(username, passwordNew, forgetToken); return response; } /** - * 登陆状态的重置密码 + * 登陆状态的重置密码方法,用于处理已登录用户重置密码的业务逻辑。 + * 1. 从请求中读取cookie获取登录token,若不存在则认为用户未登录,返回相应错误信息。 + * 2. 若token存在,从redis中获取用户信息字符串,若为空也认为用户未登录并返回错误。 + * 3. 若获取到用户信息字符串,将其反序列化为User对象,记录用户重置密码的日志信息。 + * 4. 调用userService的resetPasswd方法,根据传入的老密码和新密码以及用户ID进行密码重置操作(可能会先验证老密码是否正确等)。 + * 5. 最后返回重置密码操作的响应结果给客户端,包含密码是否重置成功以及相关提示信息。 */ - @ApiOperation(value="登陆状态的重置密码", notes="登陆的时候只需要输入老的密码和新密码即可") + @ApiOperation(value = "登陆状态的重置密码", notes = "登陆的时候只需要输入老的密码和新密码即可") @ApiImplicitParams({ @ApiImplicitParam(name = "passwordOld", value = "老密码", required = true, dataType = "String"), @ApiImplicitParam(name = "passwordNew", value = "新密码", required = true, dataType = "String") }) @RequestMapping("/reset_password.do") - public ServerResponse resetPasswd(String passwordOld,String passwordNew,HttpServletRequest request){ - //1.读取cookie + public ServerResponse resetPasswd(String passwordOld, String passwordNew, HttpServletRequest request) { + // 1.读取cookie String loginToken = CookieUtil.readLoginToken(request); - if(StringUtils.isEmpty(loginToken)){ + if (StringUtils.isEmpty(loginToken)) { return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); } - //2.从redis中获取用户信息 + // 2.从redis中获取用户信息 String userStr = commonCacheUtil.getCacheValue(loginToken); - if(userStr == null){ + if (userStr == null) { return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); } - User currentUser = JsonUtil.Str2Obj(userStr,User.class); - log.info("【用户{}重置密码】",currentUser); + User currentUser = JsonUtil.Str2Obj(userStr, User.class); + // 记录用户登录状态下重置密码的日志,有助于排查登录用户重置密码过程中的问题,如老密码错误等情况。 + log.info("【用户{}重置密码】", currentUser); - ServerResponse response = userService.resetPasswd(passwordOld,passwordNew,currentUser.getId()); + ServerResponse response = userService.resetPasswd(passwordOld, passwordNew, currentUser.getId()); return response; } /** - * 更新当前登陆用户信息 + * 更新当前登录用户信息方法,用于处理已登录用户更新自身信息的业务逻辑。 + * 1. 从请求中读取登录 `token`,若不存在则认为用户未登录,返回相应错误信息。 + * 2. 若 `token` 存在,从 `redis` 中获取用户信息字符串,若为空也认为用户未登录并返回错误。 + * 3. 若获取到用户信息字符串,将其反序列化为 `User` 对象,然后调用 `userService` 的 `updateInfomation` 方法,传入更新后的邮箱、电话、问题、答案以及用户 `ID`,进行用户信息更新操作(可能涉及数据库更新等)。 + * 4. 最后返回更新信息操作的响应结果给客户端,包含更新是否成功以及相关提示信息。 */ - @ApiOperation(value="更新当前登陆用户信息", notes="更新用户信息") + @ApiOperation(value = "更新当前登陆用户信息", notes = "更新用户信息") @ApiImplicitParams({ @ApiImplicitParam(name = "email", value = "邮箱", required = true, dataType = "String"), @ApiImplicitParam(name = "phone", value = "电话", required = true, dataType = "String"), @@ -204,40 +244,41 @@ public class UserController { @ApiImplicitParam(name = "answer", value = "答案", required = true, dataType = "String") }) @RequestMapping("/update_information.do") - public ServerResponse updateInformation(String email,String phone,String question,String answer,HttpServletRequest request){ - //1.读取cookie + public ServerResponse updateInformation(String email, String phone, String question, String answer, HttpServletRequest request) { + // 1.读取cookie String loginToken = CookieUtil.readLoginToken(request); - if(StringUtils.isEmpty(loginToken)){ + if (StringUtils.isEmpty(loginToken)) { return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); } - //2.从redis中获取用户信息 + // 2.从redis中获取用户信息 String userStr = commonCacheUtil.getCacheValue(loginToken); - if(userStr == null){ + if (userStr == null) { return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); } - User currentUser = JsonUtil.Str2Obj(userStr,User.class); + User currentUser = JsonUtil.Str2Obj(userStr, User.class); - ServerResponse response = userService.updateInfomation(email,phone,question,answer,currentUser.getId()); + ServerResponse response = userService.updateInfomation(email, phone, question, answer, currentUser.getId()); return response; } /** - * 登出,删除cookie和redis即可 + * 登出方法,用于处理用户登出的业务逻辑,主要是删除 `cookie` 和 `redis` 中的相关缓存记录。 + * 1. 从请求中读取登录 `token`,记录用户开始删除 `cookie` 的日志信息。 + * 2. 使用 `CookieUtil` 的 `delLoginToken` 方法删除 `cookie` 中的登录相关信息,使得客户端后续请求不再携带有效的登录标识。 + * 3. 记录用户开始删除 `redis` 缓存记录的日志信息,然后使用 `commonCacheUtil` 的 `delKey` 方法删除 `redis` 中与该用户登录相关的缓存记录,清理用户登录状态信息。 + * 4. 最后返回表示登出成功的响应结果给客户端。 */ - @ApiOperation(value="登出", notes="退出登陆,删除cookie和redis缓存") + @ApiOperation(value = "登出", notes = "退出登陆,删除cookie和redis缓存") @RequestMapping("/logout.do") - public ServerResponse logout(HttpServletRequest request,HttpServletResponse response){ + public ServerResponse logout(HttpServletRequest request, HttpServletResponse response) { log.info("【用户删除cookie】"); - //1.删除cookie + // 1.删除cookie String loginToken = CookieUtil.readLoginToken(request); - CookieUtil.delLoginToken(request,response); + CookieUtil.delLoginToken(request, response); log.info("【用户删除redis缓存】"); - //2.删除redis中缓存记录 + // 2.删除redis中缓存记录 commonCacheUtil.delKey(loginToken); return ServerResponse.createBySuccess(); } - - - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/dao/UserMapper.java b/snailmall-user-service/src/main/java/com/njupt/swg/dao/UserMapper.java index a27b541..fa8219a 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/dao/UserMapper.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/dao/UserMapper.java @@ -5,34 +5,149 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; /** + * 该接口 `UserMapper` 定义了与用户数据操作相关的方法,作为数据访问层(DAO)与数据库进行交互,主要负责对用户表执行各种数据库操作, + * 包括但不限于插入、查询、更新和删除用户记录,以及根据特定条件(如用户名、密码、邮箱等)获取用户信息或验证用户相关数据的有效性。 + * 它通过 `MyBatis` 框架的注解来实现数据库操作的映射,使得上层业务逻辑能够方便地调用这些方法来处理用户数据相关的业务逻辑, + * 实现了业务逻辑与数据访问的分离,提高了代码的可维护性和可扩展性。 + * * @Author swg. * @Date 2018/12/31 21:03 * @CONTACT 317758022@qq.com * @DESC Mapper */ @Mapper +// `@Mapper` 注解是 `MyBatis` 框架用于标识该接口为一个映射器接口的注解, +// 告诉 `MyBatis` 框架在启动时扫描并生成该接口的代理实现类,该代理类负责将接口中的方法调用转换为对应的数据库操作(通过执行预定义的 `SQL` 语句,这些语句通常在与接口同名的 `XML` 文件中定义,虽然此处未展示相关 `XML` 文件内容),从而实现与数据库的交互。 public interface UserMapper { + + /** + * 根据用户的主键 `id` 删除对应的用户记录的方法。 + * 此方法会在数据库的用户表中执行删除操作,通过主键 `id` 精准定位要删除的记录,删除成功后返回受影响的行数,通常情况下,如果成功删除一条记录,则返回值为 `1`;如果未找到匹配的记录或删除操作失败,返回值可能为 `0` 或其他错误码(具体取决于数据库的实现和操作结果)。 + * + * @param id 要删除的用户记录的主键值,作为唯一标识用于在数据库中确定要删除的特定用户记录,其数据类型为 `Integer`,必须是数据库中用户表主键字段对应的有效类型。 + * @return 返回一个整数,表示删除操作影响的行数,用于指示操作是否成功以及成功删除的记录数量。 + */ int deleteByPrimaryKey(Integer id); + /** + * 向数据库用户表中插入一条完整的用户记录的方法。 + * 它接受一个 `User` 类型的对象作为参数,该对象包含了要插入的用户的各种信息(如用户名、密码、邮箱等,具体属性取决于 `User` 类的定义), + * 方法会根据 `User` 对象的属性与数据库表字段的映射关系(这种映射关系在 `MyBatis` 的配置或实体类注解中定义,此处未详细展示)将数据插入到对应的字段中, + * 插入成功后返回受影响的行数,通常插入一条记录成功时返回值为 `1`;若插入操作失败,返回值可能为 `0` 或其他错误码(依据数据库的具体实现和操作情况而定)。 + * + * @param record 要插入到数据库的用户记录对象,包含了完整的用户信息,其属性将被映射到数据库表中的相应字段进行插入操作,数据类型为 `User`。 + + * @return 返回一个整数,表示插入操作影响的行数,用于判断插入操作是否成功以及成功插入的记录数量。 + */ int insert(User record); + /** + * 选择性插入用户记录的方法,与 `insert` 方法类似,但只会将 `User` 对象中不为 `null` 的属性值插入到数据库中。 + * 这种方式可以避免插入不必要的 `null` 值到数据库,提高数据的准确性和存储效率,同时也能根据实际需求灵活插入部分属性值, + * 插入成功后同样返回受影响的行数,成功插入一条记录时通常返回 `1`;若插入操作失败,返回值可能为 `0` 或其他错误码(取决于数据库的实现和操作结果)。 + * + * @param record 要插入的用户记录对象,包含了用户的各种信息,但只有不为 `null` 的属性值会被用于插入操作,根据对象属性与数据库表字段的映射关系将非 `null` 值插入到对应的字段,数据类型为 `User`。 + * @return 返回一个整数,表示选择性插入操作影响的行数,用于指示操作是否成功以及成功插入的记录数量。 + */ int insertSelective(User record); + /** + * 根据用户主键 `id` 从数据库中查询并获取对应的用户记录的方法。 + * 它会在数据库用户表中查找主键值为指定 `id` 的记录,如果找到匹配的记录,则返回包含该用户详细信息的 `User` 对象(详细信息包括用户名、密码、邮箱、创建时间等,具体取决于 `User` 类的定义和数据库表中的字段); + * 如果未找到匹配的记录,返回值可能为 `null`(具体取决于 `MyBatis` 的配置和查询结果处理方式,也有可能返回一个空的 `User` 对象,需根据实际情况确定)。 + * + * @param id 要查询的用户记录的主键值,作为查询条件用于在数据库中精准定位要获取的用户记录,数据类型为 `Integer`。 + * @return 返回一个 `User` 对象,表示查询到的用户记录,如果未找到则可能返回 `null` 或者空的 `User` 对象(根据具体配置)。 + */ User selectByPrimaryKey(Integer id); + /** + * 根据用户主键 `id` 选择性更新用户记录的方法,只会更新 `User` 对象中不为 `null` 的属性值到数据库中对应的记录。 + * 这样可以在更新用户信息时,仅修改实际需要更新的字段,而保持其他字段不变,更新成功后返回受影响的行数,通常成功更新一条记录时返回值为 `1`; + * 如果更新操作失败,返回值可能为 `0` 或其他错误码(取决于数据库的实现和操作结果)。 + * + * @param record 包含要更新的用户信息的对象,只有不为 `null` 的属性值会被用于更新数据库中对应 `id` 的用户记录,根据对象属性与数据库表字段的映射关系进行更新操作,数据类型为 `User`。 + * @return 返回一个整数,表示选择性更新操作影响的行数,用于判断更新操作是否成功以及成功更新的记录数量。 + */ int updateByPrimaryKeySelective(User record); + /** + * 根据用户主键 `id` 更新用户记录的方法,会将 `User` 对象中的所有属性值更新到数据库中对应的记录。 + * 此方法会根据 `User` 对象的属性与数据库表字段的映射关系,将所有属性值覆盖更新到数据库中对应的字段,更新成功后返回受影响的行数, + * 成功更新一条记录时通常返回 `1`;若更新操作失败,返回值可能为 `0` 或其他错误码(取决于数据库的实现和操作结果)。 + * + * @param record 要更新的用户记录对象,包含了完整的用户信息,其所有属性值将被更新到数据库中对应 `id` 的用户记录,根据对象属性与数据库表字段的映射关系进行更新操作,数据类型为 `User`。 + * @return 返回一个整数,表示更新操作影响的行数,用于指示更新操作是否成功以及成功更新的记录数量。 + */ int updateByPrimaryKey(User record); + /** + * 根据用户名在数据库中查询对应的用户 `id` 的方法。 + * 它会在数据库用户表中查找指定用户名的记录,并返回该用户的 `id`,如果找到匹配的用户名,返回对应的 `id` 值; + * 如果未找到匹配的用户名,返回值可能为 `null`(具体取决于 `MyBatis` 的配置和查询结果处理方式,也有可能返回一个默认值,需根据实际情况确定)。 + * 此方法在一些业务场景中(如根据用户名获取用户详细信息时,可能需要先获取 `id` 再进行进一步查询)非常有用,用于快速定位用户记录的 `id`。 + * + * @param username 要查询的用户名,作为查询条件用于在数据库中定位用户记录,数据类型为 `String`。 + * @return 返回一个整数,表示查询到的用户 `id`,如果未找到则可能返回 `null` 或者默认值(根据具体配置)。 + */ Integer selectByUsername(String username); - User selectByUsernameAndPasswd(@Param("username") String username,@Param("password") String md5Passwd); + /** + * 根据用户名和密码(密码为经过 `MD5` 加密后的字符串)在数据库中查询用户记录的方法。 + * 它会在数据库用户表中查找指定用户名和加密密码匹配的记录,如果找到匹配的记录,则返回包含该用户详细信息的 `User` 对象(详细信息如用户名、密码、邮箱等,取决于 `User` 类的定义和数据库表中的字段); + * 如果未找到匹配的记录,返回值可能为 `null`(具体取决于 `MyBatis` 的配置和查询结果处理方式,也有可能返回一个空的 `User` 对象,需根据实际情况确定)。 + * 该方法主要用于用户登录验证场景,通过传入用户名和加密后的密码,在数据库中查找是否存在匹配的用户记录,以确定用户登录是否合法。 + * + * @param username 要查询的用户名,作为查询条件之一用于在数据库中定位用户记录,数据类型为 `String`。 + * @param md5Passwd 经过 `MD5` 加密后的用户密码,作为查询条件之一与用户名一起用于在数据库中查找匹配的用户记录,确保密码在数据库中的安全性(数据库中存储的密码也应为加密形式),数据类型为 `String`。 + * @return 返回一个 `User` 对象,表示查询到的用户记录,如果未找到则可能返回 `null` 或者空的 `User` 对象(根据具体配置)。 + */ + User selectByUsernameAndPasswd(@Param("username") String username, @Param("password") String md5Passwd); + /** + * 根据邮箱在数据库中查询对应的用户 `id` 的方法。 + * 它会在数据库用户表中查找指定邮箱的记录,并返回该用户的 `id`,如果找到匹配的邮箱,返回对应的 `id` 值; + * 如果未找到匹配的邮箱,返回值可能为 `null`(具体取决于 `MyBatis` 的配置和查询结果处理方式,也有可能返回一个默认值,需根据实际情况确定)。 + * 此方法在用户注册或其他需要验证邮箱唯一性的业务场景中非常有用,用于快速确定邮箱是否已被注册(通过查询是否存在对应的 `id`)。 + * + * @param str 要查询的邮箱地址,作为查询条件用于在数据库中定位用户记录,数据类型为 `String`。 + * @return 返回一个整数,表示查询到的用户 `id`,如果未找到则可能返回 `null` 或者默认值(根据具体配置)。 + */ Integer selectByEmail(String str); + /** + * 根据用户名在数据库中获取用户详细信息的方法。 + * 它会在数据库用户表中查找指定用户名的记录,并返回包含该用户详细信息的 `User` 对象(详细信息如用户名、密码、邮箱、创建时间等,取决于 `User` 类的定义和数据库表中的字段); + * 如果未找到匹配的用户名,返回值可能为 `null`(具体取决于 `MyBatis` 的配置和查询结果处理方式,也有可能返回一个空的 `User` 对象,需根据实际情况确定)。 + * 该方法在需要展示用户详细信息或根据用户名获取用户完整信息的业务场景中使用,例如用户个人信息页面展示、用户信息更新前获取原始信息等。 + * + * @param username 要查询的用户名,作为查询条件用于在数据库中定位用户记录,数据类型为 `String`。 + * @return 返回一个 `User` 对象,表示查询到的用户详细信息,如果未找到则可能返回 `null` 或者空的 `User` 对象(根据具体配置)。 + */ User getUserByUsername(String username); + /** + * 根据用户名、问题和答案在数据库中获取用户详细信息的方法。 + * 它会在数据库用户表中查找指定用户名、问题和答案(通常用于忘记密码场景下的验证)匹配的记录,如果找到匹配的记录,则返回包含该用户详细信息的 `User` 对象(详细信息如用户名、密码、邮箱等,取决于 `User` 类的定义和数据库表中的字段); + * 如果未找到匹配的记录,返回值可能为 `null`(具体取决于 `MyBatis` 的配置和查询结果处理方式,也有可能返回一个空的 `User` 对象,需根据实际情况确定)。 + * 该方法主要用于忘记密码流程中,根据用户提供的用户名、问题和答案来验证用户身份,并获取用户详细信息,以便后续进行密码重置等操作。 + * + * @param username 要查询的用户名,作为查询条件之一用于在数据库中定位用户记录,数据类型为 `String`。 + * @param question 要查询的问题,作为查询条件之一与用户名一起用于在数据库中定位用户记录,通常是用户在忘记密码时设置的安全问题,数据类型为 `String`。 + * @param answer 要查询的答案,作为查询条件之一与用户名和问题一起用于在数据库中定位用户记录,是用户对安全问题的回答,用于验证用户身份,数据类型为 `String`。 + * @return 返回一个 `User` 对象,表示查询到的用户详细信息,如果未找到则可能返回 `null` 或者空的 `User` 对象(根据具体配置)。 + */ User getUserByUsernameQuestionAnswer(String username, String question, String answer); + /** + * 检查邮箱有效性的方法,根据传入的邮箱地址和可选的用户 `id`(用于在特定场景下排除自身邮箱检查),在数据库中检查该邮箱是否已被其他用户使用。 + * 如果邮箱未被其他用户使用(或者在更新邮箱时,排除了当前用户自己的邮箱且该邮箱是当前用户自己的邮箱),则返回 `1`,表示邮箱有效; + * 如果邮箱已被其他用户使用,则返回 `0`,表示邮箱无效。此方法在用户注册和更新邮箱等业务场景中用于验证邮箱的唯一性。 + * + * @param email 要检查的邮箱地址,作为检查条件用于在数据库中查找是否存在其他用户使用该邮箱,数据类型为 `String`。 + * @param userId 用户 `id`(可选参数),在更新邮箱场景中用于排除当前用户自己的邮箱检查,如果传入 `null`,则表示不考虑用户 `id`,直接检查邮箱的唯一性; + * 如果传入具体的用户 `id`,则在检查邮箱唯一性时会排除该 `id` 对应的用户自己的邮箱(假设该用户正在更新自己的邮箱),数据类型为 `Integer`,并通过 `@Param` 注解指定参数名为 `userId` 且非必需(`required = false`)。 + * @return 返回一个整数,`1` 表示邮箱有效,`0` 表示邮箱无效,用于指示邮箱检查的结果。 + */ Integer checkEmailValid(@Param("email") String email,@Param("userId") Integer userId); -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/entity/User.java b/snailmall-user-service/src/main/java/com/njupt/swg/entity/User.java index 4354b81..847ac67 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/entity/User.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/entity/User.java @@ -9,35 +9,54 @@ import java.io.Serializable; import java.util.Date; /** + * 这个类 `User` 是项目中的用户实体类,用于封装与用户相关的各种属性信息,它对应着数据库中存储用户数据的表结构, + * 在整个项目的不同层(如数据访问层、业务逻辑层、控制层等)之间传递用户相关的数据,是实现面向对象编程与数据库交互的重要基础。 + * 通过使用 `Lombok` 注解,简化了代码中对常规方法(如构造函数、`Getter` 和 `Setter` 方法、`toString` 方法等)的编写,提高了代码的简洁性和可读性。 + * * @Author swg. * @Date 2018/12/31 21:01 * @CONTACT 317758022@qq.com * @DESC 用户实体类 */ +// `@Data` 注解是 `Lombok` 提供的一个便捷注解,它会自动为类中的所有非静态、非 `final` 的字段生成 `Getter`、`Setter` 方法, +// 以及 `equals`、`hashCode` 和 `toString` 方法(不过这里因为又单独使用了 `@ToString` 注解,所以 `toString` 方法会按照 `@ToString` 注解的配置来生成),方便对类中属性的访问和操作,减少了大量重复的代码编写工作。 @Data +// `@NoArgsConstructor` 注解由 `Lombok` 提供,它会为该类生成一个无参的构造函数,这在一些情况下(比如使用某些框架进行对象实例化时要求类必须有默认的无参构造函数)是很有用的,使得创建 `User` 类的实例更加方便灵活。 @NoArgsConstructor +// `@AllArgsConstructor` 注解同样来自 `Lombok`,它会为类生成一个包含所有字段的全参构造函数,这样在初始化 `User` 类对象时,可以通过传入所有属性的值一次性完成对象的创建,便于在需要完整初始化对象的场景中使用。 @AllArgsConstructor +// `@ToString` 注解用于生成一个方便查看对象信息的 `toString` 方法,默认情况下它会输出类名以及所有字段的名称和对应的值,便于在调试代码、打印对象信息等场景中直观地了解对象的状态。 @ToString +// 实现 `Serializable` 接口表示该类的对象可以被序列化,意味着可以将 `User` 类的对象转换为字节流进行存储(比如保存到文件中或者在网络传输过程中进行传递等), +// 并且在需要的时候能够从字节流中反序列化还原出原来的对象,这在很多涉及数据持久化和分布式系统的场景中是非常必要的操作。 public class User implements Serializable { + // 用户的唯一标识符,通常对应数据库表中的主键字段,用于在系统中唯一确定一个用户,其数据类型为 `Integer`,可以存储整数形式的用户 `ID` 值。 private Integer id; + // 用户名,用于用户登录以及在系统中标识用户的身份,是一个字符串类型的属性,存储用户在注册时设定的用户名信息。 private String username; + // 用户密码,存储用户登录时需要输入验证的密码信息,以加密后的字符串形式存储(在实际应用中应该采用安全的加密算法进行加密处理,比如 `MD5` 等常见加密方式),保证用户密码的安全性,数据类型为 `String`。 private String password; + // 用户的电子邮箱地址,可用于接收系统发送的通知邮件、找回密码等功能相关的邮件信息,是一个字符串类型的属性,存储符合邮箱格式规范的邮箱地址。 private String email; + // 用户的电话号码,可用于联系用户或者作为一些验证操作(如手机验证码登录等功能,如果有相关拓展功能的话)的依据,数据类型为 `String`,存储用户的手机号码等电话号码信息。 private String phone; + // 安全问题,通常用于用户忘记密码时,通过回答预先设置的安全问题来验证身份,进而进行密码重置等操作,是一个字符串类型的属性,存储用户自己设置的安全问题内容。 private String question; + // 安全问题的答案,与 `question` 属性相对应,用于在忘记密码验证身份环节,用户输入答案与数据库中存储的答案进行比对验证,数据类型为 `String`,存储用户针对所设置安全问题的答案内容。 private String answer; - //角色0-管理员,1-普通用户 + // 用户角色字段,用于区分不同权限的用户,这里定义了两种角色,`0` 表示管理员,拥有系统的高级管理权限(如用户管理、系统配置等权限),`1` 表示普通用户,只具有普通的使用系统功能的权限,数据类型为 `Integer`。 private Integer role; + // 用户创建时间,记录用户账号在系统中被创建的具体时间点,数据类型为 `Date`,可以准确记录创建时间的日期和时间信息,方便后续进行数据分析、用户行为统计等操作。 private Date createTime; + // 用户信息更新时间,每当用户的相关信息(如密码、邮箱、电话号码等)发生修改时,会更新这个字段的值,记录最后一次更新的时间点,数据类型为 `Date`,同样便于跟踪用户信息的变更情况以及进行相关的数据统计和分析。 private Date updateTime; - } \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/service/IUserService.java b/snailmall-user-service/src/main/java/com/njupt/swg/service/IUserService.java index e50c449..f3329b5 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/service/IUserService.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/service/IUserService.java @@ -5,35 +5,123 @@ import com.njupt.swg.entity.User; import com.njupt.swg.vo.UserResVO; /** + * 该接口 `IUserService` 定义了一系列与用户相关的业务逻辑方法,作为业务逻辑层对外提供服务的契约, + * 它规定了处理用户登录、注册、信息验证、密码重置、信息更新以及获取用户详细信息等功能的方法签名, + * 不同的业务实现类需要实现该接口来具体完成这些方法对应的业务逻辑,从而实现业务逻辑与控制层、数据访问层的解耦, + * 方便代码的维护、扩展以及单元测试等操作,提高整个项目的可维护性和可扩展性。 + * * @Author swg. * @Date 2018/12/31 21:07 * @CONTACT 317758022@qq.com * @DESC */ public interface IUserService { - /** 用户登陆 **/ - ServerResponse login(String username,String password); - /** 用户注册 **/ + /** + * 用户登录方法,用于处理用户登录的业务逻辑。 + * 接收用户名和密码作为参数,在业务逻辑层内部会调用数据访问层(如 `UserMapper` 等相关数据操作接口)去数据库中验证用户名和密码是否匹配, + * 同时可能还会进行一些其他与登录相关的逻辑处理(比如记录登录日志、更新用户登录相关状态等), + * 最后返回一个 `ServerResponse` 类型的对象,其中 `ServerResponse` 用于封装操作的响应结果(包含成功或失败状态码、提示信息等), + * 如果登录成功,`UserResVO` 则可能包含一些与登录用户相关的详细信息(比如用户角色、权限信息、额外的用户资料等,具体取决于 `UserResVO` 的定义),用于后续的业务处理或者返回给客户端展示。 + * + * @param username 要登录的用户名,是用户登录操作必不可少的关键参数,由客户端传入,用于在数据库中查找对应的用户记录进行密码验证,数据类型为 `String`。 + * @param password 要登录的用户密码,同样是登录操作的核心参数之一,需要与传入的用户名一起进行匹配验证,数据类型为 `String`,并且在实际应用中通常应该是经过加密处理后的密码字符串(比如采用 `MD5` 等加密方式)。 + * @return 返回 `ServerResponse` 类型的对象,包含登录操作的响应结果以及登录成功时对应的用户详细信息(如果成功),客户端可根据此对象判断登录是否成功并获取相应的用户信息。 + */ + ServerResponse login(String username, String password); + + /** + * 用户注册方法,用于处理用户注册的业务逻辑。 + * 接收一个 `User` 类型的对象作为参数,该对象包含了用户注册时填写的各种详细信息(如用户名、密码、邮箱、电话号码等,具体取决于 `User` 类的定义), + * 在业务逻辑层内部会调用数据访问层进行一系列的注册相关操作,比如验证用户名和邮箱是否已被使用(通常会涉及到数据库查询操作,确保唯一性)、将用户信息插入到数据库中, + * 同时可能还会有一些其他与注册相关的逻辑处理(比如发送注册成功邮件、初始化用户相关的默认配置等),最后返回一个 `ServerResponse` 类型的对象,用于封装注册操作的响应结果(包含成功或失败状态码、提示信息等),告知客户端注册是否成功。 + * + * @param user 包含用户注册详细信息的 `User` 对象,作为注册操作的数据来源,其属性将被用于在数据库中创建新的用户记录,数据类型为 `User`。 + * @return 返回 `ServerResponse` 类型的对象,包含注册操作的响应结果,客户端可根据此对象得知注册是否成功以及获取相应的提示信息。 + */ ServerResponse register(User user); - /** 判断用户名和邮箱是否重复 **/ + /** + * 判断用户名和邮箱是否重复方法,用于处理验证用户名和邮箱唯一性的业务逻辑。 + * 接收要验证的字符串和类型作为参数,根据传入的 `type` 参数来确定 `str` 代表的是用户名还是邮箱,然后调用数据访问层去数据库中查询验证, + * 判断传入的用户名或者邮箱在系统中是否已经存在,最后返回一个 `ServerResponse` 类型的对象,用于封装验证操作的响应结果(包含验证是否通过、相应的提示信息等),告知客户端验证结果。 + * + * @param str 要验证的用户名或者邮箱内容,是验证操作的关键输入数据,客户端传入此参数,其具体代表的是用户名还是邮箱由 `type` 参数决定,数据类型为 `String`。 + * @param type 用于指定 `str` 参数类型的字符串,取值应该是能够明确表示 `str` 代表用户名还是邮箱的标识(比如可能取值为 "username" 表示用户名,"email" 表示邮箱等),数据类型为 `String`,用于业务逻辑层准确判断要验证的内容并进行相应的数据库查询验证操作。 + * @return 返回 `ServerResponse` 类型的对象,包含验证操作的响应结果,客户端可根据此对象得知用户名或邮箱是否可用以及获取相应的提示信息。 + */ ServerResponse checkValid(String str, String type); - /** 根据用户名去获取设置的忘记密码的问题 **/ + /** + * 根据用户名去获取设置的忘记密码的问题方法,用于处理忘记密码流程中获取用户预先设置的安全问题的业务逻辑。 + * 接收用户名作为唯一参数,在业务逻辑层内部会调用数据访问层根据传入的用户名去数据库中查找对应的用户记录,并获取该用户设置的忘记密码相关的安全问题(通常是用户在注册或者个人信息设置阶段设置好的), + * 最后返回一个 `ServerResponse` 类型的对象,用于封装获取问题操作的响应结果(包含是否获取到问题、相应的提示信息等),告知客户端获取问题的情况,若获取成功则可以返回问题内容供客户端展示给用户进行后续的答案填写操作。 + * + * @param username 要获取忘记密码问题的用户名,作为查找用户记录以及获取对应问题的关键依据,由客户端传入,数据类型为 `String`。 + * @return 返回 `ServerResponse` 类型的对象,包含获取问题操作的响应结果,客户端可根据此对象得知是否获取到问题以及获取相应的提示信息。 + */ ServerResponse getQuestionByUsername(String username); - /** 校验问题对应的答案是否正确 **/ + /** + * 校验问题对应的答案是否正确方法,用于处理忘记密码流程中验证用户输入的答案是否正确的业务逻辑。 + * 接收用户名、问题和答案作为参数,在业务逻辑层内部会调用数据访问层根据传入的用户名、问题和答案去数据库中查找对应的用户记录,并验证用户输入的答案与数据库中存储的该用户对应问题的答案是否一致, + * 最后返回一个 `ServerResponse` 类型的对象,用于封装校验答案操作的响应结果(包含答案是否正确、相应的提示信息等),告知客户端验证结果,若答案正确则可以继续后续的密码重置等操作。 + * + * @param username 要校验答案对应的用户名,用于在数据库中定位用户记录进行答案验证,由客户端传入,数据类型为 `String`。 + * @param question 要校验答案对应的问题,同样用于在数据库中准确查找用户记录以及对应存储的答案进行比对验证,由客户端传入,数据类型为 `String`。 + * @param answer 用户输入的要校验的答案,与数据库中存储的答案进行比对,由客户端传入,数据类型为 `String`。 + * @return 返回 `ServerResponse` 类型的对象,包含校验答案操作的响应结果,客户端可根据此对象得知答案是否正确以及获取相应的提示信息。 + */ ServerResponse checkAnswer(String username, String question, String answer); - /** 重置密码 **/ + /** + * 重置密码方法(用于忘记密码的场景),用于处理忘记密码后重置密码的业务逻辑。 + * 接收用户名、新密码和前端保存的 `forgetToken`(可能是在忘记密码流程中前面步骤生成的用于验证身份等用途的令牌)作为参数, + * 在业务逻辑层内部会调用数据访问层以及进行一些其他相关的验证逻辑(比如验证 `forgetToken` 的有效性、通过用户名等信息进一步确认用户身份等),然后将新密码更新到数据库中对应的用户记录, + * 最后返回一个 `ServerResponse` 类型的对象,用于封装密码重置操作的响应结果(包含密码是否重置成功、相应的提示信息等),告知客户端重置密码的情况。 + * + * @param username 要重置密码的用户名,用于在数据库中定位用户记录进行密码更新操作,由客户端传入,数据类型为 `String`。 + * @param passwordNew 要设置的新密码,作为重置密码操作的关键数据,将被更新到数据库中对应的用户记录,由客户端传入,数据类型为 `String`。 + * @param forgetToken 前端保存的用于忘记密码流程中验证身份等用途的令牌,用于确保密码重置操作的合法性和安全性,由客户端传入,数据类型为 `String`。 + * @return 返回 `ServerResponse` 类型的对象,包含密码重置操作的响应结果,客户端可根据此对象得知密码是否重置成功以及获取相应的提示信息。 + */ ServerResponse forgetResetPasswd(String username, String passwordNew, String forgetToken); - /** 登陆状态下重置密码 **/ + /** + * 登陆状态下重置密码方法,用于处理已登录用户重置密码的业务逻辑。 + * 接收老密码、新密码和用户 `ID` 作为参数,在业务逻辑层内部会先验证老密码是否正确(通过调用数据访问层根据用户 `ID` 等信息查找数据库中的用户记录并比对密码), + * 若老密码验证通过,则将新密码更新到数据库中对应的用户记录,同时可能还会进行一些其他与密码更新相关的逻辑处理(比如记录密码更新日志、更新相关的登录状态等), + * 最后返回一个 `ServerResponse` 类型的对象,用于封装密码重置操作的响应结果(包含密码是否重置成功、相应的提示信息等),告知客户端重置密码的情况。 + * + * @param passwordOld 已登录用户当前使用的老密码,用于验证用户身份,确保只有知道当前密码的用户才能进行密码重置操作,由客户端传入,数据类型为 `String`。 + * @param passwordNew 要设置的新密码,作为重置密码操作的关键数据,将被更新到数据库中对应的用户记录,由客户端传入,数据类型为 `String`。 + * @param userId 已登录用户的唯一标识符,用于在数据库中准确查找对应的用户记录进行密码验证和更新操作,由客户端传入,数据类型为 `int`。 + * @return 返回 `ServerResponse` 类型的对象,包含密码重置操作的响应结果,客户端可根据此对象得知密码是否重置成功以及获取相应的提示信息。 + */ ServerResponse resetPasswd(String passwordOld, String passwordNew, int userId); - /** 登陆状态下更新个人信息(更新策略为:如果用户某一项不填,表示保持原样不变) **/ + /** + * 登陆状态下更新个人信息方法(更新策略为:如果用户某一项不填,表示保持原样不变),用于处理已登录用户更新自身部分信息(如邮箱、电话、安全问题、答案等)的业务逻辑。 + * 接收邮箱、电话、安全问题、答案以及用户 `ID` 作为参数,在业务逻辑层内部会根据传入的参数以及更新策略,调用数据访问层去数据库中查找对应的用户记录, + * 然后只更新用户传入了新值的那些字段(对于未传入值的字段保持数据库中原有的值不变),同时可能还会进行一些其他与信息更新相关的逻辑处理(比如记录信息更新日志、更新相关的更新时间字段等), + * 最后返回一个 `ServerResponse` 类型的对象,用于封装信息更新操作的响应结果(包含信息是否更新成功、相应的提示信息等),告知客户端更新信息的情况。 + * + * @param email 用户要更新的邮箱地址,如果不填则表示保持原有邮箱地址不变,由客户端传入,数据类型为 `String`。 + * @param phone 用户要更新的电话号码,如果不填则表示保持原有电话号码不变,由客户端传入,数据类型为 `String`。 + * @param question 用户要更新的安全问题,如果不填则表示保持原有安全问题不变,由客户端传入,数据类型为 `String`。 + * @param answer 用户要更新的安全问题的答案,如果不填则表示保持原有答案不变,由客户端传入,数据类型为 `String`。 + * @param userId 要更新信息的已登录用户的唯一标识符,用于在数据库中准确查找对应的用户记录进行信息更新操作,由客户端传入,数据类型为 `Integer`。 + * @return 返回 `ServerResponse` 类型的对象,包含信息更新操作的响应结果,客户端可根据此对象得知信息是否更新成功以及获取相应的提示信息。 + */ ServerResponse updateInfomation(String email, String phone, String question, String answer, Integer userId); + /** + * 根据用户 `ID` 从数据库中获取用户详细信息的方法,用于在需要获取已登录用户或者指定 `ID` 用户的详细信息的业务场景中(比如展示用户个人信息页面等)进行数据查询操作。 + * 在业务逻辑层内部会调用数据访问层根据传入的用户 `ID` 去数据库中查找对应的用户记录,然后将获取到的用户信息封装到 `UserResVO` 类型的对象中返回(`UserResVO` 可能包含了更适合展示或者业务使用的用户信息格式和内容,具体取决于其定义), + * 如果未找到对应的用户记录,则可能返回 `null` 或者一个默认的空 `UserResVO` 对象(具体取决于业务逻辑层的实现方式)。 + * + * @param userId 要获取详细信息的用户的唯一标识符,作为在数据库中查找用户记录的关键依据,由客户端传入,数据类型为 `Integer`。 + * @return 返回 `UserResVO` 类型的对象,包含查询到的用户详细信息,如果未找到对应用户记录则可能返回 `null` 或者空的 `UserResVO` 对象(根据具体实现),用于后续的业务处理或者展示给客户端。 + */ UserResVO getUserInfoFromDB(Integer userId); -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/service/UserServiceImpl.java b/snailmall-user-service/src/main/java/com/njupt/swg/service/UserServiceImpl.java index e2cc35b..e3d085c 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/service/UserServiceImpl.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/service/UserServiceImpl.java @@ -8,6 +8,7 @@ import com.njupt.swg.common.utils.MD5Util; import com.njupt.swg.dao.UserMapper; import com.njupt.swg.entity.User; import com.njupt.swg.vo.UserResVO; +import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.curator.framework.CuratorFramework; diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/swagger/SwaggerConfig.java b/snailmall-user-service/src/main/java/com/njupt/swg/swagger/SwaggerConfig.java index cb1775e..d6e74e1 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/swagger/SwaggerConfig.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/swagger/SwaggerConfig.java @@ -9,34 +9,52 @@ import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; /** + * 这个类 `SwaggerConfig` 是用于配置 `Swagger` 的配置类,通过 `Spring` 的配置机制(`@Configuration` 注解标识), + * 它定义了如何生成 `Swagger` 接口文档相关的信息以及配置 `Swagger` 的核心组件 `Docket`,使得在项目中能够方便地生成清晰、规范的接口文档,方便前后端开发人员进行接口的对接以及对项目接口的整体了解和测试等操作。 + * * @Author swg. * @Date 2019/1/8 11:39 * @CONTACT 317758022@qq.com * @DESC */ @Configuration +// `@Configuration` 注解表明这个类是一个 `Spring` 的配置类,`Spring` 容器在启动时会扫描并解析该类,将其中定义的 `Bean`(通过 `@Bean` 注解方法定义的对象)注册到容器中, +// 以便在项目的其他地方可以方便地注入和使用这些对象,在这里主要用于配置 `Swagger` 相关的组件和信息。 public class SwaggerConfig { - // 接口版本号 + + // 接口版本号,用于标识当前接口文档所对应的项目接口版本,方便使用者了解接口的迭代情况以及不同版本之间的差异,这里定义为 "3.0",数据类型为 `String`。 private final String version = "3.0"; - // 接口大标题 + // 接口大标题,通常展示在接口文档的顶部,是对整个接口文档内容的一个总体概括性标题,此处设置为 "快乐蜗牛商城V3.0文档",用于明确文档所属的项目及版本,数据类型为 `String`。 private final String title = "快乐蜗牛商城V3.0文档"; - // 具体的描述 + // 具体的描述,用于详细说明接口文档涵盖的服务内容等相关信息,在这里明确指出是 "用户服务",让查看文档的人能快速知晓该文档主要聚焦的业务范围,数据类型为 `String`。 private final String description = "用户服务"; - // 服务说明url + // 服务说明url,一般指向一个网页地址,该地址可以提供关于整个服务更详细的说明、使用条款、服务协议等相关信息,这里设置为 "http://www.kingeid.com",数据类型为 `String`。 private final String termsOfServiceUrl = "http://www.kingeid.com"; - // 接口作者联系方式 + // 接口作者联系方式,通过 `Contact` 类的实例来封装作者的相关联系信息,包括姓名、个人主页(如 `GitHub` 主页等)以及邮箱地址,方便使用者在有问题或者需要进一步沟通时能联系到接口的作者,这里进行了相应的初始化设置。 private final Contact contact = new Contact("fourColor", "https://github.com/sunweiguo", "sunweiguode@gmail.com"); + /** + * 这个方法使用 `@Bean` 注解,用于创建并返回一个 `Docket` 对象,`Docket` 是 `Swagger` 框架中用于配置和定制接口文档生成的核心组件。 + * 通过对 `Docket` 的配置,可以指定要生成文档的接口范围、添加接口相关的元信息(如标题、版本、描述等)以及其他各种定制化的设置, + * 最终生成符合项目需求的接口文档,返回的 `Docket` 对象会被 `Spring` 容器管理并在生成接口文档的过程中发挥关键作用。 + * + * @return 返回一个配置好的 `Docket` 对象,用于后续 `Swagger` 生成接口文档的相关操作。 + */ @Bean public Docket buildDocket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(buildApiInf()) .select().build(); } + /** + * 这个私有方法用于构建 `ApiInfo` 对象,`ApiInfo` 是用于封装接口文档的基本信息(如标题、版本、描述、服务网址、作者联系方式等)的对象, + * 这些信息会展示在生成的 `Swagger` 接口文档的开头部分,让使用者能够快速了解接口文档的整体概况和关键信息,通过 `ApiInfoBuilder` 来逐步构建并返回 `ApiInfo` 对象。 + * + * @return 返回一个构建好的 `ApiInfo` 对象,包含了接口文档的标题、版本、描述、服务网址以及作者联系方式等信息,用于配置 `Docket` 对象中的接口文档基本信息部分。 + */ private ApiInfo buildApiInf() { return new ApiInfoBuilder().title(title).termsOfServiceUrl(termsOfServiceUrl).description(description) .version(version).contact(contact).build(); } - } \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/vo/UserResVO.java b/snailmall-user-service/src/main/java/com/njupt/swg/vo/UserResVO.java index 009f0c4..042db86 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/vo/UserResVO.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/vo/UserResVO.java @@ -1,24 +1,38 @@ package com.njupt.swg.vo; import lombok.Data; - import java.util.Date; /** + * 这个类 `UserResVO` 是作为值对象(Value Object,简称 VO)存在的,主要用于在不同层之间传递用户相关的数据,尤其是在向客户端(如前端页面)返回用户信息时使用。 + * 它对从数据库或业务逻辑处理后得到的用户信息进行了有选择性的封装,只包含了前端可能需要展示或者使用的部分用户属性,与实际数据库实体类(如 `com.njupt.swg.entity.User`)相比,可能更贴合前端的需求, + * 并且通过使用 `Lombok` 的 `@Data` 注解简化了代码中常规方法(如 `Getter`、`Setter` 方法等)的编写,提高了代码的简洁性和可读性。 + * * @Author swg. * @Date 2019/1/1 11:50 * @CONTACT 317758022@qq.com * @DESC */ @Data +// `@Data` 注解是 `Lombok` 提供的一个便捷注解,它会自动为类中的所有非静态、非 `final` 的字段生成 `Getter`、`Setter` 方法, +// 以及 `equals`、`hashCode` 和 `toString` 方法,方便对类中属性的访问和操作,减少了大量重复的代码编写工作,使得在使用这个类的对象时,可以很方便地获取和设置各个属性的值。 public class UserResVO { + // 用户的唯一标识符,用于在系统中唯一确定一个用户,该属性会传递到前端,方便前端在进行一些与用户相关的操作(如展示用户特定信息、发起针对特定用户的请求等)时使用,数据类型为 `int`。 private int id; + // 用户名,用于在前端页面展示给用户或者进行一些基于用户名的操作(如显示欢迎信息、根据用户名查找相关记录等),是一个字符串类型的属性,存储用户在注册时设定的用户名信息,数据类型为 `String`。 private String username; + // 用户的电子邮箱地址,前端可能会利用该邮箱地址展示给用户查看,或者用于一些与邮件相关的功能提示(如告知用户可以通过该邮箱接收系统通知等),数据类型为 `String`。 private String email; + // 用户角色字段,用于区分不同权限的用户,前端可以根据该角色信息来展示不同的用户界面或者控制用户对某些功能的访问权限,这里定义了不同的整数值表示不同角色(具体含义可能在业务中有相应定义),数据类型为 `int`。 private int role; + // 用户的电话号码,前端可以展示该电话号码信息给用户,或者用于一些与电话相关的功能交互(如点击拨打按钮等,若有相关功能拓展的话),数据类型为 `String`。 private String phone; + // 安全问题,通常用于在前端页面提示用户之前设置过的安全问题相关信息(比如在忘记密码等流程中展示给用户回顾),是一个字符串类型的属性,存储用户自己设置的安全问题内容,数据类型为 `String`。 private String question; + // 安全问题的答案,虽然从安全角度来说一般不会直接展示给用户,但在某些特定的业务逻辑中(比如验证用户是否正确输入过答案等情况)可能会涉及到对该属性的使用,数据类型为 `String`。 private String answer; - private Date createTime;//返回前端的是时间戳 + // 用户创建时间,不过这里需要注意的是,在返回前端时通常是以时间戳的形式(便于前端进行时间的格式化展示等操作,根据前端具体的时间处理机制来定),用于前端展示用户账号的创建时间信息,数据类型为 `Date`。 + private Date createTime; + // 用户信息更新时间,同样可以在前端展示给用户,让用户了解自己的信息最后一次更新是什么时候,数据类型为 `Date`。 private Date updateTime; -} +} \ No newline at end of file