pull request #4

Closed
pb4nq52pf wants to merge 51 commits from zy into feature/cx

@ -1,20 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这是 Maven 项目对象模型POM配置文件的根元素声明了 XML 文档的版本和编码格式,
同时通过 xmlns 和 xsi:schemaLocation 属性定义了 XML 命名空间以及对应的 XML 模式文档位置,以遵循 Maven POM 4.0.0 版本的规范 -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 定义 Maven 项目对象模型的版本,这里使用的是 4.0.0 版本,它规定了整个 pom.xml 文件的基本结构和语法规则 -->
<modelVersion>4.0.0</modelVersion>
<!-- 配置项目的父级依赖,通过指定 groupId、artifactId 和 version当前项目继承自 Spring Boot 的启动器父项目,
这样可以复用父项目中定义的很多默认配置,比如插件配置、依赖管理等,使得子项目的配置更加简洁,保持一致性 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 当前项目的 groupId通常代表组织或公司等的唯一标识与 artifactId 一起构成了项目在 Maven 仓库中的唯一坐标,
用于区分不同来源的项目,这里标识该项目属于 com.njupt.swg 这个组织或模块下 -->
<groupId>com.njupt.swg</groupId>
<!-- 当前项目的 artifactId用于唯一标识项目在 Maven 仓库中的名称,通常与项目的实际名称相关,
这里表示该项目名为 spring-cloud-for-snailmall -->
<artifactId>spring-cloud-for-snailmall</artifactId>
<!-- 当前项目的版本号,采用了 0.0.1-SNAPSHOT 这种快照版本形式,意味着这是一个处于开发阶段、不稳定的版本,
常用于项目开发过程中,后续随着项目的完善和发布,会更新为正式的版本号 -->
<version>0.0.1-SNAPSHOT</version>
<!-- 项目的显示名称,方便在 Maven 相关工具或者项目管理场景中直观地识别该项目 -->
<name>spring-cloud-for-snailmall</name>
<!-- 项目的简要描述,清晰说明了该项目是聚合工程的父工程,即它作为一个父项目,会包含多个子模块项目,用于统一管理这些子项目的配置等 -->
<description>聚合工程的父工程</description>
<!-- 指定项目的打包类型为 pom表明这是一个聚合工程的父工程主要用于管理子模块的依赖、构建等配置本身不会生成可执行的代码包
而是协调各个子模块的构建过程 -->
<packaging>pom</packaging>
<!-- 定义项目包含的子模块列表,在这里罗列了多个子模块的名称,每个子模块在 Maven 构建时会被当作独立的项目进行处理,
但又能从父项目这里继承一些通用的配置,方便进行统一管理和构建,比如各个子模块可以继承这里定义的依赖版本等 -->
<modules>
<module>snailmall-eureka-server</module>
<module>snailmall-config-server</module>
@ -28,14 +45,23 @@
<module>snailmall-order-service</module>
</modules>
<!-- 定义项目的一些通用属性,这些属性可以在整个项目的构建过程以及依赖配置等地方被引用,方便统一管理一些项目相关的设置 -->
<properties>
<!-- 指定项目构建时源文件的编码格式为 UTF-8确保不同环境下代码中的中文等字符能正确处理避免乱码问题 -->
<project.build.sourceencoding>UTF-8</project.build.sourceencoding>
<!-- 指定项目生成报告(如测试报告等)时输出的编码格式为 UTF-8保证报告内容的字符编码正确便于查看和分析 -->
<project.reporting.outputencoding>UTF-8</project.reporting.outputencoding>
<!-- 指定项目使用的 Java 版本为 1.8,整个项目在编译、运行等过程中会基于这个 Java 版本进行相应的处理 -->
<java.version>1.8</java.version>
</properties>
<!-- 依赖管理部分,在这里定义了项目所依赖的各种外部库、框架等资源的版本信息,
子项目在添加依赖时无需指定版本号(除非要使用特定版本,可覆盖此处定义的版本),就能自动继承这些统一管理的依赖版本,
方便控制项目整体的依赖版本一致性,避免版本冲突等问题 -->
<dependencyManagement>
<dependencies>
<!-- Spring Cloud 依赖管理,通过导入 Spring Cloud 的依赖管理 POM 文件,统一管理 Spring Cloud 相关依赖的版本,
使得项目中使用 Spring Cloud 各个组件时能保证版本的兼容性和一致性,避免因版本不一致导致的问题 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
@ -44,201 +70,175 @@
<scope>import</scope>
</dependency>
<!--MYSQL-->
<!-- MYSQL -->
<!-- 引入 MySQL 的 JDBC 驱动依赖,指定版本为 5.1.46,用于在项目中建立与 MySQL 数据库的连接,使得项目可以通过 JDBC 规范与 MySQL 数据库进行交互,
执行 SQL 语句实现数据的持久化操作,如查询、插入、更新和删除数据等 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--mybatis-->
<!-- mybatis -->
<!-- 引入 MyBatis 与 Spring Boot 集成的启动器依赖,指定版本为 1.2.0MyBatis 是一个优秀的持久层框架,
通过这个启动器可以方便地在 Spring Boot 项目中使用 MyBatis 进行数据库访问操作,结合 XML 配置文件或者注解来编写 SQL 语句,实现数据持久化 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<!--json序列化和反序列-->
<!-- json 序列化和反序列 -->
<!-- 引入 Jackson 的 mapper 依赖,指定版本为 1.9.13Jackson 是常用的 JSON 处理库,用于在 Java 对象和 JSON 格式数据之间进行序列化和反序列化操作,
比如将 Java 对象转换为 JSON 字符串发送给前端,或者将接收到的 JSON 数据解析为 Java 对象,在 Web 开发中经常会用到 -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<!-- 引入另一个 Jackson 相关依赖,用于处理特定格式(如 Avro 格式)的 JSON 数据序列化和反序列化,指定版本为 2.9.0
在需要处理特殊格式 JSON 数据的场景下发挥作用 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-avro</artifactId>
<version>2.9.0</version>
</dependency>
<!--druid-->
<!-- druid -->
<!-- 引入阿里巴巴的 Druid 依赖,指定版本为 1.0.18Druid 是一款性能优秀的数据库连接池,除了基本的连接管理功能外,
还提供了丰富的监控、统计、扩展等功能,能够更好地管理数据库连接,提升数据库访问的效率和稳定性 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
</dependency>
<!--工具包,各种数据结构-->
<!-- 工具包,各种数据结构 -->
<!-- 引入 Google 的 Guava 依赖,指定版本为 20.0Guava 是一个包含了很多实用工具类和集合扩展的库,
例如提供了更方便的字符串处理、集合操作、缓存机制等功能,可以提高 Java 开发的效率,减少重复造轮子 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
<!-- 引入 Apache Commons Lang3 依赖,指定版本为 3.5,它提供了大量对 Java 基本类型、字符串、数组等操作的实用工具方法,
比如字符串的判空、格式化,数组的拷贝、填充等功能,补充了 Java 标准库中一些功能的不足,方便日常开发 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<!-- 引入 Commons Collections 依赖,指定版本为 3.2.1,它提供了一系列扩展的集合类和集合相关的工具方法,
比如各种特殊的集合实现(如不可变集合、多值映射等)以及对集合进行操作的便捷方法,丰富了 Java 集合框架的功能 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!--时间处理-->
<!-- 时间处理 -->
<!-- 引入 Joda-Time 依赖,指定版本为 2.3Joda-Time 是一个处理日期和时间的强大库,提供了比 Java 标准库中更方便、灵活的日期时间操作方法,
例如日期的格式化、解析、计算时间间隔等功能,在涉及到复杂的日期时间处理业务场景中很实用 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.3</version>
</dependency>
<!-- ftpclient -->
<!-- 引入 Commons Net 依赖,指定版本为 3.1,常用于实现 FTP 客户端相关功能,比如与 FTP 服务器进行文件传输、目录操作等,
在需要进行 FTP 相关业务操作的项目中会用到这个库 -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.1</version>
</dependency>
<!-- file upload -->
<!-- 引入 Commons Fileupload 依赖,指定版本为 1.2.2,用于在项目中实现文件上传功能,提供了方便的 API 来处理文件上传的相关逻辑,
配合服务器端的配置,可以让用户通过网页等方式上传文件到服务器 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<!-- 引入 Commons IO 依赖,指定版本为 2.0.1,它提供了一系列对文件和输入输出流操作的实用工具方法,
可以简化文件读写、复制、移动等操作的代码编写,提高文件处理相关的开发效率 -->
<dependency>
<groupId>commons-io</groupId>
<groupId>commons-io</artifactId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
<!-- mybatis 分页-->
<!-- mybatis 分页 -->
<!-- 引入 PageHelper 依赖,指定版本为 4.1.0PageHelper 是一款常用的 MyBatis 分页插件,它能够方便地在 MyBatis 进行数据库查询时实现分页功能,
只需简单配置和调用相关 API就能轻松获取分页数据简化了分页查询的开发难度 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.0</version>
</dependency>
<!-- 引入 MyBatis Paginator 依赖,指定版本为 1.2.17,它同样是用于在 MyBatis 中实现分页功能的工具,提供了另一种分页相关的实现方式和功能扩展,
可以根据具体项目需求选择使用,与其他分页插件配合使用时也能满足更复杂的分页场景 -->
<dependency>
<groupId>com.github.miemiedev</groupId>
<artifactId>mybatis-paginator</artifactId>
<version>1.2.17</version>
</dependency>
<!-- 引入 JSqlParser 依赖,指定版本为 0.9.4JSqlParser 是一个 SQL 解析器库,可用于解析、修改和分析 SQL 语句,
在一些需要对 SQL 语句进行动态处理、验证或者优化的场景中会发挥作用,比如根据不同条件动态拼接 SQL 等情况 -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.4</version>
</dependency>
<!-- alipay 与支付宝demo中依赖的包的版本是一致的-->
<!-- alipay 与支付宝 demo 中依赖的包的版本是一致的 -->
<!-- 引入 Commons Codec 依赖,指定版本为 1.10,它提供了一些常用的编解码工具方法,比如对字符串进行加密、解密、编码转换等操作,
在与支付宝等涉及数据加密、签名验证等交互场景中可能会用到 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<!-- 引入 Commons Configuration 依赖,指定版本为 1.10,它用于读取和管理各种配置文件,提供了方便的 API 来获取配置项的值,
可以灵活地处理项目中的配置信息,比如读取 properties 文件、XML 配置文件等中的配置参数 -->
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
<!-- 引入 Commons Lang 依赖,指定版本为 2.6,它提供了一些基础的字符串、数组等操作的工具方法,虽然部分功能在后续的 Commons Lang3 中有所更新和扩展,
但在一些旧项目或者特定场景下可能还会用到这个版本的相关功能 -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- 引入 Commons Logging 依赖,指定版本为 1.2,它是一个通用的日志抽象层,为不同的日志实现框架(如 Log4j、Logback 等)提供了统一的接口,
方便在项目中切换或集成不同的日志框架,增强了日志管理的灵活性 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- 引入 Google ZXing 核心依赖,指定版本为 2.1ZXing 是一个用于处理二维码和条形码的开源库,
可以用于生成、解析二维码和条形码等操作,在涉及到扫码相关业务功能的项目中会用到 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>2.1</version>
</dependency>
<!-- 引入 Google 的 Gson 依赖,指定版本为 2.8.2Gson 是一个用于将 Java 对象和 JSON 格式数据进行相互转换的库,
与前面提到的 Jackson 类似,但使用方式和特点有所不同,开发者可以根据项目需求选择使用 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<!-- 引入 Hamcrest 核心依赖,指定版本为 1.3Hamcrest 是一个用于编写测试断言的框架,提供了更丰富、易读的断言方式,
常用于单元测试等场景中,方便对测试结果进行验证 -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
</dependency>
<!--redis-->
<!-- redis -->
<!-- 引入 Jedis 依赖,指定版本为 2.9.0Jedis 是 Redis 的 Java 客户端库,用于在 Java 项目中与 Redis 缓存数据库进行交互,
比如向 Redis 中存储数据、获取数据、执行 Redis 支持的各种命令等,实现缓存功能或者其他基于 Redis 的业务逻辑 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.6</version>
</dependency>
<!--logback-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>1.1.2</version>
</dependency>
<!--curator-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<!--alipay-->
<dependency>
<groupId>com.alipay</groupId>
<artifactId>sdk-java</artifactId>
<version>20161213</version>
</dependency>
<dependency>
<groupId>com.alipay</groupId>
<artifactId>trade-sdk</artifactId>
<version>20161215</version>
</dependency>
<!-- 自动生成API文档 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
</

@ -5,11 +5,21 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
// 表明这是一个Spring Boot应用的主配置类它整合了多个Spring Boot相关的配置功能
// 例如自动配置、组件扫描等能够让Spring Boot应用顺利启动并运行。
@SpringBootApplication
// 用于启用服务发现客户端功能使得该应用可以注册到服务注册中心比如Eureka等
// 并且能够发现注册中心中其他已注册的服务,方便服务间的调用和协作。
@EnableDiscoveryClient
// 开启配置服务器功能,该应用能够作为配置中心服务器,对外提供配置信息的管理与服务,
// 其他微服务可以从这个配置服务器获取相应的配置内容,实现配置的集中管理和动态更新等。
@EnableConfigServer
public class SnailmallConfigServerApplication {
// 这是Java应用程序的入口方法Java虚拟机JVM会从这里开始执行程序。
// 它接收命令行传入的参数args通过SpringApplication的run方法启动Spring Boot应用
// 传入的参数包括当前应用的主类SnailmallConfigServerApplication.class以及命令行参数args
// 以此启动整个Spring Boot应用的运行环境并加载相关配置进行初始化等操作。
public static void main(String[] args) {
SpringApplication.run(SnailmallConfigServerApplication.class, args);
}

@ -4,13 +4,19 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
// @SpringBootApplication是一个组合注解它整合了多个Spring Boot相关的注解
// 例如@Configuration、@EnableAutoConfiguration、@ComponentScan等用于标记这是一个Spring Boot应用的启动类
@SpringBootApplication
// @EnableEurekaServer注解用于开启Eureka Server的功能使得当前应用可以作为服务注册与发现机制中的服务注册中心
// 其他微服务可以将自身信息注册到这个中心上来,方便进行服务间的调用和管理
@EnableEurekaServer
public class SnailmallEurekaServerApplication {
// main方法是Java程序的入口点在这里通过SpringApplication的run方法来启动Spring Boot应用
// 第一个参数SnailmallEurekaServerApplication.class表示当前应用的主配置类
// 第二个参数args用于接收外部传入的命令行参数
public static void main(String[] args) {
SpringApplication.run(SnailmallEurekaServerApplication.class, args);
}
}

@ -1,66 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这是 Maven 项目对象模型POM配置文件的根元素声明了 XML 的版本和编码格式,
同时通过 xmlns 和 xsi:schemaLocation 属性定义了 XML 命名空间以及对应的 XSD 模式文档位置,以遵循 Maven POM 4.0.0 版本的规范 -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 定义 Maven 项目对象模型的版本号,这里使用的是 4.0.0 版本,该版本规定了整个 pom.xml 文件的基本结构和语法规则 -->
<modelVersion>4.0.0</modelVersion>
<!-- 配置项目的父级依赖信息,通过指定 groupId、artifactId 和 version使得当前项目可以继承父项目的相关配置
比如依赖管理、插件管理等方面的配置,减少了重复配置工作,有助于保持项目结构的一致性和规范性 -->
<parent>
<groupId>com.njupt.swg</groupId>
<artifactId>spring-cloud-for-snailmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 当前项目的构件标识符artifactId在 Maven 仓库中用于唯一标识该项目,通常与项目实际的名称相关联,
这里表示此项目为名为 snailmall-keygen-service 的模块 -->
<artifactId>snailmall-keygen-service</artifactId>
<!-- 当前项目的版本号,这里采用了 0.0.1-SNAPSHOT 这种快照版本形式SNAPSHOT 意味着这是一个处于开发阶段、不稳定的版本,
常用于项目开发过程中,后续随着项目的完善和发布,会更新为正式的版本号 -->
<version>0.0.1-SNAPSHOT</version>
<!-- 项目的显示名称,主要用于在 Maven 相关的工具界面或者项目管理场景中,让人更直观地识别该项目 -->
<name>snailmall-keygen-service</name>
<!-- 对项目的简要描述,清晰地说明了该项目是一个用于生成全局唯一 ID 的服务,并且采用了雪花算法来实现这一功能 -->
<description>全局唯一ID生产服务-雪花算法</description>
<!-- 项目依赖配置部分,在这里列出了项目运行过程中所需要依赖的各种外部库、框架等资源 -->
<dependencies>
<!-- 引入 Spring Boot 的基础启动器依赖,它会自动引入一系列 Spring Boot 项目运行所必需的基础组件和配置,
比如自动配置、日志框架集成等,为构建 Spring Boot 应用提供了基础支撑 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 引入 Spring Boot 的 Web 启动器依赖,它会自动整合构建 Web 应用所需的关键依赖,
例如 Spring MVC 框架用于处理 HTTP 请求和响应,内置的 Tomcat 服务器用于接收和处理外部的网络请求等,
方便开发基于 Spring Boot 的 Web 服务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Curator 的 recipes常用操作配方依赖Curator 是用于简化与 Zookeeper 交互的 Java 库,
recipes 模块提供了一些基于 Curator 核心功能之上的常用分布式应用开发中涉及 Zookeeper 的操作实现,
比如分布式锁、分布式计数器等功能,方便开发者在分布式系统中借助 Zookeeper 实现相关业务逻辑 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<!-- 引入 Spring Boot 的 Actuator 依赖Actuator 为 Spring Boot 应用提供了一系列用于监控和管理应用的端点,
通过这些端点可以查看应用的健康状态、获取运行时的各种指标信息(如内存使用情况、线程信息等),
还能进行一些配置的动态调整等操作,有助于对应用进行有效的运维管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 引入 Spring Cloud 配置客户端依赖,使得当前项目能够作为客户端与 Spring Cloud 配置中心(如 Spring Cloud Config Server进行交互
从配置中心获取项目的配置信息,实现配置的集中管理和动态更新,提高配置的灵活性以及可维护性 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- 引入 Spring Cloud Bus 的 AMQP高级消息队列协议依赖Spring Cloud Bus 借助消息队列(如 RabbitMQ、Kafka 等)实现微服务之间的事件传播,
常用于配置的动态刷新场景,通过 AMQP 协议来传递消息,增强了微服务之间的交互协作能力 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- 引入 Spring Cloud Zipkin 依赖Zipkin 用于分布式链路追踪,在微服务架构中,项目集成该依赖后,
可以将应用的请求链路信息发送到 Zipkin 服务端进行收集和分析,方便排查性能问题、定位故障点等,
提升了对分布式系统的监控和问题排查能力 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- 引入 Lombok 依赖Lombok 是一个通过注解来简化 Java 代码编写的工具,
它可以根据注解自动为类生成构造函数、Getter/Setter 方法、toString 方法等,减少了大量的样板代码,
让 Java 代码更加简洁易读,提高开发效率 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 引入 Spring Cloud 的 Eureka 客户端依赖,使得项目可以将自身注册到 Eureka 服务注册中心,
同时也能从 Eureka 发现其他已注册的服务,这是实现微服务架构中服务发现和注册功能的关键依赖,
便于微服务之间的相互调用和协作 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- 项目构建相关配置部分,用于指定项目在构建过程中的一些设置,比如插件的使用等 -->
<build>
<plugins>
<!-- 引入 Spring Boot 的 Maven 插件,这个插件在项目构建过程中有诸多重要功能,
例如它可以将项目打包成可执行的 JAR 文件,并且在打包过程中把应用运行所需的依赖、配置信息等都嵌入到 JAR 文件中,
还能方便地启动 Spring Boot 应用进行本地测试等操作,极大地简化了 Spring Boot 项目的构建和部署流程 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
@ -68,4 +108,4 @@
</plugins>
</build>
</project>
</project>

@ -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
* workerIDworkerIDSenquenceZKZookeeper
* workerID01024
*/
@PostConstruct
public void initWorkerId() throws Exception {
long workerID = workerIDSenquence.getSequence(null);
// 使用Preconditions进行前置条件校验确保workerID合法
Preconditions.checkArgument(workerID >= 0L && workerID < 1024L);
workerId = workerID;
}
/**
* ID
* 退
*
* 0
* IDID
*/
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);

@ -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();
}

@ -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;
/**
* 使@PostConstructZookeeper
* CuratorFrameworkFactoryZookeeper105000
* ZookeeperZK_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_"
* ZookeeperZK_PATHCreateMode.EPHEMERAL_SEQUENTIAL
*
* ID
*/
public long getSequence(String hostname) throws Exception {
if(StringUtils.isBlank(hostname)){
hostname = "snowflake_";

@ -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());

@ -1,108 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这是 Maven 项目配置文件pom.xml的根元素声明了 XML 文档的版本及编码格式,
同时通过 xmlns 和 xsi:schemaLocation 属性定义了 XML 命名空间以及对应的 XML 模式文档位置,确保遵循 Maven 项目对象模型POM4.0.0 版本的规范 -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 定义 Maven 项目对象模型的版本,这里指定为 4.0.0 版本,该版本规定了整个 pom.xml 文件应遵循的基本结构与语法规则 -->
<modelVersion>4.0.0</modelVersion>
<!-- 配置项目的父级依赖,通过指定父项目的 groupId、artifactId 和 version当前项目能够继承父项目的诸多配置信息
例如依赖管理、插件管理等方面的配置,这样可以减少重复配置工作,保证项目结构的一致性和可维护性 -->
<parent>
<groupId>com.njupt.swg</groupId>
<artifactId>spring-cloud-for-snailmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 当前项目的构件标识符artifactId用于在 Maven 仓库中唯一标识该项目,通常与项目的实际名称相关,
这里表明该项目是名为 snailmall-shipping-service 的模块 -->
<artifactId>snailmall-shipping-service</artifactId>
<!-- 当前项目的版本号,采用了 0.0.1-SNAPSHOT 这种快照版本格式,其中 SNAPSHOT 表示这是一个处于开发阶段、不稳定的版本,
常用于项目开发过程中,后续随着项目的成熟会更新为正式的发布版本号 -->
<version>0.0.1-SNAPSHOT</version>
<!-- 项目的显示名称,方便在 Maven 相关工具或者项目管理场景中直观地对项目进行识别 -->
<name>snailmall-shipping-service</name>
<!-- 项目的简要描述,清晰地说明了该项目是提供收货地址相关功能的服务 -->
<description>收货地址服务</description>
<!-- 项目依赖配置部分,在此处罗列了项目运行所需依赖的各种外部库、框架等资源 -->
<dependencies>
<!-- 引入 Spring Boot 的基础启动器依赖,它会自动引入一系列构建 Spring Boot 应用的基础组件和配置,
比如自动配置机制、日志框架的集成等,为整个 Spring Boot 项目的运行提供了基础支撑 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--spring cloud相关-->
<!-- spring cloud 相关 -->
<!-- 引入 Spring Boot 的 Web 启动器依赖,它整合了构建 Web 应用所需的关键依赖,例如包含 Spring MVC 框架用于处理 HTTP 请求和响应,
以及内置的 Tomcat 服务器用于接收并处理外部网络请求等,方便开发者基于 Spring Boot 快速搭建 Web 服务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Spring Cloud 配置客户端依赖,该依赖使得项目能够作为客户端与 Spring Cloud 配置中心(如 Spring Cloud Config Server进行交互
从而从配置中心获取项目的配置信息,实现配置的集中管理和动态更新,提高了配置的灵活性以及便于统一维护 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- 引入 Spring Cloud 的 Eureka 客户端依赖,借助此依赖,项目可以将自身注册到 Eureka 服务注册中心,
同时也能够从 Eureka 发现其他已注册的服务,这是实现微服务架构中服务发现与注册功能的关键所在,有助于微服务之间相互调用与协作 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入 Spring Cloud Bus 的 AMQP高级消息队列协议依赖Spring Cloud Bus 通过借助消息队列(如 RabbitMQ、Kafka 等)实现微服务之间的事件传播,
常用于配置的动态刷新等场景,基于 AMQP 协议进行消息传递,增强了微服务之间的交互能力 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- 引入 Spring Boot 的 Actuator 依赖Actuator 为 Spring Boot 应用提供了一系列用于监控和管理应用的端点,
例如可以通过这些端点查看应用的健康状态、获取运行时的各类指标信息(像内存使用情况、线程信息等),还能进行一些配置的动态调整操作,
对应用的运维管理十分有帮助 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 引入 Spring Cloud 的 OpenFeign 依赖OpenFeign 是一个声明式的 HTTP 客户端,它简化了微服务之间的 HTTP 接口调用,
开发者只需定义接口并添加相应注解,就能方便地调用其他微服务提供的接口,提高了微服务间通信的便捷性 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 引入 Spring Cloud Zipkin 依赖Zipkin 用于分布式链路追踪,在微服务架构下,项目集成该依赖后,
能够将应用的请求链路信息发送到 Zipkin 服务端进行收集与分析,这对于排查性能问题、定位故障发生位置等操作非常有帮助 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- 引入 Lombok 依赖Lombok 是一款通过注解来简化 Java 代码编写的工具它可以根据特定注解自动为类生成构造函数、Getter/Setter 方法、toString 方法等,
能够有效减少大量样板代码,使 Java 代码更加简洁、易读,进而提升开发效率 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--jackson-->
<!-- jackson -->
<!-- 引入 Jackson 的 mapper 依赖Jackson 是常用的 JSON 处理库,主要用于在 Java 对象和 JSON 格式数据之间进行序列化和反序列化操作,
比如将 Java 对象转换为 JSON 字符串发送给前端,或者将接收到的 JSON 数据解析为 Java 对象,在 Web 开发场景中应用广泛 -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
</dependency>
<!--druid-->
<!-- druid -->
<!-- 引入阿里巴巴的 Druid 依赖Druid 是一款性能出色的数据库连接池,除了具备基本的数据库连接管理功能外,
还提供了丰富的监控、统计以及扩展功能,能够更好地管理数据库连接,提升数据库访问的效率与稳定性 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--util-->
<!-- util -->
<!-- 引入 Google 的 Guava 依赖Guava 是一个包含众多实用工具类和集合扩展功能的库,
例如提供了更便捷的字符串处理、集合操作、缓存机制等功能,有助于提高 Java 开发效率,减少开发者重复编写类似功能代码的工作量 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- 引入 Apache Commons Lang3 依赖,它提供了大量针对 Java 基本类型、字符串、数组等操作的实用工具方法,
比如字符串的判空、格式化,数组的拷贝、填充等功能,能够补充 Java 标准库中部分功能的不足,方便在日常开发中使用 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- 引入 Commons Collections 依赖,它提供了一系列扩展的集合类以及集合相关的工具方法,
例如各种特殊的集合实现(如不可变集合、多值映射等)以及针对集合进行操作的便捷方法,丰富了 Java 集合框架的功能 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<!--MYSQL-->
<!-- MYSQL -->
<!-- 引入 MySQL 的 JDBC 驱动依赖,用于在 Java 项目中建立与 MySQL 数据库的连接,使得项目可以依据 JDBC 规范与 MySQL 数据库进行交互,
进而执行 SQL 语句,实现数据的持久化操作,例如查询、插入、更新以及删除数据库中的数据等 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<!-- mybatis -->
<!-- 引入 MyBatis 与 Spring Boot 集成的启动器依赖MyBatis 是一款优秀的持久层框架,
通过此启动器,开发者能够方便地在 Spring Boot 项目中使用 MyBatis 进行数据库访问操作,结合 XML 配置文件或者注解来编写 SQL 语句,实现数据的持久化功能 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--jedis-->
<!-- jedis -->
<!-- 引入 Jedis 依赖Jedis 是 Redis 的 Java 客户端库,借助它可以在 Java 项目中与 Redis 缓存数据库进行交互,
比如向 Redis 中存储数据、获取数据以及执行 Redis 支持的各种命令等操作,从而实现缓存功能或者其他基于 Redis 的业务逻辑 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--joda time-->
<!-- joda time -->
<!-- 引入 Joda-Time 依赖Joda-Time 是一个处理日期和时间的强大库,它提供了比 Java 标准库更为方便、灵活的日期时间操作方法,
例如日期的格式化、解析以及计算时间间隔等功能,在涉及复杂日期时间处理的业务场景中非常实用 -->
<dependency>
<groupId>joda-time</groupId>
<groupId>joda-time</artifactId>
<artifactId>joda-time</artifactId>
</dependency>
<!--分页-->
<!-- 分页 -->
<!-- 引入 PageHelper 依赖PageHelper 是一款常用的 MyBatis 分页插件,它能够方便地在 MyBatis 进行数据库查询时实现分页功能,
只需简单配置和调用相关 API就能轻松获取分页数据简化了分页查询的开发难度 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
<!-- 引入 MyBatis Paginator 依赖,它同样是用于在 MyBatis 中实现分页功能的工具,提供了另一种分页相关的实现方式和功能扩展,
可以根据具体项目需求选择使用,与其他分页插件配合使用时也能满足更复杂的分页场景 -->
<dependency>
<groupId>com.github.miemiedev</groupId>
<artifactId>mybatis-paginator</artifactId>
</dependency>
<!-- 引入 JSqlParser 依赖JSqlParser 是一个 SQL 解析器库,可用于解析、修改和分析 SQL 语句,
在一些需要对 SQL 语句进行动态处理、验证或者优化的场景中会发挥作用,比如根据不同条件动态拼接 SQL 等情况 -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
@ -110,8 +165,11 @@
</dependencies>
<!-- 项目构建相关配置部分,用于指定项目在构建过程中的一些设置,例如资源文件的处理方式以及插件的使用等 -->
<build>
<!--编译xml文件-->
<!-- 编译 xml 文件 -->
<!-- 配置项目的资源文件,这里指定在编译项目时,会将 src/main/java 目录下的所有 xml 文件也作为资源文件进行处理,
通常这些 xml 文件可能是 MyBatis 的 Mapper 配置文件等,确保它们在构建过程中能够被正确打包并使用 -->
<resources>
<resource>
<directory>src/main/java</directory>
@ -121,6 +179,9 @@
</resource>
</resources>
<plugins>
<!-- 引入 Spring Boot 的 Maven 插件,该插件在项目构建过程中有诸多重要功能,
例如它能够将项目打包成可执行的 JAR 文件,并且在打包过程中把应用运行所需的依赖、配置信息等一并嵌入到 JAR 文件内,
同时还方便启动 Spring Boot 应用进行本地测试等操作,极大地简化了 Spring Boot 项目的构建和部署流程 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
@ -128,4 +189,4 @@
</plugins>
</build>
</project>
</project>

@ -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
* keyRedis
* jedisPoolWrapperJedisJedis
* Redis0Jedis.select(0)使JedissetRedis
* SnailmallExceptionRedis
*/
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
* keyvalueRedis
* valuenulljedisPoolWrapperJedis
* JedisRedis0使Jedisgetvalue
* SnailmallExceptionRedisvaluenull
*/
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
* keyRedis使setnx
* Redisexpire
* result0jedisPoolWrapperJedis
* Jedis使setnxresult
* 使expireSnailmallExceptionRedis
* setnx10
*/
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
* keyRedis
* jedisPoolWrapperJedisJedis
* 使JedisdelSnailmallExceptionRedis
*/
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 {
}
}

@ -14,14 +14,28 @@ import javax.annotation.PostConstruct;
* @CONTACT 317758022@qq.com
* @DESC redisredishash
*/
// 使用@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;
/**
* 使@PostConstructJedis
*
* 1. JedisPoolConfigJedis
* 2. parametersparameters.getRedisMaxTotal()JedisPoolConfig
* 3. 使JedisPoolConfigparametersRedis2000"xxx"JedisPooljedisPool
* 4. log.error便log.info
*/
@PostConstruct
public void init(){
try {
@ -36,7 +50,10 @@ public class JedisPoolWrapper {
}
}
/**
* JedisPoolJedisJedisRedis
*/
public JedisPool getJedisPool() {
return jedisPool;
}
}
}

@ -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*******/
// 以下是Zookeepercurator相关配置curator是操作Zookeeper的框架相关配置参数的定义部分同样使用@Value注解获取配置值。
// 用于存放Zookeeper服务器的主机地址通过读取配置文件中名为"zk.host"的配置项获取值后续在连接Zookeeper服务器时会用到该地址信息。
/*****curator config start*******/
@Value("${zk.host}")
private String zkHost;
/*****curator config end*******/
}
}

@ -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_";

@ -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());
}

@ -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;
}
}
}

@ -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;
}
}
}

@ -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<T> 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 <T>ServerResponse<T> createBySuccess(){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
}
// 创建一个表示成功的ServerResponse实例接收一个自定义的消息参数message使用ResponseEnum.SUCCESS的状态码以及传入的message来初始化用于在成功的同时需要向客户端返回特定提示消息的场景。
public static <T>ServerResponse<T> createBySuccessMessage(String message){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message);
}
// 创建一个表示成功的ServerResponse实例接收一个泛型参数data使用ResponseEnum.SUCCESS的状态码以及传入的数据来初始化用于在业务处理成功且有具体业务数据需要返回给客户端的场景。
public static <T>ServerResponse<T> createBySuccess(T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data);
}
// 创建一个表示成功的ServerResponse实例接收自定义的消息参数message和泛型参数data使用ResponseEnum.SUCCESS的状态码、传入的message以及data来初始化适用于成功且既有提示消息又有业务数据需要返回的综合场景。
public static <T>ServerResponse<T> createBySuccess(String message,T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data);
}
/**
* ServerResponse便
*/
/**
*
*/
// 创建一个表示失败的ServerResponse实例使用ResponseEnum.ERROR中定义的状态码和描述信息来初始化适用于一般性的错误情况只告知客户端操作失败没有更详细的错误消息定制。
public static <T>ServerResponse<T> createByError(){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc());
}
// 创建一个表示失败的ServerResponse实例接收一个自定义的错误消息参数msg使用ResponseEnum.ERROR的状态码以及传入的msg来初始化用于在出现错误且需要向客户端返回具体错误描述的场景。
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
}
// 创建一个表示失败的ServerResponse实例接收状态码和自定义的错误消息两个参数用于在需要根据具体业务逻辑返回特定状态码以及对应错误消息的场景下构建响应对象更加灵活地表示不同类型的失败情况。
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
}
}
}

@ -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
* CookietokenCookie
*
* @param response HTTPCookie
* @param token tokenCookie
*/
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
* HTTPCookietoken
*
* @param request HTTPCookie
* @return CookietokenCookienull
*/
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 HTTPCookie便Cookie
* @param response HTTPCookieCookie
*/
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 {
}
}
}
}

@ -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
* formatStrDateTimeFormatter
* 使DateTimeFormatterdateTimeStrDateTimeDateTimejoda-time
* DateTimeJavaDateDate
*
* @param dateTimeStr formatStr
* @param formatStr "yyyy-MM-dd HH:mm:ss"
* @return Datejoda-time
*/
public static Date strToDate(String dateTimeStr, String formatStr){
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
/**
* Date
* DatenullnullStringUtils.EMPTYnull
* Datenull使joda-timeDateTimetoStringformatStrDate
*
* @param date Date
* @param formatStr "yyyy-MM-dd HH:mm:ss"
* @return Datenull
*/
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
* DateTimeFormatterdateTimeStrDateTimeDate
*
*
* @param dateTimeStr STANDARD_FORMAT
* @return Datejoda-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
* DatenullnullDateDateTime
* 便使
*
* @param date Date
* @return Datenull
*/
public static String dateToStr(Date date){
if(date == null){
return StringUtils.EMPTY;
@ -50,6 +88,16 @@ public class DateTimeUtil {
return dateTime.toString(STANDARD_FORMAT);
}
/**
* Date19701100:00:00 UTC
* Datenullnullnull
* DatenullSimpleDateFormat使"yyyy-MM-dd HH:mm:ss"DateDateDategetTime
* ParseException
*
* @param date Date
* @return DatenullnullParseException
* @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使SimpleDateFormatDateDate
*
*
* @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());
}
}
}

@ -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 <T>
* @return
* JavaJSON
* nullnull
* null使ObjectMapperwriteValueAsStringJSONIOExceptionnull
*
* @param obj JSONJavaJavaObjectMapper
* @param <T> 便
* @return JSONnullnull
*/
public static <T> 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 <T>
* @return
* obj2StringJavaJSONJSON便使JSON
* nullnull
* null使ObjectMapperwriterWithDefaultPrettyPrinterJSONIOExceptionnull
*
* @param obj JSONJavaJavaObjectMapper
* @param <T> 便
* @return JSONnullnullJSON便
*/
public static <T> 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 <T>
* @return
* JSONJava
* StringUtils.isEmptynullJavaclazznullnull
* StringT使ObjectMapperreadValueclazzJSONJavaIOExceptionJSONnull
*
* @param str JSONJava
* @param clazz JavaObjectMapperJSONJava
* @param <T> Java便
* @return Javanullnull
*/
public static <T> T String2Obj(String str,Class<T> clazz){
if(StringUtils.isEmpty(str) || clazz == null){
@ -88,11 +105,14 @@ public class JsonUtil {
}
/**
*
* @param str
* @param typeReference
* @param <T>
* @return
* TypeReference
* TypeReferencenullnull
* TypeReferenceString使ObjectMapperreadValueTypeReferenceJSONJavaIOExceptionnull
*
* @param str JSON
* @param typeReference TypeReferenceJavaObjectMapperJSONJava
* @param <T> 便
* @return JavaTypeReferencenullnull
*/
public static <T> 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 <T>
* @return
* Java
* 使ObjectMappergetTypeFactoryconstructParametricTypecollectionClasselementClassesJavaType
* 使ObjectMapperreadValueJavaTypeJSONJavaIOExceptionnull
*
* @param str JSON
* @param collectionClass JavaList.classSet.class
* @param elementClasses JavaList<String>elementClassesString.class
* @param <T> 便
* @return Javanull
*/
public static <T> T Str2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses);
@ -123,4 +146,4 @@ public class JsonUtil {
return null;
}
}
}
}

@ -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
* MD5MD5
*
* @param origin
* @param charsetname
* @return
* @param origin MD5
* @param charsetname null使getBytesgetBytesMD5
* @return MD5null
*/
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"};
}
}

@ -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;
/**
*
* HTTPCookieCookieUtil.readLoginTokenCookietoken
* StringUtils.isBlankStringUtilsnullSnailmallException
* 使CommonCacheUtilcommonCacheUtil.getCacheValueJSON
* nullSnailmallException使ResponseEnum.NEED_LOGIN
* JsonUtil.Str2ObjJsonUtilJSONUser
*
* @param httpServletRequest HTTPCookie
* @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;
}
}
}

@ -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"
* BaseControllergetCurrentUserHTTP
* IDShippingshippingServiceaddServerResponse
*
* @param httpServletRequest HTTPCookie
* @param shipping
* @return ServerResponse
*/
/**
*
*/
@ -45,6 +60,14 @@ public class ShippingController extends BaseController{
return shippingService.add(user.getId(),shipping);
}
/**
* HTTP"/shipping/del.do"
* getCurrentUserIDIDIntegershippingIdshippingServicedelServerResponse
*
* @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"
* IDShippingshippingshippingServiceupdateServerResponse
*
* @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"
* HTTPIDIDshippingIdshippingServiceselectServerResponse
*
* @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"
* IDpageNum1pageSize10@RequestParamshippingServicelistIDServerResponse<PageInfo>PageInfo
*
* @param httpServletRequest HTTP
* @param pageNum 1使
* @param pageSize 10
* @return ServerResponse<PageInfo>PageInfo
*/
/**
*
*/
@ -83,6 +131,17 @@ public class ShippingController extends BaseController{
return shippingService.list(user.getId(),pageNum,pageSize);
}
/**
* IDHTTP"/shipping/getShipping.do"
* IDshippingIdHTTPCookie使EnumerationhttpServletRequest.getHeaderNames"snailmall_login_token"equalsIgnoreCase
* StringUtils.isBlankServerResponse使ResponseEnum.NEED_LOGIN
* CommonCacheUtiluserJsonStrnullServerResponse
* JsonUtil.Str2ObjJsonUtilJSONUserIDIDshippingServicegetShippingByIdIDServerResponse
*
* @param httpServletRequest HTTP
* @param shippingId
* @return ServerResponseID
*/
/**
* id
*/
@ -91,7 +150,7 @@ public class ShippingController extends BaseController{
log.info("【开始根据{}获取地址】",shippingId);
User user = null;
Enumeration<String> 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{
}
}

@ -1,9 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!-- 此为MyBatis的Mapper配置文件namespace属性指定了该Mapper对应的接口全限定名
MyBatis会依据这个namespace将这里定义的SQL语句与对应接口中的方法进行关联方便调用和映射。
这里对应的接口是com.njupt.swg.dao.ShippingMapper意味着这个文件中的SQL语句是为该接口中定义的方法提供具体实现的 -->
<mapper namespace="com.njupt.swg.dao.ShippingMapper" >
<!-- 定义了一个名为BaseResultMap的结果映射配置用于将从数据库查询返回的结果集映射到Java对象具体类型为com.njupt.swg.entity.Shipping上。
通过<constructor>元素来指定构造函数参数与数据库表列之间的映射关系详细说明了每个列对应的Java类型和JDBC类型等信息
这样MyBatis就能准确地将查询结果中的数据填充到对应的Java对象中 -->
<resultMap id="BaseResultMap" type="com.njupt.swg.entity.Shipping" >
<constructor >
<idArg column="id" jdbcType="INTEGER" javaType="java.lang.Integer" />
<!-- 以下每个<arg>元素都对应着Shipping实体类构造函数的一个参数指定了数据库表列名、对应的JDBC类型以及Java类型
按照顺序依次为user_id、receiver_name等列用于将查询结果准确地映射到Shipping对象的对应属性上 -->
<arg column="user_id" jdbcType="INTEGER" javaType="java.lang.Integer" />
<arg column="receiver_name" jdbcType="VARCHAR" javaType="java.lang.String" />
<arg column="receiver_phone" jdbcType="VARCHAR" javaType="java.lang.String" />
@ -17,171 +25,201 @@
<arg column="update_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
</constructor>
</resultMap>
<!-- 定义了一个名为Base_Column_List的SQL片段包含了一系列数据库表中的列名这些列名通常是在查询、插入、更新等操作中经常需要用到的。
通过定义这个片段可以在其他SQL语句中方便地复用这些列名提高代码的可维护性和简洁性 -->
<sql id="Base_Column_List" >
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
</sql>
<!-- 定义了一个名为selectByPrimaryKey的查询语句用于根据主键id从数据库表mmall_shipping中查询记录。
resultMap属性指定了使用前面定义的BaseResultMap来将查询结果映射到对应的Java对象Shipping类型
parameterType指定了传入参数的类型为java.lang.Integer也就是主键的类型意味着这个查询语句期望接收一个整数类型的主键值作为参数。
SQL语句内部通过<include>标签引用了Base_Column_List片段来指定要查询的具体列然后在where子句中通过#{id,jdbcType=INTEGER}的方式,
根据传入的主键值进行筛选,以获取对应主键的记录 -->
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
select
<include refid="Base_Column_List" />
from mmall_shipping
where id = #{id,jdbcType=INTEGER}
</select>
<!-- 定义了一个名为deleteByPrimaryKey的删除语句用于根据主键id从数据库表mmall_shipping中删除对应的记录。
parameterType指定了传入参数的类型为java.lang.Integer即主键的类型表明该删除语句期望接收一个整数类型的主键值作为参数
SQL语句中通过where子句使用#{id,jdbcType=INTEGER}的方式,根据传入的主键值确定要删除的具体记录 -->
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from mmall_shipping
where id = #{id,jdbcType=INTEGER}
</delete>
<!-- 定义了一个名为insert的插入语句用于向mmall_shipping表中插入一条新记录parameterType指定了传入参数的类型为com.njupt.swg.entity.Shipping
意味着这个插入语句期望接收一个Shipping类型的对象作为参数该对象包含了要插入的各列数据。
useGeneratedKeys="true"表示启用数据库自增长主键功能keyProperty="id"指定了将数据库生成的主键值赋给插入对象的id属性方便后续获取新插入记录的主键值。
SQL语句中明确列出了要插入的各列名以及对应的参数值通过#{列名,jdbcType=类型}的方式指定参数,如#{id,jdbcType=INTEGER}
对于create_time和update_time列使用了数据库函数now()来插入当前时间 -->
<insert id="insert" parameterType="com.njupt.swg.entity.Shipping" useGeneratedKeys="true" keyProperty="id">
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>
<!-- 定义了一个名为insertSelective的插入语句相较于前面的insert语句它更具选择性只会插入那些非空值的列。
通过<trim>标签结合<if>标签来实现动态SQL拼接<trim>标签用于去除多余的逗号等字符,<if>标签根据传入的Shipping对象的属性是否为null来决定是否添加对应的列和值到SQL语句中。
例如如果Shipping对象的id属性不为null就会在SQL语句的列部分添加id列在值部分添加对应的#{id,jdbcType=INTEGER}参数,以此类推,对其他列也进行类似的条件判断插入操作,
这样可以更灵活地根据实际传入的对象数据情况进行插入操作避免插入不必要的null值 -->
<insert id="insertSelective" parameterType="com.njupt.swg.entity.Shipping" >
insert into mmall_shipping
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
<if test="id!= null" >
id,
</if>
<if test="userId != null" >
<if test="userId!= null" >
user_id,
</if>
<if test="receiverName != null" >
<if test="receiverName!= null" >
receiver_name,
</if>
<if test="receiverPhone != null" >
<if test="receiverPhone!= null" >
receiver_phone,
</if>
<if test="receiverMobile != null" >
<if test="receiverMobile!= null" >
receiver_mobile,
</if>
<if test="receiverProvince != null" >
<if test="receiverProvince!= null" >
receiver_province,
</if>
<if test="receiverCity != null" >
<if test="receiverCity!= null" >
receiver_city,
</if>
<if test="receiverDistrict != null" >
<if test="receiverDistrict!= null" >
receiver_district,
</if>
<if test="receiverAddress != null" >
<if test="receiverAddress!= null" >
receiver_address,
</if>
<if test="receiverZip != null" >
<if test="receiverZip!= null" >
receiver_zip,
</if>
<if test="createTime != null" >
<if test="createTime!= null" >
create_time,
</if>
<if test="updateTime != null" >
<if test="updateTime!= null" >
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
<if test="id!= null" >
#{id,jdbcType=INTEGER},
</if>
<if test="userId != null" >
<if test="userId!= null" >
#{userId,jdbcType=INTEGER},
</if>
<if test="receiverName != null" >
<if test="receiverName!= null" >
#{receiverName,jdbcType=VARCHAR},
</if>
<if test="receiverPhone != null" >
<if test="receiverPhone!= null" >
#{receiverPhone,jdbcType=VARCHAR},
</if>
<if test="receiverMobile != null" >
<if test="receiverMobile!= null" >
#{receiverMobile,jdbcType=VARCHAR},
</if>
<if test="receiverProvince != null" >
<if test="receiverProvince!= null" >
#{receiverProvince,jdbcType=VARCHAR},
</if>
<if test="receiverCity != null" >
<if test="receiverCity!= null" >
#{receiverCity,jdbcType=VARCHAR},
</if>
<if test="receiverDistrict != null" >
<if test="receiverDistrict!= null" >
#{receiverDistrict,jdbcType=VARCHAR},
</if>
<if test="receiverAddress != null" >
<if test="receiverAddress!= null" >
#{receiverAddress,jdbcType=VARCHAR},
</if>
<if test="receiverZip != null" >
<if test="receiverZip!= null" >
#{receiverZip,jdbcType=VARCHAR},
</if>
<if test="createTime != null" >
<if test="createTime!= null" >
now(),
</if>
<if test="updateTime != null" >
<if test="updateTime!= null" >
now(),
</if>
</trim>
</insert>
<!-- 定义了一个名为updateByPrimaryKeySelective的更新语句用于根据主键id有选择性地更新mmall_shipping表中的记录。
通过<set>标签结合<if>标签实现动态SQL拼接<if>标签根据传入的Shipping对象的各属性是否为null来决定是否添加对应的更新语句到SQL中。
例如如果Shipping对象的userId属性不为null就会在<set>标签内添加user_id = #{userId,jdbcType=INTEGER}这样的更新语句,以此类推,对其他属性也进行类似判断更新操作,
最后通过where子句根据主键值#{id,jdbcType=INTEGER})确定要更新的具体记录,这样可以只更新那些有实际值变化的列,避免不必要的全列更新 -->
<update id="updateByPrimaryKeySelective" parameterType="com.njupt.swg.entity.Shipping" >
update mmall_shipping
<set >
<if test="userId != null" >
<if test="userId!= null" >
user_id = #{userId,jdbcType=INTEGER},
</if>
<if test="receiverName != null" >
<if test="receiverName!= null" >
receiver_name = #{receiverName,jdbcType=VARCHAR},
</if>
<if test="receiverPhone != null" >
<if test="receiverPhone!= null" >
receiver_phone = #{receiverPhone,jdbcType=VARCHAR},
</if>
<if test="receiverMobile != null" >
<if test="receiverMobile!= null" >
receiver_mobile = #{receiverMobile,jdbcType=VARCHAR},
</if>
<if test="receiverProvince != null" >
<if test="receiverProvince!= null" >
receiver_province = #{receiverProvince,jdbcType=VARCHAR},
</if>
<if test="receiverCity != null" >
<if test="receiverCity!= null" >
receiver_city = #{receiverCity,jdbcType=VARCHAR},
</if>
<if test="receiverDistrict != null" >
<if test="receiverDistrict!= null" >
receiver_district = #{receiverDistrict,jdbcType=VARCHAR},
</if>
<if test="receiverAddress != null" >
<if test="receiverAddress!= null" >
receiver_address = #{receiverAddress,jdbcType=VARCHAR},
</if>
<if test="receiverZip != null" >
<if test="receiverZip!= null" >
receiver_zip = #{receiverZip,jdbcType=VARCHAR},
</if>
<if test="createTime != null" >
<if test="createTime!= null" >
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null" >
<if test="updateTime!= null" >
update_time = now(),
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<!-- 定义了一个名为updateByPrimaryKey的更新语句用于根据主键id更新mmall_shipping表中的记录。
该语句会对Shipping对象对应的所有列进行更新不管传入的对象各属性是否为null都会将对应列的值更新到数据库表中
通过where子句根据主键值#{id,jdbcType=INTEGER})确定要更新的具体记录 -->
<update id="updateByPrimaryKey" parameterType="com.njupt.swg.entity.Shipping" >
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}
</update>
<!-- 定义了一个名为deleteByUserIdShippingId的删除语句用于根据用户IDuserId和地址IDshippingId从mmall_shipping表中删除对应的记录。
parameterType指定了传入参数的类型为map意味着这个删除语句期望接收一个包含userId和shippingId键值对的Map对象作为参数
SQL语句中通过WHERE子句使用#{userId}和#{shippingId}的方式根据传入的Map中的对应值确定要删除的具体记录 -->
<delete id="deleteByUserIdShippingId" parameterType="map">
DELETE from mmall_shipping WHERE user_id=#{userId} and id = #{shippingId}
</delete>
<update id="updateByShipping" parameterType="com.njupt.swg.entity.Shipping">
<!-- 定义了一个名为updateByShipping的更新语句用于根据Shipping对象的相关属性以及主键id和用户IDuserId来更新mmall_shipping表中的记录。
该语句会对Shipping对象对应的多个列进行更新不管传入的对象各属性是否为null都会将对应列的值更新到数据库表中
通过where子句根据主键值#{id,jdbcType=INTEGER}和用户ID值#{userId,jdbcType=INTEGER})确定要更新的具体记录 -->
<update id="updateByShipping" parameterType="com.njupt.swg.entity.Shipping">
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}
</update>
<!-- 定义了一个名为selectByUserIdShippingId的查询语句用于根据用户IDuserId和地址IDshippingId从mmall_shipping表中查询对应的记录。
parameterType指定了传入参数的类型为map意味着这个查询语句期望接收一个包含userId和shippingId键值对的Map对象作为参数
resultMap属性指定了使用前面定义的BaseResultMap来将查询结果映射到对应的Java对象Shipping类型
SQL语句中通过<include>标签引用了Base_Column_List片段来指定要查询的具体列然后在where子句中通过#{userId}和#{shippingId}的方式,
根据传入的Map中的对应值进行筛选以获取符合条件的记录 -->
<select id="selectByUserIdShippingId" parameterType="map" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> from mmall_shipping where user_id = #{userId} and id = #{shippingId}
</select>
<select id="selectByUserId" parameterType="int" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> from mmall_shipping where user_id = #{userId}
</select>
</mapper>
SELECT <include refid="Base_Column_List"/>

@ -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;
}

@ -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;
}

@ -1,33 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 整个 logback 配置的根元素,所有的日志配置都在此元素内进行定义,遵循 logback 的 XML 配置规范 -->
<configuration>
<!-- 定义一个名为 LOG_HOME 的属性,其值为 "/logs/shipping/",这个属性可以在后续配置中被引用,
通常用于指定日志文件的存储路径等相关设置,方便统一管理日志文件的存放位置,若后续需要修改日志文件存储位置,
只需更改此处属性值即可,无需在多处配置中逐个修改 -->
<property name="LOG_HOME" value="/logs/shipping/" />
<!-- 配置名为 "Console" 的日志输出组件Appender类型是 ch.qos.logback.core.ConsoleAppender
作用是将日志信息输出到控制台,常用于开发阶段方便查看实时日志,便于调试代码 -->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 编码器Encoder配置用于定义日志输出到控制台的格式 -->
<encoder>
<!-- 具体的日志格式定义,各部分含义如下:
%d{H:mm}:表示输出日期时间,精确到小时和分钟;
%-5level输出日志级别占 5 个字符宽度且左对齐;
[%logger{16}]:输出日志记录器的名称,最长显示 16 个字符;
%msg输出日志消息的具体内容
%n换行符用于使每条日志信息换行显示提高可读性 -->
<pattern>%d{H:mm} %-5level [%logger{16}] %msg%n</pattern>
</encoder>
</appender>
<!-- 配置名为 "normalLog" 的日志输出组件,类型为 ch.qos.logback.core.rolling.RollingFileAppender
它用于将日志输出到文件,并支持按照一定规则对日志文件进行滚动管理(如按时间、大小等进行切割、备份等操作) -->
<appender name="normalLog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 滚动策略配置这里采用基于时间的滚动策略TimeBasedRollingPolicy
按照日期来对日志文件进行切割和管理,方便按天、月等时间周期查看不同时间段的日志记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 定义日志文件的命名模式,会在 LOG_HOME 所指定的路径下生成类似 web.normal.2024-12-17.log 这样的文件(以实际日期为准),
每天都会生成一个新的日志文件,便于对日志进行分类存储和查看 -->
<FileNamePattern>${LOG_HOME}/web.normal.%d{yyyy-MM-dd}.log
</FileNamePattern>
<!-- 设置保留的历史日志文件数量,这里规定最多保留 30 天的日志文件,超过这个数量的旧日志文件会被自动删除,
避免日志文件过多占用大量磁盘空间 -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<!-- 触发滚动的策略配置这里采用基于文件大小的触发策略SizeBasedTriggeringPolicy
当单个日志文件大小达到 10MB 时,就会触发滚动操作,比如进行日志文件的切割、备份等处理,
结合前面的时间滚动策略,从时间和文件大小两个维度来合理管理日志文件 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<!-- 日志布局Layout配置用于定义日志在文件中的具体输出格式 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<!-- 具体的日志格式,各部分含义如下:
%d{HH:mm:ss.SSS}:输出日期时间,精确到小时、分钟、秒以及毫秒;
[%thread]:输出产生该日志的线程名称;
%-5level输出日志级别占 5 个字符宽度且左对齐;
%logger{16}:输出日志记录器的名称,最长显示 16 个字符;
-:一个连接符,用于分隔日志记录器名称和日志消息内容;
%msg输出日志消息的具体内容
%n换行符使每条日志信息换行显示 -->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{16} - %msg%n
</pattern>
</layout>
<!-- 配置一个过滤器LevelFilter用于根据日志级别来决定是否允许该日志输出到这个 Appender 对应的日志文件中 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 指定要过滤的日志级别为 ERROR -->
<level>ERROR</level>
<!-- 如果日志级别匹配(即当前日志是 ERROR 级别),则拒绝该日志输出,也就是不会将 ERROR 级别的日志记录到这个 normalLog 对应的日志文件中 -->
<onMatch>DENY</onMatch>
<!-- 如果日志级别不匹配(即不是 ERROR 级别),则接受该日志输出,允许非 ERROR 级别的日志正常记录到该日志文件中 -->
<onMismatch>ACCEPT</onMismatch>
</filter>
</appender>
<!-- 配置名为 "errorLog" 的日志输出组件,同样是基于滚动文件的方式将日志输出到文件,结构和前面的 "normalLog" 类似,
但在日志级别过滤方面有所不同,主要用于专门记录 ERROR 级别的日志 -->
<appender name="errorLog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
@ -44,18 +84,23 @@
</layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<!-- 对于 ERROR 级别的日志,这里设置为接受输出,即只有 ERROR 级别的日志会被记录到这个 errorLog 对应的日志文件中 -->
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 配置一个名为 "com.oursnail" 的日志记录器Logger指定其日志级别为 debug
意味着这个记录器会记录 DEBUG 及以上级别的日志信息,并且将这些日志输出到 "normalLog" 和 "errorLog" 这两个 Appender 对应的日志文件中,
通过这种方式可以针对特定的包(这里是 com.oursnail 包及其子包下的代码)进行日志的分级管理和分类输出 -->
<logger name="com.oursnail" level="debug" >
<appender-ref ref="normalLog" />
<appender-ref ref="errorLog" />
</logger>
<!-- 配置根日志记录器Root Logger它是所有日志记录器的默认父级这里设置其日志级别为 info
也就是会记录 INFO 及以上级别的日志信息,并且将这些日志输出到 "Console" 这个 Appender 对应的控制台中,
对整个应用的日志输出做了一个基础的级别和输出目标的配置 -->
<root level="info">
<appender-ref ref="Console" />
</root>

@ -1,105 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这是 Maven 项目配置文件pom.xml的根元素声明了 XML 文档的版本和编码格式,
同时通过 xmlns 属性定义了一系列 XML 命名空间相关的配置,用于遵循 Maven 项目对象模型POM的规范 -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 定义了 Maven 项目对象模型的版本,这里使用的是 4.0.0 版本,它规定了整个 pom.xml 文件的基本结构和语法规则 -->
<modelVersion>4.0.0</modelVersion>
<!-- 配置项目的父级依赖,通过指定 groupId、artifactId 和 version继承了父项目的一些配置信息
比如依赖管理、插件配置等,使得当前项目可以复用父项目已有的配置,减少重复配置工作,保持项目结构的一致性 -->
<parent>
<groupId>com.njupt.swg</groupId>
<artifactId>spring-cloud-for-snailmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 当前项目的 artifactId用于唯一标识项目在 Maven 仓库中的名称,通常与项目的实际名称相关,
这里表示该项目是名为 snailmall-user-service 的模块 -->
<artifactId>snailmall-user-service</artifactId>
<!-- 当前项目的版本号,同样采用了 0.0.1-SNAPSHOT 这个快照版本SNAPSHOT 表示这是一个开发过程中的不稳定版本,
常用于项目开发阶段,后续随着项目的迭代会更新为正式的发布版本号 -->
<version>0.0.1-SNAPSHOT</version>
<!-- 项目的显示名称,方便在 Maven 相关的工具或者项目管理中直观地识别该项目 -->
<name>snailmall-user-service</name>
<!-- 项目的简要描述,用于说明项目的大致用途或者功能,这里描述为用于 Spring Boot 的示例项目 -->
<description>Demo project for Spring Boot</description>
<!-- 项目依赖配置部分,在这里列出了项目运行所需要依赖的各种外部库、框架等资源 -->
<dependencies>
<!--spring cloud相关-->
<!-- spring cloud 相关 -->
<!-- 引入 Spring Boot 的 web 启动器依赖,它会自动引入一系列构建 Web 应用所需要的基础依赖,
比如 Spring MVC、内置的 Tomcat 服务器等,方便开发基于 Spring Boot 的 Web 服务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Spring Cloud 配置客户端依赖,用于让当前项目能够作为客户端从配置中心(如 Spring Cloud Config Server获取配置信息
实现配置的集中管理和动态更新,提高配置的灵活性和可维护性 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- 引入 Spring Cloud 的 Eureka 客户端依赖,使得项目可以将自身注册到 Eureka 服务注册中心,
同时也能从 Eureka 发现其他已注册的服务,是实现微服务架构中服务发现和注册功能的关键依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入 Spring Cloud Bus 的 AMQP高级消息队列协议依赖Spring Cloud Bus 可以借助消息队列(如 RabbitMQ、Kafka 等)实现微服务之间的事件传播,
常用于配置的动态刷新等场景,通过 AMQP 协议进行消息传递,增强了微服务之间的交互能力 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- 引入 Spring Cloud Zipkin 依赖Zipkin 用于分布式链路追踪,通过在项目中集成该依赖,
可以将应用的请求链路信息发送到 Zipkin 服务端进行收集和分析,方便排查微服务架构下的性能问题、故障定位等 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- 引入 Spring Boot 的 Actuator 依赖Actuator 提供了一系列用于监控和管理 Spring Boot 应用的端点,
比如查看应用的健康状态、获取运行时的各种指标信息、进行一些配置的动态调整等功能,方便对应用进行运维管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 引入 Lombok 依赖Lombok 是一个通过注解来简化 Java 代码编写的工具,
例如可以通过注解自动生成构造函数、Getter/Setter 方法、toString 方法等,减少了大量的样板代码,提高代码的简洁性 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--jackson-->
<!-- jackson -->
<!-- 引入 Jackson 的 mapper 依赖Jackson 是一个常用的 JSON 处理库,用于在 Java 对象和 JSON 格式数据之间进行序列化和反序列化操作,
比如将 Java 对象转换为 JSON 字符串发送给前端,或者将接收到的 JSON 数据解析为 Java 对象,在 Web 开发中经常会用到 -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
</dependency>
<!--druid-->
<!-- druid -->
<!-- 引入阿里巴巴的 Druid 依赖Druid 是一款性能优秀的数据库连接池,除了基本的连接管理功能外,
还提供了丰富的监控、统计、扩展等功能,能够更好地管理数据库连接,提升数据库访问的效率和稳定性 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--util-->
<!-- util -->
<!-- 引入 Google 的 Guava 依赖Guava 是一个包含了很多实用工具类和集合扩展的库,
例如提供了更方便的字符串处理、集合操作、缓存机制等功能,可以提高 Java 开发的效率,减少重复造轮子 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- 引入 Apache Commons Lang3 依赖,它提供了大量对 Java 基本类型、字符串、数组等操作的实用工具方法,
比如字符串的判空、格式化,数组的拷贝、填充等功能,补充了 Java 标准库中一些功能的不足,方便日常开发 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- 引入 Commons Collections 依赖,它提供了一系列扩展的集合类和集合相关的工具方法,
比如各种特殊的集合实现(如不可变集合、多值映射等)以及对集合进行操作的便捷方法,丰富了 Java 集合框架的功能 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<!--MYSQL-->
<!-- MYSQL -->
<!-- 引入 MySQL 的 JDBC 驱动依赖,用于在 Java 项目中建立与 MySQL 数据库的连接,使得项目能够通过 JDBC 规范与 MySQL 数据库进行交互,
执行 SQL 语句,实现数据的持久化操作,如查询、插入、更新和删除数据等 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<!-- mybatis -->
<!-- 引入 MyBatis 与 Spring Boot 集成的启动器依赖MyBatis 是一个优秀的持久层框架,
通过这个启动器可以方便地在 Spring Boot 项目中使用 MyBatis 进行数据库访问操作,结合 XML 配置文件或者注解来编写 SQL 语句,实现数据持久化 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--jedis-->
<!-- jedis -->
<!-- 引入 Jedis 依赖Jedis 是 Redis 的 Java 客户端库,用于在 Java 项目中与 Redis 缓存数据库进行交互,
比如向 Redis 中存储数据、获取数据、执行 Redis 支持的各种命令等,实现缓存功能或者其他基于 Redis 的业务逻辑 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--joda time-->
<!-- joda time -->
<!-- 引入 Joda-Time 依赖Joda-Time 是一个处理日期和时间的强大库,提供了比 Java 标准库中更方便、灵活的日期时间操作方法,
例如日期的格式化、解析、计算时间间隔等功能,在涉及到复杂的日期时间处理业务场景中很实用 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<!--curator-->
<!-- curator -->
<!-- 引入 Curator 框架的核心依赖Curator 是用于简化与 Zookeeper 交互的 Java 库,
在分布式系统中Zookeeper 常用于服务注册与发现、分布式锁、配置管理等场景Curator 提供了更高级、易用的 API 来操作 Zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<!-- 引入 Curator 框架的recipes常用操作配方依赖它基于 Curator 框架的核心功能,提供了一些更常用、便捷的分布式应用开发中涉及到 Zookeeper 的操作实现,
比如分布式锁、分布式计数器等功能的具体实现,方便开发者快速构建分布式相关的业务逻辑 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<!-- 引入 Springfox Swagger2 依赖,用于在项目中集成 Swagger2 API 文档生成工具,
通过在代码中添加相关注解Swagger2 可以自动生成美观、详细且易于交互的 API 文档,方便前后端开发人员进行接口对接、测试以及 API 的使用说明 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!-- 引入 Springfox Swagger UI 依赖,它提供了一个可视化的界面,基于 Swagger2 生成的 API 文档数据,
可以在浏览器中方便地查看和交互 API 接口信息,更直观地展示 API 的请求参数、返回结果等内容 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
@ -107,8 +159,11 @@
</dependencies>
<!-- 项目构建相关配置部分,用于指定项目在构建过程中的一些设置,比如资源文件的处理、插件的使用等 -->
<build>
<!--编译xml文件-->
<!-- 编译 xml 文件 -->
<!-- 配置项目的资源文件,这里指定了在编译项目时,会将 src/main/java 目录下的所有 xml 文件也作为资源文件进行处理,
通常这些 xml 文件可能是 MyBatis 的 Mapper 配置文件等,确保它们在构建过程中能被正确打包和使用 -->
<resources>
<resource>
<directory>src/main/java</directory>
@ -118,6 +173,9 @@
</resource>
</resources>
<plugins>
<!-- 引入 Spring Boot 的 Maven 插件,这个插件在项目构建过程中有很多重要作用,
比如可以将项目打包成可执行的 JAR 文件,并且在 JAR 文件中嵌入了应用运行所需的依赖、配置信息等,
还能方便地启动 Spring Boot 应用进行本地测试等 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
@ -125,4 +183,4 @@
</plugins>
</build>
</project>
</project>

@ -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 注解用于启用 Swagger2Swagger2 是一个强大的 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);
}
}
}

@ -13,21 +13,31 @@ import redis.clients.jedis.JedisPool;
* @CONTACT 317758022@qq.com
* @DESC
*/
// 使用Spring框架的@Component注解将该类标记为一个组件意味着Spring会对这个类进行管理在需要使用这个类的实例时可以通过依赖注入等方式获取方便在整个应用的各个组件之间进行协作和复用。
@Component
// 使用lombok的@Slf4j注解用于自动生成日志相关的代码方便在类中记录各种操作相关的日志信息便于后续查看操作情况以及进行问题排查比如当与Redis交互出现异常等情况时可以详细记录错误信息帮助定位和解决问题。
@Slf4j
public class CommonCacheUtil {
// 通过Spring的依赖注入机制使用@Autowired注解自动注入JedisPoolWrapper类型的实例JedisPoolWrapper应该是对JedisPoolJedis连接池进行了一定封装的类通过它可以获取到Jedis连接池对象进而获取Jedis客户端实例来操作Redis缓存。
@Autowired
private JedisPoolWrapper jedisPoolWrapper;
/**
* Rediskeyvalue
*
* JedisPoolWrapperJedisPoolJedisPoolnullJedisJedisRedis
* try-with-resourcesJedisJedis使JedisJedisselect(0)Redis0Redis0使setRedis
* log.errorredisSnailmallExceptionredis便
*/
/**
* key
*/
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);
@ -39,6 +49,12 @@ public class CommonCacheUtil {
}
}
/**
* Rediskey
*
* valuenullJedisPoolWrapperJedisPoolJedisPoolnulltry-with-resourcesJedisRedis0使getRedisvalue
* redisSnailmallExceptionredisnull
*/
/**
* key
*/
@ -46,7 +62,7 @@ public class CommonCacheUtil {
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);
@ -59,6 +75,13 @@ public class CommonCacheUtil {
return value;
}
/**
* Redis使setnxset if not exists
*
* result0JedisPoolWrapperJedisPoolJedisPoolnulltry-with-resourcesJedisRedis0使setnxRedissetnx10result
* setnxexpireexpire
* redisSnailmallExceptionredisresult1
*/
/**
* key
*/
@ -66,7 +89,7 @@ public class CommonCacheUtil {
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);
@ -81,12 +104,17 @@ public class CommonCacheUtil {
return result;
}
/**
* RediskeyRedis
*
* JedisPoolWrapperJedisPoolJedisPoolnulltry-with-resourcesJedisRedis0使delRedisredisSnailmallExceptionredis便
*/
/**
* key
*/
public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
@ -101,4 +129,4 @@ public class CommonCacheUtil {
}
}

@ -14,14 +14,26 @@ import javax.annotation.PostConstruct;
* @CONTACT 317758022@qq.com
* @DESC redisredishash
*/
// 使用Spring框架的@Component注解将该类标记为一个组件意味着Spring会对这个类进行管理使其能够参与到依赖注入等Spring容器的相关功能中方便在其他需要使用该类实例的地方通过自动注入等方式获取实例从而实现不同组件之间的协作。
@Component
// 使用lombok的@Slf4j注解用于自动生成日志相关的代码方便在类中记录各种操作相关的日志信息便于后续查看操作情况以及进行问题排查例如在初始化Jedis连接池时出现成功或失败的情况都可以通过日志清晰地记录下来方便了解系统状态以及定位问题。
@Slf4j
public class JedisPoolWrapper {
// 通过Spring的依赖注入机制使用@Autowired注解自动注入Parameters类型的实例Parameters类应该是用于存储系统相关配置参数的类在这里主要是期望从中获取与Redis连接池配置相关的参数例如最大连接数、最大空闲连接数等信息。
@Autowired
private Parameters parameters;
// 定义一个私有成员变量jedisPool用于存储Jedis连接池对象初始化为null后续会在初始化方法中根据配置参数来创建实际的Jedis连接池实例。
private JedisPool jedisPool = null;
/**
* 使Spring@PostConstruct
*
* JedisPoolConfigJedisPoolConfigJedis
* ParametersRedisJedisPoolConfigparameters.getRedisMaxTotal()config.setMaxTotalJedisPoolConfig
* 使JedisPoolConfigParametersRedisparameters.getRedisHost()parameters.getRedisPort()2000"xxx"JedisPooljedisPoolJedis
* log.errorredis便log.inforedis
*/
@PostConstruct
public void init(){
try {
@ -36,7 +48,12 @@ public class JedisPoolWrapper {
}
}
/**
* JedisPool
* JedisPoolJedisRedis
* initJedisPoolinitJedisPooljedisPoolnullnullnull
*/
public JedisPool getJedisPool() {
return jedisPool;
}
}
}

@ -10,24 +10,36 @@ import org.springframework.stereotype.Component;
* @CONTACT 317758022@qq.com
* @DESC
*/
// 使用Spring框架的@Component注解将这个类标记为一个组件意味着该类会被Spring容器进行管理能够参与到依赖注入等Spring相关的功能中方便在其他需要使用此类实例的地方通过自动注入等方式获取实例实现不同组件之间的协作。
@Component
// 使用lombok的@Data注解该注解会自动为类生成一系列常用方法比如针对类中所有非静态成员变量这里包含了redis相关配置属性以及zk相关配置属性等的Getter和Setter方法方便在其他地方获取和设置这些变量的值同时还会生成equals、hashCode、toString方法等减少了手动编写这些重复代码的工作量让代码更加简洁高效。
@Data
public class Parameters {
// 以下是Redis相关配置参数的定义部分通过Spring的@Value注解结合配置文件中的属性占位符例如${redis.host}来从配置文件通常是application.properties或application.yml等中读取对应的配置值并注入到对应的成员变量中。
/*****redis config start*******/
// 使用@Value("${redis.host}")注解,从配置文件中读取名为"redis.host"的配置属性值并将其注入到这个私有成员变量redisHost中该变量用于存储Redis服务器的主机地址以便后续在初始化Jedis连接池等操作中使用该地址来连接Redis服务器。
@Value("${redis.host}")
private String redisHost;
// 使用@Value("${redis.port}")注解,从配置文件中读取名为"redis.port"的配置属性值并将其注入到这个私有成员变量redisPort中该变量用于存储Redis服务器的端口号用于明确连接Redis服务器时的端口信息确保能够正确地建立连接。
@Value("${redis.port}")
private int redisPort;
// 此处注解可能存在配置属性名与变量含义不匹配的情况(实际按名称看应该是获取"redis.max-total",但变量名含义是最大空闲连接数),按正确逻辑理解,应该是从配置文件中读取名为"redis.max-total"的配置属性值注入到这个私有成员变量redisMaxTotal中该变量用于存储Jedis连接池中允许的最大连接数量控制连接池的整体规模避免过多连接造成资源浪费或性能问题。
@Value("${redis.max-idle}")
private int redisMaxTotal;
// 同样此处注解可能存在配置属性名与变量含义不匹配的情况(实际按名称看应该是获取"redis.max-idle",但变量名含义是最大活跃连接数),按正确逻辑理解,应该是从配置文件中读取名为"redis.max-idle"的配置属性值注入到这个私有成员变量redisMaxIdle中该变量用于存储Jedis连接池中允许的最大空闲连接数量合理控制空闲连接资源提高连接池的利用效率。
@Value("${redis.max-total}")
private int redisMaxIdle;
// 使用@Value("${redis.max-wait-millis}")注解,从配置文件中读取名为"redis.max-wait-millis"的配置属性值并将其注入到这个私有成员变量redisMaxWaitMillis中该变量用于存储获取Jedis连接时的最大等待时间单位是毫秒如果在规定时间内无法获取到连接可能会抛出相应的异常以此来控制获取连接的超时情况避免长时间等待造成系统阻塞等问题。
@Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis;
/*****redis config end*******/
// 以下是Zookeepercurator是Zookeeper的客户端框架相关配置参数的定义部分同样通过@Value注解结合配置文件中的属性占位符来读取配置值并注入到对应的成员变量中。
/*****curator config start*******/
// 使用@Value("${zk.host}")注解,从配置文件中读取名为"zk.host"的配置属性值并将其注入到这个私有成员变量zkHost中该变量用于存储Zookeeper服务器的主机地址后续在使用curator客户端连接Zookeeper服务器时会用到这个地址信息用于构建正确的连接路径等操作。
@Value("${zk.host}")
private String zkHost;
/*****curator config end*******/
}
}

@ -7,40 +7,59 @@ package com.njupt.swg.common.constants;
* @DESC
*/
public class Constants {
// 此部分定义了一系列自定义的状态码常量,用于在整个应用中统一标识不同的响应状态,方便在不同的业务逻辑处理中进行状态判断与相应的操作。
/**自定义状态码 start**/
// 定义一个整型常量RESP_STATUS_OK值为200通常用于表示请求处理成功的状态。在接口返回响应给客户端时如果业务操作顺利完成就可以使用这个状态码来告知客户端请求已成功处理后续客户端可根据此状态码进行相应的后续操作比如正常展示返回的数据等。
public static final int RESP_STATUS_OK = 200;
// 定义整型常量RESP_STATUS_NOAUTH值为401该常量代表未授权的状态。当用户尝试访问某个受保护的资源或接口但没有提供有效的认证信息或者权限不足时服务器会返回这个状态码给客户端提示客户端需要进行身份认证或者获取相应权限后才能再次发起访问请求。
public static final int RESP_STATUS_NOAUTH = 401;
// 定义整型常量RESP_STATUS_INTERNAL_ERROR值为500用于表示服务器内部出现错误的情况。例如在服务器端代码执行过程中发生了未预期的异常导致无法正常完成请求处理时就会返回这个状态码给客户端告知客户端请求处理失败是由于服务器内部问题客户端可以根据此情况提示用户联系系统管理员等进一步排查问题。
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
// 定义整型常量RESP_STATUS_BADREQUEST值为400此常量表示请求的参数存在问题比如请求参数格式不符合接口要求、缺少必要的参数等情况。当服务器接收到这样的请求时会返回这个状态码给客户端让客户端知晓需要修正请求参数后重新发起请求以保证请求能够被正确处理。
public static final int RESP_STATUS_BADREQUEST = 400;
/**自定义状态码 end**/
// 以下是关于Redis中存储用户相关数据时使用的键key前缀的常量定义通过统一使用这个前缀能方便地在Redis中对用户相关的缓存数据进行分类管理和查找等操作。
/***redis user相关的key以这个打头**/
public static final String TOKEN_PREFIX = "user_";
// 这里定义了一个内部接口RedisCacheExtime用于集中管理用户登录信息在Redis中的过期时间相关常量这样做可以使代码中涉及到该过期时间的地方保持一致性并且通过接口的形式将相关逻辑进行了封装和分组方便后续维护与修改。
/**
* redis
*/
public interface RedisCacheExtime{
// 定义一个整型常量REDIS_SESSION_EXTIME用于表示用户登录在Redis中的缓存过期时间其值计算为60 * 60 * 10也就是30分钟按照每秒为单位换算。意味着在用户登录后相关的登录状态等信息在Redis缓存中会保存30分钟一旦超过这个时间缓存数据将会自动过期后续如果需要调整用户登录缓存的过期时长只需在这个接口内修改该常量的值即可统一生效。
int REDIS_SESSION_EXTIME = 60 * 60 * 10;//30分钟
}
// 以下是针对用户注册时判断重复的参数类型所定义的常量,用于明确在注册流程中需要检查哪些具体参数是否已被其他用户使用,从而保证注册信息的唯一性。
/** 用户注册判断重复的参数类型 start **/
// 定义一个字符串常量EMAIL值为"email",在用户注册的逻辑中,使用这个常量来标识需要检查用户输入的邮箱地址是否已经被其他用户注册过,比如在数据库查询验证环节,通过这个常量来指定要比对的字段为邮箱字段,以防止重复注册相同邮箱的情况出现。
public static final String EMAIL = "email";
// 定义字符串常量USERNAME值为"username",同样在用户注册过程中,该常量用于表明要检查用户名是否重复,在进行数据库查询或者其他验证操作时,依据这个常量来确定是针对用户名这个字段进行重复性的判断,确保每个用户名在系统中具有唯一性。
public static final String USERNAME = "username";
/** 用户注册判断重复的参数类型 end **/
// 定义了一个内部接口Role用于对用户在系统中不同角色的相关常量进行统一管理这样在涉及用户角色判断、权限分配以及业务逻辑根据角色进行不同处理等场景时可以清晰地通过这些常量来区分不同的用户角色提高代码的可读性和可维护性。
/** 用户角色 **/
public interface Role{
// 定义整型常量ROLE_CUSTOME值为0用于表示普通用户这一角色。在系统的权限控制体系中普通用户通常具有相对受限的操作权限只能进行一些常规的业务操作通过这个常量可以方便地在代码中对普通用户相关的权限判断、业务逻辑处理等进行标识和区分。
int ROLE_CUSTOME = 0;//普通用户
// 定义整型常量ROLE_ADMIN值为1用来代表管理员用户角色。管理员用户在系统中往往拥有更高的权限能够执行诸如系统配置、数据管理等更多的关键操作借助这个常量可以在代码里清晰地识别出管理员角色并进行相应的权限相关处理。
int ROLE_ADMIN = 1;//管理员用户
}
// 定义一个字符串常量USER_REGISTER_DISTRIBUTE_LOCK_PATH值为"/user_reg"该常量用于指定在分布式环境下用户注册操作时所使用的分布式锁的路径信息。在分布式系统中为了保证用户注册操作的原子性和一致性比如避免多个并发的注册请求同时对用户名、邮箱等唯一性验证和写入数据库等关键操作产生冲突会使用分布式锁进行控制而这个常量就是定义了该分布式锁在相应的分布式锁管理组件如基于Zookeeper等实现的分布式锁中的具体路径。
/**用户注册分布式锁路径***/
public static final String USER_REGISTER_DISTRIBUTE_LOCK_PATH = "/user_reg";
}
}

@ -14,20 +14,29 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @CONTACT 317758022@qq.com
* @DESC
*/
// 使用Spring框架的@ControllerAdvice注解表明这个类是一个全局的异常处理类它可以拦截并处理在整个应用中由Spring管理的控制器Controller抛出的异常能够统一对异常情况进行处理避免在每个控制器方法中都单独编写异常处理逻辑使代码更加简洁和易于维护。
@ControllerAdvice
// 使用@ResponseBody注解结合@ControllerAdvice注解意味着这个类中处理异常的方法返回的结果会直接作为响应体ResponseBody返回给客户端通常是以JSON等格式的数据进行响应方便客户端获取异常相关的信息并进行相应展示或处理。
@ResponseBody
// 使用lombok的@Slf4j注解用于自动生成日志相关的代码方便在类中记录各种异常相关的日志信息便于后续查看异常情况以及进行问题排查比如记录异常的详细信息、错误消息等有助于定位和解决出现的问题。
@Slf4j
public class ExceptionHandlerAdvice {
// 使用Spring的@ExceptionHandler注解指定这个方法用于处理Exception类型的异常也就是可以捕获所有未被更具体的异常处理器处理的异常情况它是一种兜底的异常处理方式确保任何未被处理的异常都能在这里被统一处理避免程序因异常而崩溃。
@ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){
// 使用日志记录异常的详细信息通过log.error方法记录异常的消息内容e.getMessage()以及完整的异常堆栈信息e方便后续查看具体的异常原因和出现异常的位置等情况有助于进行问题排查和定位。
log.error(e.getMessage(),e);
// 创建并返回一个ServerResponse类型的响应对象调用ServerResponse的静态方法createByErrorCodeMessage传入自定义的系统内部错误状态码Constants.RESP_STATUS_INTERNAL_ERROR通常表示服务器内部出现了问题以及提示客户端的消息"系统异常,请稍后再试"),告知客户端请求处理出现了系统级别的异常,让客户端可以根据这个提示信息进行相应的操作,比如提示用户稍后重新发起请求等。
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试");
}
// 使用@ExceptionHandler注解指定这个方法用于处理SnailmallException类型的异常这是一个自定义的异常类型意味着当应用中抛出了这个特定类型的异常时会由这个方法来进行专门的处理相较于处理通用Exception类型的异常这里可以根据自定义异常的特点进行更针对性的响应处理。
@ExceptionHandler(SnailmallException.class)
public ServerResponse handleException(SnailmallException e){
// 同样记录异常的详细信息到日志中,方便后续排查问题,记录异常的消息内容以及完整的异常堆栈信息。
log.error(e.getMessage(),e);
// 创建并返回一个ServerResponse类型的响应对象调用ServerResponse的静态方法createByErrorCodeMessage传入自定义异常中携带的状态码e.getExceptionStatus()通常自定义异常会根据不同的业务场景设置不同的状态码来表示具体的错误情况以及异常的消息内容e.getMessage()),这样可以将更具体的错误信息和对应的状态码返回给客户端,让客户端能够准确知晓出现的具体问题并进行相应处理。
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage());
}
}
}

@ -9,14 +9,19 @@ import lombok.Getter;
* @CONTACT 317758022@qq.com
* @DESC
*/
// 使用lombok的@Getter注解该注解会自动为类中的私有成员变量这里是exceptionStatus生成对应的Getter方法方便在其他地方获取这个变量的值避免手动编写Getter方法的重复代码使代码更加简洁。
@Getter
// 定义了一个名为SnailmallException的类它继承自Java内置的RuntimeException类意味着这是一个运行时异常不需要在方法声明中显式地抛出与受检异常不同在程序运行过程中如果出现符合其定义的异常情况时可以直接抛出由合适的异常处理器来捕获处理。
public class SnailmallException extends RuntimeException{
// 定义一个私有整型成员变量exceptionStatus用于存储异常对应的状态码初始值设置为ResponseEnum.ERROR.getCode()这里推测ResponseEnum是一个枚举类型用于统一管理各种响应状态码相关的信息通过调用其ERROR对应的getCode()方法获取默认的错误状态码作为初始值,后续可以根据具体的异常情况进行修改。
private int exceptionStatus = ResponseEnum.ERROR.getCode();
// 定义了一个构造函数接收一个字符串类型的参数msg用于创建一个SnailmallException实例。在构造函数内部通过调用父类RuntimeException的构造函数并传入msg参数将异常消息传递给父类这样在抛出该异常时就能携带这个自定义的消息信息方便在异常处理等地方获取并展示具体的异常原因给用户或进行日志记录等操作。
public SnailmallException(String msg){
super(msg);
}
// 定义了另一个构造函数接收一个整型参数code和一个字符串参数msg用于创建一个更具定制化的SnailmallException实例。在这个构造函数中首先同样调用父类的构造函数将msg参数传递给父类以设置异常消息然后将传入的code参数赋值给exceptionStatus变量用于覆盖默认的异常状态码使得可以根据不同的业务场景设置不同的状态码来准确表示具体的异常情况方便在异常处理逻辑中根据状态码进行不同的处理操作。
public SnailmallException(int code,String msg){
super(msg);
exceptionStatus = code;

@ -8,18 +8,27 @@ import lombok.Getter;
* @CONTACT 317758022@qq.com
* @DESC
*/
// 使用lombok的@Getter注解由于这是一个枚举类型该注解会自动为枚举中的成员变量这里的code和desc生成对应的Getter方法方便在其他地方获取这些变量的值这样在使用该枚举时能够便捷地获取到每个枚举实例所对应的状态码和描述信息避免手动编写Getter方法的重复代码使代码更加简洁。
@Getter
// 定义了一个名为ResponseEnum的枚举类型在Java中枚举常用于表示一组固定的常量值这里它主要用于统一管理各种响应相关的状态码和对应的描述信息方便在整个应用中根据不同的业务场景使用合适的枚举实例来表示响应情况。
public enum ResponseEnum {
// 定义了一个名为SUCCESS的枚举实例传入参数0和"SUCCESS"分别对应状态码和描述信息意味着在业务处理成功的场景下可以使用这个枚举实例来表示成功的响应状态通过其Getter方法可以获取对应的状态码0以及描述信息"SUCCESS",便于在接口返回数据等场景中进行统一的状态标识和信息传递。
SUCCESS(0,"SUCCESS"),
// 定义了一个名为ERROR的枚举实例传入参数1和"ERROR"用于表示出现错误的响应状态当业务处理过程中发生一般性错误时可以使用这个枚举实例来传达错误信息外部通过获取其状态码1和描述信息"ERROR"来知晓出现了错误情况,进而进行相应的处理,比如展示错误提示给用户等。
ERROR(1,"ERROR"),
// 定义了一个名为ILLEGAL_ARGUMENTS的枚举实例传入参数2和"ILLEGAL_ARGUMENTS"通常用于表示接收到的请求参数不符合要求、不合法的情况例如参数格式错误、缺少必要参数等场景下可使用该枚举实例来反馈这种参数非法的响应状态方便调用者根据状态码2和描述信息"ILLEGAL_ARGUMENTS"判断是参数问题并进行相应的参数修正等操作。
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"),
// 定义了一个名为NEED_LOGIN的枚举实例传入参数10和"NEED_LOGIN"主要应用在需要用户登录但用户未登录的场景下比如访问受登录保护的接口时若用户未登录就可以返回这个枚举实例所对应的状态码10和描述信息"NEED_LOGIN",提示客户端(如前端页面)需要用户进行登录操作后才能继续访问相应资源。
NEED_LOGIN(10,"NEED_LOGIN");
// 定义一个私有整型成员变量code用于存储每个枚举实例对应的状态码通过构造函数进行赋值在具体的业务逻辑中可以通过对应的枚举实例获取这个状态码来进行不同的处理操作比如根据状态码判断业务流程的走向等。
private int code;
// 定义一个私有字符串成员变量desc用于存储每个枚举实例对应的描述信息同样通过构造函数进行赋值在需要向客户端或者用户展示具体的响应提示内容时可以获取这个描述信息进行展示让用户更直观地了解响应的具体含义。
private String desc;
// 定义了一个构造函数接收一个整型参数code和一个字符串参数desc用于初始化枚举实例对应的状态码和描述信息在每个枚举实例声明时如SUCCESS(0,"SUCCESS")等)传入的参数会通过这个构造函数来给对应的成员变量赋值,确保每个枚举实例都有正确的状态码和描述信息与之对应。
ResponseEnum(int code,String desc){
this.code = code;
this.desc = desc;
}
}
}

@ -14,66 +14,93 @@ import java.io.Serializable;
* @CONTACT 317758022@qq.com
* @DESC
*/
// 使用lombok的@Getter注解该注解会自动为类中的私有成员变量这里的status、msg和data生成对应的Getter方法方便在其他地方获取这些变量的值避免手动编写Getter方法的重复代码使得代码更加简洁在使用ServerResponse对象时能够轻松获取其包含的状态码、消息以及业务数据等信息。
@Getter
// 使用Jackson的@JsonSerialize注解来配置该类在序列化例如转换为JSON格式以便在网络传输或者存储时使用时的行为这里设置include属性为JsonSerialize.Inclusion.NON_NULL表示在序列化过程中只包含非空的属性值也就是如果某个属性如msg或者data的值为null那么在最终序列化生成的JSON数据中就不会包含对应的字段这样可以减少传输的数据量并且使序列化后的结果更加符合实际需求避免传递不必要的空值信息。
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
// 让这个类实现Serializable接口表示该类的对象可以被序列化和反序列化这在很多场景下非常有用比如将服务器响应对象存储到文件中、在网络中传输响应对象等情况时通过序列化将对象转换为字节流进行存储或传输然后再通过反序列化将字节流还原为对象实现数据的持久化和跨网络的交互等功能。
public class ServerResponse<T> implements Serializable {
// 定义一个私有整型成员变量status用于存储响应的状态码通过不同的状态码来表示响应的不同情况例如成功、失败、需要登录等在业务逻辑中可以根据这个状态码来判断请求处理的结果状态进而进行相应的后续操作。
private int status;
// 定义一个私有字符串成员变量msg用于存储响应的提示消息这个消息可以是对响应状态的具体描述比如在成功时可以是“操作成功”在失败时可以是具体的错误原因等方便客户端如前端应用获取并展示给用户让用户了解操作的结果情况。
private String msg;
// 定义一个泛型类型的私有成员变量data用于存储具体的业务数据这里使用泛型使得这个响应类可以适用于不同类型的业务数据返回情况例如可以返回用户信息、商品列表等各种类型的数据增加了类的通用性和灵活性。
private T data;
// 定义一个默认的无参构造函数主要用于在一些情况下比如反序列化时创建一个空的ServerResponse对象后续可以再通过Setter方法虽然这里没有显式定义但可以通过lombok的@Setter注解自动生成或者手动编写或者其他方式来设置对象的各个属性值。
public ServerResponse(){}
// 定义一个私有构造函数接收一个整型参数status用于创建一个只设置了状态码的ServerResponse对象通常在一些内部逻辑中已知具体的状态码但暂时不需要设置消息和业务数据时可以使用这个构造函数来创建对象后续再根据具体情况补充其他属性值。
private ServerResponse(int status){
this.status = status;
}
// 定义一个私有构造函数接收一个整型参数status和一个字符串参数msg用于创建一个设置了状态码和提示消息的ServerResponse对象在已知响应状态码以及对应的描述消息时可以通过这个构造函数来初始化对象方便在业务逻辑中根据具体情况返回相应的响应对象告知客户端操作的结果情况。
private ServerResponse(int status,String msg){
this.status = status;
this.msg = msg;
}
// 定义一个私有构造函数接收一个整型参数status和一个泛型参数data用于创建一个设置了状态码和业务数据的ServerResponse对象在请求处理成功且有具体业务数据需要返回给客户端时可以使用这个构造函数来构建包含相应数据的响应对象让客户端能够获取到操作结果以及所需的数据信息。
private ServerResponse(int status,T data){
this.status = status;
this.data = data;
}
// 定义一个私有构造函数接收一个整型参数status、一个字符串参数msg和一个泛型参数data用于创建一个完整设置了状态码、提示消息以及业务数据的ServerResponse对象这是最全面的构造方式在各种不同的业务场景下根据实际的响应情况包含状态、具体描述以及要返回的数据都可以通过这个构造函数来创建合适的响应对象返回给客户端。
private ServerResponse(int status,String msg,T data){
this.status = status;
this.msg = msg;
this.data = data;
}
// 使用Jackson的@JsonIgnore注解标记这个方法该注解表示在序列化对象时忽略这个方法也就是不会将这个方法的返回值作为字段包含在序列化后的结果中例如JSON数据中不会出现这个方法对应的字段。这个方法用于判断响应是否成功通过比较当前对象的状态码与ResponseEnum.SUCCESS通常是一个枚举类型用于统一管理响应相关的状态码等信息中定义的成功状态码是否相等来返回一个布尔值如果相等则表示响应成功否则表示响应失败方便在业务逻辑中快速判断响应的结果状态。
@JsonIgnore
public boolean isSuccess(){
return this.status == ResponseEnum.SUCCESS.getCode();
}
/**
* 便ServerResponse便
*/
/**
*
*/
// 这个静态方法用于创建一个表示成功的ServerResponse对象使用ResponseEnum.SUCCESS中定义的状态码和描述信息来初始化对象通常在业务操作顺利完成且没有额外需要返回的具体业务数据以及自定义消息时可以调用这个方法返回一个简单表示成功的响应对象给客户端告知客户端请求处理成功。
public static <T>ServerResponse<T> createBySuccess(){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
}
// 这个静态方法用于创建一个带有自定义提示消息的成功响应对象接收一个字符串参数message使用ResponseEnum.SUCCESS中定义的状态码以及传入的自定义消息来初始化对象在业务操作成功且希望返回一个特定的提示消息给客户端比如“新增用户成功”等可以调用这个方法让客户端展示这个自定义的消息给用户告知具体的成功情况。
public static <T>ServerResponse<T> createBySuccessMessage(String message){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message);
}
// 这个静态方法用于创建一个包含业务数据的成功响应对象接收一个泛型参数data使用ResponseEnum.SUCCESS中定义的状态码以及传入的业务数据来初始化对象在业务操作成功且有具体的数据需要返回给客户端比如查询用户列表成功后返回用户列表数据等调用这个方法来构建包含相应数据的响应对象方便客户端获取并使用这些数据进行后续操作如展示数据等。
public static <T>ServerResponse<T> createBySuccess(T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data);
}
// 这个静态方法是创建成功响应对象最全面的方式接收一个字符串参数message和一个泛型参数data使用ResponseEnum.SUCCESS中定义的状态码、传入的自定义消息以及业务数据来初始化对象在业务操作成功且既有自定义的提示消息又有具体业务数据需要返回给客户端时调用这个方法可以创建一个完整的包含状态、消息和数据的响应对象返回给客户端满足各种复杂的成功响应场景需求。
public static <T>ServerResponse<T> createBySuccess(String message,T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data);
}
/**
* 便ServerResponse便
*/
/**
*
*/
// 这个静态方法用于创建一个表示一般性失败的ServerResponse对象使用ResponseEnum.ERROR中定义的状态码和描述信息来初始化对象通常在业务操作出现未明确的错误情况且没有更具体的错误消息需要返回时可以调用这个方法返回一个简单表示失败的响应对象给客户端告知客户端请求处理出现错误。
public static <T>ServerResponse<T> createByError(){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc());
}
// 这个静态方法用于创建一个带有自定义错误提示消息的失败响应对象接收一个字符串参数msg使用ResponseEnum.ERROR中定义的状态码以及传入的自定义消息来初始化对象在业务操作失败且希望返回一个具体的错误原因给客户端比如“用户名已存在”等可以调用这个方法让客户端展示这个自定义的错误消息给用户告知具体的失败情况。
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
}
// 这个静态方法用于创建一个带有自定义状态码和错误提示消息的失败响应对象接收一个整型参数code和一个字符串参数msg使用传入的状态码和消息来初始化对象在业务操作失败且需要根据具体的业务场景返回特定的状态码以及对应的错误消息时比如不同的业务模块有不同的错误状态码定义等情况调用这个方法可以构建相应的符合要求的失败响应对象返回给客户端方便客户端根据状态码和消息进行相应的处理如提示用户、进行不同的错误提示展示等。
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
}
}
}

@ -10,39 +10,55 @@ import javax.servlet.http.HttpServletResponse;
/**
* cookie
*/
// 使用lombok的@Slf4j注解用于自动生成日志相关的代码方便在类中记录各种操作相关的日志信息便于后续查看操作情况以及进行问题排查例如在对Cookie进行写入、读取、删除等操作时通过日志记录关键信息有助于了解Cookie操作的执行情况以及可能出现的问题。
@Slf4j
public class CookieUtil {
// 定义一个私有静态常量字符串COOKIE_DOMAIN用于指定Cookie的作用域域名这里设置为"oursnail.cn"意味着该Cookie在这个域名及其子域名下有效在设置和操作Cookie时会用到这个域名信息来确定其作用范围。
private final static String COOKIE_DOMAIN = "oursnail.cn";
// 定义一个私有静态常量字符串COOKIE_NAME用于指定Cookie的名称这里设置为"snailmall_login_token"通过这个固定的名称来标识特定用途如用户登录相关的Cookie方便在后续的读取、操作等过程中进行识别和区分。
private final static String COOKIE_NAME = "snailmall_login_token";
/**
* cookie
* @param response
* @param token
* CookieCookieHttpServletResponse便Cookie
*
* @param response HttpServletResponseCookie使
* @param token Cookietoken"snailmall_login_token"Cookie
*/
public static void writeLoginToken(HttpServletResponse response,String token){
Cookie ck = new Cookie(COOKIE_NAME,token);
public static void writeLoginToken(HttpServletResponse response, String token) {
// 创建一个名为"snailmall_login_token"的Cookie对象将传入的token作为其值后续会对这个Cookie对象进行一系列属性设置使其符合业务需求。
Cookie ck = new Cookie(COOKIE_NAME, token);
// 设置Cookie的作用域域名使用之前定义好的COOKIE_DOMAIN常量确保该Cookie在指定的域名"oursnail.cn"及其子域名下有效这样在该域名相关的页面访问时浏览器会带上这个Cookie信息。
ck.setDomain(COOKIE_DOMAIN);
// 设置Cookie的路径为根目录"/"表示该Cookie在整个网站的所有页面路径下都有效无论访问哪个页面浏览器都会将这个Cookie发送给服务器方便在不同页面间共享该Cookie所携带的信息比如登录状态等
ck.setPath("/");//设值在根目录
// 设置HttpOnly属性为true这意味着该Cookie不能通过客户端脚本如JavaScript访问主要是为了增强安全性避免恶意脚本通过获取Cookie信息来进行攻击例如防止跨站脚本攻击XSS获取用户登录相关的敏感信息。
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的最大存活时间这里设置为一年60 * 60 * 24 * 365秒表示该Cookie在客户端保存一年后才会过期失效如果设置为 -1则表示永久有效若不设置这个属性即maxage不设置Cookie就不会写入硬盘只会临时保存在内存中且只在当前页面有效下次访问其他页面时该Cookie就不存在了。
ck.setMaxAge(60 * 60 * 24 * 365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
// 使用日志记录即将写入的Cookie的名称和值信息方便后续查看Cookie的写入情况例如在排查是否正确写入了期望的Cookie等问题时可以通过日志进行确认。
log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue());
// 将设置好的Cookie添加到HttpServletResponse对象中这样在响应发送给客户端时客户端浏览器就会接收到并保存这个Cookie从而实现登录相关信息通过token体现在客户端的存储。
response.addCookie(ck);
}
/**
* cookie
* @param request
* @return
* HttpServletRequest"snailmall_login_token"CookieCookieCookienull
*
* @param request HttpServletRequestCookie便Cookie
* @return "snailmall_login_token"CookienullCookie访
*/
public static String readLoginToken(HttpServletRequest request){
public static String readLoginToken(HttpServletRequest request) {
// 从HttpServletRequest对象中获取客户端发送过来的所有Cookie数组如果没有Cookie则返回null。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks){
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if (cks!= null) {
// 遍历获取到的Cookie数组逐个检查每个Cookie的名称是否与期望的"snailmall_login_token"一致。
for (Cookie ck : cks) {
// 使用日志记录当前遍历到的Cookie的名称和值信息方便后续查看Cookie的读取情况例如排查是否正确遍历到了所有Cookie以及是否有符合要求的Cookie等问题时可以通过日志进行分析。
log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 如果找到名称匹配的Cookie再次使用日志记录这个匹配的Cookie的名称和值信息然后返回其值以便调用者获取到登录相关的Cookie中存储的信息如登录令牌等进行后续操作。
log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
return ck.getValue();
}
}
@ -51,24 +67,31 @@ public class CookieUtil {
}
/**
*
* @param request
* @param response
* "snailmall_login_token"CookieCookieCookie使0Cookie
*
* @param request HttpServletRequestCookie便Cookie
* @param response HttpServletResponseCookie
*/
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
// 从HttpServletRequest对象中获取客户端发送过来的所有Cookie数组如果没有Cookie则无需进行后续删除操作。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks) {
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
if (cks!= null) {
// 遍历获取到的Cookie数组逐个检查每个Cookie的名称是否与期望的"snailmall_login_token"一致。
for (Cookie ck : cks) {
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 如果找到名称匹配的Cookie先设置其作用域域名使用之前定义好的COOKIE_DOMAIN常量确保修改的是正确域名下的该Cookie。
ck.setDomain(COOKIE_DOMAIN);
// 设置Cookie的路径为根目录"/"),保持与之前设置一致,使其在整个网站的所有页面路径下都能正确执行删除操作。
ck.setPath("/");
// 设置Cookie的最大存活时间为0表示让客户端立即删除这个Cookie使其失效不再在后续的请求中携带该Cookie信息。
ck.setMaxAge(0);//0表示消除此cookie
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 使用日志记录即将删除的Cookie的名称和值信息方便后续查看Cookie的删除情况例如在排查是否正确删除了期望的Cookie等问题时可以通过日志进行确认。
log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 将设置为失效状态的Cookie添加到HttpServletResponse对象中发送给客户端客户端浏览器接收到这个响应后会根据设置删除对应的Cookie从而实现注销时删除登录相关Cookie的功能。
response.addCookie(ck);
return;
}
}
}
}
}
}

@ -13,21 +13,45 @@ import java.util.Date;
* @DESC
*/
public class DateTimeUtil {
// 这里的注释表明该工具类可能使用了joda-time库来进行日期时间相关的操作joda-time是一个功能强大的处理日期、时间的第三方库相比于Java原生的日期时间API它提供了更方便、易用且功能丰富的方法来操作日期时间数据。
//joda-time
// 以下注释说明了这个工具类主要实现的两个功能方向即把字符串类型的日期时间表示转换为Date类型str->Date以及把Date类型的日期时间转换为字符串类型Date->str方便在不同的业务场景中对日期时间数据进行格式转换以满足存储、展示等各种需求。
//str->Date
//Date->str
// 定义一个公共静态常量字符串STANDARD_FORMAT用于指定一种标准的日期时间格式这里设置为"yyyy-MM-dd HH:mm:ss",也就是年-月-日 时:分:秒的格式,在一些方法中如果没有指定具体的日期时间格式参数时,会默认使用这个标准格式来进行日期时间和字符串之间的转换操作。
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* dateTimeStrformatStrJavaDate
* joda-timeDateTimeFormatterDateTime
* formatStrDateTimeFormatter
* 使DateTimeFormatterdateTimeStrDateTimeDateTimejoda-time便
* DateTimeJavaDateDate便使Date使
*
* @param dateTimeStr formatStr
* @param formatStr "yyyy-MM-dd HH:mm:ss"
* @return Java Datejoda-time
*/
public static Date strToDate(String dateTimeStr, String formatStr){
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date,String formatStr){
/**
* Java Date
* DatenullStringUtils.EMPTY
* DatenullDatejoda-timeDateTimeDateTime便
* 使DateTimetoStringformatStr便Date使
*
* @param date Java Datenull
* @param formatStr "yyyy-MM-dd HH:mm:ss"Date
* @return Datenull
*/
public static String dateToStr(Date date, String formatStr){
if(date == null){
return StringUtils.EMPTY;
}
@ -35,6 +59,13 @@ public class DateTimeUtil {
return dateTime.toString(formatStr);
}
/**
* dateTimeStrSTANDARD_FORMAT"yyyy-MM-dd HH:mm:ss"JavaDate
* strToDate(String dateTimeStr, String formatStr)使DateTimeFormatter便
*
* @param dateTimeStr "yyyy-MM-dd HH:mm:ss"
* @return Java Datejoda-time
*/
//固定好格式
public static Date strToDate(String dateTimeStr){
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
@ -42,6 +73,13 @@ public class DateTimeUtil {
return dateTime.toDate();
}
/**
* Java DateSTANDARD_FORMAT"yyyy-MM-dd HH:mm:ss"
* dateToStr(Date date, String formatStr)DatenullnullDateDateTime便Date
*
* @param date Java Datenull
* @return "yyyy-MM-dd HH:mm:ss"Datenull
*/
public static String dateToStr(Date date){
if(date == null){
return StringUtils.EMPTY;
@ -50,6 +88,15 @@ public class DateTimeUtil {
return dateTime.toString(STANDARD_FORMAT);
}
/**
* Java Date19701100:00:00 UTC
* Datenullnull
* nullDateSimpleDateFormat"yyyy-MM-dd HH:mm:ss"DateSimpleDateFormatparseDateDateDategetTime19701100:00:00 UTC便使使
*
* @param date Java Datenullnull
* @return Datenullnull
* @throws ParseException 使SimpleDateFormatparse
*/
//Date -> 时间戳
public static Long dateToChuo(Date date) throws ParseException {
if(date == null){
@ -59,10 +106,18 @@ public class DateTimeUtil {
return format.parse(String.valueOf(date)).getTime();
}
/**
* Java
* SimpleDateFormat"yyyy-MM-dd HH:mm:ss""1970-01-06 11:45:55"SimpleDateFormatparseDateDategetTime便
*
*
* @param args 使
* @throws ParseException 使SimpleDateFormatparseJava
*/
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());
}
}
}

@ -15,112 +15,134 @@ import java.text.SimpleDateFormat;
/**
* jackson
*/
// 使用lombok的@Slf4j注解用于自动生成日志相关的代码方便在类中记录各种操作相关的日志信息便于后续查看操作情况以及进行问题排查例如在进行对象序列化、反序列化操作出现问题时通过日志记录错误信息有助于定位和解决问题。
@Slf4j
public class JsonUtil {
// 创建一个ObjectMapper对象ObjectMapper是Jackson库中用于处理JSON数据与Java对象之间相互转换的核心类这里将其定义为静态成员变量方便在整个类的各个静态方法中共享使用避免重复创建对象提高效率。
private static ObjectMapper objectMapper = new ObjectMapper();
// 静态代码块在类加载时执行用于对ObjectMapper对象进行一系列的配置设置以满足特定的JSON处理需求确保在序列化和反序列化操作时按照期望的方式进行。
static {
// 设置序列化时包含所有字段即无论字段的值是否为null都将其列入进行转换默认情况下有些配置可能会忽略null值字段这里通过设置JsonSerialize.Inclusion.ALWAYS来确保所有字段都参与序列化使得转换后的JSON数据能完整反映Java对象的结构。
//所有字段都列入进行转换
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
// 取消默认将日期类型转换为时间戳形式的行为改为按照指定的格式进行转换这样在处理包含日期字段的Java对象转换为JSON时日期会以更易读、符合业务需求的格式呈现而不是以时间戳的形式出现方便查看和使用。
//取消默认转换timestamp形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// 配置忽略空bean即没有属性值的Java对象转JSON时的错误避免在转换这样的对象时抛出异常使得即使对象为空也能正常进行序列化操作返回相应的JSON表示可能为空对象的JSON形式
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
// 统一设置日期的格式使用DateTimeUtil类中定义的标准格式"yyyy-MM-dd HH:mm:ss"),确保在整个应用中,涉及日期类型的序列化和反序列化操作都能按照这个统一的格式进行,保证日期数据的一致性和可读性。
//统一时间的格式
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
// 配置忽略JSON数据中存在属性但对应的Java对象不存在该属性时的错误这样在进行反序列化操作时即使JSON数据包含一些Java对象中未定义的额外属性也不会抛出异常提高了对不同格式JSON数据的兼容性方便处理一些可能存在扩展属性的情况。
//忽略json存在属性但是java对象不存在属性的错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
*
* @param obj
* @param <T>
* @return
* JavaobjJSON便使
* nullnull
* null使ObjectMapperwriteValueAsStringJSONIOJSONlog.warnparse object to string errornull
*
* @param obj JSONJavaJSONObjectMapperJSON
* @param <T> 使
* @return JSONnullnullJSON
*/
public static <T> String obj2String(T obj){
if(obj == null){
public static <T> String obj2String(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj);
} catch (IOException e) {
log.warn("parse object to string error",e);
log.warn("parse object to string error", e);
return null;
}
}
/**
* 便
* @param obj
* @param <T>
* @return
* obj2StringJavaJSON便JSON使JSON便
* nullnullnull使ObjectMapperwriterWithDefaultPrettyPrinterJSONwriteValueAsStringJSONIOnull便
*
* @param obj JSONJavaJSON
* @param <T>
* @return JSONnullnullJSON便
*/
public static <T> String obj2StringPretty(T obj){
if(obj == null){
public static <T> String obj2StringPretty(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (IOException e) {
log.warn("parse object to string error",e);
log.warn("parse object to string error", e);
return null;
}
}
/**
*
* @param str
* @param clazz
* @param <T>
* @return
* JSONstrclazzJava便JSONJava
* StringUtils.isEmptyclazznullnull
* String使ObjectMapperreadValueclazzJSONJavaIOJSONnullJava
*
* @param str JavaJSONObjectMapper
* @param clazz JavaClassUser.classJSONUser
* @param <T> Javaclazz
* @return JavanullnullJava便使
*/
public static <T> T String2Obj(String str,Class<T> clazz){
if(StringUtils.isEmpty(str) || clazz == null){
public static <T> T String2Obj(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz);
return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz);
} catch (IOException e) {
log.warn("parse string to obj error",e);
log.warn("parse string to obj error", e);
return null;
}
}
/**
*
* @param str
* @param typeReference
* @param <T>
* @return
* ClassTypeReference
* JSONStringUtils.isEmptyTypeReferencenullnull
* TypeReferenceTypeReferenceString使ObjectMapperreadValueTypeReferenceJSONJavaIOnull
*
* @param str JavaJSONTypeReference
* @param typeReference TypeReference便JSONJava
* @param <T> JavaTypeReference使
* @return JavatypeReferencenullnullJava使
*/
public static <T> T Str2Obj(String str, TypeReference typeReference){
if(StringUtils.isEmpty(str) || typeReference == null){
public static <T> T Str2Obj(String str, TypeReference typeReference) {
if (StringUtils.isEmpty(str) || typeReference == null) {
return null;
}
try {
return (T) (typeReference.getType().equals(String.class)?str:objectMapper.readValue(str,typeReference));
return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference));
} catch (IOException e) {
log.warn("parse string to obj error",e);
log.warn("parse string to obj error", e);
return null;
}
}
/**
*
* @param str
* @param collectionClass
* @param elementClasses
* @param <T>
* @return
* collectionClasselementClassesJavaType
* ObjectMappergetTypeFactory使constructParametricTypeJavaTypeJavaType
* 使ObjectMapperreadValueJavaTypeJSONJavaIOnull
*
* @param str JavaJSONJavaType
* @param collectionClass List.classSet.class
* @param elementClasses User.classAddress.class
* @param <T> JavaJavaType使
* @return JavanullJava便
*/
public static <T> T Str2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses);
public static <T> T Str2Obj(String str, Class<?> collectionClass, Class<?>... elementClasses) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
try {
return objectMapper.readValue(str,javaType);
return objectMapper.readValue(str, javaType);
} catch (IOException e) {
log.warn("parse string to obj error",e);
log.warn("parse string to obj error", e);
return null;
}
}
}
}

@ -6,6 +6,8 @@ import java.security.MessageDigest;
* MD5
*/
public class MD5Util {
// 这个私有静态方法用于将字节数组转换为十六进制字符串表示形式。
// 它通过遍历字节数组中的每个字节调用byteToHexString方法将每个字节转换为对应的十六进制字符表示然后将这些字符依次追加到StringBuffer对象中最后返回拼接好的十六进制字符串结果该方法在后续的MD5编码过程中用于将字节形式的摘要结果转换为便于查看和使用的十六进制字符串形式。
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将其转换为无符号的等效值范围变为0到255然后分别计算出高4位和低4位对应的十六进制数字通过除以16取整得到高4位取余得到低4位最后从预定义的十六进制字符数组hexDigits中取出对应的字符并拼接起来返回从而实现将字节转换为十六进制字符串的功能。
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
@ -23,37 +27,9 @@ public class MD5Util {
return hexDigits[d1] + hexDigits[d2];
}
/**
* MD5
*
* @param origin
* @param charsetname
* @return
*/
private static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString.toUpperCase();
}
public static String MD5EncodeUtf8(String origin) {
//这里可以加盐
return MD5Encode(origin, "utf-8");
}
public static void main(String[] args) {
System.out.println(MD5EncodeUtf8("123456"));
}
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}
/**
* MD5MD5
*
* @param origin MD5MD5
* @param charsetname "utf-8"null使MD5MD5
* @return MD5

@ -14,21 +14,29 @@ import org.springframework.stereotype.Component;
* @CONTACT 317758022@qq.com
* @DESC zkcurator
*/
// 使用Spring框架的@Component注解将这个类标记为一个组件意味着该类会被Spring容器进行管理能够参与到依赖注入等Spring相关的功能中方便在其他需要使用此类实例的地方通过自动注入等方式获取实例实现不同组件之间的协作。
@Component
public class ZkClient {
// 通过Spring的依赖注入机制使用@Autowired注解自动注入Parameters类型的实例Parameters类应该是用于存储系统相关配置参数的类在这里主要是期望从中获取与Zookeeper连接相关的配置参数例如Zookeeper服务器的主机地址等信息以便后续构建Zookeeper客户端连接时使用。
@Autowired
private Parameters parameters;
// 使用Spring的@Bean注解该注解用于告诉Spring容器这个方法会返回一个对象这个对象会被Spring管理并注册到容器中其他组件可以通过依赖注入的方式获取这个对象在这里就是用于创建并返回一个CuratorFramework类型的Zookeeper客户端对象方便在整个应用中使用该客户端与Zookeeper服务器进行交互。
@Bean
public CuratorFramework getZkClient(){
CuratorFrameworkFactory.Builder builder= CuratorFrameworkFactory.builder()
public CuratorFramework getZkClient() {
// 使用CuratorFrameworkFactory的builder方法创建一个构建器对象CuratorFramework是用于操作Zookeeper的客户端框架通过构建器模式可以方便地配置客户端的各种参数如连接字符串、连接超时时间、重试策略等后续再基于这个构建器来构建出最终的客户端实例。
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
// 通过调用构建器的connectString方法传入从Parameters实例中获取的Zookeeper主机地址parameters.getZkHost()来设置要连接的Zookeeper服务器的地址信息这样客户端就能知道要连接到哪台Zookeeper服务器上。
.connectString(parameters.getZkHost())
// 通过调用构建器的connectionTimeoutMs方法设置连接超时时间为3000毫秒即如果在3秒内无法建立与Zookeeper服务器的连接则认为连接超时会触发相应的超时处理逻辑避免长时间等待导致程序阻塞等问题。
.connectionTimeoutMs(3000)
// 通过调用构建器的retryPolicy方法设置重试策略为RetryNTimes类型这里传入参数5和10表示当连接Zookeeper服务器出现问题时会尝试重试5次每次重试的间隔时间为10毫秒通过这种重试机制来增加连接成功的概率应对可能出现的临时性网络问题等情况。
.retryPolicy(new RetryNTimes(5, 10));
// 使用构建器的build方法构建出CuratorFramework类型的Zookeeper客户端对象这个对象封装了与Zookeeper服务器交互的各种功能例如创建节点、读取节点数据、监听节点变化等操作都可以通过这个客户端对象来实现。
CuratorFramework framework = builder.build();
// 调用构建好的Zookeeper客户端对象的start方法来启动客户端启动后客户端就会尝试按照配置的参数去连接Zookeeper服务器开始建立连接并准备进行后续的交互操作只有启动后客户端才能正常使用。
framework.start();
// 将启动后的Zookeeper客户端对象返回这样Spring容器就会将这个对象管理起来其他需要与Zookeeper进行交互的组件可以通过依赖注入的方式获取这个客户端对象进而使用其功能来操作Zookeeper。
return framework;
}
}

@ -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;
/**
* cookieredis
* key
* CookieRedis
*
* 访keyCookie
* 访
*
* @param session HttpSession
* sessionIdCookieRedis便
* @param response HttpServletResponseCookie
* 便
* @param username
*
* @param password
* @return ServerResponse<UserResVO>ServerResponse
* isSuccessUserResVO
* 便
*/
@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<UserResVO> login(HttpSession session, HttpServletResponse response, String username, String password){
log.info("【用户{}开始登陆】",username);
ServerResponse<UserResVO> 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<UserResVO> login(HttpSession session, HttpServletResponse response, String username, String password) {
// 使用日志记录用户开始登录的信息,通过占位符{}的方式将用户名动态地记录到日志中,方便后续查看具体是哪个用户在什么时间发起了登录请求,
// 有助于排查登录相关的问题,例如某个用户频繁登录失败等情况时,可以通过日志分析原因。
log.info("【用户{}开始登陆】", username);
// 调用userService的login方法传入用户名和密码参数由userService的具体实现类来执行用户登录验证等相关的业务逻辑处理
// 例如可能会在数据库中查询用户信息验证用户名和密码是否匹配等操作最终返回一个ServerResponse<UserResVO>类型的对象,
// 包含了登录操作的结果以及登录成功后对应的用户相关信息(如果登录成功)。
ServerResponse<UserResVO> 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 strConstants
*
* @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;
}
/**
*
* cookieoursnai.cnhosts127.0.0.1 oursnail.cn
* loginGEThttp://oursnail.cn:8081/user/login.do?username=admin&password=123456
* tokenhttp://oursnail.cn:8081/user/get_user_info.do
* Cookie"oursnai.cn"
* hosts"127.0.0.1 oursnail.cn"使访Cookie
* loginGETPOST便
* 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;
}
/**
* ,cookieredis
*/
@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();
}
}
/**
*
*
*
*
* @

@ -1,9 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!-- 此 XML 文件是 MyBatis 的映射文件Mapper用于定义与数据库交互的 SQL 语句以及数据库结果与 Java 对象之间的映射关系。
namespace 属性指定了该映射文件对应的接口全限定名(这里是 com.njupt.swg.dao.UserMapperMyBatis 通过这个命名空间将 XML 中的 SQL 操作与对应的接口方法关联起来,
使得在代码中调用接口方法时MyBatis 能准确找到对应的 SQL 语句去执行数据库操作。 -->
<mapper namespace="com.njupt.swg.dao.UserMapper" >
<!-- resultMap 元素用于定义从数据库查询结果到 Java 对象的映射规则,它的 id 属性(这里是 "BaseResultMap")是该映射关系的唯一标识符,
在其他 SQL 语句配置中可以通过这个 id 来引用此映射关系。type 属性指定了映射的目标 Java 对象类型,即 com.njupt.swg.entity.User 类,
表示将数据库查询结果按照下面定义的规则映射到这个 User 类的对象实例上。 -->
<resultMap id="BaseResultMap" type="com.njupt.swg.entity.User" >
<!-- constructor 元素用于指定使用构造函数来实例化目标 Java 对象,在构造函数中通过 idArg 和 arg 元素来定义参数与数据库列的映射关系。 -->
<constructor >
<!-- idArg 元素用于定义主键列与 Java 对象构造函数参数的映射关系。这里将数据库表中的 "id" 列映射到 Java 中的 Integer 类型的构造函数参数上,
同时通过 jdbcType 属性指定了该列在数据库中的 JDBC 类型为 INTEGER确保数据类型的正确转换。 -->
<idArg column="id" jdbcType="INTEGER" javaType="java.lang.Integer" />
<!-- arg 元素用于定义普通列与 Java 对象构造函数参数的映射关系。以下依次将数据库表中的各列与 User 类的相应构造函数参数进行映射,
明确了各列对应的 Java 类型以及在数据库中的 JDBC 类型,例如将 "username" 列映射到 String 类型的参数等,方便在查询结果返回时创建对应的 Java 对象。 -->
<arg column="username" jdbcType="VARCHAR" javaType="java.lang.String" />
<arg column="password" jdbcType="VARCHAR" javaType="java.lang.String" />
<arg column="email" jdbcType="VARCHAR" javaType="java.lang.String" />
@ -15,158 +27,190 @@
<arg column="update_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
</constructor>
</resultMap>
<!-- sql 元素用于定义可复用的 SQL 片段,通过给它一个唯一的 id这里是 "Base_Column_List"),方便在其他 SQL 语句中通过 <include> 标签引用这个片段,
避免重复编写相同的列选择语句,提高代码的复用性和可维护性。这里定义的是查询时常用的列名列表,对应数据库表中的各列。 -->
<sql id="Base_Column_List" >
id, username, password, email, phone, question, answer, role, create_time, update_time
</sql>
<!-- select 元素用于定义一个查询 SQL 语句id 属性("selectByPrimaryKey")是该查询语句的唯一标识,与对应的接口方法名相对应,
resultMap 属性指定了使用前面定义的 "BaseResultMap" 来映射查询结果到 Java 对象parameterType 属性指定了传入参数的类型为 Java 的 Integer 类型,
表示这个查询是根据主键(通常是一个整数类型的 id 值)来查询对应的记录,并按照定义的映射规则将结果转换为 User 对象返回。 -->
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
select
<!-- 通过 <include> 标签引用前面定义的 "Base_Column_List" SQL 片段,这样就将常用的列选择语句包含进来,避免重复编写,使得 SQL 语句更简洁易维护。 -->
<include refid="Base_Column_List" />
from mmall_user
where id = #{id,jdbcType=INTEGER}
</select>
<!-- delete 元素用于定义一个删除 SQL 语句id 属性("deleteByPrimaryKey"标识该删除语句parameterType 属性指定传入参数类型为 Integer
表示此 SQL 语句用于根据主键(传入的整数类型的 id 值来删除数据库表mmall_user中的对应记录。 -->
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from mmall_user
where id = #{id,jdbcType=INTEGER}
</delete>
<!-- insert 元素用于定义一个插入 SQL 语句id 属性("insert"是该插入语句的标识parameterType 属性指定传入参数的类型为 com.njupt.swg.entity.User 类,
意味着要插入的数据来源于一个 User 类型的对象,此 SQL 语句将 User 对象中的各属性值插入到数据库表mmall_user的对应列中
其中对于时间相关的列create_time 和 update_time使用了数据库函数 now() 来获取当前时间插入,确保插入的记录包含正确的时间戳信息。 -->
<insert id="insert" parameterType="com.njupt.swg.entity.User" >
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>
<!-- insertSelective 元素同样用于定义插入 SQL 语句,但与上面的 "insert" 不同,它采用了动态 SQL 的方式,更具灵活性。
通过 <trim><if> 标签组合,根据传入的 User 对象中属性值是否为 null 来动态决定要插入哪些列及其对应的值,避免插入 null 值到数据库中,
例如只有当 User 对象中的某个属性(如 id、username 等)不为 null 时,才会将对应的列及其值添加到插入语句中,使得插入操作更加智能和符合实际业务需求。 -->
<insert id="insertSelective" parameterType="com.njupt.swg.entity.User" >
insert into mmall_user
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
<if test="id!= null" >
id,
</if>
<if test="username != null" >
<if test="username!= null" >
username,
</if>
<if test="password != null" >
<if test="password!= null" >
password,
</if>
<if test="email != null" >
<if test="email!= null" >
email,
</if>
<if test="phone != null" >
<if test="phone!= null" >
phone,
</if>
<if test="question != null" >
<if test="question!= null" >
question,
</if>
<if test="answer != null" >
<if test="answer!= null" >
answer,
</if>
<if test="role != null" >
<if test="role!= null" >
role,
</if>
<if test="createTime != null" >
<if test="createTime!= null" >
create_time,
</if>
<if test="updateTime != null" >
<if test="updateTime!= null" >
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
<if test="id!= null" >
#{id,jdbcType=INTEGER},
</if>
<if test="username != null" >
<if test="username!= null" >
#{username,jdbcType=VARCHAR},
</if>
<if test="password != null" >
<if test="password!= null" >
#{password,jdbcType=VARCHAR},
</if>
<if test="email != null" >
<if test="email!= null" >
#{email,jdbcType=VARCHAR},
</if>
<if test="phone != null" >
<if test="phone!= null" >
#{phone,jdbcType=VARCHAR},
</if>
<if test="question != null" >
<if test="question!= null" >
#{question,jdbcType=VARCHAR},
</if>
<if test="answer != null" >
<if test="answer!= null" >
#{answer,jdbcType=VARCHAR},
</if>
<if test="role != null" >
<if test="role!= null" >
#{role,jdbcType=INTEGER},
</if>
<if test="createTime != null" >
<if test="createTime!= null" >
now(),
</if>
<if test="updateTime != null" >
<if test="updateTime!= null" >
now(),
</if>
</trim>
</insert>
<!-- updateByPrimaryKeySelective 元素用于定义一个根据主键选择性更新的 SQL 语句,通过动态 SQL<set> 和 <if> 标签组合)来实现,
根据传入的 User 对象中各属性值是否为 null 来动态决定要更新哪些列的值,只更新那些不为 null 的属性对应的列,避免将非必要的字段更新为 null
从而更精准地对数据库表中的记录进行部分更新操作,提高数据更新的灵活性和准确性,更新操作是基于主键(通过 where 子句指定 id 值)来定位要更新的记录。 -->
<update id="updateByPrimaryKeySelective" parameterType="com.njupt.swg.entity.User" >
update mmall_user
<set >
<if test="username != null" >
<if test="username!= null" >
username = #{username,jdbcType=VARCHAR},
</if>
<if test="password != null" >
<if test="password!= null" >
password = #{password,jdbcType=VARCHAR},
</if>
<if test="email != null" >
<if test="email!= null" >
email = #{email,jdbcType=VARCHAR},
</if>
<if test="phone != null" >
<if test="phone!= null" >
phone = #{phone,jdbcType=VARCHAR},
</if>
<if test="question != null" >
<if test="question!= null" >
question = #{question,jdbcType=VARCHAR},
</if>
<if test="answer != null" >
<if test="answer!= null" >
answer = #{answer,jdbcType=VARCHAR},
</if>
<if test="role != null" >
<if test="role!= null" >
role = #{role,jdbcType=INTEGER},
</if>
<if test="createTime != null" >
<if test="createTime!= null" >
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null" >
<if test="updateTime!= null" >
update_time = now(),
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<!-- updateByPrimaryKey 元素用于定义一个根据主键更新的 SQL 语句,它会将传入的 User 对象中的所有属性值更新到数据库表中对应主键的记录上,
无论属性值是否为 null都会进行更新操作相较于 updateByPrimaryKeySelective 更加简单直接,适用于需要完整更新记录所有字段的场景,
更新操作同样是基于主键(通过 where 子句指定 id 值)来定位要更新的记录。 -->
<update id="updateByPrimaryKey" parameterType="com.njupt.swg.entity.User" >
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}
</update>
<!-- select 元素用于定义一个查询 SQL 语句根据传入的用户名parameterType="string"来查询数据库表mmall_user中同名的记录数量
通过 count(1) 函数统计满足条件的记录数结果类型resultType为 int表示返回的是一个整数类型的记录数量用于判断用户名在表中是否存在等业务场景。 -->
<select id="selectByUsername" resultType="int" parameterType="string" >
select
count(1)
count(1)
from mmall_user
where username = #{username}
</select>
<!-- 与上面的 selectByUsername 类似,此 select 元素用于根据传入的邮箱地址parameterType="string"来查询数据库表mmall_user中同邮箱的记录数量
同样通过 count(1) 函数统计记录数,结果类型为 int用于判断邮箱在表中是否存在等相关业务逻辑判断场景。 -->
<select id="selectByEmail" resultType="int" parameterType="string" >
select
count(1)
count(1)
from mmall_user
where email = #{email}
</select>
<!-- select 元素用于根据用户名和密码(通过 #{username} 和 #{password} 传入参数来查询数据库表mmall_user中的记录
并使用前面定义的 "BaseResultMap" 来映射查询结果到 User 对象,适用于用户登录验证等场景,通过传入的用户名和密码查找对应的用户记录信息。 -->
<select id="selectByUsernameAndPasswd" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
@ -175,6 +219,8 @@
and password = #{password}
</select>
<!-- select 元素用于根据用户名parameterType="string"来查询数据库表mmall_user中的记录
并通过 "BaseResultMap" 映射查询结果到 User 对象,用于根据用户名获取对应的用户详细信息等业务场景。 -->
<select id="getUserByUsername" resultMap="BaseResultMap" parameterType="string" >
select
<include refid="Base_Column_List"/>
@ -182,7 +228,8 @@
where username = #{username}
</select>
<!-- select 元素用于根据用户名、问题和答案(通过 #{username}、#{question} 和 #{answer} 传入参数来查询数据库表mmall_user中的记录
同样使用 "BaseResultMap" 映射查询结果到 User 对象,常用于找回密码等业务场景中,根据用户输入的相关信息查找对应的用户记录。 -->
<select id="getUserByUsernameQuestionAnswer" resultMap="BaseResultMap" >
select
<include refid="Base_Column_List"/>
@ -192,11 +239,13 @@
and answer = #{answer}
</select>
<!-- select 元素用于根据传入的邮箱地址email以及排除指定的用户 iduserId来查询数据库表mmall_user中满足条件的记录数量
通过 count(1) 函数统计记录数,结果类型为 int常用于验证邮箱是否被其他用户使用除了当前指定用户外等业务逻辑场景。 -->
<select id="checkEmailValid" resultType="int">
select
count(1)
count(1)
from mmall_user
where email = #{email}
and id != #{userId}
and id!= #{userId}
</select>
</mapper>

@ -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;
}

@ -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
*
*
* userMapperselectByUsername
* 0
* SnailmallException
*
*
* MD5Util.MD5EncodeUtf8MD5
* 使userMapperselectByUsernameAndPasswd
* null
* SnailmallException
*
*
* UserResVO
* resultUserID
* UserResVOnew Date()
* ServerResponse.createBySuccessServerResponseUserResVO
*
*
* @param username
* @param password
* @return ServerResponse<UserResVO>UserResVO
* 便
*/
@Override
public ServerResponse<UserResVO> login(String username,String password) {
//1.校验参数不能为空
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){
public ServerResponse<UserResVO> 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.isBlankSnailmallException
*
*
* 2.
* - InterProcessLockInterProcessMutexzkClientConstants.USER_REGISTER_DISTRIBUTE_LOCK_PATH
* 线
* - do-whilelock.acquire3000TimeUnit.MILLISECONDS
*
* - log.info线便线
* - this.checkValidConstants.USERNAME
* ServerResponse
* - this.checkValidConstants.EMAIL
* 使
* - Constants.Role.ROLE_CUSTOME
* MD5使MD5Util.MD5EncodeUtf8userMapperinsert
* retryfalse
* log.info...
*
*
* 3.
* finally
* locknulllock.releaselog.info线便
* e.printStackTrace便
*
* 4.
*
* ServerResponse.createBySuccessMessageServerResponse
*
* @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.isBlankstrtypestr
* SnailmallException
*
* 2.
* typeConstants.USERNAME
* userMapperselectByUsernamestr
* 0使ServerResponse.createByErrorMessageServerResponse
* 使
*
* typeConstants.EMAIL
* userMapperselectByEmailstr
* 0使ServerResponse.createByErrorMessageServerResponse
* 使
*
* 3.
* 0
* ServerResponse.createBySuccessServerResponse
*
*
* @param str type
* @param type strConstants
* @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.createByErrorMessageServerResponse
*
*
* 2.
* userMappergetUserByUsernameUseruser
* nullServerResponse.createByErrorMessageServerResponse
*
*
* 3.
* usernulluser.getQuestion
* StringUtils.isBlank
* ServerResponse.createByErrorMessageServerResponse
*
*
* 4.
* ServerResponse.createBySuccessServerResponse
* 便便
*
* @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.createByErrorMessageServerResponse
* token
*
* 2. token
* userMappergetUserByUsernameQuestionAnswer
* Useruserusernulltoken
*
* - RedistokencommonCacheUtilgetCacheValueConstants.TOKEN_PREFIXkeytoken
* tokenforgetToken
* - StringUtils.isNotBlankforgetTokenRedistoken
* ServerResponse.createBySuccesstokenServerResponse
* 使tokentoken
* - RedisforgetTokentokentoken
* tokenUUID.randomUUID().toStringtoken
* commonCacheUtilcacheNxExpiretokenConstants.TOKEN_PREFIXkeyRedis60 * 60 * 1212
* ServerResponse.createBySuccesstokenServerResponse便使token
*
* 3.
* usernull
* ServerResponse.createByErrorMessageServerResponse
*
* @param username token
* @param question
* @param answer
* @return ServerResponsetokentokentoken
* 便
*/
@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.isBlanktoken
* ServerResponse.createByErrorMessageServerResponse
*
*
* 2.
* userMappergetUserByUsernameUseruser
* nullServerResponse.createByErrorMessageServerResponse
*
*
* 3. token
* usernullRedistokencommonCacheUtilgetCacheValueConstants.TOKEN_PREFIXkeytoken
* tokenredisToken
* redisTokennullRedistokentokenServerResponse.createByErrorMessagetokenServerResponse
* token
*
* 4. token
* redisTokenStringUtils.equalsforgetTokenRedisredisToken
* tokenServerResponse.createByErrorMessagetokenServerResponse
* token
*
* 5.
* tokenMD5Util.MD5EncodeUtf8MD5MD5Passwd
* user.getPassword().equals
* ServerResponse.createByErrorMessage使ServerResponse
* 使
*
* 6.
* user.setPasswordMD5Passwd
* userMapperupdateByPrimaryKeySelectiveuserID
* resultresult0ServerResponse.createBySuccessMessageServerResponse
*
*
* 7.
* result0ServerResponse.createByErrorMessageServerResponse
*
*
* @param username token
* @param passwordNew
* @param forgetToken tokenRedistoken
* @return ServerResponsetoken
* 便
*/
/**
*
*
* 1.
* 使StringUtils.isBlankusernamepasswordNewtokenforgetToken
* ServerResponse.createByErrorMessageServerResponse
*
*
* 2.
* userMappergetUserByUsernameUseruser
* usernull
* ServerResponse.createByErrorMessageServerResponse
*
* 3. token
* usernullcommonCacheUtilgetCacheValueConstants.TOKEN_PREFIXkey
* RedistokenredisToken
* redisTokennullRedistokentokenServerResponse.createByErrorMessagetokenServerResponse
* tokentoken
*
* 4. token
* RedisredisToken使StringUtils.equalsforgetTokenRedisredisToken
* tokenServerResponse.createByErrorMessagetokenServerResponse
* tokentoken
*
* 5.
* tokenpasswordNewMD5Util.MD5EncodeUtf8MD5MD5Passwd
* 使user.getPassword().equalsMD5Passwduser
* ServerResponse.createByErrorMessage使ServerResponse
* 使
*
* 6.
* token
* user.setPassworduserMD5PasswduserMapperupdateByPrimaryKeySelective
* userID
* updateCountupdateCount0
* ServerResponse.createBySuccessMessageServerResponse
*
* 7.
* updateCount0
* ServerResponse.createByErrorMessageServerResponse
*
* @param username token
* @param passwordNew
* @param forgetToken tokenRedistoken
* @return ServerResponsetoken
* 便
*/
@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.
* userMapperselectByPrimaryKeyIDuserIdUseruser
* usernullIDID
* ServerResponse.createByErrorMessageServerResponse
*
*
* 2.
* 使StringUtils.isBlankemailphonequestionanswer
*
* ServerResponse.createByErrorMessage!ServerResponse
*
*
* 3.
* 使
* userMappercheckEmailValidemailIDuserId使
* queryCount0使
* ServerResponse.createByErrorMessage~ServerResponse使
*
* 4.
* UserupdateUsersetIDIDuserId
* emailphonequestionanswer
* userMapperupdateByPrimaryKeySelectiveupdateUseruserId
* updateCount
*
* 5.
* updateCount0ServerResponse.createBySuccessMessageServerResponse
*
* updateCount0
* ServerResponse.createByErrorMessageServerResponse
*
* @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("更新用户信息失败");
}
/**
* IDUserResVO
*
* UserResVOuserResVO
* userMapperselectByPrimaryKeyIDuserIdUseruserDB
* userDBnullID
* userResVOsetUserResVO
* userResVO便
* userDBnullUserResVO
*
* @param userId
* @return UserResVOID便
* 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;
}
}
}

@ -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 URLtermsOfServiceUrldescriptionversioncontact
* 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();
}
}

@ -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数据发送的类型为webZipkin用于分布式链路追踪通过这种方式可以将应用的链路追踪数据发送到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

@ -1,33 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这是整个 logback 配置文件的根节点,所有的日志配置相关内容都在这个节点下进行定义 -->
<configuration>
<!-- 定义一个名为 LOG_HOME 的属性,其值为 "/logs/user/",用于后续配置中作为日志文件的存储路径基础部分,
通过这种属性定义的方式,可以方便地统一修改日志文件的存储位置,提高配置的灵活性 -->
<property name="LOG_HOME" value="/logs/user/" />
<!-- 配置一个名为 Console 的日志输出 Appender类型为 ch.qos.logback.core.ConsoleAppender它用于将日志输出到控制台
方便在开发调试阶段或者查看实时日志信息时使用 -->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 定义编码器Encoder用于指定日志输出的格式 -->
<encoder>
<!-- 这里配置的日志格式为时间精确到小时和分钟、日志级别占5个字符宽度且左对齐、日志记录器名称最长16个字符以及日志消息内容最后换行 -->
<pattern>%d{H:mm} %-5level [%logger{16}] %msg%n</pattern>
</encoder>
</appender>
<!-- 配置一个名为 normalLog 的日志输出 Appender类型为 ch.qos.logback.core.rolling.RollingFileAppender
它用于将日志输出到文件,并且支持日志文件的滚动策略(即按照一定规则对日志文件进行切割、备份等操作) -->
<appender name="normalLog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 配置滚动策略这里使用的是基于时间的滚动策略TimeBasedRollingPolicy按照日期来切割日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 定义日志文件的命名模式,会在 LOG_HOME 路径下生成形如 web.normal.2024-12-17.log 的文件(以实际日期为准),
每天会生成一个新的日志文件,方便按天对日志进行管理和查看 -->
<FileNamePattern>${LOG_HOME}/web.normal.%d{yyyy-MM-dd}.log
</FileNamePattern>
<!-- 配置保留的历史日志文件数量,这里设置为最多保留 30 天的日志文件,超过这个数量的旧日志文件会被自动删除,
避免日志文件过多占用磁盘空间 -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<!-- 配置基于文件大小的触发策略SizeBasedTriggeringPolicy当单个日志文件大小达到 10MB 时,会触发滚动操作,
也就是进行日志文件的切割、备份等处理,与基于时间的滚动策略结合使用,从时间和文件大小两个维度来管理日志文件 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<!-- 定义日志的布局格式,与前面 Console Appender 中的类似,不过这里时间精确到了毫秒,并且包含了线程信息等,
用于规范日志在文件中的输出格式 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{16} - %msg%n
</pattern>
</layout>
<!-- 配置一个过滤器LevelFilter用于根据日志级别来决定是否接受该日志进行输出
这里对于 ERROR 级别的日志进行过滤 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 指定过滤的日志级别为 ERROR -->
<level>ERROR</level>
<!-- 如果日志级别匹配(这里就是 ERROR 级别),则拒绝该日志输出,也就是不会记录到这个 normalLog 对应的日志文件中 -->
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
<!-- 如果日志级别不匹配(即不是 ERROR 级别),则接受该日志输出,会正常记录到日志文件中 -->
<onMismatch>ACCEPT</onMatch>
</filter>
</appender>
<!-- 配置一个名为 errorLog 的日志输出 Appender同样是基于滚动文件的方式结构和前面的 normalLog 类似,但在日志级别过滤方面有所不同 -->
<appender name="errorLog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
@ -44,18 +69,22 @@
</layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<!-- 对于 ERROR 级别的日志,这里设置为接受输出,也就是只有 ERROR 级别的日志会记录到这个 errorLog 对应的日志文件中 -->
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 配置一个名为 com.oursnail 的日志记录器Logger指定其日志级别为 debug意味着这个记录器会记录 DEBUG 及以上级别的日志信息,
并且将日志输出到 normalLog 和 errorLog 这两个 Appender 对应的日志文件中,实现了对特定包下日志的分级和分类输出管理 -->
<logger name="com.oursnail" level="debug" >
<appender-ref ref="normalLog" />
<appender-ref ref="errorLog" />
</logger>
<!-- 配置根日志记录器Root Logger它是所有日志记录器的默认父级这里设置其日志级别为 info
也就是会记录 INFO 及以上级别的日志信息,并且将日志输出到 Console 这个 Appender 对应的控制台中,
对整个应用的日志输出做了一个基础的级别和输出目标的配置 -->
<root level="info">
<appender-ref ref="Console" />
</root>

Loading…
Cancel
Save