From bd62c859612bbfaed174a574bb1480c4a3256c12 Mon Sep 17 00:00:00 2001 From: pb4nq52pf <2947588492@qq.com> Date: Wed, 18 Dec 2024 19:59:35 +0800 Subject: [PATCH] accept pr4 --- pom.xml | 166 +++--- .../swg/SnailmallEurekaServerApplication.java | 8 +- snailmall-keygen-service/pom.xml | 42 +- .../swg/keygen/SnowFlakeKeyGenerator.java | 36 +- .../com/njupt/swg/keygen/TimeService.java | 5 + .../njupt/swg/keygen/WorkerIDSenquence.java | 21 + .../njupt/swg/web/KeyGeneratorController.java | 8 +- snailmall-shipping-service/pom.xml | 85 +++- .../com/njupt/swg/cache/CommonCacheUtil.java | 36 +- .../com/njupt/swg/cache/JedisPoolWrapper.java | 19 +- .../java/com/njupt/swg/cache/Parameters.java | 18 +- .../njupt/swg/common/constants/Constants.java | 10 +- .../exception/ExceptionHandlerAdvice.java | 14 + .../common/exception/SnailmallException.java | 11 +- .../njupt/swg/common/resp/ResponseEnum.java | 12 +- .../njupt/swg/common/resp/ServerResponse.java | 28 +- .../njupt/swg/common/utils/CookieUtil.java | 50 +- .../njupt/swg/common/utils/DateTimeUtil.java | 59 ++- .../com/njupt/swg/common/utils/JsonUtil.java | 77 ++- .../com/njupt/swg/common/utils/MD5Util.java | 19 +- .../njupt/swg/controller/BaseController.java | 15 +- .../swg/controller/ShippingController.java | 63 ++- .../java/com/njupt/swg/dao/ShippingMapper.xml | 178 ++++--- .../java/com/njupt/swg/entity/Shipping.java | 16 + .../main/java/com/njupt/swg/entity/User.java | 16 +- .../src/main/resources/logback.xml | 49 +- snailmall-user-service/pom.xml | 80 ++- .../swg/SnailmallUserServiceApplication.java | 20 +- .../njupt/swg/controller/UserController.java | 262 +++++----- .../com/njupt/swg/dao/mapper/UserMapper.xml | 155 ++++-- .../main/java/com/njupt/swg/entity/User.java | 31 +- .../njupt/swg/service/UserServiceImpl.java | 476 ++++++++++++++---- .../com/njupt/swg/swagger/SwaggerConfig.java | 38 +- .../src/main/resources/bootstrap.yml | 50 ++ .../src/main/resources/logback.xml | 35 +- 35 files changed, 1646 insertions(+), 562 deletions(-) diff --git a/pom.xml b/pom.xml index 0920b96..3512910 100644 --- a/pom.xml +++ b/pom.xml @@ -1,20 +1,37 @@ + + 4.0.0 + org.springframework.boot spring-boot-starter-parent 2.0.3.RELEASE + com.njupt.swg + spring-cloud-for-snailmall + 0.0.1-SNAPSHOT + spring-cloud-for-snailmall + 聚合工程的父工程 + pom + snailmall-eureka-server snailmall-config-server @@ -28,14 +45,23 @@ snailmall-order-service + + UTF-8 + UTF-8 + 1.8 + + org.springframework.cloud spring-cloud-dependencies @@ -44,201 +70,175 @@ import - + + mysql mysql-connector-java 5.1.46 - + + org.mybatis.spring.boot mybatis-spring-boot-starter 1.2.0 - + + org.codehaus.jackson jackson-mapper-asl 1.9.13 + com.fasterxml.jackson.dataformat jackson-dataformat-avro 2.9.0 - + + com.alibaba druid 1.0.18 - + + com.google.guava guava 20.0 + org.apache.commons commons-lang3 3.5 + commons-collections commons-collections 3.2.1 - + + joda-time joda-time 2.3 + commons-net commons-net 3.1 + commons-fileupload commons-fileupload 1.2.2 + - commons-io + commons-io commons-io 2.0.1 - + + com.github.pagehelper pagehelper 4.1.0 + com.github.miemiedev mybatis-paginator 1.2.17 + com.github.jsqlparser jsqlparser 0.9.4 - + + commons-codec commons-codec 1.10 + commons-configuration commons-configuration 1.10 + commons-lang commons-lang 2.6 + commons-logging commons-logging 1.2 + com.google.zxing core 2.1 + com.google.code.gson gson 2.8.2 + org.hamcrest hamcrest-core 1.3 - + + redis.clients jedis 2.9.0 - - - - org.projectlombok - lombok - 1.16.6 - - - - ch.qos.logback - logback-core - 1.1.6 - - - ch.qos.logback - logback-classic - 1.1.6 - - - ch.qos.logback - logback-access - 1.1.2 - - - - org.apache.curator - curator-framework - 2.12.0 - - - org.apache.curator - curator-recipes - 2.12.0 - - - - - com.alipay - sdk-java - 20161213 - - - com.alipay - trade-sdk - 20161215 - - - - - io.springfox - springfox-swagger2 - 2.5.0 - - - io.springfox - springfox-swagger-ui - 2.5.0 - - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - + + + 4.0.0 + com.njupt.swg spring-cloud-for-snailmall 0.0.1-SNAPSHOT + snailmall-keygen-service + 0.0.1-SNAPSHOT + snailmall-keygen-service + 全局唯一ID生产服务-雪花算法 + + org.springframework.boot spring-boot-starter + org.springframework.boot spring-boot-starter-web + org.apache.curator curator-recipes + org.springframework.boot spring-boot-starter-actuator + org.springframework.cloud spring-cloud-config-client + org.springframework.cloud spring-cloud-starter-bus-amqp + org.springframework.cloud spring-cloud-starter-zipkin + org.projectlombok lombok + org.springframework.cloud spring-cloud-starter-netflix-eureka-client + + org.springframework.boot spring-boot-maven-plugin @@ -68,4 +108,4 @@ - + \ No newline at end of file diff --git a/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/SnowFlakeKeyGenerator.java b/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/SnowFlakeKeyGenerator.java index 9f75fb2..19053bb 100644 --- a/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/SnowFlakeKeyGenerator.java +++ b/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/SnowFlakeKeyGenerator.java @@ -17,38 +17,62 @@ import com.google.common.base.Preconditions; * @CONTACT 317758022@qq.com * @DESC */ +// 使用了lombok的@Slf4j注解,用于自动生成日志相关的代码,方便在类中进行日志记录 @Slf4j +// 将该类标记为Spring的服务组件,名称为"snowFlakeKeyGenerator",方便在Spring容器中进行依赖注入和管理 @Service("snowFlakeKeyGenerator") -public class SnowFlakeKeyGenerator implements KeyGenerator{ +public class SnowFlakeKeyGenerator implements KeyGenerator { + // 通过Spring的依赖注入机制,自动注入WorkerIDSenquence类型的实例,用于获取相关序列信息 @Autowired private WorkerIDSenquence workerIDSenquence; + // 定义时间戳起始时间(纪元时间),后续生成唯一ID时会基于此时间进行计算,初始值在静态代码块中赋值 public static final long EPOCH; + // 用于表示序列号占用的位数,这里定义为12位 private static final long SEQUENCE_BITS = 12L; + // 用于表示工作机器ID占用的位数,这里定义为10位 private static final long WORKER_ID_BITS = 10L; + // 序列号的掩码,用于在对序列号进行操作时进行位运算的限制,通过计算得到的值(2的12次方 - 1) private static final long SEQUENCE_MASK = 4095L; + // 工作机器ID左移的位数,用于在生成唯一ID时将工作机器ID放置到合适的二进制位位置 private static final long WORKER_ID_LEFT_SHIFT_BITS = 12L; + // 时间戳左移的位数,用于在生成唯一ID时将时间戳放置到合适的二进制位位置 private static final long TIMESTAMP_LEFT_SHIFT_BITS = 22L; + // 工作机器ID的最大值,通过计算得到(2的10次方),用于限制工作机器ID的取值范围 private static final long WORKER_ID_MAX_VALUE = 1024L; + // 定义一个时间服务类的实例,用于获取当前时间等时间相关操作,初始化为默认的TimeService实例 private static TimeService timeService = new TimeService(); + // 记录当前工作机器的ID,初始值在初始化方法中赋值 private static long workerId; + // 当前的序列号,用于在同一时间戳内区分不同的生成请求,每次生成新ID时会进行相应变化 private long sequence; + // 记录上一次生成ID时的时间戳,用于对比当前时间戳,判断是否需要更新序列号等操作 private long lastTime; + // 默认构造函数 public SnowFlakeKeyGenerator() { } /** - * 初始化workerID 从ZK获取序列 + * 初始化workerID,通过调用workerIDSenquence的方法从ZK(可能是Zookeeper)获取序列 + * 同时会进行参数校验,确保获取到的workerID在合法的取值范围内(0到1024之间) */ @PostConstruct public void initWorkerId() throws Exception { long workerID = workerIDSenquence.getSequence(null); + // 使用Preconditions进行前置条件校验,确保workerID合法 Preconditions.checkArgument(workerID >= 0L && workerID < 1024L); workerId = workerID; } + /** + * 生成唯一ID的核心方法,遵循雪花算法的基本逻辑 + * 首先获取当前时间戳,然后检查时间是否正常(防止时间倒退情况) + * 如果当前时间戳和上一次时间戳相同,则对序列号进行递增处理,若序列号达到最大值则等待下一个时间周期 + * 如果时间戳不同,则重置序列号为0 + * 最后根据时间戳、工作机器ID和序列号组装生成唯一的长整型ID,并返回 + */ public synchronized Number generateKey() { long currentMillis = timeService.getCurrentMillis(); Preconditions.checkState(this.lastTime <= currentMillis, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", new Object[]{Long.valueOf(this.lastTime), Long.valueOf(currentMillis)}); @@ -68,6 +92,10 @@ public class SnowFlakeKeyGenerator implements KeyGenerator{ return Long.valueOf(currentMillis - EPOCH << 22 | workerId << 12 | this.sequence); } + /** + * 当序列号达到最大值时,等待直到进入下一个时间周期(即获取到大于当前时间戳的时间) + * 通过循环不断获取当前时间,直到时间大于传入的上一次时间戳 + */ private long waitUntilNextTime(long lastTime) { long time; for(time = timeService.getCurrentMillis(); time <= lastTime; time = timeService.getCurrentMillis()) { @@ -77,10 +105,14 @@ public class SnowFlakeKeyGenerator implements KeyGenerator{ return time; } + /** + * 设置时间服务类的实例,用于替换默认的时间服务实现,方便进行时间相关功能的定制或替换 + */ public static void setTimeService(TimeService timeService) { timeService = timeService; } + // 静态代码块,用于初始化EPOCH(纪元时间),将时间设置为2016年11月1日0时0分0秒0毫秒 static { Calendar calendar = Calendar.getInstance(); calendar.set(2016, 10, 1); diff --git a/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/TimeService.java b/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/TimeService.java index 70cc28c..38d73c4 100644 --- a/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/TimeService.java +++ b/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/TimeService.java @@ -5,11 +5,16 @@ package com.njupt.swg.keygen; * @Date 2019/1/6 21:45 * @CONTACT 317758022@qq.com * @DESC + * 1111 */ +// 定义了一个名为TimeService的类,该类主要用于提供时间相关的服务,可能会被其他类用于获取时间信息等操作 public class TimeService { + // 默认构造函数,目前为空,主要用于创建该类的实例对象时进行必要的初始化(此处暂时无额外初始化操作) public TimeService() { } + // 该方法用于获取当前时间的毫秒数,其内部通过调用Java标准库中的System.currentTimeMillis()方法来实现, + // 返回的是从1970年1月1日00:00:00 UTC到当前时刻的时间差,以毫秒为单位,方便在其他地方基于这个时间进行相关逻辑处理 public long getCurrentMillis() { return System.currentTimeMillis(); } diff --git a/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/WorkerIDSenquence.java b/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/WorkerIDSenquence.java index ac3f3b8..af2d980 100644 --- a/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/WorkerIDSenquence.java +++ b/snailmall-keygen-service/src/main/java/com/njupt/swg/keygen/WorkerIDSenquence.java @@ -15,17 +15,31 @@ import javax.annotation.PostConstruct; /** * 基于ZK节点序列 */ +// 使用@Component注解将该类标记为Spring容器中的一个组件,方便Spring进行管理和依赖注入等操作 @Component +// 使用lombok的@Slf4j注解,用于自动生成日志相关代码,便于在类中记录各种日志信息 @Slf4j public class WorkerIDSenquence { + // 使用@Value注解从Spring配置文件(或环境变量等配置源)中读取名为"zk.host"的配置值,并注入到该变量中, + // 此变量表示Zookeeper服务器的主机地址,用于后续连接Zookeeper @Value("${zk.host}") private String ZkHost ; + // 定义一个静态的常量字符串,表示在Zookeeper中的节点路径,用于存放与雪花算法工作ID相关的信息, + // 这里的路径是固定的,后续操作会基于此路径在Zookeeper中进行节点的创建、查询等操作 private static final String ZK_PATH = "/snowflake/workID"; + // 定义一个静态的CuratorFramework类型的变量,CuratorFramework是用于操作Zookeeper的客户端框架, + // 在这里用于与Zookeeper服务器进行交互,比如创建节点、查询节点等,此处先声明,后续在初始化方法中进行实例化并启动 private static CuratorFramework client; + /** + * 该方法使用了@PostConstruct注解,意味着此方法会在类实例化且依赖注入完成后自动被调用,用于初始化Zookeeper相关节点 + * 首先通过CuratorFrameworkFactory创建一个连接到Zookeeper服务器的客户端实例,配置了重试策略(最多重试10次,每次间隔5000毫秒) + * 然后启动客户端连接,连接成功后会在日志中记录相应信息,接着检查在Zookeeper中指定的节点路径(ZK_PATH)是否存在, + * 如果不存在,则创建该节点,并设置节点的创建模式为持久化(CreateMode.PERSISTENT),确保节点在服务器重启等情况下依然存在 + */ @PostConstruct void initZKNode() throws Exception { client = CuratorFrameworkFactory.newClient(ZkHost,new RetryNTimes(10, 5000)); @@ -37,6 +51,13 @@ public class WorkerIDSenquence { } } + /** + * 该方法用于获取一个序列值,这个序列值可能用于雪花算法中的工作ID分配等场景 + * 首先判断传入的主机名(hostname)是否为空,如果为空则设置一个默认的主机名"snowflake_" + * 然后通过Zookeeper客户端在指定的节点路径(ZK_PATH)下创建一个临时顺序节点(CreateMode.EPHEMERAL_SEQUENTIAL), + * 节点的完整路径会基于传入的主机名进行拼接,创建成功后,从节点路径字符串中提取最后四位数字作为序列值(这里假设节点路径命名有一定规范), + * 最后将提取到的序列值以长整型的形式返回,这个序列值可用于后续相关业务逻辑,比如作为工作ID的一部分等 + */ public long getSequence(String hostname) throws Exception { if(StringUtils.isBlank(hostname)){ hostname = "snowflake_"; diff --git a/snailmall-keygen-service/src/main/java/com/njupt/swg/web/KeyGeneratorController.java b/snailmall-keygen-service/src/main/java/com/njupt/swg/web/KeyGeneratorController.java index d2fbcc3..2465dac 100644 --- a/snailmall-keygen-service/src/main/java/com/njupt/swg/web/KeyGeneratorController.java +++ b/snailmall-keygen-service/src/main/java/com/njupt/swg/web/KeyGeneratorController.java @@ -12,15 +12,21 @@ import org.springframework.web.bind.annotation.RestController; * @CONTACT 317758022@qq.com * @DESC 全局唯一ID生成服务 */ +// @RestController是Spring框架提供的一个复合注解,它结合了@Controller和@ResponseBody注解的功能, +// 表明这个类是一个处理HTTP请求的控制器类,并且方法的返回值会直接以JSON等格式响应给客户端,无需额外配置视图解析等操作 @RestController +// @RequestMapping注解在类级别上可以用于指定该控制器类处理的请求的基础路径,此处未指定具体路径,所以默认可处理多种请求路径下的相关请求(具体由方法上的@RequestMapping细化) @RequestMapping public class KeyGeneratorController { - + // 通过Spring的依赖注入机制,使用@Autowired注解结合@Qualifier注解,按照名称("snowFlakeKeyGenerator")精准注入一个实现了KeyGenerator接口的实例, + // 这个实例将用于后续生成特定的键值(可能是唯一ID等,具体看KeyGenerator的实现逻辑) @Autowired @Qualifier("snowFlakeKeyGenerator") private KeyGenerator keyGenerator; + // @RequestMapping注解在方法级别上用于指定该方法处理的具体请求路径,这里表示该方法处理"/keygen"这个相对路径的HTTP请求, + // 当接收到对应的请求时,会调用keyGenerator的generateKey方法来生成一个键值(Number类型),然后将其转换为长整型并进一步转换为字符串返回给客户端 @RequestMapping("/keygen") public String generateKey() throws Exception { return String.valueOf(keyGenerator.generateKey().longValue()); diff --git a/snailmall-shipping-service/pom.xml b/snailmall-shipping-service/pom.xml index 8e67419..32998a2 100644 --- a/snailmall-shipping-service/pom.xml +++ b/snailmall-shipping-service/pom.xml @@ -1,108 +1,163 @@ + + 4.0.0 + com.njupt.swg spring-cloud-for-snailmall 0.0.1-SNAPSHOT + snailmall-shipping-service + 0.0.1-SNAPSHOT + snailmall-shipping-service + 收货地址服务 + + org.springframework.boot spring-boot-starter - + + org.springframework.boot spring-boot-starter-web + org.springframework.cloud spring-cloud-config-client + org.springframework.cloud spring-cloud-starter-netflix-eureka-client + org.springframework.cloud spring-cloud-starter-bus-amqp + org.springframework.boot spring-boot-starter-actuator + org.springframework.cloud spring-cloud-starter-openfeign + org.springframework.cloud spring-cloud-starter-zipkin + org.projectlombok lombok - + + org.codehaus.jackson jackson-mapper-asl - + + com.alibaba druid - + + com.google.guava guava + org.apache.commons commons-lang3 + commons-collections commons-collections - + + mysql mysql-connector-java - + + org.mybatis.spring.boot mybatis-spring-boot-starter - + + redis.clients jedis - + + - joda-time + joda-time joda-time - + + com.github.pagehelper pagehelper + com.github.miemiedev mybatis-paginator + com.github.jsqlparser jsqlparser @@ -110,8 +165,11 @@ + - + + src/main/java @@ -121,6 +179,9 @@ + org.springframework.boot spring-boot-maven-plugin @@ -128,4 +189,4 @@ - + \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java index 66c6499..f756dd7 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java @@ -13,21 +13,27 @@ import redis.clients.jedis.JedisPool; * @CONTACT 317758022@qq.com * @DESC */ +// 使用@Component注解将该类标记为Spring容器中的一个组件,这样Spring可以对其进行管理,方便进行依赖注入等操作 @Component +// 使用lombok的@Slf4j注解,用于自动生成日志相关的代码,使得在类中可以方便地记录各种日志信息,便于调试和问题排查 @Slf4j public class CommonCacheUtil { + // 通过Spring的依赖注入机制,使用@Autowired注解自动注入JedisPoolWrapper类型的实例, + // JedisPoolWrapper应该是对Jedis连接池进行了一定封装的类,后续操作Redis会借助这个实例获取Jedis连接池来获取Jedis客户端实例进行相关操作 @Autowired private JedisPoolWrapper jedisPoolWrapper; - /** - * 缓存永久key + * 缓存永久key的方法,用于将给定的键值对以永久存储的方式存入Redis中(这里所说的永久是指没有设置过期时间) + * 首先尝试从jedisPoolWrapper获取Jedis连接池,如果连接池不为空,则从连接池中获取一个Jedis客户端资源, + * 接着选择Redis的第0个数据库(Jedis.select(0)操作),然后使用Jedis客户端的set方法将键值对存入Redis中, + * 如果在这个过程中出现任何异常,会记录错误日志,并抛出一个自定义的SnailmallException异常,提示Redis报错 */ public void cache(String key, String value) { try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis Jedis = pool.getResource()) { Jedis.select(0); Jedis.set(key, value); @@ -40,13 +46,16 @@ public class CommonCacheUtil { } /** - * 获取缓存key + * 获取缓存key对应的value值的方法,尝试从Redis中获取指定键对应的缓存值 + * 先初始化一个用于接收返回值的变量value为null,然后尝试从jedisPoolWrapper获取Jedis连接池, + * 若连接池不为空,则从中获取Jedis客户端资源,选择Redis的第0个数据库后,使用Jedis客户端的get方法获取指定键对应的value值, + * 如果出现异常,同样会记录错误日志并抛出SnailmallException异常提示Redis报错,最后将获取到的value值返回(可能为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); value = Jedis.get(key); @@ -60,13 +69,18 @@ public class CommonCacheUtil { } /** - * 过期key + * 过期key相关操作的方法,先尝试将给定的键值对以原子操作的方式存入Redis(使用setnx,只有键不存在时才设置成功), + * 然后设置该键在Redis中的过期时间(以秒为单位,由传入的expire参数指定) + * 首先初始化一个用于记录操作结果的变量result为0,接着尝试从jedisPoolWrapper获取Jedis连接池, + * 若连接池不为空,则获取Jedis客户端资源,选择数据库后,使用setnx方法设置键值对并将返回结果赋值给result, + * 随后使用expire方法设置键的过期时间,若在过程中出现异常,记录错误日志并抛出SnailmallException异常提示Redis报错, + * 最后将操作结果(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); @@ -82,11 +96,13 @@ public class CommonCacheUtil { } /** - * 删除缓存key + * 删除缓存key的方法,用于从Redis中删除指定的键 + * 先尝试从jedisPoolWrapper获取Jedis连接池,若连接池不为空,则获取Jedis客户端资源,选择数据库后, + * 使用Jedis客户端的del方法尝试删除指定的键,若在删除过程中出现异常,记录错误日志并抛出SnailmallException异常提示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 { @@ -101,4 +117,4 @@ public class CommonCacheUtil { -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java index addfe24..c8e9fe4 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java @@ -14,14 +14,28 @@ import javax.annotation.PostConstruct; * @CONTACT 317758022@qq.com * @DESC 只做了单个redis,但是课程中实现的redis客户端集群,要掌握一致性hash算法 */ +// 使用@Component注解将该类标记为Spring容器中的一个组件,使得Spring能够对其进行管理,方便进行依赖注入等相关操作。 @Component +// 使用lombok的@Slf4j注解,用于自动生成日志相关的代码,这样在类中就可以方便地记录各种操作的日志信息,便于后续的调试以及问题排查。 @Slf4j public class JedisPoolWrapper { + + // 通过Spring的依赖注入机制,使用@Autowired注解自动注入Parameters类型的实例, + // 从命名来看,Parameters类应该是用于存放各种配置参数的,此处可能是包含了Redis相关的配置参数,比如连接池最大连接数等,用于后续初始化Jedis连接池。 @Autowired private Parameters parameters; + // 声明一个JedisPool类型的私有变量,用于存放Jedis连接池的实例,初始值设为null,会在后续的初始化方法中进行实例化赋值。 private JedisPool jedisPool = null; + /** + * 使用@PostConstruct注解标记的方法,此方法会在类实例化且依赖注入完成后自动被调用,用于初始化Jedis连接池。 + * 具体步骤如下: + * 1. 首先创建一个JedisPoolConfig对象,该对象用于配置Jedis连接池的各种属性,比如最大连接数、最大空闲连接数以及最大等待时间等。 + * 2. 通过调用parameters对象的相应方法(parameters.getRedisMaxTotal()等)获取配置参数,并设置到JedisPoolConfig对象中,来定制连接池的属性。 + * 3. 使用配置好的JedisPoolConfig对象以及从parameters对象获取到的Redis主机地址、端口号、超时时间(此处固定为2000毫秒)和密码(此处为"xxx",实际应用中应妥善保管)等信息,创建一个JedisPool实例,并赋值给jedisPool变量,完成连接池的初始化。 + * 4. 如果在初始化过程中出现任何异常,会通过log.error记录错误日志,方便排查问题;若初始化成功,则通过log.info记录成功的提示信息。 + */ @PostConstruct public void init(){ try { @@ -36,7 +50,10 @@ public class JedisPoolWrapper { } } + /** + * 对外提供的获取JedisPool实例的方法,外部类可以通过调用这个方法来获取已经初始化好的Jedis连接池实例,进而从连接池中获取Jedis客户端资源来操作Redis数据库。 + */ public JedisPool getJedisPool() { return jedisPool; } -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/Parameters.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/Parameters.java index 9121848..fde750b 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/Parameters.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/cache/Parameters.java @@ -10,24 +10,36 @@ import org.springframework.stereotype.Component; * @CONTACT 317758022@qq.com * @DESC */ +// 使用@Component注解将该类标记为Spring容器中的一个组件,使得Spring可以对其进行管理,方便在其他需要的地方通过依赖注入获取该类的实例。 @Component +// 使用lombok的@Data注解,这个注解会自动帮我们生成类的一些常用方法,比如Getter、Setter方法,以及toString、hashCode、equals等方法,简化代码编写,提高开发效率。 @Data public class Parameters { + // 以下是Redis相关配置参数的定义部分,使用@Value注解从Spring配置文件(或者环境变量等配置源)中读取对应配置项的值,并注入到相应的变量中。 + + // 用于存放Redis服务器的主机地址,通过读取配置文件中名为"redis.host"的配置项来获取具体的值,供后续连接Redis时使用。 /*****redis config start*******/ @Value("${redis.host}") private String redisHost; + // 用于存放Redis服务器的端口号,从配置文件中"redis.port"配置项获取对应的值,一般Redis默认端口是6379,不过可以通过配置来指定不同端口。 @Value("${redis.port}") private int redisPort; - @Value("${redis.max-idle}") - private int redisMaxTotal; + // 这里变量名可能存在笔误,按照常规理解应该是用于存放Redis连接池的最大连接数,通过读取"redis.max-total"配置项获取对应的值,它决定了连接池中最多能创建多少个连接。 @Value("${redis.max-total}") + private int redisMaxTotal; + // 同样可能存在笔误,推测是用于存放Redis连接池的最大空闲连接数,从"redis.max-idle"配置项获取值,用于限制连接池中处于空闲状态的最大连接数量。 + @Value("${redis.max-idle}") private int redisMaxIdle; + // 用于存放Redis连接池的最大等待时间(以毫秒为单位),通过读取"redis.max-wait-millis"配置项获取值,当连接池中的连接都被占用,新的获取连接请求会等待这个时间,如果超时还没获取到连接则会抛出异常。 @Value("${redis.max-wait-millis}") private int redisMaxWaitMillis; /*****redis config end*******/ + // 以下是Zookeeper(curator相关配置,curator是操作Zookeeper的框架)相关配置参数的定义部分,同样使用@Value注解获取配置值。 + + // 用于存放Zookeeper服务器的主机地址,通过读取配置文件中名为"zk.host"的配置项获取值,后续在连接Zookeeper服务器时会用到该地址信息。 /*****curator config start*******/ @Value("${zk.host}") private String zkHost; /*****curator config end*******/ -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/constants/Constants.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/constants/Constants.java index 2f79a24..95f95de 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/constants/Constants.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/constants/Constants.java @@ -7,17 +7,25 @@ package com.njupt.swg.common.constants; * @DESC */ public class Constants { + // 以下是自定义状态码相关的常量定义部分,用于在整个项目中统一表示不同的状态情况,方便进行状态判断和处理逻辑的编写。 + + // 表示请求成功的状态码,通常对应HTTP状态码中的200,表示请求已成功被服务器接收、理解并处理,常用于正常的业务响应场景。 /**自定义状态码 start**/ 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中与用户相关的键(key)的前缀定义部分,用于在Redis存储用户相关数据时,方便统一管理和区分不同类型的数据键名。 + + // 定义了一个字符串常量,作为Redis中存储用户相关信息的键的前缀,后续在使用Redis存储如用户token等信息时,键名会以这个前缀开始,便于归类和识别,这里前缀设定为"shipping_"。 /***redis user相关的key以这个打头**/ public static final String TOKEN_PREFIX = "shipping_"; diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java index cef87ac..44e2c30 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java @@ -14,19 +14,33 @@ import org.springframework.web.bind.annotation.ResponseBody; * @CONTACT 317758022@qq.com * @DESC 全局异常处理 */ +// @ControllerAdvice注解用于定义全局的异常处理类,它可以对多个Controller中的异常进行统一处理,增强了代码的健壮性和异常处理的集中性。 @ControllerAdvice +// @ResponseBody注解表示该类中处理异常的方法返回的结果会直接写入HTTP响应的正文(比如以JSON格式等),而不是去寻找视图进行解析渲染。 @ResponseBody +// 使用lombok的@Slf4j注解,自动生成日志相关代码,方便在类中记录异常相关的详细信息,便于后续排查问题。 @Slf4j public class ExceptionHandlerAdvice { + + // @ExceptionHandler注解用于指定该方法处理的异常类型,这里表示这个方法会处理所有继承自Exception类的异常(也就是几乎所有的异常情况)。 + // 当捕获到Exception类型的异常时,会进入这个方法进行处理。 @ExceptionHandler(Exception.class) public ServerResponse handleException(Exception e){ + // 使用日志记录异常的详细信息,包括异常消息和异常堆栈信息,方便开发人员定位问题所在。 log.error(e.getMessage(),e); + // 通过ServerResponse的静态方法创建一个包含错误信息的响应对象,使用了Constants类中定义的表示服务器内部错误的状态码(RESP_STATUS_INTERNAL_ERROR), + // 并设置相应的错误提示消息,告知客户端系统出现异常,让其稍后再试,最后将这个响应对象返回,客户端将会收到对应的错误响应信息。 return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); } + // 同样使用@ExceptionHandler注解,不过这里指定处理的是SnailmallException类型的异常,这应该是项目自定义的一种异常类型。 + // 当出现这种特定的异常时,会执行这个方法来进行针对性处理。 @ExceptionHandler(SnailmallException.class) public ServerResponse handleException(SnailmallException e){ + // 记录该自定义异常的详细信息到日志中,方便后续查看异常出现的原因等情况。 log.error(e.getMessage(),e); + // 通过ServerResponse的静态方法创建响应对象,使用自定义异常中携带的状态码(通过e.getExceptionStatus()获取)以及异常消息(e.getMessage())来构建响应, + // 然后将这个响应返回给客户端,以便客户端根据具体的状态码和消息进行相应处理。 return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); } diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java index 363f19d..80f1e87 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java @@ -9,17 +9,26 @@ import lombok.Getter; * @CONTACT 317758022@qq.com * @DESC */ +// 使用lombok的@Getter注解,会自动为类中的成员变量生成对应的Getter方法,方便在其他地方获取该变量的值,简化了代码编写,无需手动编写Getter方法的代码。 @Getter +// 定义了一个名为SnailmallException的类,它继承自Java内置的RuntimeException类,这意味着它是一个运行时异常,不需要在方法声明中显式抛出,可以在程序运行过程中随时抛出并中断当前的执行流程。 public class SnailmallException extends RuntimeException{ + // 定义一个私有成员变量exceptionStatus,用于存储异常对应的状态码,初始值设置为ResponseEnum.ERROR.getCode(), + // 从命名推测ResponseEnum应该是一个枚举类,用于定义各种响应相关的枚举值,这里获取的是表示错误的状态码,默认情况下该异常会携带这个错误状态码。 private int exceptionStatus = ResponseEnum.ERROR.getCode(); + // 这是一个构造函数,用于创建SnailmallException实例,接收一个字符串类型的参数msg,该参数表示异常的详细消息内容。 + // 在构造函数内部,通过调用父类(RuntimeException)的构造函数,将msg传递给父类,完成对异常消息的初始化设置,这样在抛出该异常时可以携带相应的消息提示。 public SnailmallException(String msg){ super(msg); } + // 这是另一个构造函数,用于创建SnailmallException实例,接收两个参数,一个是整型的code,表示异常的状态码,另一个是字符串类型的msg,表示异常的详细消息内容。 + // 首先同样调用父类的构造函数,将msg传递给父类完成异常消息的初始化,然后将传入的code赋值给当前类的exceptionStatus变量,用于更新异常对应的状态码, + // 这样就可以根据不同的业务场景,灵活地设置异常状态码和消息来准确传达异常相关的信息。 public SnailmallException(int code,String msg){ super(msg); exceptionStatus = code; } -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java index e4e59c7..6b8c56d 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java @@ -8,18 +8,28 @@ import lombok.Getter; * @CONTACT 317758022@qq.com * @DESC 基本的返回状态描述 */ +// 使用lombok的@Getter注解,该注解会自动为枚举类中的成员变量(这里的code和desc)生成对应的Getter方法,方便在其他地方获取这些变量的值,使得代码更加简洁,无需手动编写Getter方法代码。 @Getter +// 定义了一个名为ResponseEnum的枚举类,用于表示不同的响应状态或情况,在整个项目中可以通过使用这个枚举来统一规范响应相关的状态码和对应的描述信息。 public enum ResponseEnum { + // 定义了一个名为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"); + // 定义了一个私有整型变量code,用于存储每个枚举常量对应的状态码,不同的枚举常量会有各自对应的状态码值,通过构造函数进行初始化赋值。 private int code; + // 定义了一个私有字符串变量desc,用于存储每个枚举常量对应的描述信息,用于更直观地展示该枚举常量所代表的具体含义,同样通过构造函数进行初始化赋值。 private 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-shipping-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java index cc261fa..6887848 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java @@ -14,65 +14,91 @@ import java.io.Serializable; * @CONTACT 317758022@qq.com * @DESC 作为本项目的通用的返回封装类 */ +// 使用lombok的@Getter注解,会自动为类中的非静态成员变量(这里的status、msg、data)生成对应的Getter方法,方便在其他地方获取这些变量的值,减少了手动编写Getter方法的代码量。 @Getter +// 使用lombok的@NoArgsConstructor注解,会为该类生成一个无参构造函数,方便在一些需要默认构造实例的场景下使用,比如反序列化等操作。 @NoArgsConstructor +// 使用Jackson的@JsonSerialize注解来配置序列化相关的行为,这里设置include属性为JsonSerialize.Inclusion.NON_NULL,表示在将对象序列化为JSON格式时,只包含非空的属性,忽略值为null的属性,有助于减少网络传输的数据量以及使返回的JSON数据更加简洁。 @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// 该类实现了Serializable接口,意味着这个类的实例可以被序列化和反序列化,常用于在网络传输、持久化存储等场景中保存和恢复对象的状态。 public class ServerResponse implements Serializable { + // 用于存储响应的状态码,不同的状态码代表不同的响应情况,例如成功、失败、参数错误等,通过构造函数等方式进行赋值。 private int status; + // 用于存储响应的提示消息,比如成功时的提示信息或者失败时具体的错误描述等,方便客户端了解具体的响应含义。 private String msg; + // 定义了一个泛型变量data,用于存储具体的业务数据,其类型根据实际使用场景确定(由创建ServerResponse实例时传入的具体类型决定),可以是单个对象、集合等各种类型的数据。 private T data; + // 私有构造函数,只接收一个状态码参数,用于在特定场景下创建ServerResponse实例时,只初始化状态码,其他属性保持默认值(null或初始值),一般在内部逻辑根据具体情况来灵活构建响应对象时使用。 private ServerResponse(int status){ this.status = status; } + // 私有构造函数,接收状态码和提示消息两个参数,用于创建带有指定状态码和对应提示消息的ServerResponse实例,方便在需要明确返回错误消息或者特定提示信息的场景下构建响应对象。 private ServerResponse(int status,String msg){ this.status = status; this.msg = msg; } + // 私有构造函数,接收状态码和业务数据两个参数,用于创建带有指定状态码以及对应业务数据的ServerResponse实例,在业务处理成功且需要返回具体数据时可使用该构造函数来构建响应对象。 private ServerResponse(int status,T data){ this.status = status; this.data = data; } + // 私有构造函数,接收状态码、提示消息以及业务数据三个参数,用于创建一个完整的包含状态码、提示消息和业务数据的ServerResponse实例,适用于各种综合场景下灵活构建响应对象。 private ServerResponse(int status,String msg,T data){ this.status = status; this.msg = msg; this.data = data; } + // 使用Jackson的@JsonIgnore注解,标记该方法在对象序列化时会被忽略,不会被转换为JSON格式的数据。 + // 这个方法用于判断当前响应是否表示成功,通过比较状态码和ResponseEnum.SUCCESS.getCode()(即表示成功的状态码)是否相等来返回判断结果,方便在其他地方快速判断响应是否成功。 @JsonIgnore public boolean isSuccess(){ return this.status == ResponseEnum.SUCCESS.getCode(); } + /** + * 以下是一系列静态方法,用于创建表示成功的ServerResponse实例,方便在业务处理成功的不同场景下快速构建相应的响应对象返回给客户端。 + */ /** * 成功的方法 */ + // 创建一个表示成功的ServerResponse实例,使用ResponseEnum.SUCCESS中定义的状态码和描述信息来初始化,适用于只需要简单告知客户端操作成功的场景,没有额外的提示消息和具体业务数据返回。 public static ServerResponse createBySuccess(){ return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); } + // 创建一个表示成功的ServerResponse实例,接收一个自定义的消息参数message,使用ResponseEnum.SUCCESS的状态码以及传入的message来初始化,用于在成功的同时需要向客户端返回特定提示消息的场景。 public static ServerResponse createBySuccessMessage(String message){ return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); } + // 创建一个表示成功的ServerResponse实例,接收一个泛型参数data,使用ResponseEnum.SUCCESS的状态码以及传入的数据来初始化,用于在业务处理成功且有具体业务数据需要返回给客户端的场景。 public static ServerResponse createBySuccess(T data){ return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); } + // 创建一个表示成功的ServerResponse实例,接收自定义的消息参数message和泛型参数data,使用ResponseEnum.SUCCESS的状态码、传入的message以及data来初始化,适用于成功且既有提示消息又有业务数据需要返回的综合场景。 public static ServerResponse createBySuccess(String message,T data){ return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); } + /** + * 以下是一系列静态方法,用于创建表示失败的ServerResponse实例,方便在业务处理出现问题的不同场景下快速构建相应的响应对象返回给客户端。 + */ /** * 失败的方法 */ + // 创建一个表示失败的ServerResponse实例,使用ResponseEnum.ERROR中定义的状态码和描述信息来初始化,适用于一般性的错误情况,只告知客户端操作失败,没有更详细的错误消息定制。 public static ServerResponse createByError(){ return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); } + // 创建一个表示失败的ServerResponse实例,接收一个自定义的错误消息参数msg,使用ResponseEnum.ERROR的状态码以及传入的msg来初始化,用于在出现错误且需要向客户端返回具体错误描述的场景。 public static ServerResponse createByErrorMessage(String msg){ return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); } + // 创建一个表示失败的ServerResponse实例,接收状态码和自定义的错误消息两个参数,用于在需要根据具体业务逻辑返回特定状态码以及对应错误消息的场景下构建响应对象,更加灵活地表示不同类型的失败情况。 public static ServerResponse createByErrorCodeMessage(int code,String msg){ return new ServerResponse<>(code,msg); } -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java index 0233e8d..ea79f22 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java @@ -10,60 +10,86 @@ import javax.servlet.http.HttpServletResponse; /** * cookie读写 */ +// 使用lombok的@Slf4j注解,用于自动生成日志相关的代码,方便在类中记录各种操作相关的日志信息,便于后续查看操作情况以及进行问题排查。 @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 表示HTTP响应对象,通过它可以向客户端发送响应信息,包括设置Cookie等操作。 + * @param token 表示登录生成的令牌(token),是要存储到Cookie中的具体值,用于后续验证用户登录状态等操作。 */ public static void writeLoginToken(HttpServletResponse response,String token){ + // 创建一个Cookie对象,使用预先定义好的COOKIE_NAME作为Cookie的名称,传入的token作为Cookie的值,以此构建一个用于存储登录信息的Cookie实例。 Cookie ck = new Cookie(COOKIE_NAME,token); + // 设置Cookie的作用域域名,使其与前面定义的COOKIE_DOMAIN一致,确保该Cookie能在指定的域名下被正确识别和使用。 ck.setDomain(COOKIE_DOMAIN); + // 设置Cookie的路径为根目录("/"),这意味着该Cookie在整个网站的所有页面路径下都有效,方便在不同页面间共享和使用这个Cookie。 ck.setPath("/");//设值在根目录 + // 设置Cookie的HttpOnly属性为true,这样浏览器脚本(如JavaScript)就无法访问该Cookie,能有效避免跨站脚本攻击(XSS攻击),提高安全性。 ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击 + // 设置Cookie的最大存活时间,这里设置为一年(60 * 60 * 24 * 365秒),表示该Cookie在客户端浏览器上保存一年的时间,-1表示永久有效,若不设置该属性,Cookie默认只在内存中存在,仅在当前页面有效,不会写入硬盘持久化存储。 ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒,maxage不设置的话,cookie就不会写入硬盘,只会写在内存,只在当前页面有效 + // 使用日志记录即将写入的Cookie的名称和值,方便后续查看写入操作以及排查可能出现的问题,比如Cookie是否按预期设置等情况。 log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); + // 通过HTTP响应对象将构建好的Cookie添加到响应中,发送给客户端浏览器,使得浏览器可以接收到并存储这个Cookie。 response.addCookie(ck); } /** - * 读取登陆的cookie - * @param request - * @return + * 该方法用于从客户端的HTTP请求中读取登录相关的Cookie值,也就是获取之前存储的登录令牌(token),用于验证用户是否已经登录等操作。 + * + * @param request 表示HTTP请求对象,通过它可以获取客户端发送过来的各种信息,包括携带的Cookie信息。 + * @return 返回从Cookie中读取到的登录令牌(token)值,如果没有找到对应的Cookie,则返回null。 */ public static String readLoginToken(HttpServletRequest request){ + // 通过HTTP请求对象获取客户端发送过来的所有Cookie数组,如果没有Cookie则返回null。 Cookie[] cks = request.getCookies(); - if(cks != null){ + if(cks!= null){ + // 遍历获取到的Cookie数组,逐个检查每个Cookie。 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的值,也就是登录令牌(token)的值,用于后续的业务逻辑处理,比如验证登录状态等。 log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); return ck.getValue(); } } } + // 如果遍历完所有Cookie都没有找到对应的登录Cookie,则返回null,表示没有获取到登录相关的令牌信息。 return null; } /** - * 注销的时候进行删除 - * @param request - * @param response + * 该方法用于在用户注销登录时,从客户端删除存储的登录相关Cookie,清除用户的登录状态标识。 + * + * @param request 表示HTTP请求对象,用于获取客户端携带的Cookie信息,以便查找并删除对应的登录Cookie。 + * @param response 表示HTTP响应对象,通过它向客户端发送响应,实现删除Cookie的操作(通过设置相应属性并重新发送Cookie给客户端)。 */ public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ + // 获取客户端发送过来的所有Cookie数组,如果没有则返回null。 Cookie[] cks = request.getCookies(); - if(cks != null){ + if(cks!= null){ + // 遍历所有Cookie,查找名称与登录相关Cookie名称(COOKIE_NAME)一致的Cookie,以便进行删除操作。 for(Cookie ck:cks) { if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ + // 设置要删除的Cookie的作用域域名,与之前设置的一致,确保能准确删除对应的Cookie。 ck.setDomain(COOKIE_DOMAIN); + // 设置Cookie的路径为根目录,与之前设置保持一致,这是准确删除该Cookie的必要条件之一。 ck.setPath("/"); + // 设置Cookie的最大存活时间为0秒,表示立即删除该Cookie,让客户端浏览器清除掉这个Cookie的存储信息。 ck.setMaxAge(0);//0表示消除此cookie + // 使用日志记录即将删除的Cookie的名称和值,方便查看删除操作以及后续排查问题,比如是否成功删除等情况。 log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + // 通过HTTP响应对象将设置好删除属性的Cookie重新发送给客户端,实现从客户端删除该Cookie的操作,完成后直接返回,因为找到并处理了对应的登录Cookie就无需继续遍历其他Cookie了。 response.addCookie(ck); return; } @@ -71,4 +97,4 @@ public class CookieUtil { } } -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java index 8655b2a..9583315 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java @@ -13,20 +13,42 @@ import java.util.Date; * @DESC 时间转换的工具类 */ public class DateTimeUtil { + // 这里注释表明代码中使用了joda-time这个日期时间处理的库,它提供了更方便、强大的日期时间操作功能,相比于Java原生的日期时间API在某些方面更易用。 //joda-time + // 以下注释说明了这个工具类主要提供的两个功能方向,即把字符串类型的日期时间表示转换为Date类型,以及把Date类型转换为字符串类型的日期时间表示,方便在不同的业务场景下进行日期时间格式的转换操作。 //str->Date //Date->str + // 定义了一个静态的常量字符串,用于表示标准的日期时间格式,采用"yyyy-MM-dd HH:mm:ss"这种常见的格式,方便在一些方法中作为默认的格式来进行日期时间的转换操作。 public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + /** + * 将指定格式的字符串类型的日期时间转换为Date类型。 + * 首先根据传入的格式字符串(formatStr)创建一个DateTimeFormatter对象,这个对象用于定义如何解析传入的日期时间字符串。 + * 然后使用该DateTimeFormatter对象解析传入的日期时间字符串(dateTimeStr),得到一个DateTime类型的对象,DateTime是joda-time库中的日期时间表示类。 + * 最后将DateTime对象转换为Java原生的Date类型并返回,这样就完成了从特定格式字符串到Date类型的转换。 + * + * @param dateTimeStr 要转换的字符串类型的日期时间,其格式需要与传入的formatStr参数一致。 + * @param formatStr 用于指定日期时间字符串的格式,例如"yyyy-MM-dd HH:mm:ss"等格式字符串。 + * @return 转换后的Date类型的日期时间对象,如果解析出现问题可能会抛出异常(由joda-time库的解析逻辑决定)。 + */ public static Date strToDate(String dateTimeStr, String formatStr){ DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); return dateTime.toDate(); } + /** + * 将Date类型的日期时间对象转换为指定格式的字符串表示形式。 + * 首先判断传入的Date对象是否为null,如果是null则直接返回一个空字符串(StringUtils.EMPTY,可能是自定义的表示空字符串的常量,一般用于避免返回null导致的空指针问题等情况)。 + * 如果Date对象不为null,则使用它创建一个joda-time库中的DateTime对象,然后调用toString方法并传入指定的格式字符串(formatStr),将Date对象按照指定格式转换为字符串并返回。 + * + * @param date 要转换的Date类型的日期时间对象。 + * @param formatStr 用于指定转换后的字符串的日期时间格式,例如"yyyy-MM-dd HH:mm:ss"等格式字符串。 + * @return 转换后的字符串类型的日期时间表示,如果传入的Date对象为null则返回空字符串。 + */ public static String dateToStr(Date date,String formatStr){ if(date == null){ return StringUtils.EMPTY; @@ -35,6 +57,14 @@ public class DateTimeUtil { return dateTime.toString(formatStr); } + /** + * 将字符串类型的日期时间转换为Date类型,使用类中定义的标准格式(STANDARD_FORMAT)进行解析。 + * 先根据标准格式创建一个DateTimeFormatter对象,然后用它解析传入的日期时间字符串(dateTimeStr)得到DateTime对象,最后将其转换为Date类型并返回。 + * 这个方法适用于传入的日期时间字符串符合标准格式的情况,简化了调用,无需每次都传入格式字符串参数。 + * + * @param dateTimeStr 要转换的字符串类型的日期时间,其格式需要符合STANDARD_FORMAT定义的格式。 + * @return 转换后的Date类型的日期时间对象,如果解析出现问题可能会抛出异常(由joda-time库的解析逻辑决定)。 + */ //固定好格式 public static Date strToDate(String dateTimeStr){ DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT); @@ -42,6 +72,14 @@ public class DateTimeUtil { return dateTime.toDate(); } + /** + * 将Date类型的日期时间对象转换为使用标准格式(STANDARD_FORMAT)表示的字符串形式。 + * 先判断传入的Date对象是否为null,若是则返回空字符串,若不为null则基于该Date对象创建一个DateTime对象,再按照标准格式将其转换为字符串并返回。 + * 这个方法方便在需要按照统一标准格式输出日期时间字符串的场景下使用,无需每次指定格式字符串。 + * + * @param date 要转换的Date类型的日期时间对象。 + * @return 转换后的字符串类型的日期时间表示,如果传入的Date对象为null则返回空字符串。 + */ public static String dateToStr(Date date){ if(date == null){ return StringUtils.EMPTY; @@ -50,6 +88,16 @@ public class DateTimeUtil { return dateTime.toString(STANDARD_FORMAT); } + /** + * 将Date类型的日期时间对象转换为时间戳(从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数)。 + * 首先判断传入的Date对象是否为null,如果是null则直接返回null。 + * 如果Date对象不为null,则创建一个SimpleDateFormat对象,使用类中定义的标准格式("yyyy-MM-dd HH:mm:ss")来格式化日期时间,然后将Date对象转换为字符串后再解析为Date类型(这里感觉有点多余,其实可以直接获取Date对象的getTime方法获取时间戳,但可能是出于某些业务逻辑或者格式统一等考虑),最后获取其对应的时间戳(以毫秒为单位)并返回。 + * 注意这个方法可能会抛出ParseException异常,在解析日期时间字符串出现问题时会抛出该异常,调用者需要进行相应的异常处理。 + * + * @param date 要转换的Date类型的日期时间对象。 + * @return 转换后的时间戳(以毫秒为单位),如果传入的Date对象为null则返回null,若解析出现问题会抛出ParseException异常。 + * @throws ParseException 当解析日期时间字符串出现问题时抛出该异常,由SimpleDateFormat的解析逻辑决定。 + */ //Date -> 时间戳 public static Long dateToChuo(Date date) throws ParseException { if(date == null){ @@ -59,10 +107,19 @@ public class DateTimeUtil { return format.parse(String.valueOf(date)).getTime(); } + /** + * 这是类的主方法,是Java程序的入口点,在这里主要是进行了一个简单的日期时间转换示例操作。 + * 首先创建一个SimpleDateFormat对象,使用"yyyy-MM-dd HH:mm:ss"格式来解析日期时间。 + * 然后定义一个日期时间字符串(time),接着使用SimpleDateFormat对象解析该字符串得到一个Date对象,最后打印出该Date对象对应的时间戳(以毫秒为单位)。 + * 这个主方法可以用于简单测试类中的日期时间转换相关方法的功能,不过在实际应用中可能不会这样直接写在工具类里,可以考虑单独写测试类等方式进行功能测试。 + * + * @param args 命令行参数,在这里未使用到。 + * @throws ParseException 当解析日期时间字符串出现问题时抛出该异常,由SimpleDateFormat的解析逻辑决定。 + */ 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"; Date date = format.parse(time); System.out.print("Format To times:"+date.getTime()); } -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java index 12daca4..36d9ca2 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java @@ -15,35 +15,46 @@ import java.text.SimpleDateFormat; /** * jackson的序列化和反序列化 */ +// 使用lombok的@Slf4j注解,用于自动生成日志相关的代码,方便在类中记录各种操作相关的日志信息,便于后续查看操作情况以及进行问题排查,比如在序列化或反序列化出现错误时记录详细的错误信息。 @Slf4j public class JsonUtil { + // 创建一个ObjectMapper对象,ObjectMapper是Jackson库中用于处理JSON序列化和反序列化的核心类,通过它可以将Java对象转换为JSON字符串,也可以将JSON字符串转换为Java对象,这里创建一个实例用于后续的相关操作。 private static ObjectMapper objectMapper = new ObjectMapper(); + // 静态代码块,在类加载时执行,用于对ObjectMapper对象进行一系列的配置,以满足特定的JSON处理需求。 static { + // 设置序列化时包含所有字段,即不管字段是否为null,都会将其列入进行JSON转换,这样可以保证完整的对象结构信息能在JSON中体现,与之对应的还有其他包含策略,例如只包含非null字段等,这里选择了总是包含所有字段的策略。 //所有字段都列入进行转换 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); + // 取消默认将日期类型转换为时间戳形式的行为,通常在JSON序列化时,日期对象可能会被默认转换为时间戳(从1970年1月1日00:00:00 UTC到该日期的毫秒数),这里配置为false,表示按照其他自定义的日期格式进行转换,方便在JSON中呈现更直观的日期格式。 //取消默认转换timestamp形式 objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); + // 配置忽略空bean(即没有属性值的Java对象)转换为JSON时出现的错误,在某些情况下,如果尝试将一个没有任何属性值的空对象转换为JSON,可能会抛出异常,这里设置为false后,遇到这种情况就不会抛出异常,而是返回一个合适的表示(比如空对象对应的JSON表示),增强了程序的健壮性。 //忽略空bean转json的错误 objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); + // 统一设置日期的格式,通过传入一个SimpleDateFormat对象,指定了日期格式为DateTimeUtil类中定义的标准格式("yyyy-MM-dd HH:mm:ss"),这样在序列化和反序列化涉及日期类型时,都会按照这个统一格式进行处理,便于前后端在日期格式上保持一致,避免因格式不一致导致的解析错误等问题。 //统一时间的格式 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); + // 配置忽略JSON中存在属性,但对应的Java对象不存在该属性时的错误,在反序列化JSON字符串为Java对象时,如果JSON中有一些Java对象中未定义的额外属性,默认情况下可能会抛出异常,这里设置为false后,就会忽略这些未知属性,只对Java对象中定义的属性进行赋值,提高了对不同JSON结构的兼容性。 //忽略json存在属性,但是java对象不存在属性的错误 objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); } /** - * 序列化方法,将对象转为字符串 - * @param obj - * @param - * @return + * 这是一个序列化方法,用于将Java对象转换为JSON字符串格式。 + * 如果传入的对象为null,则直接返回null。 + * 如果对象不为null,先判断对象是否本身就是字符串类型,如果是则直接返回该字符串(因为已经是符合要求的格式了),否则使用ObjectMapper对象的writeValueAsString方法将对象转换为JSON字符串并返回,如果在转换过程中出现IOException异常(比如对象的某些属性无法正确序列化等情况),则记录警告日志,并返回null表示转换失败。 + * + * @param obj 要转换为JSON字符串的Java对象,其类型可以是任意Java对象,只要该对象及其包含的属性都能被ObjectMapper正确序列化。 + * @param 泛型参数,用于表示传入对象的类型,在方法返回值中也保持一致的类型,方便处理各种不同类型的对象序列化。 + * @return 转换后的JSON字符串,如果传入对象为null或者序列化过程出现异常则返回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); return null; @@ -51,17 +62,20 @@ public class JsonUtil { } /** - * 序列化方法,同上,只是输出的格式是美化的,便于测试 - * @param obj - * @param - * @return + * 这也是一个序列化方法,功能和obj2String方法类似,都是将Java对象转换为JSON字符串,但这个方法输出的JSON字符串格式是经过美化的,更便于查看和测试,通常在开发调试阶段使用,能更清晰地看到JSON结构和数据内容。 + * 如果传入的对象为null,则直接返回null。 + * 如果对象不为null,先判断对象是否本身就是字符串类型,如果是则直接返回该字符串,否则使用ObjectMapper对象的writerWithDefaultPrettyPrinter方法获取一个能输出美化格式的写入器,再通过它将对象转换为JSON字符串并返回,如果在转换过程中出现IOException异常,则记录警告日志,并返回null表示转换失败。 + * + * @param obj 要转换为JSON字符串的Java对象,其类型可以是任意Java对象,只要该对象及其包含的属性都能被ObjectMapper正确序列化。 + * @param 泛型参数,用于表示传入对象的类型,在方法返回值中也保持一致的类型,方便处理各种不同类型的对象序列化。 + * @return 转换后的JSON字符串,如果传入对象为null或者序列化过程出现异常则返回null,正常情况下返回美化格式的JSON字符串,方便查看和测试。 */ 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); return null; @@ -69,11 +83,14 @@ public class JsonUtil { } /** - * 比较简单的反序列化的方法,将字符串转为单个对象 - * @param str - * @param clazz - * @param - * @return + * 这是一个相对简单的反序列化方法,用于将JSON字符串转换为单个Java对象。 + * 如果传入的字符串为空(StringUtils.isEmpty判断,可能是自定义的工具方法判断字符串是否为空,包括null和空字符串情况)或者传入的目标Java类对象(clazz)为null,则直接返回null。 + * 如果字符串和目标类都不为空,先判断目标类是否就是String类型,如果是则直接将传入的字符串强制转换为对应类型(T)并返回(因为本身就是字符串,无需进行反序列化操作了),否则使用ObjectMapper对象的readValue方法,按照指定的目标类(clazz)将JSON字符串反序列化并返回对应的Java对象,如果在反序列化过程中出现IOException异常(比如JSON格式不符合目标类结构等情况),则记录警告日志,并返回null表示反序列化失败。 + * + * @param str 要反序列化的JSON字符串,需要符合对应Java对象的结构和数据类型要求,否则可能导致反序列化失败。 + * @param clazz 目标Java类对象,用于指定反序列化后生成的对象类型,ObjectMapper会根据这个类型信息来解析JSON字符串并创建相应的Java对象。 + * @param 泛型参数,用于表示目标Java类的类型,在方法返回值中也返回该类型的对象,方便处理各种不同类型的反序列化需求。 + * @return 反序列化后的Java对象,如果传入的字符串为空、目标类为null或者反序列化过程出现异常则返回null。 */ public static T String2Obj(String str,Class clazz){ if(StringUtils.isEmpty(str) || clazz == null){ @@ -88,11 +105,14 @@ public class JsonUtil { } /** - * 复杂对象的反序列化(通用) - * @param str - * @param typeReference - * @param - * @return + * 这是一个复杂对象的反序列化通用方法,用于处理一些较为复杂的类型结构的反序列化情况,比如包含泛型的类型等,通过传入TypeReference类型的参数来指定具体的复杂类型结构信息。 + * 如果传入的字符串为空或者传入的TypeReference对象为null,则直接返回null。 + * 如果都不为空,先判断TypeReference所表示的类型是否就是String类型,如果是则直接返回传入的字符串(因为无需进行反序列化操作了),否则使用ObjectMapper对象的readValue方法,按照TypeReference指定的复杂类型结构将JSON字符串反序列化并返回对应的Java对象,如果在反序列化过程中出现IOException异常,则记录警告日志,并返回null表示反序列化失败。 + * + * @param str 要反序列化的JSON字符串,需要符合对应复杂类型结构的要求,否则可能导致反序列化失败。 + * @param typeReference TypeReference类型的参数,用于详细指定复杂的Java对象类型结构信息,例如包含泛型的集合类型等,ObjectMapper会依据这个信息来准确解析JSON字符串并生成相应的Java对象。 + * @param 泛型参数,用于表示复杂类型的最终对象类型,在方法返回值中也返回该类型的对象,方便处理各种复杂类型的反序列化需求。 + * @return 反序列化后的Java对象,如果传入的字符串为空、TypeReference为null或者反序列化过程出现异常则返回null。 */ public static T Str2Obj(String str, TypeReference typeReference){ if(StringUtils.isEmpty(str) || typeReference == null){ @@ -107,12 +127,15 @@ public class JsonUtil { } /** - * 第二种方式实现复杂对象的反序列化 - * @param str - * @param collectionClass - * @param elementClasses - * @param - * @return + * 这是第二种方式实现复杂对象的反序列化方法,主要用于处理一些集合类型以及其元素类型明确的复杂对象反序列化情况,通过传入集合类以及元素类等信息来构建具体的Java类型结构,然后进行反序列化操作。 + * 首先使用ObjectMapper的getTypeFactory方法获取类型工厂对象,再通过该工厂对象的constructParametricType方法,传入集合类(collectionClass)以及元素类(elementClasses)信息构建出具体的JavaType对象,这个对象表示了要反序列化的复杂类型结构。 + * 然后使用ObjectMapper对象的readValue方法,按照构建好的JavaType类型结构将JSON字符串反序列化并返回对应的Java对象,如果在反序列化过程中出现IOException异常,则记录警告日志,并返回null表示反序列化失败。 + * + * @param str 要反序列化的JSON字符串,需要符合通过传入的集合类和元素类构建的复杂类型结构要求,否则可能导致反序列化失败。 + * @param collectionClass 表示集合类型的Java类对象,例如List.class、Set.class等,用于指定反序列化后的对象是哪种集合类型。 + * @param elementClasses 可变参数,表示集合中元素的Java类对象,可以传入多个,用于指定集合中元素的具体类型,例如在List中,elementClasses就是String.class,通过这些信息构建出完整的复杂类型结构用于反序列化。 + * @param 泛型参数,用于表示最终反序列化后的复杂对象类型,在方法返回值中也返回该类型的对象,方便处理各种复杂类型的反序列化需求。 + * @return 反序列化后的Java对象,如果在反序列化过程中出现异常则返回null。 */ public static T Str2Obj(String str,Class collectionClass,Class... elementClasses){ JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); @@ -123,4 +146,4 @@ public class JsonUtil { return null; } } -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java index e6e5c8a..5497882 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java @@ -6,6 +6,8 @@ import java.security.MessageDigest; * MD5加密工具类 */ public class MD5Util { + // 将字节数组转换为十六进制字符串的方法 + // 该方法通过遍历字节数组中的每个字节,调用byteToHexString方法将每个字节转换为对应的十六进制表示形式,并依次添加到StringBuffer中,最后返回拼接好的十六进制字符串 private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) @@ -14,6 +16,8 @@ public class MD5Util { return resultSb.toString(); } + // 将单个字节转换为十六进制字符串的方法 + // 首先处理字节为负数的情况(在Java中字节是有符号的,范围是 -128 到 127),将其转换为无符号的整数(通过加上256),然后分别计算出十六进制表示中的高位和低位数字对应的字符,最后返回由这两个字符组成的十六进制字符串 private static String byteToHexString(byte b) { int n = b; if (n < 0) @@ -24,16 +28,17 @@ public class MD5Util { } /** - * 返回大写MD5 + * 此方法用于对给定的字符串进行MD5加密编码,并返回大写形式的MD5结果字符串 * - * @param origin - * @param charsetname - * @return + * @param origin 要进行MD5加密的原始字符串 + * @param charsetname 用于指定编码字符集的名称,如果为null或者空字符串,则使用默认字符集对原始字符串进行getBytes操作获取字节数组;如果指定了字符集名称,则按照该字符集进行getBytes操作获取字节数组,后续用于MD5摘要计算 + * @return 经过MD5加密并转换为大写形式的结果字符串,如果在加密过程中出现异常,会返回null(当前代码中捕获异常但没有做其他处理,实际应用中可根据需求完善异常处理逻辑) */ private static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); + // 获取MD5消息摘要算法的实例,用于后续计算字符串的MD5摘要信息 MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString.getBytes())); @@ -44,16 +49,20 @@ public class MD5Util { return resultString.toUpperCase(); } + // 对外提供的一个方便的MD5加密方法,默认使用UTF-8字符集对传入的原始字符串进行MD5加密,并返回加密后的大写结果字符串 + // 该方法内部调用了MD5Encode方法,传入原始字符串和"utf-8"字符集名称来进行具体的加密操作,同时注释提到此处可以添加加盐操作(加盐是一种提高密码安全性的手段,在实际应用中可根据需要进一步完善) public static String MD5EncodeUtf8(String origin) { //这里可以加盐 return MD5Encode(origin, "utf-8"); } + // 主方法,是Java程序的入口点,在这里主要用于测试MD5EncodeUtf8方法,传入字符串"123456"进行MD5加密,并将加密后的结果输出打印到控制台,方便简单验证MD5加密功能是否正常 public static void main(String[] args) { System.out.println(MD5EncodeUtf8("123456")); } + // 定义一个静态的十六进制字符数组,用于在将字节转换为十六进制字符串时,根据字节对应的数值查找对应的十六进制字符表示,数组中依次存放了0到f的十六进制字符表示 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-shipping-service/src/main/java/com/njupt/swg/controller/BaseController.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/controller/BaseController.java index 8c0e6ba..99d3063 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/controller/BaseController.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/controller/BaseController.java @@ -18,12 +18,25 @@ import javax.servlet.http.HttpServletRequest; * @CONTACT 317758022@qq.com * @DESC */ +// 使用lombok的@Slf4j注解,用于自动生成日志相关的代码,方便在类中记录各种操作相关的日志信息,便于后续查看操作情况以及进行问题排查,比如在获取当前用户信息出现异常等情况时记录详细的错误日志。 @Slf4j public class BaseController { + // 通过Spring的依赖注入机制,使用@Autowired注解自动注入CommonCacheUtil类型的实例,CommonCacheUtil类应该是用于操作缓存(可能是Redis等缓存)的工具类,后续会借助它从缓存中获取用户相关信息。 @Autowired private CommonCacheUtil commonCacheUtil; + /** + * 该方法用于从请求中获取当前登录用户的信息,其大致逻辑如下: + * 首先,尝试从HTTP请求中读取登录相关的Cookie信息(通过调用CookieUtil.readLoginToken方法),这个Cookie中存储的应该是登录令牌(token),用于标识用户的登录状态。 + * 如果读取到的登录令牌为空(即StringUtils.isBlank判断为真,StringUtils应该是自定义的字符串工具类,用于判断字符串是否为空,包括null和空字符串情况),说明用户未登录,此时会抛出一个自定义的SnailmallException异常,提示用户未登录,无法获取当前用户信息。 + * 接着,如果获取到了登录令牌,就使用这个令牌作为键,通过注入的CommonCacheUtil实例从缓存中获取对应的用户信息字符串(通过调用commonCacheUtil.getCacheValue方法),缓存中存储的应该是将用户对象转换为JSON字符串后的形式。 + * 如果从缓存中获取到的用户信息字符串为null,意味着缓存中不存在该用户信息或者出现了其他异常情况,此时会抛出一个SnailmallException异常,使用ResponseEnum.NEED_LOGIN中定义的状态码和描述信息来表明需要用户登录,即当前用户的登录状态可能存在问题或者已失效等情况。 + * 最后,如果从缓存中成功获取到了用户信息字符串,就通过JsonUtil.Str2Obj方法(JsonUtil应该是用于处理JSON序列化和反序列化的工具类)将用户信息字符串反序列化为User类型的对象并返回,这样就获取到了当前登录用户的完整信息对象。 + * + * @param httpServletRequest 表示HTTP请求对象,通过它可以获取客户端发送过来的各种信息,包括存储用户登录令牌的Cookie信息等,用于后续查找用户信息的操作。 + * @return 返回获取到的当前登录用户对应的User类型对象,如果在获取过程中出现上述提到的异常情况则会抛出相应异常,终止方法的执行。 + */ User getCurrentUser(HttpServletRequest httpServletRequest){ String loginToken = CookieUtil.readLoginToken(httpServletRequest); if(StringUtils.isBlank(loginToken)){ @@ -36,4 +49,4 @@ public class BaseController { User user = JsonUtil.Str2Obj(userJsonStr,User.class); return user; } -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/controller/ShippingController.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/controller/ShippingController.java index ca554ce..ae113fa 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/controller/ShippingController.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/controller/ShippingController.java @@ -26,16 +26,31 @@ import java.util.Enumeration; * @CONTACT 317758022@qq.com * @DESC 都需要先登陆才可以看到地址或者操作地址 */ +// @RestController是Spring框架提供的一个复合注解,它结合了@Controller和@ResponseBody注解的功能,表明这个类是一个处理HTTP请求的控制器类,并且方法的返回值会直接以JSON等格式响应给客户端,无需额外配置视图解析等操作。 +// 这里@RequestMapping注解在类级别上指定了该控制器类处理的请求的基础路径为"/shipping/",意味着这个类中所有方法处理的请求路径都以"/shipping/"开头。 @RestController @RequestMapping("/shipping/") +// 使用lombok的@Slf4j注解,用于自动生成日志相关的代码,方便在类中记录各种操作相关的日志信息,便于后续查看请求处理情况以及进行问题排查,比如记录请求参数、处理结果以及出现异常时的详细错误信息等。 @Slf4j +// 该类继承自BaseController类,意味着它可以继承BaseController中定义的属性和方法,比如获取当前用户信息的方法等,便于在当前控制器类的各个方法中复用相关逻辑。 public class ShippingController extends BaseController{ + // 通过Spring的依赖注入机制,使用@Autowired注解自动注入IShippingService接口的实现类实例,IShippingService应该是定义了与地址相关的业务逻辑方法的接口,比如添加、删除、更新地址等操作,具体的业务实现由对应的实现类来完成。 @Autowired private IShippingService shippingService; + // 同样通过@Autowired注解注入CommonCacheUtil类型的实例,CommonCacheUtil类应该是用于操作缓存(可能是Redis等缓存)的工具类,在一些方法中会借助它来获取缓存中的用户信息等数据。 @Autowired private CommonCacheUtil commonCacheUtil; + /** + * 该方法用于处理添加地址的HTTP请求,对应"/shipping/add.do"这个请求路径。 + * 首先,通过调用父类(BaseController)的getCurrentUser方法,从传入的HTTP请求中获取当前登录用户的信息,这个方法内部会进行一系列验证,比如检查用户是否登录等操作,如果用户未登录会抛出相应异常。 + * 获取到当前用户后,将用户的ID以及要添加的地址信息(Shipping类型的对象,应该包含了地址的详细内容,如收件人、地址、联系方式等)传递给shippingService的add方法,由具体的业务实现类来处理添加地址的逻辑,并返回一个ServerResponse类型的响应对象,这个对象会包含操作的结果状态、提示消息以及可能的业务数据等信息,最终将这个响应对象返回给客户端,告知客户端添加地址操作的结果。 + * + * @param httpServletRequest 表示HTTP请求对象,通过它可以获取客户端发送过来的各种信息,包括用于获取当前用户信息的相关数据(如Cookie等),同时也可以传递一些请求相关的参数等。 + * @param shipping 表示要添加的地址信息对象,包含了地址相关的详细内容,作为参数传递给业务层方法用于添加地址操作。 + * @return 返回一个ServerResponse类型的响应对象,包含了添加地址操作的结果相关信息,例如操作是否成功、提示消息以及可能的业务数据等,客户端会接收到这个响应对象并根据其中的信息进行相应处理。 + */ /** * 添加地址 */ @@ -45,6 +60,14 @@ public class ShippingController extends BaseController{ return shippingService.add(user.getId(),shipping); } + /** + * 该方法用于处理删除地址的HTTP请求,对应"/shipping/del.do"这个请求路径。 + * 同样先通过调用父类的getCurrentUser方法获取当前登录用户信息,然后将用户的ID以及要删除的地址的ID(Integer类型的shippingId参数,表示要删除的地址在系统中的唯一标识)传递给shippingService的del方法,由业务实现类来处理删除地址的具体逻辑,最后返回包含操作结果信息的ServerResponse响应对象给客户端,告知客户端删除地址操作是否成功等情况。 + * + * @param httpServletRequest 表示HTTP请求对象,用于获取当前用户信息以及传递请求相关的参数等。 + * @param shippingId 表示要删除的地址在系统中的唯一标识,作为参数传递给业务层方法用于确定要删除的具体地址。 + * @return 返回一个ServerResponse类型的响应对象,包含了删除地址操作的结果相关信息,供客户端根据其中的信息进行相应处理。 + */ /** * 删除地址 */ @@ -54,6 +77,14 @@ public class ShippingController extends BaseController{ return shippingService.del(user.getId(),shippingId); } + /** + * 该方法用于处理更新地址的HTTP请求,对应"/shipping/update.do"这个请求路径。 + * 先是调用父类方法获取当前登录用户信息,接着将用户的ID以及包含更新后地址信息的Shipping对象(shipping参数,包含了更新后的地址相关详细内容)传递给shippingService的update方法,由业务实现类按照业务逻辑来更新地址信息,并返回一个ServerResponse响应对象给客户端,告知客户端更新地址操作的结果情况。 + * + * @param httpServletRequest 表示HTTP请求对象,用于获取当前用户信息以及传递更新地址相关的参数等。 + * @param shipping 表示包含更新后地址信息的对象,作为参数传递给业务层方法用于更新地址操作。 + * @return 返回一个ServerResponse类型的响应对象,包含了更新地址操作的结果相关信息,供客户端进行相应处理。 + */ /** * 更新地址 */ @@ -63,6 +94,14 @@ public class ShippingController extends BaseController{ return shippingService.update(user.getId(),shipping); } + /** + * 该方法用于处理选择选中地址的HTTP请求,对应"/shipping/select.do"这个请求路径。 + * 先从HTTP请求中获取当前登录用户信息(通过调用父类方法),然后将用户的ID以及要选择的地址的ID(shippingId参数)传递给shippingService的select方法,由业务实现类来处理选择地址的具体逻辑,最后返回包含操作结果信息的ServerResponse响应对象给客户端,告知客户端选择地址操作是否成功等情况。 + * + * @param httpServletRequest 表示HTTP请求对象,用于获取当前用户信息以及传递请求相关的参数等。 + * @param shippingId 表示要选择的地址在系统中的唯一标识,作为参数传递给业务层方法用于确定要选择的具体地址。 + * @return 返回一个ServerResponse类型的响应对象,包含了选择地址操作的结果相关信息,供客户端根据其中的信息进行相应处理。 + */ /** * 选择选中的地址 */ @@ -72,6 +111,15 @@ public class ShippingController extends BaseController{ return shippingService.select(user.getId(),shippingId); } + /** + * 该方法用于处理获取地址列表的HTTP请求,对应"/shipping/list.do"这个请求路径。 + * 首先通过调用父类方法获取当前登录用户信息,然后将用户的ID以及分页相关的参数(pageNum表示当前页码,默认值为1;pageSize表示每页显示的记录数,默认值为10,通过@RequestParam注解从请求中获取并设置默认值)传递给shippingService的list方法,由业务实现类根据用户ID和分页参数来查询并返回相应的地址列表信息,最终以ServerResponse类型的响应对象返回给客户端,其中PageInfo应该是包含了分页相关信息以及地址列表数据的对象,客户端可以根据响应获取到地址列表并进行展示等操作。 + * + * @param httpServletRequest 表示HTTP请求对象,用于获取当前用户信息以及传递分页相关的请求参数等。 + * @param pageNum 表示当前页码,通过请求参数获取,默认值为1,如果请求中未传递该参数则使用默认值,用于确定要获取哪一页的地址列表信息。 + * @param pageSize 表示每页显示的记录数,通过请求参数获取,默认值为10,用于确定每页展示的地址数量,业务实现类会根据这个参数进行分页查询操作。 + * @return 返回一个ServerResponse类型的响应对象,包含了地址列表查询操作的结果相关信息,如操作是否成功、提示消息以及包含地址列表和分页信息的PageInfo对象等,客户端可据此进行相应处理,比如展示地址列表等操作。 + */ /** * 地址列表 */ @@ -83,6 +131,17 @@ public class ShippingController extends BaseController{ return shippingService.list(user.getId(),pageNum,pageSize); } + /** + * 该方法用于处理根据地址ID获取地址详细信息的HTTP请求,对应"/shipping/getShipping.do"这个请求路径。 + * 首先通过日志记录开始根据给定的地址ID(shippingId参数)获取地址信息的操作,然后尝试从HTTP请求的头部信息中获取用户登录相关的令牌(Cookie)信息,通过遍历请求头的所有名称(使用Enumeration来遍历,httpServletRequest.getHeaderNames方法获取所有头部名称的枚举),查找名为"snailmall_login_token"(忽略大小写比较,通过equalsIgnoreCase方法)的头部信息,获取其对应的值(即登录令牌)。 + * 如果获取到的登录令牌值为空(StringUtils.isBlank判断),则记录错误日志并返回一个表示用户未登录的ServerResponse响应对象,使用ResponseEnum.NEED_LOGIN中定义的状态码和描述信息告知客户端用户未登录,无法获取地址信息。 + * 接着,如果获取到了登录令牌值,就通过注入的CommonCacheUtil实例,以登录令牌作为键从缓存中获取对应的用户信息字符串(userJsonStr),如果获取到的用户信息字符串为null,说明缓存中不存在用户信息或者出现其他异常情况,此时记录错误日志并返回同样表示用户未登录的ServerResponse响应对象。 + * 如果成功从缓存中获取到了用户信息字符串,就通过JsonUtil.Str2Obj方法(JsonUtil是用于处理JSON序列化和反序列化的工具类)将用户信息字符串反序列化为User类型的对象,同时记录获取到的用户信息日志,最后将用户的ID以及要获取的地址的ID传递给shippingService的getShippingById方法,由业务实现类根据ID获取地址详细信息,并返回包含操作结果和地址信息的ServerResponse响应对象给客户端,告知客户端获取地址操作的结果情况。 + * + * @param httpServletRequest 表示HTTP请求对象,用于获取请求头部的登录令牌信息以及传递请求相关的参数等,同时也可以获取其他一些请求相关的数据用于后续操作。 + * @param shippingId 表示要获取详细信息的地址在系统中的唯一标识,作为参数传递给业务层方法用于确定要获取的具体地址。 + * @return 返回一个ServerResponse类型的响应对象,包含了根据地址ID获取地址操作的结果相关信息以及可能的地址详细信息,客户端会根据其中的信息进行相应处理,比如展示地址详情等操作。 + */ /** * 根据id获取地址 */ @@ -91,7 +150,7 @@ public class ShippingController extends BaseController{ log.info("【开始根据{}获取地址】",shippingId); User user = null; Enumeration headerNames = httpServletRequest.getHeaderNames(); - if (headerNames != null) { + if (headerNames!= null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); if(name.equalsIgnoreCase("snailmall_login_token")){ @@ -117,4 +176,4 @@ public class ShippingController extends BaseController{ -} +} \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/dao/ShippingMapper.xml b/snailmall-shipping-service/src/main/java/com/njupt/swg/dao/ShippingMapper.xml index 99c7e2a..bef1ff6 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/dao/ShippingMapper.xml +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/dao/ShippingMapper.xml @@ -1,9 +1,17 @@ - + + + + @@ -17,171 +25,201 @@ + - id, user_id, receiver_name, receiver_phone, receiver_mobile, receiver_province, receiver_city, - receiver_district, receiver_address, receiver_zip, create_time, update_time + id, user_id, receiver_name, receiver_phone, receiver_mobile, receiver_province, receiver_city, + receiver_district, receiver_address, receiver_zip, create_time, update_time + + delete from mmall_shipping where id = #{id,jdbcType=INTEGER} + - insert into mmall_shipping (id, user_id, receiver_name, - receiver_phone, receiver_mobile, receiver_province, - receiver_city, receiver_district, receiver_address, - receiver_zip, create_time, update_time - ) - values (#{id,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{receiverName,jdbcType=VARCHAR}, - #{receiverPhone,jdbcType=VARCHAR}, #{receiverMobile,jdbcType=VARCHAR}, #{receiverProvince,jdbcType=VARCHAR}, - #{receiverCity,jdbcType=VARCHAR}, #{receiverDistrict,jdbcType=VARCHAR}, #{receiverAddress,jdbcType=VARCHAR}, - #{receiverZip,jdbcType=VARCHAR}, now(), now() - ) + insert into mmall_shipping (id, user_id, receiver_name, + receiver_phone, receiver_mobile, receiver_province, + receiver_city, receiver_district, receiver_address, + receiver_zip, create_time, update_time + ) + values (#{id,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{receiverName,jdbcType=VARCHAR}, + #{receiverPhone,jdbcType=VARCHAR}, #{receiverMobile,jdbcType=VARCHAR}, #{receiverProvince,jdbcType=VARCHAR}, + #{receiverCity,jdbcType=VARCHAR}, #{receiverDistrict,jdbcType=VARCHAR}, #{receiverAddress,jdbcType=VARCHAR}, + #{receiverZip,jdbcType=VARCHAR}, now(), now() + ) + insert into mmall_shipping - + id, - + user_id, - + receiver_name, - + receiver_phone, - + receiver_mobile, - + receiver_province, - + receiver_city, - + receiver_district, - + receiver_address, - + receiver_zip, - + create_time, - + update_time, - + #{id,jdbcType=INTEGER}, - + #{userId,jdbcType=INTEGER}, - + #{receiverName,jdbcType=VARCHAR}, - + #{receiverPhone,jdbcType=VARCHAR}, - + #{receiverMobile,jdbcType=VARCHAR}, - + #{receiverProvince,jdbcType=VARCHAR}, - + #{receiverCity,jdbcType=VARCHAR}, - + #{receiverDistrict,jdbcType=VARCHAR}, - + #{receiverAddress,jdbcType=VARCHAR}, - + #{receiverZip,jdbcType=VARCHAR}, - + now(), - + now(), + update mmall_shipping - + user_id = #{userId,jdbcType=INTEGER}, - + receiver_name = #{receiverName,jdbcType=VARCHAR}, - + receiver_phone = #{receiverPhone,jdbcType=VARCHAR}, - + receiver_mobile = #{receiverMobile,jdbcType=VARCHAR}, - + receiver_province = #{receiverProvince,jdbcType=VARCHAR}, - + receiver_city = #{receiverCity,jdbcType=VARCHAR}, - + receiver_district = #{receiverDistrict,jdbcType=VARCHAR}, - + receiver_address = #{receiverAddress,jdbcType=VARCHAR}, - + receiver_zip = #{receiverZip,jdbcType=VARCHAR}, - + create_time = #{createTime,jdbcType=TIMESTAMP}, - + update_time = now(), where id = #{id,jdbcType=INTEGER} + update mmall_shipping set user_id = #{userId,jdbcType=INTEGER}, - receiver_name = #{receiverName,jdbcType=VARCHAR}, - receiver_phone = #{receiverPhone,jdbcType=VARCHAR}, - receiver_mobile = #{receiverMobile,jdbcType=VARCHAR}, - receiver_province = #{receiverProvince,jdbcType=VARCHAR}, - receiver_city = #{receiverCity,jdbcType=VARCHAR}, - receiver_district = #{receiverDistrict,jdbcType=VARCHAR}, - receiver_address = #{receiverAddress,jdbcType=VARCHAR}, - receiver_zip = #{receiverZip,jdbcType=VARCHAR}, - create_time = #{createTime,jdbcType=TIMESTAMP}, - update_time = now() + receiver_name = #{receiverName,jdbcType=VARCHAR}, + receiver_phone = #{receiverPhone,jdbcType=VARCHAR}, + receiver_mobile = #{receiverMobile,jdbcType=VARCHAR}, + receiver_province = #{receiverProvince,jdbcType=VARCHAR}, + receiver_city = #{receiverCity,jdbcType=VARCHAR}, + receiver_district = #{receiverDistrict,jdbcType=VARCHAR}, + receiver_address = #{receiverAddress,jdbcType=VARCHAR}, + receiver_zip = #{receiverZip,jdbcType=VARCHAR}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + update_time = now() where id = #{id,jdbcType=INTEGER} - + DELETE from mmall_shipping WHERE user_id=#{userId} and id = #{shippingId} - - + + update mmall_shipping set receiver_name = #{receiverName,jdbcType=VARCHAR}, @@ -195,14 +233,12 @@ create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = now() where id = #{id,jdbcType=INTEGER} - and user_id = #{userId,jdbcType=INTEGER} + and user_id = #{userId,jdbcType=INTEGER} - + - - - \ No newline at end of file + SELECT \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/entity/Shipping.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/entity/Shipping.java index da5beca..521a337 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/entity/Shipping.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/entity/Shipping.java @@ -7,32 +7,48 @@ import lombok.NoArgsConstructor; import java.util.Date; +// 使用lombok的@Data注解,该注解会自动为类生成一系列常用方法,包括所有非静态成员变量的Getter、Setter方法,以及equals、hashCode、toString方法等,方便在其他地方对类的对象进行属性访问、比较以及以字符串形式展示等操作,减少了手动编写这些重复代码的工作量。 @Data +// 使用lombok的@NoArgsConstructor注解,会为该类生成一个无参构造函数,在一些需要默认创建类实例的场景下(比如反序列化等)能够方便地使用,确保可以通过无参的方式构造出Shipping类的对象。 @NoArgsConstructor +// 使用lombok的@AllArgsConstructor注解,会为该类生成一个包含所有参数的构造函数,调用这个构造函数时可以一次性传入所有成员变量的值来创建Shipping类的对象,适用于需要完整初始化对象所有属性的情况。 @AllArgsConstructor public class Shipping { + // 定义一个Integer类型的私有成员变量id,用于存储地址记录在系统中的唯一标识,通常对应数据库表中的主键字段,用于区分不同的地址记录。 private Integer id; + // 定义一个Integer类型的私有成员变量userId,用于存储该地址所属用户的唯一标识,通过这个字段可以关联到具体的用户,表明该地址是哪个用户的收货地址。 private Integer userId; + // 定义一个String类型的私有成员变量receiverName,用于存储收货人的姓名,方便在物流配送等场景中明确收件人信息。 private String receiverName; + // 定义一个String类型的私有成员变量receiverPhone,用于存储收货人的固定电话号码,作为一种联系方式,方便快递员等相关人员与收件人沟通联系。 private String receiverPhone; + // 定义一个String类型的私有成员变量receiverMobile,用于存储收货人的手机号码,在现代物流配送中,手机号码通常是更常用的联系方式,便于接收快递相关的通知等信息。 private String receiverMobile; + // 定义一个String类型的私有成员变量receiverProvince,用于存储收货地址所在的省份信息,明确地址的大致地理位置范围。 private String receiverProvince; + // 定义一个String类型的私有成员变量receiverCity,用于存储收货地址所在的城市信息,进一步细化地址的地理位置,方便物流准确配送。 private String receiverCity; + // 定义一个String类型的私有成员变量receiverDistrict,用于存储收货地址所在的区(县)信息,更加精确地定位收货地址,有助于快递准确送达。 private String receiverDistrict; + // 定义一个String类型的私有成员变量receiverAddress,用于存储详细的收货地址信息,如街道名称、门牌号等具体内容,确保快递能够准确送到收件人手中。 private String receiverAddress; + // 定义一个String类型的私有成员变量receiverZip,用于存储收货地址对应的邮政编码,虽然在现在的物流配送中邮政编码的使用频率相对降低,但在一些情况下仍可能有辅助定位等作用。 private String receiverZip; + // 使用Jackson的@JsonFormat注解来配置日期类型(Date)的成员变量createTime在序列化为JSON格式时的格式,指定shape为JsonFormat.Shape.STRING,表示将日期转换为字符串形式, + // pattern属性设置为"yyyy-MM-dd HH:mm:ss.SSS",即按照年-月-日 时:分:秒.毫秒的格式进行序列化,这样在将Shipping对象转换为JSON数据时,createTime字段会以指定的格式呈现,方便与前端等进行日期数据的交互和展示。 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS") private Date createTime; + // 同样使用@JsonFormat注解来配置日期类型的成员变量updateTime在序列化为JSON格式时的格式,和createTime一样,将其转换为指定格式的字符串形式,便于在JSON数据中准确展示该日期信息,方便数据交互和查看更新时间等情况。 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS") private Date updateTime; } \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/java/com/njupt/swg/entity/User.java b/snailmall-shipping-service/src/main/java/com/njupt/swg/entity/User.java index 4354b81..726c3da 100644 --- a/snailmall-shipping-service/src/main/java/com/njupt/swg/entity/User.java +++ b/snailmall-shipping-service/src/main/java/com/njupt/swg/entity/User.java @@ -14,30 +14,44 @@ import java.util.Date; * @CONTACT 317758022@qq.com * @DESC 用户实体类 */ +// 使用lombok的@Data注解,它会自动帮我们生成一系列常用的方法,比如针对类中所有非静态成员变量(这里的id、username、password等)的Getter和Setter方法,方便在其他地方获取和设置这些变量的值;同时还会生成equals方法用于比较两个对象是否相等、hashCode方法用于在一些基于哈希的数据结构(如HashSet、HashMap等)中确定对象的存储位置,以及toString方法用于将对象以字符串形式展示出来,这样可以减少手动编写这些重复代码的工作量,让代码更加简洁。 @Data +// 使用lombok的@NoArgsConstructor注解,它会为这个类生成一个无参构造函数,使得在某些需要默认创建类实例的场景下(例如在进行反序列化操作,或者只是单纯想创建一个空对象后续再去设置各个属性值时)能够方便地构造出User类的对象,提高了代码的灵活性和通用性。 @NoArgsConstructor +// 使用lombok的@AllArgsConstructor注解,会为该类生成一个包含所有参数的构造函数,在创建User类对象时,可以一次性传入所有成员变量对应的参数值来初始化对象,适用于已知所有属性值并且希望一次性完成对象初始化的情况,比如从数据库查询到完整的用户数据后,使用这个构造函数快速创建对应的User对象来进行后续操作。 @AllArgsConstructor +// 使用lombok的@ToString注解,虽然@Data注解已经包含了生成toString方法的功能,但这里再次显式使用可以进一步确保按照我们期望的方式生成toString方法(如果后续对@Data注解的默认行为有修改等情况,这个注解能保证toString方法的正确生成),它会将对象的各个属性值以一定的格式拼接成字符串返回,方便在调试或者需要直观查看对象内容时使用,例如打印对象时就能看到对象各个属性的具体值情况。 @ToString +// 让这个类实现Serializable接口,表示该类的对象可以被序列化和反序列化,这在很多场景下非常有用,比如将用户对象存储到文件中、在网络中传输用户对象等情况时,通过序列化将对象转换为字节流进行存储或传输,然后再通过反序列化将字节流还原为对象,实现数据的持久化和跨网络的交互等功能。 public class User implements Serializable { + // 定义一个Integer类型的私有成员变量id,用于存储用户在系统中的唯一标识,通常对应数据库表中的主键字段,通过这个id可以在系统中准确地定位和区分不同的用户。 private Integer id; + // 定义一个String类型的私有成员变量username,用于存储用户的用户名,是用户登录系统或者在系统中展示的一个重要标识,一般具有唯一性,方便用户进行身份识别和操作。 private String username; + // 定义一个String类型的私有成员变量password,用于存储用户的登录密码,密码通常会经过加密等安全处理后存储在数据库中,在用户登录时会验证输入的密码与存储的密码是否匹配,以此来确认用户的身份合法性。 private String password; + // 定义一个String类型的私有成员变量email,用于存储用户的电子邮箱地址,可用于接收系统发送的通知、找回密码等功能相关的邮件信息,方便与用户进行信息沟通和交互。 private String email; + // 定义一个String类型的私有成员变量phone,用于存储用户的电话号码,同样可以作为一种联系方式,用于接收短信验证码、系统重要通知等,也是验证用户身份以及方便沟通的重要信息。 private String phone; + // 定义一个String类型的私有成员变量question,用于存储用户设置的密保问题,在用户忘记密码等情况下,可以通过回答正确的密保问题来重置密码,增加账号的安全性和找回密码的便利性。 private String question; + // 定义一个String类型的私有成员变量answer,用于存储用户对密保问题设置的答案,与question字段配合使用,用于验证用户身份,确保只有知道正确答案的用户才能进行密码重置等敏感操作。 private String answer; + // 定义一个Integer类型的私有成员变量role,用于标识用户在系统中的角色,这里通过注释说明0表示管理员角色,1表示普通用户角色,不同的角色在系统中通常具有不同的权限,例如管理员可能可以进行更多的系统管理操作,而普通用户只能进行一些常规的业务操作。 //角色0-管理员,1-普通用户 private Integer role; + // 定义一个Date类型的私有成员变量createTime,用于记录用户账号在系统中创建的时间,方便进行数据统计、审计等操作,例如查看用户的注册时间分布情况等,通常在用户注册成功时会自动设置这个时间值。 private Date createTime; + // 定义一个Date类型的私有成员变量updateTime,用于记录用户账号信息在系统中最后一次更新的时间,每次用户修改了自己的部分信息(如密码、邮箱等)后,都会更新这个时间值,便于跟踪用户信息的变更情况以及进行相关的数据处理和分析。 private Date updateTime; - } \ No newline at end of file diff --git a/snailmall-shipping-service/src/main/resources/logback.xml b/snailmall-shipping-service/src/main/resources/logback.xml index a1617ae..a3d079c 100644 --- a/snailmall-shipping-service/src/main/resources/logback.xml +++ b/snailmall-shipping-service/src/main/resources/logback.xml @@ -1,33 +1,73 @@ + + + + + %d{H:mm} %-5level [%logger{16}] %msg%n + + + ${LOG_HOME}/web.normal.%d{yyyy-MM-dd}.log + 30 + 10MB + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{16} - %msg%n + + ERROR + DENY + ACCEPT + + @@ -44,18 +84,23 @@ ERROR + ACCEPT DENY - + - + diff --git a/snailmall-user-service/pom.xml b/snailmall-user-service/pom.xml index 24a242b..4fcd1c9 100644 --- a/snailmall-user-service/pom.xml +++ b/snailmall-user-service/pom.xml @@ -1,105 +1,157 @@ + + 4.0.0 + com.njupt.swg spring-cloud-for-snailmall 0.0.1-SNAPSHOT + snailmall-user-service + 0.0.1-SNAPSHOT + snailmall-user-service + Demo project for Spring Boot + - + + org.springframework.boot spring-boot-starter-web + org.springframework.cloud spring-cloud-config-client + org.springframework.cloud spring-cloud-starter-netflix-eureka-client + org.springframework.cloud spring-cloud-starter-bus-amqp + org.springframework.cloud spring-cloud-starter-zipkin + org.springframework.boot spring-boot-starter-actuator + org.projectlombok lombok - + + org.codehaus.jackson jackson-mapper-asl - + + com.alibaba druid - + + com.google.guava guava + org.apache.commons commons-lang3 + commons-collections commons-collections - + + mysql mysql-connector-java - + + org.mybatis.spring.boot mybatis-spring-boot-starter - + + redis.clients jedis - + + joda-time joda-time - + + org.apache.curator curator-framework + org.apache.curator curator-recipes + io.springfox springfox-swagger2 + io.springfox springfox-swagger-ui @@ -107,8 +159,11 @@ + - + + src/main/java @@ -118,6 +173,9 @@ + org.springframework.boot spring-boot-maven-plugin @@ -125,4 +183,4 @@ - + \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/SnailmallUserServiceApplication.java b/snailmall-user-service/src/main/java/com/njupt/swg/SnailmallUserServiceApplication.java index 1006562..22e9dfa 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/SnailmallUserServiceApplication.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/SnailmallUserServiceApplication.java @@ -5,14 +5,30 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import springfox.documentation.swagger2.annotations.EnableSwagger2; +// @SpringBootApplication 是一个组合注解,它整合了多个 Spring 相关的注解,包括 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 等。 +// 意味着这个类是一个 Spring Boot 应用的主启动类,Spring Boot 会基于这个类所在的包及其子包去扫描并自动配置各种组件、加载配置文件、启动内置的服务器(如 Tomcat 等)等, +// 从而简化了 Spring 应用的启动和配置过程,让开发者可以更便捷地开发基于 Spring 的应用程序。 @SpringBootApplication +// @EnableDiscoveryClient 注解用于开启服务发现功能,在微服务架构中,它允许应用能够将自身注册到服务注册中心(比如 Eureka、Consul 等), +// 并且可以发现其他已注册的服务,方便服务之间的相互调用和协作,实现了微服务之间的解耦和动态发现机制,提高了系统的可扩展性和灵活性。 @EnableDiscoveryClient +// @EnableSwagger2 注解用于启用 Swagger2,Swagger2 是一个强大的 API 文档生成工具,它可以根据代码中的注解自动生成美观、清晰且易于交互的 API 文档, +// 方便前后端开发人员进行接口的对接、测试以及其他开发人员对 API 的了解和使用,极大地提高了开发效率和接口的可读性。 @EnableSwagger2 public class SnailmallUserServiceApplication { + /** + * 这是 Java 应用程序的入口方法,在 Spring Boot 应用中,通过调用 SpringApplication.run 方法来启动整个 Spring Boot 应用。 + * 第一个参数 SnailmallUserServiceApplication.class 指定了 Spring Boot 应用的主配置类,也就是当前这个类本身, + * 它包含了应用启动所需的各种配置信息(通过前面的注解体现)以及组件扫描等规则。 + * 第二个参数 args 用于接收命令行传入的参数,开发者可以在启动应用时通过命令行传入一些自定义的配置参数、运行模式等信息, + * Spring Boot 会根据这些参数进行相应的处理(比如加载不同的配置文件、设置运行环境等),然后启动整个应用,初始化各种组件、加载配置、启动服务器等, + * 最终使应用处于运行状态,可以对外提供服务。 + * + * @param args 命令行参数,用于在启动应用时传入一些自定义的配置或控制信息,其具体内容和使用方式取决于应用的具体需求和设计。 + */ public static void main(String[] args) { SpringApplication.run(SnailmallUserServiceApplication.class, args); } -} - +} \ 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..f0f7a31 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 @@ -29,215 +29,189 @@ import javax.servlet.http.HttpSession; */ //TODO 先全部开放GET请求 +// 使用@RequestMapping注解,将该类下所有的请求处理方法的请求路径都映射到以"/user"开头的路径下, +// 这样便于对用户相关的接口进行统一的路径管理,使得接口的结构更加清晰,符合RESTful风格的接口设计规范。 @RequestMapping("user") +// @RestController注解是一个组合注解,相当于同时使用了@Controller和@ResponseBody注解。 +// 表示这个类是一个Spring MVC的控制器类,并且类中所有方法的返回值都会直接作为响应体(ResponseBody)返回给客户端, +// 通常返回的数据格式为JSON等,方便与前端进行数据交互,处理客户端发送的关于用户相关的各种请求。 @RestController +// 使用lombok的@Slf4j注解,用于自动生成日志记录相关的代码,方便在类中的各个方法执行过程中记录日志信息, +// 比如记录用户操作的相关情况、接口调用的状态等,有助于后续进行问题排查、系统监控以及数据分析等工作。 @Slf4j -// 表示标识这个类是swagger的资源 +// @Api注解用于标识这个类是Swagger的资源,在Swagger生成的接口文档中对这个控制器类进行描述。 +// 通过设置value属性指定类在文档中的显示名称,tags属性设置相关的标签,便于对用户服务相关的接口在文档中进行分类展示, +// 让接口的使用者(如前端开发人员、测试人员等)能够直观地了解接口所属的功能模块以及大致用途等信息。 @Api(value = "UserController", tags = {"用户服务接口"}) public class UserController { + // 通过Spring的依赖注入机制,使用@Autowired注解自动注入IUserService类型的实例。 + // IUserService应该是一个定义了与用户业务逻辑相关操作的接口,其具体的实现类由Spring容器根据配置进行实例化并注入到此处。 + // 通过这个接口可以调用诸如用户登录、注册、信息查询、修改等各种业务方法,以此实现具体的用户相关功能处理。 @Autowired private IUserService userService; + // 同样使用@Autowired注解注入CommonCacheUtil类型的实例,CommonCacheUtil类通常是用于缓存操作的工具类, + // 在这里主要负责与Redis等缓存系统进行交互,例如在用户登录后将用户信息缓存到Redis中,或者从Redis中获取缓存的用户信息等操作, + // 方便对用户相关的数据进行缓存管理,从而提高系统的性能,减少对数据库的频繁访问,提升数据获取的效率。 @Autowired private CommonCacheUtil commonCacheUtil; /** - * 用户登陆:验证参数、登陆、写到cookie中并且写到redis中 - * 用户登陆以后,点击其他需要登陆才能看的页面时,先判断是否前端是否有这个key,没有则提示需要登陆 + * 此方法用于处理用户登录的请求,实现了包括验证登录参数、执行用户登录逻辑、将登录相关信息写到Cookie中以及缓存到Redis中等一系列操作, + * 以此完成用户登录的功能,并最终返回登录后的相关结果给客户端。 + * 当用户登录成功后,在访问其他需要登录权限才能查看的页面时,前端可以先判断是否存在代表登录状态的特定key(存储在Cookie中), + * 如果不存在则提示用户需要登录,以此来控制页面的访问权限,确保系统的安全性和数据的保密性。 + * + * @param session HttpSession对象,它代表了用户与服务器之间的一次会话,服务器可以通过它来存储用户在本次会话期间的相关信息, + * 例如在这里可以获取会话的唯一标识(sessionId),并将其作为用户登录状态的标识,存储到Cookie和Redis中,方便后续的验证和操作。 + * @param response HttpServletResponse对象,用于向客户端发送响应,通过这个对象可以将一些信息(如登录成功后生成的Cookie)发送给客户端浏览器, + * 让浏览器进行保存,以便在后续的请求中携带这些信息,用于服务器端验证用户的登录状态等操作。 + * @param username 用户名,作为登录请求的必传参数,由客户端传入,不能为空,它与密码一起用于在登录验证时核对用户的身份, + * 是判断用户是否合法登录系统的关键输入信息之一。 + * @param password 用户密码,同样是登录请求的必传参数,不能为空,它和用户名共同构成了验证用户身份的要素,确保只有合法的用户能够登录系统。 + * @return 返回一个ServerResponse类型的响应对象,其中ServerResponse是一个自定义的通用响应类, + * 它包含了登录操作的结果状态(如成功或失败,通过isSuccess方法判断)以及登录成功后用户相关的详细信息(通过UserResVO对象进行封装), + * 方便客户端根据返回的结果判断登录是否成功,并获取相应的用户信息进行后续的展示、处理等操作,例如在前端页面展示用户的基本信息等。 */ - @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); + // 调用userService的login方法,传入用户名和密码参数,由userService的具体实现类来执行用户登录验证等相关的业务逻辑处理, + // 例如可能会在数据库中查询用户信息,验证用户名和密码是否匹配等操作,最终返回一个ServerResponse类型的对象, + // 包含了登录操作的结果以及登录成功后对应的用户相关信息(如果登录成功)。 + ServerResponse userVOServerResponse = userService.login(username, password); + if (userVOServerResponse.isSuccess()) { + // 如果登录成功,那么需要将用户的登录状态信息存储到Redis中,并且把代表用户的sessionId写到前端浏览器的Cookie中, + // 这样后续用户访问其他页面时,服务器可以通过验证Cookie中的sessionId以及Redis中的缓存信息来确认用户的登录状态。 + log.info("【用户{}cookie开始写入】", username); + // 调用CookieUtil工具类的writeLoginToken方法,将sessionId写入到响应的Cookie中,传递给客户端浏览器保存, + // 使得浏览器在后续的请求中会自动带上这个Cookie,方便服务器识别用户身份。 + CookieUtil.writeLoginToken(response, session.getId()); + // 调用commonCacheUtil的cacheNxExpire方法,先将用户信息通过JsonUtil的obj2String方法序列化为JSON字符串格式, + // 然后将其存储到Redis中,并设置过期时间为30分钟(通过Constants.RedisCacheExtime.REDIS_SESSION_EXTIME来指定), + // 这样可以在一定时间内缓存用户信息,避免频繁查询数据库获取相同的用户信息,提高系统性能,同时在过期后自动清除缓存数据,保证数据的时效性。 + log.info("【用户{}redis开始写入】", username); commonCacheUtil.cacheNxExpire(session.getId(), JsonUtil.obj2String(userVOServerResponse.getData()), Constants.RedisCacheExtime.REDIS_SESSION_EXTIME); } - log.info("【用户{}登陆成功】",username); + // 使用日志记录用户登录成功的信息,同样通过占位符记录用户名,方便后续统计登录情况、排查登录相关问题等, + // 例如可以统计每日登录成功的用户数量等信息,通过日志进行辅助分析。 + log.info("【用户{}登陆成功】", username); return userVOServerResponse; } /** - * 用户注册,要判断用户名和邮箱是否重复,这里用了分布式锁来防止用户名和邮箱可能出现重复 + * 该方法用于处理用户注册的请求,在用户注册的过程中,需要判断用户名和邮箱是否已经在系统中被其他用户使用,为了防止在高并发场景下出现用户名或邮箱重复注册的问题, + * 这里使用了分布式锁机制来保证用户名和邮箱的唯一性,确保注册信息的合法性以及系统数据的一致性,最后返回注册操作的结果给客户端。 + * + * @param user 一个User类型的对象,作为注册请求的参数,由客户端传入,它包含了用户注册时填写的详细信息,如用户名、邮箱、密码等, + * 这些信息需要符合系统定义的用户实体结构要求,是进行用户注册操作的关键输入数据,将用于后续在数据库中插入新用户记录等操作。 + * @return 返回一个ServerResponse类型的响应对象,该对象包含了用户注册操作的结果状态(如成功或失败)以及可能的相关提示信息等, + * 方便客户端根据返回的结果判断注册是否成功,并知晓注册失败的原因等情况,进而采取相应的后续操作,例如提示用户重新填写注册信息等。 */ - @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类型的对象,包含注册结果以及相关的提示信息等内容。 ServerResponse response = userService.register(user); log.info("【用户注册成功】"); return response; } /** - * 判断用户名和邮箱是否重复 + * 此方法用于处理验证用户名和邮箱是否重复的请求,通过传入的参数来判断指定的用户名或邮箱在系统中是否已经被其他用户使用, + * 然后返回相应的验证结果给客户端,以此辅助用户在进行注册、修改信息等操作时确保输入的用户名和邮箱的唯一性。 + * + * @param str 一个字符串类型的输入参数,由客户端传入,它用于传递要验证的用户名或邮箱等具体信息, + * 根据另一个参数type的取值来确定其具体代表的是用户名还是邮箱,是验证操作的关键输入数据之一。 + * @param type 同样是一个字符串类型的参数,由客户端传入,用于指定str参数所代表的类型,其取值应该是预定义的表示用户名或邮箱等类型的常量(例如在Constants类中定义的相关常量), + * 通过这个参数明确要验证的是用户名还是邮箱是否重复,从而决定验证操作的具体逻辑走向。 + * @return 返回一个ServerResponse类型的响应对象,该对象包含了验证操作的结果状态(如重复或不重复)以及相关的提示信息等, + * 方便客户端根据返回结果判断输入的用户名或邮箱是否可用,进而采取相应的后续操作,例如提示用户重新输入等。 */ - @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 + * 这个方法用于处理获取登录状态用户信息的请求,在本地进行测试时,由于Cookie是写到"oursnai.cn"域名下面的, + * 所以需要在本地的hosts文件中添加"127.0.0.1 oursnail.cn"这个解析记录,使得本地测试环境能够模拟真实的域名访问情况,从而正确地获取到Cookie信息。 + * 在浏览器中进行测试时,需要将login方法暂时开放为GET请求(原本可能是POST等其他请求方式,这里只是为了方便测试进行的临时调整), + * 然后按照指定的请求路径(如http://oursnail.cn:8081/user/login.do?username=admin&password=123456)进行登录操作,获取到登录状态的Cookie信息后, + * 再通过同样的域名(http://oursnail.cn:8081/user/get_user_info.do)来请求这个获取用户信息的接口,否则可能因为域名不匹配等原因无法获取到代表登录状态的token,进而不能获取用户信息。 + * 最终会根据用户的登录状态返回相应的用户个人信息给客户端,如果用户未登录则返回相应的提示信息告知客户端无法获取信息。 + * + * @param request HttpServletRequest对象,用于接收客户端发送的请求,通过它可以获取请求中的各种信息, + * 在这里主要是用于获取客户端发送过来的包含在请求中的Cookie信息,以便从中读取代表登录状态的token,进而获取登录用户的相关信息。 + * @return 返回一个ServerResponse类型的响应对象,该对象包含了获取用户信息操作的结果状态(如成功获取到信息或用户未登录等情况)以及如果成功获取到的登录用户的个人信息(通过相关的用户信息对象表示), + * 方便客户端根据返回结果判断是否成功获取到用户信息,并进行相应的展示等后续操作,例如在前端页面展示用户的基本信息等。 */ - @ApiOperation(value="获取用户个人信息", notes="登陆状态下获取") + @ApiOperation(value = "获取用户个人信息", notes = "登陆状态下获取") @RequestMapping("/get_user_info.do") - public ServerResponse getUserInfo(HttpServletRequest request){ + public ServerResponse getUserInfo(HttpServletRequest request) { + // 调用CookieUtil工具类的readLoginToken方法,从请求对象中读取代表登录状态的token(该token存储在Cookie中), + // 如果能够读取到则说明用户可能处于登录状态,若读取不到则表示用户未登录,后续需要进行相应的处理。 String loginToken = CookieUtil.readLoginToken(request); - if(StringUtils.isEmpty(loginToken)){ + if (StringUtils.isEmpty(loginToken)) { log.info("【用户未登录,无法获取当前用户信息】"); return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); } + // 调用commonCacheUtil的getCacheValue方法,根据读取到的登录token从缓存(如Redis)中获取对应的用户信息字符串, + // 如果获取到的用户信息字符串为null,则可能是缓存过期或者用户未登录等情况,同样需要进行相应的处理。 String userStr = commonCacheUtil.getCacheValue(loginToken); - if(userStr == null){ + if (userStr == null) { log.info("【用户未登录,无法获取当前用户信息】"); return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); } - User currentUser = JsonUtil.Str2Obj(userStr,User.class); + // 调用JsonUtil的Str2Obj方法,将从缓存中获取到的用户信息字符串反序列化为User类型的对象,以便后续进行业务逻辑处理, + // 例如可以调用userService的相关方法进一步获取数据库中的详细用户信息等操作。 + User currentUser = JsonUtil.Str2Obj(userStr, User.class); UserResVO userResVO = userService.getUserInfoFromDB(currentUser.getId()); - return ServerResponse.createBySuccess("登陆用户获取自身信息成功",userResVO); + return ServerResponse.createBySuccess("登陆用户获取自身信息成功", userResVO); } /** - * 根据用户名去拿到对应的问题 + * 此方法用于处理根据用户名去获取对应的问题的请求,通常在用户忘记密码时,首先需要根据用户名去获取之前设置的用于找回密码的相关问题, + * 然后将获取到的问题信息返回给客户端,以便用户进行后续的密码找回操作。 + * + * @param username 用户名,作为请求的必传参数,由客户端传入,不能为空,用于在系统中查找对应的用户设置的找回密码相关问题, + * 是获取问题操作的关键输入信息,通过这个用户名来定位具体用户的相关设置信息。 + * @return 返回一个ServerResponse类型的响应对象,该对象包含了获取问题操作的结果状态(如成功获取到问题或未找到对应的用户等情况)以及如果成功获取到的相关问题信息, + * 方便客户端根据返回结果判断是否成功获取到问题,并进行相应的展示等后续操作,例如在前端页面展示问题让用户回答等。 */ - @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; } - /** - * 校验答案是否正确 - */ - @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); - return response; - } - - - /** - * 忘记密码的重置密码 - */ - @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); - return response; - } - - /** - * 登陆状态的重置密码 - */ - @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 - String loginToken = CookieUtil.readLoginToken(request); - if(StringUtils.isEmpty(loginToken)){ - return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); - } - //2.从redis中获取用户信息 - String userStr = commonCacheUtil.getCacheValue(loginToken); - if(userStr == null){ - return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); - } - User currentUser = JsonUtil.Str2Obj(userStr,User.class); - log.info("【用户{}重置密码】",currentUser); - - ServerResponse response = userService.resetPasswd(passwordOld,passwordNew,currentUser.getId()); - return response; - } - - /** - * 更新当前登陆用户信息 - */ - @ApiOperation(value="更新当前登陆用户信息", notes="更新用户信息") - @ApiImplicitParams({ - @ApiImplicitParam(name = "email", value = "邮箱", required = true, dataType = "String"), - @ApiImplicitParam(name = "phone", value = "电话", required = true, dataType = "String"), - @ApiImplicitParam(name = "question", value = "问题", required = true, dataType = "String"), - @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 - String loginToken = CookieUtil.readLoginToken(request); - if(StringUtils.isEmpty(loginToken)){ - return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); - } - //2.从redis中获取用户信息 - String userStr = commonCacheUtil.getCacheValue(loginToken); - if(userStr == null){ - return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息"); - } - User currentUser = JsonUtil.Str2Obj(userStr,User.class); - - ServerResponse response = userService.updateInfomation(email,phone,question,answer,currentUser.getId()); - return response; - } - - /** - * 登出,删除cookie和redis即可 - */ - @ApiOperation(value="登出", notes="退出登陆,删除cookie和redis缓存") - @RequestMapping("/logout.do") - public ServerResponse logout(HttpServletRequest request,HttpServletResponse response){ - log.info("【用户删除cookie】"); - //1.删除cookie - String loginToken = CookieUtil.readLoginToken(request); - CookieUtil.delLoginToken(request,response); - log.info("【用户删除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/mapper/UserMapper.xml b/snailmall-user-service/src/main/java/com/njupt/swg/dao/mapper/UserMapper.xml index 9694e49..97b43ed 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/dao/mapper/UserMapper.xml +++ b/snailmall-user-service/src/main/java/com/njupt/swg/dao/mapper/UserMapper.xml @@ -1,9 +1,21 @@ - + + + + + + + @@ -15,158 +27,190 @@ + + id, username, password, email, phone, question, answer, role, create_time, update_time + + + + delete from mmall_user where id = #{id,jdbcType=INTEGER} + + - insert into mmall_user (id, username, password, - email, phone, question, - answer, role, create_time, - update_time) - values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, - #{email,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}, #{question,jdbcType=VARCHAR}, - #{answer,jdbcType=VARCHAR}, #{role,jdbcType=INTEGER}, now(), - now()) + insert into mmall_user (id, username, password, + email, phone, question, + answer, role, create_time, + update_time) + values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, + #{email,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}, #{question,jdbcType=VARCHAR}, + #{answer,jdbcType=VARCHAR}, #{role,jdbcType=INTEGER}, now(), + now()) + + insert into mmall_user - + id, - + username, - + password, - + email, - + phone, - + question, - + answer, - + role, - + create_time, - + update_time, - + #{id,jdbcType=INTEGER}, - + #{username,jdbcType=VARCHAR}, - + #{password,jdbcType=VARCHAR}, - + #{email,jdbcType=VARCHAR}, - + #{phone,jdbcType=VARCHAR}, - + #{question,jdbcType=VARCHAR}, - + #{answer,jdbcType=VARCHAR}, - + #{role,jdbcType=INTEGER}, - + now(), - + now(), + + update mmall_user - + username = #{username,jdbcType=VARCHAR}, - + password = #{password,jdbcType=VARCHAR}, - + email = #{email,jdbcType=VARCHAR}, - + phone = #{phone,jdbcType=VARCHAR}, - + question = #{question,jdbcType=VARCHAR}, - + answer = #{answer,jdbcType=VARCHAR}, - + role = #{role,jdbcType=INTEGER}, - + create_time = #{createTime,jdbcType=TIMESTAMP}, - + update_time = now(), where id = #{id,jdbcType=INTEGER} + + update mmall_user set username = #{username,jdbcType=VARCHAR}, - password = #{password,jdbcType=VARCHAR}, - email = #{email,jdbcType=VARCHAR}, - phone = #{phone,jdbcType=VARCHAR}, - question = #{question,jdbcType=VARCHAR}, - answer = #{answer,jdbcType=VARCHAR}, - role = #{role,jdbcType=INTEGER}, - create_time = #{createTime,jdbcType=TIMESTAMP}, - update_time = now() + password = #{password,jdbcType=VARCHAR}, + email = #{email,jdbcType=VARCHAR}, + phone = #{phone,jdbcType=VARCHAR}, + question = #{question,jdbcType=VARCHAR}, + answer = #{answer,jdbcType=VARCHAR}, + role = #{role,jdbcType=INTEGER}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + update_time = now() where id = #{id,jdbcType=INTEGER} + + - + + - + + \ 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..13c2c15 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 @@ -14,30 +14,39 @@ import java.util.Date; * @CONTACT 317758022@qq.com * @DESC 用户实体类 */ +// 使用lombok的@Data注解,它会自动为类生成一系列常用的方法,包括但不限于所有属性的Getter和Setter方法、toString方法、equals方法和hashCode方法等, +// 减少了手动编写这些重复代码的工作量,让代码更加简洁,方便在其他地方对类的属性进行访问和操作以及进行对象的比较等操作。 @Data +// 使用lombok的@NoArgsConstructor注解,它会为类自动生成一个无参构造函数,在一些需要通过默认构造方式创建对象的场景下(比如某些框架进行对象实例化时)会很有用, +// 确保类具有默认的构造方式来创建实例。 @NoArgsConstructor +// 使用lombok的@AllArgsConstructor注解,会为类自动生成一个包含所有属性的有参构造函数,方便在创建对象时可以一次性传入所有属性的值进行初始化, +// 适用于需要明确指定各个属性值来创建对象的情况,提高了对象创建的灵活性和便捷性。 @AllArgsConstructor +// 使用lombok的@ToString注解,它会自动重写类的toString方法,生成一个方便查看对象属性值的字符串表示形式,在调试或者日志输出等场景中, +// 能够直观地看到对象各个属性的具体内容,有助于快速了解对象的状态。 @ToString +// 实现Serializable接口,表示这个类的对象可以被序列化,即在网络传输或者保存到文件等场景下,可以将对象转换为字节流的形式,之后也可以再从字节流反序列化为对象, +// 方便对象的持久化存储和在不同环境间的传递等操作,通常需要定义一个序列化版本号(serialVersionUID)来确保序列化和反序列化的兼容性,这里没有显式定义,会由Java自动生成一个默认的版本号。 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-普通用户 + // 用户角色,用整数表示,0代表管理员角色,1代表普通用户角色,通过这个属性可以区分不同权限的用户,在系统中进行不同级别的操作和访问控制。 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/UserServiceImpl.java b/snailmall-user-service/src/main/java/com/njupt/swg/service/UserServiceImpl.java index e2cc35b..7136e92 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 @@ -26,34 +26,81 @@ import java.util.concurrent.TimeUnit; * @CONTACT 317758022@qq.com * @DESC 登陆错误 code=1,msg=xxx 登陆成功 code=0,data=UserRespVO */ +// @Service注解用于将当前类标记为Spring框架中的服务层组件,表明该类是一个业务逻辑处理类,会被Spring容器管理, +// 方便在其他组件(如控制器层)中通过依赖注入的方式进行调用,实现业务逻辑与其他层的解耦。 @Service +// @Slf4j注解是lombok提供的一个便捷方式,用于自动在类中生成一个名为log的日志记录器对象, +// 可以通过它方便地记录不同级别(如info、error等)的日志信息,便于在程序运行过程中对业务流程、异常情况等进行记录和跟踪,方便后续的调试与监控。 @Slf4j -public class UserServiceImpl implements IUserService{ +// 该类实现了IUserService接口,意味着它需要按照接口中定义的方法契约来实现具体的业务逻辑, +// 这样的设计符合面向接口编程的原则,使得代码结构更加清晰,便于替换不同的实现类(例如在测试环境或不同业务场景下使用不同的实现逻辑), +// 同时也方便其他代码依赖于接口进行调用,而不用关心具体的实现细节。 +public class UserServiceImpl implements IUserService { + + // 通过Spring的依赖注入机制,使用@Autowired注解自动装配一个UserMapper类型的实例。 + // UserMapper通常是一个MyBatis的Mapper接口,定义了与数据库中用户表进行交互的各种方法,比如查询用户信息、插入用户记录等操作, + // 在这里被注入后,就可以在业务逻辑方法中调用它的方法来实现与数据库的数据交互,以完成诸如登录验证、注册用户等业务功能。 @Autowired private UserMapper userMapper; + // 同样使用@Autowired注解注入CuratorFramework类型的实例,CuratorFramework是一个用于与Zookeeper进行交互的客户端框架相关的类, + // 在本代码中可能是用于实现分布式锁功能(结合后续代码逻辑来看),通过与Zookeeper的协作来保证在分布式环境下关键业务操作(如用户注册)的原子性和并发控制, + // 避免多个实例同时操作产生的数据不一致等问题。 @Autowired private CuratorFramework zkClient; + // 注入CommonCacheUtil类型的实例,CommonCacheUtil是一个用于缓存操作的工具类, + // 大概率是用于和缓存系统(如Redis)进行交互,实现对常用数据(比如用户登录状态信息、临时验证信息等)的缓存管理, + // 通过缓存数据可以减少对数据库的频繁访问,提高系统的整体性能和响应速度。 @Autowired private CommonCacheUtil commonCacheUtil; + /** + * 此方法实现了用户登录的业务逻辑,整体流程包含以下几个关键步骤: + * + * 步骤一:参数校验 + * 首先对传入的用户名和密码参数进行非空校验,通过StringUtils.isEmpty方法判断,如果用户名或密码为空字符串, + * 则抛出SnailmallException异常,并给出相应的提示信息“温馨提示:用户名或密码不能为空”,确保登录操作的基础参数完整。 + * + * 步骤二:用户名存在性校验 + * 在参数非空的基础上,调用userMapper的selectByUsername方法,根据传入的用户名去数据库中查询对应的用户记录, + * 该方法返回的结果是匹配该用户名的记录数量。如果查询到的记录数量为0,意味着数据库中不存在该用户名对应的用户, + * 此时抛出SnailmallException异常,提示“温馨提示:用户名不存在”,说明用户输入的用户名有误,无法进行后续登录验证。 + * + * 步骤三:密码校验 + * 当确认用户名存在后,将传入的密码通过MD5Util.MD5EncodeUtf8方法进行MD5加密处理, + * 然后使用加密后的密码和原始用户名再次调用userMapper的selectByUsernameAndPasswd方法去数据库中查询用户记录, + * 目的是验证用户输入的密码是否正确。如果查询结果为null,即没有找到匹配用户名和加密后密码的用户记录, + * 则抛出SnailmallException异常,提示“温馨提示:用户名或者密码不正确,请重试”,告知用户登录信息有误。 + * + * 步骤四:构建返回结果 + * 若前面的用户名和密码校验都通过,说明用户登录成功。此时创建一个UserResVO对象, + * 将从数据库查询到的用户记录(resultUser)中的相关信息(如用户ID、用户名、邮箱、电话、角色、问题、答案、创建时间等) + * 分别设置到UserResVO对象的对应属性中,其中更新时间设置为当前日期(new Date()), + * 最后通过ServerResponse.createBySuccess方法构建一个表示成功的ServerResponse对象,将提示信息“用户登陆成功”和封装好的UserResVO对象作为参数传入, + * 返回给调用者,通常这个调用者可能是控制器层,然后再将结果返回给前端客户端,告知用户登录成功以及返回相关的用户信息。 + * + * @param username 客户端传入的用户名,不能为空,用于在数据库中查找对应的用户记录进行登录验证。 + * @param password 客户端传入的用户密码,不能为空,需要与数据库中存储的密码进行比对验证用户身份。 + * @return 返回一个ServerResponse类型的对象,包含登录操作的结果状态(成功则为“用户登陆成功”提示信息)以及登录成功后的用户详细信息(封装在UserResVO对象中), + * 方便前端根据返回结果进行页面跳转、显示用户信息等后续操作。 + */ @Override - public ServerResponse login(String username,String password) { - //1.校验参数不能为空 - if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){ + public ServerResponse login(String username, String password) { + // 1.校验参数不能为空 + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { throw new SnailmallException("温馨提示:用户名或密码不能为空"); } - //2.根据用户名去取用户信息(本系统用户名不能重复) + // 2.根据用户名去取用户信息(本系统用户名不能重复) int resultCount = userMapper.selectByUsername(username); - if(resultCount == 0){ + if (resultCount == 0) { throw new SnailmallException("温馨提示:用户名不存在"); } - //3.走到这一步,说明存在该用户,下面就执行登陆校验 + // 3.走到这一步,说明存在该用户,下面就执行登陆校验 String md5Passwd = MD5Util.MD5EncodeUtf8(password); - User resultUser = userMapper.selectByUsernameAndPasswd(username,md5Passwd); - if (resultUser == null){ + User resultUser = userMapper.selectByUsernameAndPasswd(username, md5Passwd); + if (resultUser == null) { throw new SnailmallException("温馨提示:用户名或者密码不正确,请重试"); } - //4.走到这一步,说明用户名密码正确,应该返回成功 + // 4.走到这一步,说明用户名密码正确,应该返回成功 UserResVO userResVO = new UserResVO(); userResVO.setId(resultUser.getId()); userResVO.setUsername(resultUser.getUsername()); @@ -65,58 +112,96 @@ public class UserServiceImpl implements IUserService{ userResVO.setCreateTime(resultUser.getCreateTime()); userResVO.setUpdateTime(new Date()); - return ServerResponse.createBySuccess("用户登陆成功",userResVO); + return ServerResponse.createBySuccess("用户登陆成功", userResVO); } + /** + * 该方法用于处理用户注册的业务逻辑,整体逻辑较为复杂,主要涉及以下几个重要部分: + * + * 1. 参数校验 + * 首先对传入的User对象中的关键属性(用户名、邮箱、密码、问题、答案)进行非空校验, + * 通过StringUtils.isBlank方法判断,如果这些属性中有任何一个为空字符串,就抛出SnailmallException异常, + * 并提示“参数不能为空,请仔细填写”,确保用户注册时提供的必要信息完整,避免后续因数据缺失导致的问题。 + * + * 2. 分布式锁获取与处理 + * - 初始化分布式锁对象:创建一个InterProcessLock类型的锁对象(具体实现为InterProcessMutex,通过zkClient和指定的锁路径Constants.USER_REGISTER_DISTRIBUTE_LOCK_PATH来构造), + * 用于在分布式环境下对用户注册操作进行并发控制,避免多个线程或实例同时进行注册导致的数据冲突(比如用户名或邮箱重复注册等问题)。 + * - 尝试获取锁的循环:进入一个do-while循环,不断尝试获取分布式锁,通过调用lock.acquire方法,并设置超时时间为3000毫秒(以TimeUnit.MILLISECONDS为单位), + * 如果成功获取到锁,就执行以下操作: + * - 记录获取锁的日志信息:通过log.info方法记录当前正在注册的用户邮箱以及获取锁的线程名称,方便后续查看日志排查问题,了解锁的获取情况以及注册操作的执行线程。 + * - 用户名重复性校验:调用this.checkValid方法,传入用户名和Constants.USERNAME常量,校验用户名是否已经在数据库中存在, + * 如果返回的ServerResponse对象表示校验不成功(即用户名已存在),则直接将该响应对象返回,终止当前注册流程,告知客户端用户名重复不能注册。 + * - 邮箱重复性校验:同样调用this.checkValid方法,传入邮箱和Constants.EMAIL常量,校验邮箱是否已经存在于数据库中, + * 若返回结果表示校验不成功(即邮箱已存在),则返回该响应对象,结束注册流程,提示客户端邮箱已被使用不能注册。 + * - 插入用户数据:当用户名和邮箱的重复性校验都通过后,设置用户的角色为普通用户(通过Constants.Role.ROLE_CUSTOME常量指定), + * 并对用户密码进行MD5加密处理(使用MD5Util.MD5EncodeUtf8方法),然后调用userMapper的insert方法将用户信息插入到数据库中,完成用户注册的核心操作, + * 最后将循环控制变量retry设置为false,跳出循环,结束尝试获取锁和注册操作的循环过程。 + * 如果在尝试获取锁的过程中失败(即没有在超时时间内获取到锁),则通过log.info方法记录“【获取锁失败,继续尝试...】”的日志信息, + * 提示后续会继续尝试获取锁来进行注册操作(虽然代码中此处没有明确体现等待或延迟等具体的重试策略,但逻辑上会不断循环尝试获取锁)。 + * + * 3. 锁释放与异常处理 + * 在finally块中,无论是否成功获取锁以及是否完成注册操作,都会对锁进行释放操作,以避免锁资源一直被占用导致的死锁等问题。 + * 如果锁对象lock不为null,就调用lock.release方法释放锁,并通过log.info方法记录释放锁的日志信息,包括释放锁的用户邮箱和线程名称,方便查看锁的释放情况。 + * 如果在释放锁的过程中出现异常,通过e.printStackTrace方法打印异常堆栈信息,便于排查锁释放环节出现的问题,不过这种情况属于异常情况,正常情况下应该能顺利释放锁。 + * + * 4. 返回注册结果 + * 如果整个注册流程顺利完成(即没有因为参数问题、用户名或邮箱重复问题以及锁相关的异常等情况中断), + * 最后通过ServerResponse.createBySuccessMessage方法构建一个表示注册成功的ServerResponse对象,返回“注册成功”的提示信息给客户端,告知用户注册操作已成功完成。 + * + * @param user 包含用户注册信息的User对象,由客户端传入,其关键属性(用户名、邮箱、密码、问题、答案等)需满足非空校验要求, + * 用于在数据库中插入新的用户记录以及进行相关的业务逻辑判断(如重复性校验等)。 + * @return 返回一个ServerResponse对象,包含用户注册操作的结果状态(成功则返回“注册成功”提示信息)以及相应的提示信息, + * 方便客户端根据返回结果提示用户注册是否成功,并进行相应的后续操作(如跳转到登录页面等)。 + */ @Override public ServerResponse register(User user) { - //1.校验参数是否为空 - if(StringUtils.isBlank(user.getUsername()) || + // 1.校验参数是否为空 + if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getEmail()) || StringUtils.isBlank(user.getPassword()) || StringUtils.isBlank(user.getQuestion()) || - StringUtils.isBlank(user.getAnswer())){ + StringUtils.isBlank(user.getAnswer())) { throw new SnailmallException("参数不能为空,请仔细填写"); } - //---开启锁 + // ---开启锁 InterProcessLock lock = null; try { lock = new InterProcessMutex(zkClient, Constants.USER_REGISTER_DISTRIBUTE_LOCK_PATH); boolean retry = true; do { - if (lock.acquire(3000, TimeUnit.MILLISECONDS)){ - log.info(user.getEmail()+Thread.currentThread().getName()+"获取锁"); - //2.参数没问题的话,就校验一下名字是否已经存在 - ServerResponse response = this.checkValid(user.getUsername(),Constants.USERNAME); - if(!response.isSuccess()){ - //说明用户名已经重复了 + if (lock.acquire(3000, TimeUnit.MILLISECONDS)) { + log.info(user.getEmail() + Thread.currentThread().getName() + "获取锁"); + // 2.参数没问题的话,就校验一下名字是否已经存在 + ServerResponse response = this.checkValid(user.getUsername(), Constants.USERNAME); + if (!response.isSuccess()) { + // 说明用户名已经重复了 return response; } - //3.再校验一下邮箱是否存在 - response = this.checkValid(user.getEmail(),Constants.EMAIL); - if(!response.isSuccess()){ - //说明用户名已经重复了 + // 3.再校验一下邮箱是否存在 + response = this.checkValid(user.getEmail(), Constants.EMAIL); + if (!response.isSuccess()) { + // 说明用户名已经重复了 return response; } - //4.重复校验通过之后就可以塞入这条数据了 - user.setRole(Constants.Role.ROLE_CUSTOME);//普通用户 + // 4.重复校验通过之后就可以塞入这条数据了 + user.setRole(Constants.Role.ROLE_CUSTOME); // 普通用户 user.setPassword(MD5Util.MD5EncodeUtf8(user.getPassword())); userMapper.insert(user); - //跳出循环 + // 跳出循环 retry = false; } log.info("【获取锁失败,继续尝试...】"); - //可以适当休息一会 - }while (retry); - }catch (Exception e){ - log.error("【校验用户所填的用户名或者密码出现问题】",e); + // 可以适当休息一会 + } while (retry); + } catch (Exception e) { + log.error("【校验用户所填的用户名或者密码出现问题】", e); throw new SnailmallException("分布式锁校验出错"); - }finally { - //---释放锁 - if(lock != null){ + } finally { + // ---释放锁 + if (lock!= null) { try { lock.release(); - log.info(user.getEmail()+Thread.currentThread().getName()+"释放锁"); + log.info(user.getEmail() + Thread.currentThread().getName() + "释放锁"); } catch (Exception e) { e.printStackTrace(); } @@ -125,23 +210,50 @@ public class UserServiceImpl implements IUserService{ return ServerResponse.createBySuccessMessage("注册成功"); } + /** + * 此方法用于校验传入的字符串(代表用户名或邮箱)在数据库中的唯一性,具体逻辑如下: + * + * 1. 参数校验 + * 首先通过StringUtils.isBlank方法检查传入的参数str(待校验的用户名或邮箱)和type(用于表明str代表的是用户名还是邮箱的类型标识)是否为空字符串。 + * 如果其中任意一个参数为空,则抛出SnailmallException异常,并提示“参数有问题”,以此确保后续校验操作能基于有效的输入参数进行。 + * + * 2. 根据类型校验唯一性 + * 如果传入的type参数在忽略大小写的情况下与Constants.USERNAME相等,说明此次校验针对的是用户名的唯一性。 + * 此时调用userMapper的selectByUsername方法,传入str作为用户名去数据库中查询匹配的用户记录数量。 + * 若查询得到的结果数量大于0,意味着数据库中已经存在使用该用户名的用户,那么就通过ServerResponse.createByErrorMessage方法创建一个表示错误信息的ServerResponse对象, + * 返回“用户已经存在”的提示信息给调用者,告知该用户名已被占用,无法进行相应操作(比如注册新用户时使用该用户名)。 + * + * 若type参数与Constants.EMAIL相等,表明此次校验针对的是邮箱的唯一性。 + * 相应地,调用userMapper的selectByEmail方法,以str作为邮箱地址去数据库查询匹配的用户记录数量。 + * 当查询结果数量大于0时,说明该邮箱已被其他用户使用,同样通过ServerResponse.createByErrorMessage方法返回一个包含“邮箱已经存在”提示信息的ServerResponse对象给调用者, + * 提示该邮箱不能再用于当前操作(例如注册时使用该邮箱)。 + * + * 3. 返回校验成功结果 + * 如果经过上述针对用户名或邮箱的唯一性校验后,均未发现重复情况(即对应查询结果数量都不大于0), + * 则通过ServerResponse.createBySuccess方法创建一个表示成功的ServerResponse对象,返回“校验成功,用户名和邮箱都合法”的提示信息给调用者, + * 意味着传入的用户名或邮箱可以用于后续操作(如注册时可继续插入数据等)。 + * + * @param str 要校验的用户名或邮箱字符串,不能为空,其具体代表的含义由type参数确定,用于在数据库中查询验证其唯一性。 + * @param type 用于标识str代表的是用户名还是邮箱的类型参数,取值应为预定义的相关常量(如Constants类中定义的常量),不能为空,以此决定具体的校验逻辑走向。 + * @return 返回一个ServerResponse对象,包含了校验操作的结果状态(如用户名或邮箱已存在、校验成功等情况)以及相应的提示信息,方便调用者根据返回结果进行后续操作决策。 + */ @Override public ServerResponse checkValid(String str, String type) { //校验参数是否为空 - if(StringUtils.isBlank(str) || StringUtils.isBlank(type)){ + if (StringUtils.isBlank(str) || StringUtils.isBlank(type)) { throw new SnailmallException("参数有问题"); } - if(Constants.USERNAME.equalsIgnoreCase(type)){ + if (Constants.USERNAME.equalsIgnoreCase(type)) { //如果是username类型,那么就根据str为username去数据库查询 int resultCount = userMapper.selectByUsername(str); - if(resultCount > 0){ + if (resultCount > 0) { //说明数据库已经存在这个username的用户了,返回用户已存在 return ServerResponse.createByErrorMessage("用户已经存在"); } - }else if(Constants.EMAIL.equals(type)){ + } else if (Constants.EMAIL.equals(type)) { //如果是email类型,就根据str为email去数据库查询 int resultCount = userMapper.selectByEmail(str); - if(resultCount > 0){ + if (resultCount > 0) { //说明数据库已经存在这个email的用户了,返回用户已存在 return ServerResponse.createByErrorMessage("邮箱已经存在"); } @@ -149,120 +261,289 @@ public class UserServiceImpl implements IUserService{ return ServerResponse.createBySuccess("校验成功,用户名和邮箱都合法"); } + /** + * 该方法用于根据传入的用户名获取对应的找回密码问题,执行逻辑如下: + * + * 1. 参数校验 + * 通过StringUtils.isBlank方法检查传入的用户名参数是否为空字符串。 + * 如果为空,则通过ServerResponse.createByErrorMessage方法创建并返回一个包含“用户名不能为空”提示信息的ServerResponse对象给调用者, + * 告知调用者需要提供有效的用户名才能进行后续操作,避免因空用户名导致的异常情况。 + * + * 2. 查询用户信息 + * 若用户名参数非空,调用userMapper的getUserByUsername方法,传入用户名去数据库中查询对应的用户信息,将查询结果赋值给User类型的user对象。 + * 如果查询结果为null,意味着数据库中不存在该用户名对应的用户,此时通过ServerResponse.createByErrorMessage方法返回一个包含“用户不存在”提示信息的ServerResponse对象给调用者, + * 说明无法获取到对应的找回密码问题,因为用户本身不存在。 + * + * 3. 获取并校验问题 + * 若成功查询到用户信息(即user对象不为null),则获取该用户设置的找回密码问题,通过user.getQuestion方法获取对应的问题字符串。 + * 再次通过StringUtils.isBlank方法检查该问题字符串是否为空,如果为空,说明该用户没有设置对应的找回密码问题, + * 于是通过ServerResponse.createByErrorMessage方法返回一个包含“该用户没有设置对应的问题”提示信息的ServerResponse对象给调用者, + * 告知调用者无法获取到有效的问题用于找回密码操作。 + * + * 4. 返回问题成功获取结果 + * 如果经过上述步骤,成功获取到了非空的找回密码问题字符串,则通过ServerResponse.createBySuccess方法创建并返回一个包含该问题字符串的ServerResponse对象给调用者, + * 表示成功获取到了对应的找回密码问题,方便调用者(通常是前端相关代码)展示该问题给用户,以便用户进行后续的回答操作来找回密码。 + * + * @param username 要获取找回密码问题的用户名,不能为空,用于在数据库中查找对应的用户信息及设置的问题。 + * @return 返回一个ServerResponse对象,包含了获取问题操作的结果状态(如成功获取到问题、用户不存在、未设置问题等情况)以及相应的提示信息或获取到的问题内容, + * 方便调用者根据返回结果进行后续展示等操作。 + */ @Override public ServerResponse getQuestionByUsername(String username) { //1.校验参数 - if(StringUtils.isBlank(username)){ + if (StringUtils.isBlank(username)) { return ServerResponse.createByErrorMessage("用户名不能为空"); } //2.根据username去获取题目 User user = userMapper.getUserByUsername(username); - if(user == null){ + if (user == null) { return ServerResponse.createByErrorMessage("用户不存在"); } String question = user.getQuestion(); - if(StringUtils.isBlank(question)){ + if (StringUtils.isBlank(question)) { return ServerResponse.createByErrorMessage("该用户没有设置对应的问题"); } return ServerResponse.createBySuccess(question); } + /** + * 此方法用于校验用户输入的找回密码答案是否正确,并根据情况生成或获取相应的忘记密码token,具体逻辑如下: + * + * 1. 参数校验 + * 首先通过StringUtils.isBlank方法分别检查传入的用户名、问题和答案参数是否为空字符串。 + * 如果其中任意一个参数为空,则通过ServerResponse.createByErrorMessage方法创建并返回一个包含“参数有问题”提示信息的ServerResponse对象给调用者, + * 确保后续的校验及token相关操作基于完整有效的参数进行,避免因参数缺失导致的异常情况。 + * + * 2. 验证答案并处理token + * 在参数都非空的情况下,调用userMapper的getUserByUsernameQuestionAnswer方法,传入用户名、问题和答案去数据库中查询对应的用户信息, + * 将查询结果赋值给User类型的user对象。如果user对象不为null,说明数据库中存在匹配该用户名、问题和答案的用户,意味着用户输入的答案可能是正确的,接下来进行以下token相关操作: + * + * - 首先尝试从Redis缓存中获取对应的忘记密码token,通过调用commonCacheUtil的getCacheValue方法,传入由Constants.TOKEN_PREFIX和用户名拼接而成的字符串作为key去获取token值, + * 将获取到的token值赋值给forgetToken变量。 + * - 接着通过StringUtils.isNotBlank方法检查获取到的forgetToken是否为非空字符串,如果是,说明Redis中存在未过期的有效token, + * 则通过ServerResponse.createBySuccess方法创建并返回一个包含该有效token的ServerResponse对象给调用者, + * 此时就可以直接使用该token进行后续找回密码相关操作(例如验证等),无需重新生成token。 + * - 如果从Redis中获取到的forgetToken为空字符串,说明缓存中不存在对应的token或者token已过期,并且由于前面已经验证了用户输入的答案是正确的(通过查询到了对应的用户信息), + * 那么就需要重新生成一个token。通过UUID.randomUUID().toString方法生成一个唯一的字符串作为新的token, + * 然后调用commonCacheUtil的cacheNxExpire方法,将新生成的token以Constants.TOKEN_PREFIX和用户名拼接的字符串作为key,缓存到Redis中,并设置过期时间为60 * 60 * 12秒(12小时), + * 最后通过ServerResponse.createBySuccess方法创建并返回一个包含新生成token的ServerResponse对象给调用者,方便后续找回密码操作使用该新token。 + * + * 3. 返回答案错误结果 + * 如果经过数据库查询,发现没有匹配传入用户名、问题和答案的用户信息(即user对象为null),说明用户输入的答案有误, + * 则通过ServerResponse.createByErrorMessage方法创建并返回一个包含“问题答案有误”提示信息的ServerResponse对象给调用者,告知调用者答案不正确,无法进行后续找回密码操作。 + * + * @param username 要校验找回密码答案的用户名,不能为空,用于在数据库中查找对应的用户信息进行答案验证以及token相关操作。 + * @param question 找回密码时用户需要回答的问题,不能为空,与用户名和答案一起用于数据库查询验证。 + * @param answer 用户输入的找回密码答案,不能为空,用于验证是否与数据库中存储的对应用户的答案一致。 + * @return 返回一个ServerResponse对象,包含了校验答案及token操作的结果状态(如答案正确并获取到token、答案错误等情况)以及相应的提示信息或获取到的token值, + * 方便调用者根据返回结果进行后续找回密码等相关操作。 + */ @Override public ServerResponse checkAnswer(String username, String question, String answer) { //1.校验参数是否正确 - if(StringUtils.isBlank(username) || StringUtils.isBlank(question) || StringUtils.isBlank(answer)){ + if (StringUtils.isBlank(username) || StringUtils.isBlank(question) || StringUtils.isBlank(answer)) { return ServerResponse.createByErrorMessage("参数有问题"); } //2.参数没有问题之后,就可以去校验答案是否正确了 - User user = userMapper.getUserByUsernameQuestionAnswer(username,question,answer); - if(user != null){ + User user = userMapper.getUserByUsernameQuestionAnswer(username, question, answer); + if (user!= null) { //首先根据规则key去redis取,如果还有没有过期的key,就可以直接拿来用了,不用再重新生成 - String forgetToken = commonCacheUtil.getCacheValue(Constants.TOKEN_PREFIX+username); - if(StringUtils.isNotBlank(forgetToken)){ + String forgetToken = commonCacheUtil.getCacheValue(Constants.TOKEN_PREFIX + username); + if (StringUtils.isNotBlank(forgetToken)) { return ServerResponse.createBySuccess(forgetToken); } //取不到值,并且答案是对的,那么就重新生成一下吧! forgetToken = UUID.randomUUID().toString(); - commonCacheUtil.cacheNxExpire(Constants.TOKEN_PREFIX+username,forgetToken,60*60*12); + commonCacheUtil.cacheNxExpire(Constants.TOKEN_PREFIX + username, forgetToken, 60 * 60 * 12); return ServerResponse.createBySuccess(forgetToken); } return ServerResponse.createByErrorMessage("问题答案有误"); } + /** + * 该方法用于处理用户通过忘记密码流程重置密码的业务逻辑,主要步骤如下: + * + * 1. 参数校验 + * 通过StringUtils.isBlank方法分别检查传入的用户名、新密码和忘记密码token参数是否为空字符串。 + * 如果其中任意一个参数为空,则通过ServerResponse.createByErrorMessage方法创建并返回一个包含“参数有误,修改密码操作失败”提示信息的ServerResponse对象给调用者, + * 确保后续的密码重置操作基于完整有效的参数进行,避免因参数缺失导致的异常情况。 + * + * 2. 查询用户信息 + * 在参数都非空的情况下,调用userMapper的getUserByUsername方法,传入用户名去数据库中查询对应的用户信息,将查询结果赋值给User类型的user对象。 + * 如果查询结果为null,意味着数据库中不存在该用户名对应的用户,此时通过ServerResponse.createByErrorMessage方法返回一个包含“用户名不存在,修改密码操作失败”提示信息的ServerResponse对象给调用者, + * 说明无法进行密码重置操作,因为用户本身不存在。 + * + * 3. 验证token是否过期 + * 若成功查询到用户信息(即user对象不为null),接着从Redis缓存中获取对应的token,通过调用commonCacheUtil的getCacheValue方法,传入由Constants.TOKEN_PREFIX和用户名拼接而成的字符串作为key去获取token值, + * 将获取到的token值赋值给redisToken变量。 + * 如果获取到的redisToken为null,说明Redis中不存在对应的token或者token已过期,此时通过ServerResponse.createByErrorMessage方法返回一个包含“token已经过期,修改密码操作失败”提示信息的ServerResponse对象给调用者, + * 告知调用者由于token过期无法进行密码重置操作。 + * + * 4. 验证token一致性 + * 若成功获取到了非空的redisToken,通过StringUtils.equals方法检查前端传过来的forgetToken与从Redis中获取到的redisToken是否相等。 + * 如果两者不相等,说明token不一致,可能存在安全风险,此时通过ServerResponse.createByErrorMessage方法返回一个包含“token错误,修改密码操作失败”提示信息的ServerResponse对象给调用者, + * 提示调用者token不匹配,不能进行密码重置操作。 + * + * 5. 密码重复性校验 + * 若token验证通过,将传入的新密码通过MD5Util.MD5EncodeUtf8方法进行MD5加密处理,得到加密后的密码MD5Passwd。 + * 通过user.getPassword().equals方法检查加密后的新密码与数据库中存储的用户原密码是否相等,如果相等,说明新密码与原密码重复, + * 则通过ServerResponse.createByErrorMessage方法返回一个包含“不要使用重复密码!”提示信息的ServerResponse对象给调用者, + * 提示调用者不能使用重复密码进行重置操作,应更换新密码。 + * + * 6. 重置密码 + * 如果经过前面的各项验证都通过,说明可以进行密码重置操作。通过user.setPassword方法将用户对象的密码属性设置为加密后的新密码MD5Passwd, + * 然后调用userMapper的updateByPrimaryKeySelective方法,传入更新后的user对象,根据用户的主键(通常是用户ID)去数据库中更新对应的用户密码信息。 + * 该方法返回一个表示更新记录数量的整数result,如果result大于0,说明密码更新成功,通过ServerResponse.createBySuccessMessage方法创建并返回一个包含“修改密码成功”提示信息的ServerResponse对象给调用者, + * 告知调用者密码重置操作已成功完成。 + * + * 7. 返回密码重置失败结果 + * 如果密码更新操作没有成功(即result不大于0),则通过ServerResponse.createByErrorMessage方法返回一个包含“修改密码失败”提示信息的ServerResponse对象给调用者, + * 提示调用者密码重置操作出现问题,未成功完成。 + * + * @param username 要重置密码的用户名,不能为空,用于在数据库中查找对应的用户信息以及验证token等相关操作。 + * @param passwordNew 用户输入的新密码,不能为空,用于更新用户在数据库中的密码信息,且需要经过加密及重复性等校验。 + * @param forgetToken 用户在忘记密码流程中获取到的用于验证身份的token,不能为空,用于验证操作的合法性以及与Redis中缓存的token进行比对。 + * @return 返回一个ServerResponse对象,包含了重置密码操作的结果状态(如密码重置成功、token错误、密码重复等各种情况)以及相应的提示信息, + * 方便调用者根据返回结果了解密码重置操作是否成功,并进行相应的后续操作(如提示用户操作结果等)。 + */ + /** + * 此方法用于处理用户通过忘记密码流程重置密码的业务逻辑,具体步骤如下: + * + * 1. 参数校验 + * 首先使用StringUtils.isBlank方法对传入的用户名(username)、新密码(passwordNew)和忘记密码的token(forgetToken)这三个参数进行非空校验。 + * 如果其中任意一个参数为空字符串,意味着参数不完整,不符合密码重置的基本要求,此时通过ServerResponse.createByErrorMessage方法创建一个表示错误信息的ServerResponse对象, + * 返回“参数有误,修改密码操作失败”的提示信息给调用者,告知调用者参数存在问题,无法进行后续的密码重置操作。 + * + * 2. 查询用户信息 + * 在参数都通过非空校验后,调用userMapper的getUserByUsername方法,传入用户名作为参数去数据库中查询对应的用户信息,并将查询结果赋值给User类型的user对象。 + * 如果查询返回的user对象为null,说明数据库中不存在该用户名对应的用户,那么就无法进行密码重置操作, + * 通过ServerResponse.createByErrorMessage方法创建一个包含“用户名不存在,修改密码操作失败”提示信息的ServerResponse对象返回给调用者,告知调用者找不到对应的用户,重置密码操作不能继续。 + * + * 3. 验证token是否过期 + * 若成功查询到了用户信息(即user对象不为null),接着调用commonCacheUtil的getCacheValue方法,传入由Constants.TOKEN_PREFIX和用户名拼接而成的字符串作为key, + * 从Redis缓存中获取对应的token值,并将获取到的值赋给redisToken变量。 + * 如果redisToken为null,意味着在Redis中没有找到对应的token,可能是token已经过期或者不存在,此时通过ServerResponse.createByErrorMessage方法返回一个包含“token已经过期,修改密码操作失败”提示信息的ServerResponse对象给调用者, + * 提示调用者由于token问题无法进行密码重置操作,因为token在忘记密码流程中起着验证身份的关键作用,过期或不存在则无法确认操作合法性。 + * + * 4. 验证token一致性 + * 在成功从Redis获取到非空的redisToken后,使用StringUtils.equals方法比较前端传过来的forgetToken和从Redis中获取到的redisToken是否相等。 + * 如果两者不相等,说明token不一致,存在安全风险或者不符合操作流程要求,此时通过ServerResponse.createByErrorMessage方法返回一个包含“token错误,修改密码操作失败”提示信息的ServerResponse对象给调用者, + * 告知调用者token不匹配,不能继续进行密码重置操作,以此保证只有持有正确token的用户才能进行密码修改。 + * + * 5. 判断密码是否重复 + * 当token验证通过后,将传入的新密码passwordNew通过MD5Util.MD5EncodeUtf8方法进行MD5加密处理,得到加密后的密码MD5Passwd。 + * 然后使用user.getPassword().equals方法比较加密后的新密码MD5Passwd和数据库中存储的用户原密码(通过user对象获取)是否相等。 + * 如果两者相等,说明新密码与原密码重复,不符合密码修改的要求,此时通过ServerResponse.createByErrorMessage方法返回一个包含“不要使用重复密码!”提示信息的ServerResponse对象给调用者, + * 提示调用者不能使用重复密码进行重置操作,需要重新输入一个与原密码不同的新密码。 + * + * 6. 重置密码 + * 如果前面的各项验证(参数完整性、用户存在性、token有效性以及密码重复性校验)都通过,说明可以进行密码重置操作。 + * 首先通过user.setPassword方法将user对象的密码属性设置为加密后的新密码MD5Passwd,然后调用userMapper的updateByPrimaryKeySelective方法, + * 传入更新后的user对象,根据用户的主键(通常就是用户ID,此处通过前面查询用户时已确定对应的用户)去数据库中更新对应的用户密码信息。 + * 该方法会返回一个表示更新操作影响的记录数量的整数updateCount,如果updateCount大于0,说明密码更新成功, + * 通过ServerResponse.createBySuccessMessage方法创建并返回一个包含“修改密码成功”提示信息的ServerResponse对象给调用者,告知调用者密码重置操作已顺利完成。 + * + * 7. 返回密码重置失败结果 + * 如果密码更新操作没有成功(即updateCount不大于0),意味着在数据库更新密码时出现问题,可能是数据库连接异常、数据更新语句执行失败等原因, + * 此时通过ServerResponse.createByErrorMessage方法返回一个包含“修改密码失败”提示信息的ServerResponse对象给调用者,提示调用者密码重置操作没有达到预期效果,出现了故障。 + * + * @param username 要重置密码的用户名,由客户端传入,不能为空,用于在数据库中查找对应的用户信息以及后续与token相关的验证操作,是整个密码重置流程的关键标识之一。 + * @param passwordNew 用户输入的新密码,同样不能为空,是要更新到数据库中替换原密码的内容,且需要经过一系列校验(如重复性校验等)确保其合法性和安全性。 + * @param forgetToken 用户在忘记密码流程中获取到的用于验证身份的token,不能为空,用于和Redis缓存中的token进行比对以及确认本次密码重置操作的合法性。 + * @return 返回一个ServerResponse对象,包含了重置密码操作的结果状态(如密码重置成功、token错误、密码重复等各种情况)以及相应的提示信息, + * 方便调用者(如前端界面或者其他调用该服务的模块)根据返回结果知晓密码重置操作是否成功,并进行相应的后续处理(如提示用户操作结果、跳转到相应页面等)。 + */ @Override public ServerResponse forgetResetPasswd(String username, String passwordNew, String forgetToken) { //1.校验参数 - if(StringUtils.isBlank(username) || StringUtils.isBlank(passwordNew) || StringUtils.isBlank(forgetToken)){ + if (StringUtils.isBlank(username) || StringUtils.isBlank(passwordNew) || StringUtils.isBlank(forgetToken)) { return ServerResponse.createByErrorMessage("参数有误,修改密码操作失败"); } //2.根据username去获取用户 User user = userMapper.getUserByUsername(username); - if(user == null){ + if (user == null) { return ServerResponse.createByErrorMessage("用户名不存在,修改密码操作失败"); } //3.从redis中获取token,看是否超时 - String redisToken = commonCacheUtil.getCacheValue(Constants.TOKEN_PREFIX+username); - if(redisToken == null){ + String redisToken = commonCacheUtil.getCacheValue(Constants.TOKEN_PREFIX + username); + if (redisToken == null) { return ServerResponse.createByErrorMessage("token已经过期,修改密码操作失败"); } //4.看前端传过来的token与redis中取出来的token是否相等 - if(!StringUtils.equals(redisToken,forgetToken)){ + if (!StringUtils.equals(redisToken, forgetToken)) { return ServerResponse.createByErrorMessage("token错误,修改密码操作失败"); } //5.判断密码是否重复 String MD5Passwd = MD5Util.MD5EncodeUtf8(passwordNew); - if(user.getPassword().equals(MD5Passwd)){ + if (user.getPassword().equals(MD5Passwd)) { return ServerResponse.createByErrorMessage("不要使用重复密码!"); } //6.重置密码 user.setPassword(MD5Passwd); - int result = userMapper.updateByPrimaryKeySelective(user); - if(result > 0){ + int updateCount = userMapper.updateByPrimaryKeySelective(user); + if (updateCount > 0) { return ServerResponse.createBySuccessMessage("修改密码成功"); } return ServerResponse.createByErrorMessage("修改密码失败"); } - @Override - public ServerResponse resetPasswd(String passwordOld, String passwordNew, int userId) { - //1.校验参数 - if(StringUtils.isBlank(passwordOld) || StringUtils.isBlank(passwordNew)){ - return ServerResponse.createByErrorMessage("参数有误"); - } - User user = userMapper.selectByPrimaryKey(userId); - if (user == null){ - return ServerResponse.createByErrorMessage("无用户登陆"); - } - //2.校验老的密码 - String passwordOldMD5 = MD5Util.MD5EncodeUtf8(passwordOld); - if(!StringUtils.equals(passwordOldMD5,user.getPassword())){ - return ServerResponse.createByErrorMessage("老密码输错啦..."); - } - //3.重置新的密码 - user.setPassword(MD5Util.MD5EncodeUtf8(passwordNew)); - int updateCount = userMapper.updateByPrimaryKeySelective(user); - if(updateCount > 0){ - return ServerResponse.createBySuccessMessage("更新密码成功"); - } - return ServerResponse.createByErrorMessage("更新密码失败"); - } - + /** + * 该方法用于更新用户的信息,具体实现逻辑如下: + * + * 1. 获取当前登录用户 + * 首先调用userMapper的selectByPrimaryKey方法,传入用户ID(userId)作为参数去数据库中查询对应的用户信息,将查询结果赋值给User类型的user对象。 + * 如果查询返回的user对象为null,说明根据传入的用户ID在数据库中找不到对应的用户,可能是用户不存在或者传入的ID有误等原因, + * 此时通过ServerResponse.createByErrorMessage方法创建一个包含“获取当前登陆用户失败,请重新登陆”提示信息的ServerResponse对象返回给调用者, + * 告知调用者无法获取到要更新信息的用户,建议重新登录操作,以确保后续能正确获取到用户信息进行更新。 + * + * 2. 参数校验 + * 在成功获取到用户信息后,使用StringUtils.isBlank方法对传入的邮箱(email)、电话(phone)、问题(question)和答案(answer)这几个参数进行非空校验。 + * 如果这些参数中有任何一个为空字符串,意味着要更新的数据不完整,不符合更新用户信息的要求, + * 通过ServerResponse.createByErrorMessage方法创建一个包含“更新的数据不能存在空值!”提示信息的ServerResponse对象返回给调用者, + * 提示调用者需要提供完整的非空信息才能进行用户信息更新操作。 + * + * 3. 校验邮箱是否重复 + * 由于考虑到修改用户信息这个操作并发量不大,所以这里没有使用锁机制(与一些高并发场景下的操作有所区别)。 + * 接着调用userMapper的checkEmailValid方法,传入要更新的邮箱地址(email)和用户ID(userId)作为参数,去数据库中查询是否存在其他用户已经使用了该邮箱地址(排除当前要更新的这个用户本身)。 + * 如果查询返回的记录数量queryCount大于0,说明这个邮箱已经被其他用户占用了,不符合唯一性要求,此时不能使用该邮箱进行更新操作, + * 通过ServerResponse.createByErrorMessage方法创建一个包含“此邮箱已经被占用,换个试试~”提示信息的ServerResponse对象返回给调用者,告知调用者需要更换一个未被使用的邮箱地址。 + * + * 4. 构建更新用户对象并执行更新操作 + * 如果邮箱校验通过(即没有被其他用户占用),创建一个新的User对象updateUser,通过相应的set方法设置其ID为传入的用户ID(userId), + * 以及设置要更新的邮箱(email)、电话(phone)、问题(question)和答案(answer)等属性值,以此构建出一个包含了更新后信息的用户对象。 + * 然后调用userMapper的updateByPrimaryKeySelective方法,传入这个updateUser对象,根据用户的主键(userId)去数据库中更新对应的用户信息, + * 该方法会返回一个表示更新操作影响的记录数量的整数updateCount。 + * + * 5. 返回更新结果 + * 如果updateCount大于0,说明用户信息更新成功,通过ServerResponse.createBySuccessMessage方法创建并返回一个包含“更新信息成功”提示信息的ServerResponse对象给调用者, + * 告知调用者用户信息已成功更新,可以进行后续相关操作(如刷新页面展示新信息等)。 + * 如果updateCount不大于0,意味着在数据库更新用户信息时出现问题,可能是数据库连接异常、数据更新语句执行失败等原因, + * 通过ServerResponse.createByErrorMessage方法返回一个包含“更新用户信息失败”提示信息的ServerResponse对象给调用者,提示调用者用户信息更新操作没有达到预期效果,出现了故障。 + * + * @param email 要更新的用户邮箱地址,由客户端传入,不能为空(经过参数校验),且需要确保其在数据库中的唯一性(经过邮箱重复校验),用于更新用户在数据库中的邮箱信息。 + * @param phone 要更新的用户电话号码,同样不能为空,用于更新用户在数据库中的电话信息。 + * @param question 要更新的用户找回密码相关问题,不能为空,用于更新用户在数据库中设置的对应问题信息。 + * @param answer 要更新的用户找回密码相关问题的答案,不能为空,用于更新用户在数据库中设置的对应答案信息。 + * @param userId 用户的唯一标识,用于在数据库中准确查找对应的用户信息以及作为更新操作的主键依据,确保更新的是正确的用户记录。 + * @return 返回一个ServerResponse对象,包含了更新用户信息操作的结果状态(如更新成功、邮箱重复、更新失败等各种情况)以及相应的提示信息, + * 方便调用者根据返回结果知晓用户信息更新操作是否成功,并进行相应的后续处理(如提示用户操作结果、刷新页面等)。 + */ @Override public ServerResponse updateInfomation(String email, String phone, String question, String answer, Integer userId) { //1.获取当前登陆用户 User user = userMapper.selectByPrimaryKey(userId); - if (user == null){ + if (user == null) { return ServerResponse.createByErrorMessage("获取当前登陆用户失败,请重新登陆"); } //2.校验参数 - if(StringUtils.isBlank(email) || StringUtils.isBlank(phone) || StringUtils.isBlank(question) || StringUtils.isBlank(answer)){ + if (StringUtils.isBlank(email) || StringUtils.isBlank(phone) || StringUtils.isBlank(question) || StringUtils.isBlank(answer)) { return ServerResponse.createByErrorMessage("更新的数据不能存在空值!"); } //2.修改用户信息应该并发不大,所以不用加锁了,这里校验邮箱是否重复 - Integer queryCount = userMapper.checkEmailValid(email,userId); - if(queryCount > 0){ + Integer queryCount = userMapper.checkEmailValid(email, userId); + if (queryCount > 0) { //说明这个邮箱已经被其他用户占用了,所以不能使用 return ServerResponse.createByErrorMessage("此邮箱已经被占用,换个试试~"); } @@ -276,18 +557,32 @@ public class UserServiceImpl implements IUserService{ int updateCount = userMapper.updateByPrimaryKeySelective(updateUser); - if(updateCount > 0){ + if (updateCount > 0) { return ServerResponse.createBySuccessMessage("更新信息成功"); } return ServerResponse.createByErrorMessage("更新用户信息失败"); } + /** + * 此方法用于从数据库中根据用户ID获取用户的详细信息,并封装到UserResVO对象中返回,具体实现过程如下: + * + * 首先创建一个UserResVO类型的对象userResVO,用于封装要返回的用户信息。 + * 接着调用userMapper的selectByPrimaryKey方法,传入用户ID(userId)作为参数去数据库中查询对应的用户信息,将查询结果赋值给User类型的userDB对象。 + * 如果查询返回的userDB对象不为null,说明找到了对应的用户记录,此时将用户的相关信息(如用户ID、用户名、邮箱、角色、电话、问题、答案、创建时间、更新时间等) + * 通过userResVO对象的相应set方法分别设置到该对象的对应属性中,构建出一个包含完整用户信息的UserResVO对象。 + * 最后将这个封装好用户信息的userResVO对象返回给调用者,方便调用者(如前端界面或者其他业务逻辑层代码)获取用户详细信息进行后续的展示、处理等操作。 + * 如果查询返回的userDB对象为null,意味着没有找到对应的用户记录,此时直接返回一个空的UserResVO对象,调用者可以根据返回的对象情况进行相应的判断和处理。 + * + * @param userId 用户的唯一标识,用于在数据库中准确查找对应的用户信息,不能为空,是获取用户详细信息的关键依据。 + * @return 返回一个UserResVO对象,该对象封装了从数据库中查询到的对应用户ID的用户详细信息(如果用户存在),方便调用者进行后续相关操作, + * 若用户不存在则返回一个空的UserResVO对象,调用者可据此判断并做相应处理。 + */ @Override public UserResVO getUserInfoFromDB(Integer userId) { UserResVO userResVO = new UserResVO(); User userDB = userMapper.selectByPrimaryKey(userId); - if(userDB != null){ + if (userDB!= null) { userResVO.setId(userId); userResVO.setUsername(userDB.getUsername()); userResVO.setEmail(userDB.getEmail()); @@ -299,7 +594,4 @@ public class UserServiceImpl implements IUserService{ userResVO.setUpdateTime(userDB.getUpdateTime()); } return userResVO; - } - - -} + } \ No newline at end of file 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..f6d4bb9 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 @@ -14,29 +14,55 @@ import springfox.documentation.spring.web.plugins.Docket; * @CONTACT 317758022@qq.com * @DESC */ +// @Configuration 注解用于标识这个类是一个配置类,在 Spring 框架中,配置类可以替代传统的 XML 配置文件, +// 用于定义各种 Bean、配置属性以及进行一些框架相关的初始化设置等操作,Spring 容器在启动时会自动扫描并处理带有该注解的类, +// 将其中定义的 Bean 注册到容器中,方便在其他地方通过依赖注入等方式使用这些 Bean。 @Configuration public class SwaggerConfig { - // 接口版本号 + // 定义接口版本号,这里指定为 "3.0",用于在 Swagger 生成的 API 文档中清晰地展示当前接口所遵循的版本,方便使用者了解 API 的迭代情况以及兼容性等信息。 private final String version = "3.0"; - // 接口大标题 + // 接口大标题,设置为 "快乐蜗牛商城V3.0文档",它会显示在 Swagger 生成的 API 文档页面的显著位置, + // 让查看文档的人一眼就能知晓该文档对应的项目名称及大致版本情况,对整体的 API 服务有一个直观的认识。 private final String title = "快乐蜗牛商城V3.0文档"; - // 具体的描述 + // 具体的描述信息,这里描述为 "用户服务",用于更详细地说明该 API 文档所涵盖的服务范围, + // 让使用者清楚这些接口主要是围绕用户相关的功能进行提供的,比如用户的注册、登录、信息查询与修改等操作对应的接口说明都包含在此文档中。 private final String description = "用户服务"; - // 服务说明url + // 服务说明的 URL,指向 "http://www.kingeid.com",这个 URL 可以是项目的官方网站、详细的服务条款页面或者其他与该服务相关的介绍页面, + // 在 Swagger 文档中点击对应的链接,使用者可以跳转到该页面进一步了解服务的详细规则、使用限制等相关信息。 private final String termsOfServiceUrl = "http://www.kingeid.com"; - // 接口作者联系方式 + // 接口作者的联系方式相关信息,通过创建一个 Contact 类的实例来表示,这里指定了作者的名称为 "fourColor", + // 代码仓库的链接为 "https://github.com/sunweiguo",以及邮箱地址为 "sunweiguode@gmail.com",方便使用者在有问题或者建议时能够联系到接口的作者, + // 提高沟通效率,同时也体现了接口的可维护性和对使用者的友好性。 private final Contact contact = new Contact("fourColor", "https://github.com/sunweiguo", "sunweiguode@gmail.com"); + /** + * @Bean 注解用于标识这个方法会返回一个 Bean 对象,该对象会被 Spring 容器管理并注册到容器中, + * 在其他地方(比如其他类中需要使用这个 Docket 对象时)可以通过依赖注入的方式获取并使用它。 + * 这个方法主要用于构建一个 Docket 对象,Docket 是 Swagger 中用于配置 API 文档相关信息以及扫描哪些接口来生成文档的核心类。 + * 在这里它配置了 Swagger 使用的文档类型为 SWAGGER_2(当前常用的一种 Swagger 文档规范版本), + * 并通过调用 buildApiInf() 方法来设置 API 的详细信息(如标题、版本、描述、作者联系方式等),最后通过 select() 方法进行接口的选择配置(虽然这里还没有具体配置选择规则,只是简单调用了 build 方法), + * 以此来生成一个完整的 Docket 对象,用于后续 Swagger 生成符合要求的 API 文档。 + * + * @return 返回一个配置好的 Docket 对象,该对象承载了 Swagger 生成 API 文档所需要的各项配置信息, + * 包括文档类型、API 基本信息以及接口选择规则等,是 Swagger 与 Spring 集成并生成文档的关键配置对象。 + */ @Bean public Docket buildDocket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(buildApiInf()) .select().build(); } + /** + * 这个私有方法用于构建一个 ApiInfo 对象,ApiInfo 类用于封装 API 的详细信息, + * 如前面定义的标题(title)、服务条款 URL(termsOfServiceUrl)、描述(description)、版本(version)以及作者联系方式(contact)等信息, + * 通过调用 ApiInfoBuilder 类的一系列方法(链式调用),将这些信息依次设置到 ApiInfo 对象中,最后通过 build() 方法构建出一个完整的 ApiInfo 对象返回, + * 这个对象会被 buildDocket() 方法获取并设置到 Docket 对象中,用于在 Swagger 生成的 API 文档中展示这些详细的 API 相关信息,让使用者全面了解 API 的情况。 + * + * @return 返回一个构建好的 ApiInfo 对象,其中包含了 API 的各种基本信息,用于在 Swagger 文档中展示接口的相关描述、版本、作者等详情,方便使用者查看和使用 API。 + */ 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/resources/bootstrap.yml b/snailmall-user-service/src/main/resources/bootstrap.yml index a6c451b..5db503d 100644 --- a/snailmall-user-service/src/main/resources/bootstrap.yml +++ b/snailmall-user-service/src/main/resources/bootstrap.yml @@ -1,6 +1,8 @@ eureka: client: service-url: + # 配置Eureka服务端的地址,这里指定了Eureka Server的URL,客户端(当前应用)会向这个地址注册自身服务信息, + # 并且可以从这里获取到其他已注册服务的相关信息,用于实现服务发现和服务间的调用等功能。 defaultZone: http://111.231.119.253:8761/eureka # instance: # ip-address: 106.14.163.235 @@ -8,39 +10,87 @@ eureka: spring: zipkin: sender: + # 指定了Zipkin数据发送的类型为web,Zipkin用于分布式链路追踪,通过这种方式可以将应用的链路追踪数据发送到Zipkin服务端, + # 方便对微服务架构下的请求链路进行监控、分析性能问题以及排查故障等操作。 type: web application: + # 定义当前应用在注册中心(如Eureka)中的服务名称,其他服务可以通过这个名称来发现和调用本服务, + # 同时在配置一些与服务相关的路由、负载均衡等规则时也会用到这个名称作为标识。 name: user-service cloud: config: discovery: + # 开启配置中心服务发现功能,使得应用可以从配置中心获取配置信息, + # 而不是将配置文件硬编码在项目中,提高了配置的集中管理和动态更新能力。 enabled: true + # 指定配置中心服务在注册中心(如Eureka)中的服务ID,通过这个ID来查找并连接对应的配置中心服务, + # 配置中心服务负责存储和管理各个微服务的配置内容,实现配置的统一管理。 service-id: SNAILMALL-CONFIG-SERVER profile: dev bus: trace: + # 启用Spring Cloud Bus的链路追踪功能,Spring Cloud Bus可以用于在微服务架构中实现配置的动态刷新等功能, + # 开启链路追踪后能更方便地查看与配置刷新相关的请求链路情况,便于排查问题和监控。 enabled: true + # 启用Spring Cloud Bus,它可以通过消息代理(如RabbitMQ、Kafka等)来实现微服务之间的事件传播, + # 常用于配置的动态刷新、服务状态的广播等场景,增强了微服务之间的协作和交互能力。 enabled: true datasource: + # 指定数据源的类型为阿里巴巴的Druid数据源,Druid是一款性能优秀、功能强大的数据库连接池, + # 它提供了丰富的监控、扩展等功能,能够更好地管理数据库连接,提高数据库访问的效率和稳定性。 type: com.alibaba.druid.pool.DruidDataSource + # 配置数据库驱动类的全限定名,这里是针对MySQL数据库的JDBC驱动,用于建立与MySQL数据库的连接, + # 根据实际使用的数据库类型不同,需要配置相应的驱动类名。 driver-class-name: com.mysql.jdbc.Driver filters: stat + # 配置连接池中最大的活跃连接数,即同时可以使用的数据库连接数量上限,当达到这个数量后,新的连接请求会等待空闲连接释放, + # 合理设置这个值可以避免过多的数据库连接占用过多资源,同时保证应用有足够的连接来处理请求。 maxActive: 20 + # 初始化时创建的连接数量,在应用启动时会按照这个数量创建初始的数据库连接并放入连接池中备用, + # 根据应用的预估并发量等情况合理设置初始连接数可以提高应用启动后的数据库访问响应速度。 initialSize: 1 + # 配置获取连接时的最大等待时间(单位为毫秒),当连接池中没有可用连接时,请求连接的线程会等待一段时间, + # 如果超过这个时间仍未获取到连接,会抛出异常,避免线程长时间阻塞等待连接。 maxWait: 60000 + # 连接池中最小的空闲连接数量,连接池会尽量保证空闲连接数不低于这个值,通过动态调整连接的创建和销毁来维持这个空闲连接数, + # 以便在有新的连接请求时能够快速响应,减少连接创建的开销。 minIdle: 1 + # 配置每隔多长时间(单位为毫秒)对空闲连接进行一次检测,查看是否有连接超时、失效等情况, + # 定期检测空闲连接可以及时清理无效的连接,释放资源,保证连接池中的连接质量。 timeBetweenEvictionRunsMillis: 60000 + # 配置空闲连接的最小可存活时间(单位为毫秒),超过这个时间的空闲连接会被回收,释放资源, + # 避免长时间闲置的连接占用过多内存等资源,提高资源利用效率。 minEvictableIdleTimeMillis: 300000 + # 用于验证数据库连接是否有效的查询语句,连接池会定期使用这个语句来测试连接是否可用, + # 这里简单地使用'select 'x''作为验证语句,根据数据库的不同,也可以使用更合适的验证语句来确保连接正常。 validationQuery: select 'x' + # 设置在空闲状态下是否对连接进行测试,若为true,则在连接空闲时会按照配置的验证查询语句(validationQuery)进行测试, + # 及时发现并回收无效的空闲连接,保证连接的有效性。 testWhileIdle: true + # 设置在从连接池获取连接时是否进行测试,若为false,则获取连接时不会执行验证查询语句进行测试, + # 一般根据实际情况选择是否在获取连接时进行测试,避免不必要的性能开销。 testOnBorrow: false + # 设置在归还连接到连接池时是否进行测试,若为false,则归还连接时不会执行验证查询语句进行测试, + # 同样需要根据具体需求来决定是否开启这个测试,以平衡性能和连接有效性的保障。 testOnReturn: false + # 设置是否对预编译语句(Prepared Statements)进行池化管理,开启后可以提高预编译语句的复用率, + # 减少语句编译的开销,提升数据库访问性能,特别是在频繁执行相同SQL语句的场景下效果更明显。 poolPreparedStatements: true + # 配置连接池中最大允许打开的预编译语句数量,限制了预编译语句的资源占用情况,避免过多的预编译语句导致内存等资源耗尽, + # 根据应用的实际使用情况合理设置这个值可以优化性能和资源利用。 maxOpenPreparedStatements: 20 server: + # 配置当前应用启动后监听的端口号,外部客户端可以通过这个端口来访问应用提供的服务, + # 根据实际部署和网络环境等情况合理设置端口号,避免端口冲突等问题。 port: 8088 logging: + # 指定日志配置文件的位置,这里使用classpath路径下的logback.xml文件作为日志配置文件, + # 通过这个文件可以详细配置日志的输出格式、级别、输出目的地等信息,实现灵活的日志管理。 config: classpath:logback.xml mybatis: + # 指定MyBatis的Mapper XML文件的位置,MyBatis会根据这里配置的路径去扫描并加载对应的Mapper XML文件, + # 这些文件中定义了数据库操作的SQL语句以及与Java方法的映射关系,用于实现数据持久化操作。 mapper-locations: classpath:com/njupt/swg/**/**.xml + # 配置MyBatis的类型别名包路径,在Mapper XML文件中可以使用这些别名来简化实体类的引用, + # 比如可以直接使用类名(或者自定义的别名)来代替全限定类名,使SQL语句更加简洁易读。 type-aliases-package: classpath:com.njupt.swg.**.entity \ No newline at end of file diff --git a/snailmall-user-service/src/main/resources/logback.xml b/snailmall-user-service/src/main/resources/logback.xml index 0348e86..736ed85 100644 --- a/snailmall-user-service/src/main/resources/logback.xml +++ b/snailmall-user-service/src/main/resources/logback.xml @@ -1,33 +1,58 @@ + + + + + %d{H:mm} %-5level [%logger{16}] %msg%n + + + ${LOG_HOME}/web.normal.%d{yyyy-MM-dd}.log + 30 + 10MB + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{16} - %msg%n + + ERROR + DENY - ACCEPT + + ACCEPT + + @@ -44,18 +69,22 @@ ERROR + ACCEPT DENY - + - +