Compare commits

..

9 Commits

Author SHA1 Message Date
guanguan 8379d1b373 Merge remote-tracking branch 'refs/remotes/origin/gsh_branch' into branch_gsh
10 months ago
pb4nq52pf bd62c85961 accept pr4
10 months ago
陈萱 2cb4616fa2 Merge remote-tracking branch 'refs/remotes/origin/feature/cx' into gsh_branch
10 months ago
pb4nq52pf 8054e530c9 accept pr2
10 months ago
陈萱 fbc20fbe69 gsh
10 months ago
陈萱 c7e7b5f7fc 3gsh
10 months ago
陈萱 43d9f36c65 3cx
10 months ago
陈萱 5a8ee4a902 添加注释
10 months ago
guanguan 8c6ddbba50 添加注释
10 months ago

@ -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>
</

@ -12,44 +12,61 @@ import redis.clients.jedis.JedisPool;
* @Date 2019/1/1 15:03
* @CONTACT 317758022@qq.com
* @DESC
* Redis
*
* JedisPoolWrapperJedis
*/
@Component
@Slf4j
public class CommonCacheUtil {
// 自动注入JedisPoolWrapper用于获取Jedis连接池通过它来操作Redis
@Autowired
private JedisPoolWrapper jedisPoolWrapper;
/**
* key
* Redis
* SnailmallExceptionRedis
* @param key
* @param value
*/
public void cache(String key, String value) {
try {
// 获取Jedis连接池
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
Jedis.set(key, value);
if (pool!= null) {
// 从连接池中获取Jedis资源使用try-with-resources语句自动关闭资源
try (Jedis jedis = pool.getResource()) {
// 选择Redis的第0个数据库通常可以根据实际情况选择不同的数据库
jedis.select(0);
// 将键值对存入Redis
jedis.set(key, value);
}
}
} catch (Exception e) {
// 记录Redis存值失败的错误日志
log.error("redis存值失败", e);
// 抛出SnailmallException异常表示Redis报错
throw new SnailmallException("redis报错");
}
}
/**
* key
* Redis
* SnailmallExceptionRedisnull
* @param key
* @return null
*/
public String getCacheValue(String key) {
String value = null;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
value = Jedis.get(key);
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
value = jedis.get(key);
}
}
} catch (Exception e) {
@ -61,12 +78,19 @@ public class CommonCacheUtil {
/**
* key
* Redis
* 使SETNX
* SnailmallExceptionRedisSETNX
* @param key
* @param value
* @param expire
* @return 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);
@ -83,10 +107,13 @@ public class CommonCacheUtil {
/**
* key
* Redis
* SnailmallExceptionRedis
* @param key
*/
public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
@ -98,7 +125,4 @@ public class CommonCacheUtil {
}
}
}
}
}

@ -13,31 +13,65 @@ import javax.annotation.PostConstruct;
* @Author swg.
* @Date 2019/1/1 15:00
* @CONTACT 317758022@qq.com
* @DESC redisredishash
* @DESC JedisPoolRedis
* redishash
* SpringJedisPool使
*/
@Component
@Slf4j
public class JedisPoolWrapper {
// 通过Spring的依赖注入机制自动注入Parameters对象。
// Parameters类应该是用于存放各类配置参数的在这里主要用于获取与Redis连接相关的配置信息。
@Autowired
private Parameters parameters;
// 用于保存JedisPool实例初始化为null会在后续的初始化方法中根据配置情况进行实例化。
// 这个连接池对象是与Redis服务器建立连接、获取Jedis客户端实例的关键所在。
private JedisPool jedisPool = null;
/**
* @PostConstruct
* JedisPoolJedisPool
* 便
*/
@PostConstruct
public void init(){
public void init() {
try {
// 创建JedisPoolConfig对象它用于配置JedisPool连接池的各种行为和参数限制。
JedisPoolConfig config = new JedisPoolConfig();
// 设置JedisPool连接池允许创建的最大连接数量。
// 通过从注入的Parameters对象中获取对应的配置参数值来进行设置确保连接池的规模符合预期配置。
config.setMaxTotal(parameters.getRedisMaxTotal());
// 设置JedisPool连接池中最大的空闲连接数量。
// 空闲连接可以被重复利用合理设置该值有助于提高资源利用效率同样从Parameters获取配置值进行设置。
config.setMaxIdle(parameters.getRedisMaxIdle());
// 设置当从连接池中获取连接时,如果没有可用连接,最长等待的时间(单位为毫秒)。
// 防止因为长时间等待连接导致应用程序阻塞这里也是依据Parameters中的配置来设定。
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx");
// 使用配置好的JedisPoolConfig对象以及从Parameters获取的Redis服务器主机地址、端口号、超时时间这里设置为2000毫秒和密码此处示例密码为"xxx"实际应替换为真实密码等信息创建JedisPool实例。
// 这个实例将作为后续与Redis服务器进行交互的基础通过它可以获取Jedis客户端来执行各种Redis操作。
jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx");
// 如果初始化过程顺利记录一条信息日志表明Redis连接池初始化成功方便在运行时查看初始化状态。
log.info("【初始化redis连接池成功】");
}catch (Exception e){
log.error("【初始化redis连接池失败】",e);
} catch (Exception e) {
// 如果在初始化JedisPool连接池过程中出现异常记录一条错误日志包含详细的异常信息通过e参数传递异常堆栈等内容
// 这样在排查问题时可以快速定位到是在初始化连接池时出现的错误,并根据异常详情分析具体原因。
log.error("【初始化redis连接池失败】", e);
}
}
/**
* JedisPool
* RedisJedisPoolJedisRedis
* @return JedisPooljedisPoolnullnull
*/
public JedisPool getJedisPool() {
return jedisPool;
}
}
}

@ -11,23 +11,44 @@ import java.util.List;
* @Date 2019/1/1 14:27
* @CONTACT 317758022@qq.com
* @DESC
* ParametersSpring@Valueapplication.propertiesapplication.yml
* Spring@Component便使
* 使Lombok@DataGetterSettertoString便
*/
@Component
@Data
public class Parameters {
/**
* Redis
* Redis@Value
*/
/*****redis config start*******/
// 从配置文件中读取Redis服务器的主机地址配置对应的配置文件中的属性键为"redis.host"
@Value("${redis.host}")
private String redisHost;
// 从配置文件中读取Redis服务器的端口号配置对应的配置文件中的属性键为"redis.port",会被自动解析并赋值为整数类型
@Value("${redis.port}")
private int redisPort;
// 此处变量名可能存在混淆按照语义理解应该是获取Redis连接池的最大空闲连接数但变量名写的是redisMaxTotal可能需要确认是否准确。
// 从配置文件中读取对应配置,配置文件中的属性键为"redis.max-idle",赋值为整数类型
@Value("${redis.max-idle}")
private int redisMaxTotal;
// 此处同样变量名可能存在混淆按照语义理解应该是获取Redis连接池的最大连接数但变量名写的是redisMaxIdle可能需要核对准确性。
// 从配置文件中读取对应配置,配置文件中的属性键为"redis.max-total",赋值为整数类型
@Value("${redis.max-total}")
private int redisMaxIdle;
// 从配置文件中读取获取Redis连接时的最大等待时间单位为毫秒配置对应的配置文件中的属性键为"redis.max-wait-millis",赋值为整数类型
@Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis;
/*****redis config end*******/
// 用于存放不需要安全验证的管理员路径列表配置从配置文件中读取以逗号分隔的字符串并通过split方法解析为List<String>类型。
// 配置文件中对应的属性键为"security.noneSecurityAdminPaths"
@Value("#{'${security.noneSecurityAdminPaths}'.split(',')}")
private List<String> noneSecurityAdminPaths;
}
}

@ -9,24 +9,44 @@ import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
/**
*
* C - Cross O - Origin R - Resource S - Sharing
* 访
* CORS"Cross-Origin Resource Sharing"
* 使SpringSpringBeanSpring使
*/
@Configuration
public class CorsConfig {
/**
* @BeanBeanSpringCorsFilter
* CorsFilter访
*/
@Bean
public CorsFilter corsFilter() {
// 创建一个基于URL的CorsConfigurationSource对象它用于根据不同的URL路径来配置对应的CorsConfiguration跨域配置
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 创建一个CorsConfiguration对象用于具体设置跨域相关的各项配置参数比如允许的源、请求头、请求方法等。
final CorsConfiguration config = new CorsConfiguration();
// 设置是否允许跨域请求中包含认证信息如Cookie等这里设置为true表示允许携带认证信息进行跨域访问。
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("*")); //http:www.a.com
// 设置允许的跨域请求源Origin这里使用Arrays.asList("*")表示允许所有来源的请求进行跨域访问。
// 在实际更严格的场景下通常应该明确指定具体的域名等来源例如Arrays.asList("http://www.a.com")只允许来自该域名的跨域请求。
config.setAllowedOrigins(Arrays.asList("*"));
// 设置允许的请求头信息使用Arrays.asList("*")表示允许所有类型的请求头进行跨域请求,不过在实际应用中,也可以根据需求明确列出允许的具体请求头列表。
config.setAllowedHeaders(Arrays.asList("*"));
// 设置允许的请求方法如GET、POST、PUT等同样通过Arrays.asList("*")表示允许所有的请求方法进行跨域访问,也可以按需具体指定。
config.setAllowedMethods(Arrays.asList("*"));
// 设置预检请求OPTIONS请求的有效期单位为秒这里设置为300秒表示在这个时间范围内相同配置的跨域请求不需要再次发送预检请求。
config.setMaxAge(300l);
// 将配置好的CorsConfiguration应用到所有路径"/**"表示匹配所有的URL路径也就是对整个应用的所有接口都应用此跨域配置。
source.registerCorsConfiguration("/**", config);
// 创建并返回CorsFilter对象这个对象会在Spring Web应用中对进入的请求进行过滤根据配置好的跨域规则来处理跨域相关的逻辑。
return new CorsFilter(source);
}
}
}

@ -5,42 +5,75 @@ package com.njupt.swg.constants;
* @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com
* @DESC
* Constants
* 便
*/
public class Constants {
/**
*
* 便使
*/
/**自定义状态码 start**/
// 表示请求成功的状态码对应HTTP状态码中的200表示操作顺利完成符合预期。
public static final int RESP_STATUS_OK = 200;
// 表示未授权的状态码对应HTTP状态码中的401通常用于用户在没有提供有效认证信息如未登录或者权限不足等情况访问受保护资源时返回该状态码。
public static final int RESP_STATUS_NOAUTH = 401;
// 表示服务器内部错误的状态码对应HTTP状态码中的500意味着服务器在处理请求过程中出现了不可预期的错误情况。
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
// 表示请求参数错误的状态码对应HTTP状态码中的400常用于客户端发送的请求参数不符合要求、格式错误等场景。
public static final int RESP_STATUS_BADREQUEST = 400;
/**自定义状态码 end**/
/**
* RediskeyRedis便
*/
/***redis user相关的key以这个打头**/
public static final String TOKEN_PREFIX = "user_";
/**
* Redis
* 使便
*/
/**
* redis
*/
public interface RedisCacheExtime{
int REDIS_SESSION_EXTIME = 60* 30;//30分钟
public interface RedisCacheExtime {
// 定义用户登录信息在Redis中的缓存过期时间为30分钟这里通过计算将分钟换算成秒60秒 * 30分钟进行表示。
int REDIS_SESSION_EXTIME = 60 * 30; // 30分钟
}
/**
*
* 使
*/
/** 用户注册判断重复的参数类型 start **/
// 表示用于判断用户注册时邮箱是否重复的参数类型对应的字符串常量,方便在相关验证逻辑中统一使用该标识。
public static final String EMAIL = "email";
// 表示用于判断用户注册时用户名是否重复的参数类型对应的字符串常量,同样便于在用户名重复性验证逻辑中统一引用。
public static final String USERNAME = "username";
/** 用户注册判断重复的参数类型 end **/
/**
*
* 便使
*/
/** 用户角色 **/
public interface Role{
int ROLE_CUSTOME = 0;//普通用户
int ROLE_ADMIN = 1;//管理员用户
public interface Role {
// 表示普通用户角色对应的整数值为0用于在系统中标识具有普通权限的用户类型。
int ROLE_CUSTOME = 0; // 普通用户
// 表示管理员用户角色对应的整数值为1用于区分具有管理权限、可进行系统管理操作的用户类型。
int ROLE_ADMIN = 1; // 管理员用户
}
/**
*
*/
/**用户注册分布式锁路径***/
public static final String USER_REGISTER_DISTRIBUTE_LOCK_PATH = "/user_reg";
}
}

@ -11,17 +11,31 @@ import org.springframework.web.bind.annotation.RestController;
* @Date 2019/1/2 21:21
* @CONTACT 317758022@qq.com
* @DESC
* ErrorHandleControllerSpring MVC
* Spring BootErrorController
* 使@RestControllerJSON
*/
@RestController
public class ErrorHandleController implements ErrorController {
/**
* ErrorController
* "/error"
* @return "/error"
*/
@Override
public String getErrorPath() {
return "/error";
}
/**
* 使@RequestMapping"/error"访"/error"
* ServerResponse
* ServerResponsecreateByErrorCodeMessage使ResponseEnum
* @return ServerResponse
*/
@RequestMapping("/error")
public ServerResponse error() {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆或者权限不足");
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆或者权限不足");
}
}
}

@ -12,32 +12,44 @@ import java.util.Date;
* @Author swg.
* @Date 2018/12/31 21:01
* @CONTACT 317758022@qq.com
* @DESC
* @DESC
* 便
* Serializable使便
* 使Lombok
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Serializable {
// 用户的唯一标识符,通常对应数据库表中的主键字段,用于区分不同的用户个体。
private Integer id;
// 用户登录时使用的用户名,具有唯一性,方便用户进行身份认证和在系统中标识自己。
private String username;
// 用户登录时使用的密码,用于验证用户身份的重要信息,需要进行妥善的加密存储和安全处理。
private String password;
// 用户的电子邮箱地址,可用于账号找回、信息通知等功能,也需要保证其唯一性以及格式的有效性。
private String email;
// 用户的手机号码,同样可用于多种业务场景,比如短信验证、联系用户等,格式上需要符合手机号码的规范要求。
private String phone;
// 用户设置的密保问题,用于在忘记密码等情况下辅助验证用户身份,增加账号的安全性。
private String question;
// 用户针对密保问题设置的答案与question属性配合使用用于验证用户身份的真实性。
private String answer;
//角色0-管理员,1-普通用户
// 用于标识用户在系统中的角色0表示管理员角色拥有较高的系统管理权限1表示普通用户角色权限相对受限只能进行普通的业务操作。
private Integer role;
// 用户账号创建的时间,记录了用户首次注册进入系统的时间节点,通常由系统自动生成并赋值。
private Date createTime;
// 用户信息最近一次更新的时间,每当用户修改个人信息等操作后,该时间会相应更新,便于跟踪用户信息的变更情况。
private Date updateTime;
}

@ -8,18 +8,36 @@ import lombok.Getter;
* @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com
* @DESC
* SnailmallExceptionJavaRuntimeException
* 使便
* 使Lombok@GetterexceptionStatusGetter便
*/
@Getter
public class SnailmallException extends RuntimeException{
public class SnailmallException extends RuntimeException {
// 用于存储异常对应的状态码默认初始化为ResponseEnum.ERROR.getCode(),也就是使用预定义的错误状态码值,不过可以通过构造方法进行重新赋值。
private int exceptionStatus = ResponseEnum.ERROR.getCode();
public SnailmallException(String msg){
/**
* msgSnailmallException
* msgRuntimeException便
* 使
* @param msg
*/
public SnailmallException(String msg) {
super(msg);
}
public SnailmallException(int code,String msg){
/**
* codemsg
* code便
* msgRuntimeException
*
* @param code
* @param msg
*/
public SnailmallException(int code, String msg) {
super(msg);
exceptionStatus = code;
}
}
}

@ -26,43 +26,67 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst
* @Author swg.
* @Date 2019/1/3 10:21
* @CONTACT 317758022@qq.com
* @DESC controller
* userID,controller
* @DESC controller
* userIDcontroller
* AdminUserFilterZuulFilterZuul
* Spring@ComponentSpring便使@Slf4j便
*/
@Slf4j
@Component
public class AdminUserFilter extends ZuulFilter {
// 自动注入CommonCacheUtil用于从缓存如Redis中获取相关数据比如用户信息等辅助进行权限校验等操作。
@Autowired
private CommonCacheUtil commonCacheUtil;
// 自动注入Parameters对象用于获取应用程序的配置参数例如获取不需要进行安全校验的路径等配置信息。
@Autowired
private Parameters parameters;
/**
* PRE_TYPE
* @return PRE_TYPE
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
*
* PRE_DECORATION_FILTER_ORDER1便
* @return
*/
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER-1;
return PRE_DECORATION_FILTER_ORDER - 1;
}
/**
*
* HttpServletRequestURL
* URLURL"manage"
* URL"upload"
* URL
* true
* @return truefalse
*/
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//获取当前请求的url
// 获取当前请求的url
String url = request.getRequestURI();
//前端的路径不在这里校验,直接放过
if (!url.contains("manage")){
log.info("【{}不需要进行权限校验】",url);
// 前端的路径不在这里校验,直接放过
if (!url.contains("manage")) {
log.info("【{}不需要进行权限校验】", url);
return false;
}
if(url.contains("upload")){
log.info("【{}不需要进行权限校验】",url);
if (url.contains("upload")) {
log.info("【{}不需要进行权限校验】", url);
return false;
}
//从配置文件获取所有门户需要校验的路径
// 从配置文件获取所有门户需要校验的路径
// String[] passUrls = parameters.getNoneSecurityAdminPaths().toArray(new String[parameters.getNoneSecurityAdminPaths().size()]);
// for(String str:passUrls){
// //指定的路径比较特殊,也不在这里校验
@ -71,47 +95,57 @@ public class AdminUserFilter extends ZuulFilter {
// return false;
// }
// }
log.info("【{}----需要进行权限校验,必须是管理员身份才可以进入】",url);
log.info("【{}----需要进行权限校验,必须是管理员身份才可以进入】", url);
return true;
}
/**
* shouldFiltertrue
* HttpServletRequest
* CookieloginToken
* Redis
* URL"manage"
* null
* @return ServerResponsenullZuulException
* @throws ZuulException Zuul
*/
@Override
public ServerResponse run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//校验是否为管理员身份
// 校验是否为管理员身份
String loginToken = CookieUtil.readLoginToken(request);
log.info("【获取cookie----{}】",loginToken);
if(StringUtils.isEmpty(loginToken)){
log.info("【获取cookie----{}】", loginToken);
if (StringUtils.isEmpty(loginToken)) {
// // 过滤该请求,不对其进行路由
// requestContext.setSendZuulResponse(false);
// //返回错误代码
// requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH);
// return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
this.returnMsg(requestContext);
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
//throw new SnailmallException("用户未登录,无法获取当前用户信息");
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息");
// throw new SnailmallException("用户未登录,无法获取当前用户信息");
}
//2.从redis中获取用户信息
// 2.从redis中获取用户信息
String userStr = commonCacheUtil.getCacheValue(loginToken);
log.info("【从redis中获取用户信息:{}】",userStr);
if(userStr == null){
log.info("【从redis中获取用户信息:{}】", userStr);
if (userStr == null) {
// // 过滤该请求,不对其进行路由
// requestContext.setSendZuulResponse(false);
// //返回错误代码
// requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH);
// return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); //SnailmallException(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
this.returnMsg(requestContext);
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息");
}
String url = request.getRequestURI();
log.info("【获取当前url:{}】",url);
if(url.contains("manage")){
log.info("【获取当前url:{}】", url);
if (url.contains("manage")) {
log.info("【来到了管理后台,需要校验权限】");
User currentUser = JsonUtil.Str2Obj(userStr,User.class);
log.info("【当前登陆的用户为:{}】",currentUser);
if(!currentUser.getRole().equals(Constants.Role.ROLE_ADMIN)){
//不是管理员报错
User currentUser = JsonUtil.Str2Obj(userStr, User.class);
log.info("【当前登陆的用户为:{}】", currentUser);
if (!currentUser.getRole().equals(Constants.Role.ROLE_ADMIN)) {
// 不是管理员报错
log.error("【当前登陆用户不是管理员身份】");
// 过滤该请求,不对其进行路由
// requestContext.setSendZuulResponse(false);
@ -119,18 +153,25 @@ public class AdminUserFilter extends ZuulFilter {
// requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH);
// return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
this.returnMsg(requestContext);
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
//throw new SnailmallException("用户权限不够");
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息");
// throw new SnailmallException("用户权限不够");
}
}
return null;
}
//返回没权限消息
private void returnMsg(RequestContext ctx){
/**
*
* JSON"application/json; charset=utf-8"Zuul
* Constants.RESP_STATUS_OK
* ServerResponseJSON便
* @param ctx
*/
// 返回没权限消息
private void returnMsg(RequestContext ctx) {
ctx.getResponse().setContentType("application/json; charset=utf-8");
ctx.setSendZuulResponse(false); //令zuul过滤该请求不对其进行路由
ctx.setSendZuulResponse(false); // 令zuul过滤该请求不对其进行路由
ctx.setResponseStatusCode(Constants.RESP_STATUS_OK);
ctx.setResponseBody(JsonUtil.obj2String(ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息")));
ctx.setResponseBody(JsonUtil.obj2String(ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息")));
}
}
}

@ -15,38 +15,64 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst
* @Author swg.
* @Date 2019/1/3 11:21
* @CONTACT 317758022@qq.com
* @DESC
* @DESC RateLimitFilterZuulFilterZuul
* Spring@Component便Spring
*/
@Component
public class RateLimitFilter extends ZuulFilter {
//放100个令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
// 使用Google Guava库中的RateLimiter来创建一个令牌桶这里设置令牌桶的初始令牌数量为100个意味着一开始桶里有100个令牌可供请求获取用于控制请求速率。
// RateLimiter是基于令牌桶算法实现的一个限流器通过控制令牌的发放和获取来限制请求的频率。
private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
/**
* PRE_TYPE
*
* @return PRE_TYPE
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
*
* SERVLET_DETECTION_FILTER_ORDER1便
* @return
*/
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER - 1;
}
/**
* true
* Zuulrun
* @return truefalsetrue
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* shouldFiltertrue
* HttpServletRequestRATE_LIMITER
* RATE_LIMITER.tryAcquire()true
* RATE_LIMITER.tryAcquire()false
*
* @return nullnull
* @throws ZuulException Zuul
*/
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
if(!RATE_LIMITER.tryAcquire()){
//没有取到一个令牌的话,可以这样返回信息给前端
context.set("状态码",401);
context.set("error.message","用户没有获取到令牌");
if (!RATE_LIMITER.tryAcquire()) {
// 没有取到一个令牌的话,可以这样返回信息给前端(当前只是简单设置了上下文信息,还需完善后续返回给前端的完整逻辑)
context.set("状态码", 401);
context.set("error.message", "用户没有获取到令牌");
}
return null;
}
}
}

@ -6,20 +6,39 @@ import lombok.Getter;
* @Author swg.
* @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ResponseEnum
* 使便
* 使Lombok@GettercodedescGetter便
*/
@Getter
public enum ResponseEnum {
SUCCESS(0,"SUCCESS"),
ERROR(1,"ERROR"),
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"),
NEED_LOGIN(10,"NEED_LOGIN");
// 表示操作成功的状态状态码为0对应的描述信息为"SUCCESS",通常用于在业务逻辑处理成功后返回给客户端等场景,表示请求顺利完成。
SUCCESS(0, "SUCCESS"),
// 表示通用的错误状态状态码为1描述信息为"ERROR",可以用于各种未明确细分的错误情况,作为一个笼统的错误标识返回给客户端等。
ERROR(1, "ERROR"),
// 表示请求参数不合法的状态状态码为2描述信息为"ILLEGAL_ARGUMENTS",常用于客户端发送的请求参数不符合要求、格式错误等场景,方便进行参数校验相关的错误提示。
ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"),
// 表示需要用户登录的状态状态码为10描述信息为"NEED_LOGIN",一般用于当用户未提供有效登录信息却访问需要认证的资源时,提示客户端用户需要先登录才能继续操作。
NEED_LOGIN(10, "NEED_LOGIN");
// 用于存储每个枚举实例对应的状态码,不同的枚举值有不同的状态码,用于区分不同的返回情况。
private int code;
// 用于存储每个枚举实例对应的状态描述信息,直观地展示该状态所代表的含义,方便客户端等使用者理解具体的情况。
private String desc;
ResponseEnum(int code,String desc){
/**
*
* SUCCESSERROR
* @param code
* @param desc
*/
ResponseEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
}

@ -10,66 +10,156 @@ import java.io.Serializable;
* @Author swg.
* @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ServerResponse
* 便
* Serializable使便
* 使Lombok@GetterGetter使JacksonJSONnull
*/
@Getter
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ServerResponse<T> implements Serializable {
// 用于存储响应的状态码,通过不同的状态码来告知客户端请求的处理结果情况,例如成功、失败以及具体的错误类型等。
private int status;
// 用于存储响应的提示消息,是对状态码含义的一种文字描述,方便客户端直观地了解请求处理的相关情况,比如操作成功的提示或者错误原因说明等。
private String msg;
// 用于存储具体的业务数据其类型是泛型参数T意味着可以根据不同的业务场景返回不同类型的数据比如可以是一个用户对象、商品列表等具体的业务相关实体数据。
private T data;
private ServerResponse(int status){
/**
* ServerResponse使
*
* @param status
*/
private ServerResponse(int status) {
this.status = status;
}
private ServerResponse(int status,String msg){
/**
* ServerResponse
* 使
* @param status
* @param msg
*/
private ServerResponse(int status, String msg) {
this.status = status;
this.msg = msg;
}
private ServerResponse(int status,T data){
/**
* ServerResponse
*
* @param status
* @param data T
*/
private ServerResponse(int status, T data) {
this.status = status;
this.data = data;
}
private ServerResponse(int status,String msg,T data){
/**
* ServerResponse
*
* @param status
* @param msg
* @param data T
*/
private ServerResponse(int status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
/**
* 使@JsonIgnoreJSON
* ServerResponseResponseEnumSUCCESS
* @return truefalse
*/
@JsonIgnore
public boolean isSuccess(){
public boolean isSuccess() {
return this.status == ResponseEnum.SUCCESS.getCode();
}
/**
*
* 便ServerResponse
* ServerResponse使ResponseEnum
*
* @param <T>
* @return ServerResponse
*/
public static <T>ServerResponse<T> createBySuccess(){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
}
public static <T>ServerResponse<T> createBySuccessMessage(String message){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message);
}
public static <T>ServerResponse<T> createBySuccess(T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data);
}
public static <T>ServerResponse<T> createBySuccess(String message,T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data);
public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
}
/**
*
* ServerResponse使ResponseEnum
*
* @param <T>
* @param message
* @return ServerResponse
*/
public static <T>ServerResponse<T> createByError(){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc());
public static <T> ServerResponse<T> createBySuccessMessage(String message) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message);
}
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
/**
* ServerResponse使ResponseEnum
*
* @param <T> data
* @param data T
* @return ServerResponse
*/
public static <T> ServerResponse<T> createBySuccess(T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data);
}
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
/**
* ServerResponse使ResponseEnum
*
* @param <T> data
* @param message
* @param data T
* @return ServerResponse
*/
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
}
/**
* 便ServerResponse便
* ServerResponse使ResponseEnum
*
* @param <T>
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByError() {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
}
/**
* ServerResponse使ResponseEnum
* msg
* @param <T>
* @param msg
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
}
/**
* ServerResponse
* codemsg
* @param <T>
* @param code
* @param msg
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
return new ServerResponse<>(code, msg);
}
}

@ -8,67 +8,109 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* cookie
* @Author
* @Date
* @CONTACT
* @DESC CookieUtilCookieCookieCookieCookie
* 使CookieCookieCookie使@Slf4j便
*/
@Slf4j
public class CookieUtil {
// 定义Cookie的域名用于指定该Cookie在哪个域名下有效这里固定设置为"oursnail.cn"意味着只有访问该域名下的页面时对应的Cookie才会被携带和使用。
private final static String COOKIE_DOMAIN = "oursnail.cn";
private final static String COOKIE_NAME = "snailmall_login_token";
// 定义Cookie的名称用于标识登录相关的Cookie在整个项目中通过这个固定的名称来获取和操作对应的Cookie这里设置为"snailmall_login_token"。
private final static String COOKIE_NAME = "snailmall_login_token";
/**
* cookie
* @param response
* @param token
* Cookie
* CookieHTTP访Cookie使Cookie
* @param response HttpServletResponseCookie
* @param 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) {
// 创建一个新的Cookie对象名称为固定的COOKIE_NAME值为传入的token参数
Cookie ck = new Cookie(COOKIE_NAME, token);
// 设置Cookie的域名使其在指定的域名COOKIE_DOMAIN下有效确保只有访问该域名的页面时浏览器才会发送这个Cookie到服务器端。
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//设值在根目录
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的路径为根目录"/"意味着该Cookie在该域名下的所有页面路径都会被携带方便在整个网站范围内都能使用这个Cookie进行相关操作。
ck.setPath("/");
// 设置Cookie为HttpOnly属性为true这样可以防止通过JavaScript等脚本语言来访问该Cookie避免了可能的脚本攻击风险提高了Cookie的安全性。
ck.setHttpOnly(true);
// 设置Cookie的最大存活时间这里设置为一年以秒为单位进行换算60秒 * 60分钟 * 24小时 * 365天表示该Cookie在客户端浏览器上保存的时长超过这个时间后浏览器会自动删除该Cookie。
// 如果设置为-1则表示永久有效若不设置这个属性maxAge不设置Cookie就不会写入硬盘只会保存在内存中仅在当前页面会话有效关闭页面后就会失效。
ck.setMaxAge(60 * 60 * 24 * 365);
// 记录写入的Cookie的名称和值的日志信息方便在调试或者查看操作记录时了解具体情况。
log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue());
// 将设置好的Cookie添加到响应对象中这样浏览器在接收到响应时就会保存这个Cookie信息。
response.addCookie(ck);
}
/**
* cookie
* @param request
* @return
* HTTPCookie
* CookieCookieCookieCOOKIE_NAMECookienull
* @param request HttpServletRequestCookie
* @return Cookienull
*/
public static String readLoginToken(HttpServletRequest request){
public static String readLoginToken(HttpServletRequest request) {
// 获取请求中的所有Cookie数组如果没有Cookie则返回null
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks){
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if (cks!= null) {
for (Cookie ck : cks) {
// 记录每个Cookie的名称和值的日志信息方便查看请求中携带的Cookie情况
log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 通过比较Cookie的名称与预定义的登录Cookie名称COOKIE_NAME是否相等来判断是否为我们要找的登录Cookie
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 如果找到对应的登录Cookie记录其名称和值的日志信息并返回其值
log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
return ck.getValue();
}
}
}
return null;
}
/**
*
* @param request
* @param response
* Cookie
* CookieCookieCOOKIE_NAMECookie0CookieCookie
* @param request HttpServletRequestCookie
* @param response HttpServletResponseCookie使Cookie
*/
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
// 获取请求中的所有Cookie数组如果没有Cookie则返回null
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks) {
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
if (cks!= null) {
for (Cookie ck : cks) {
// 通过比较Cookie的名称与预定义的登录Cookie名称COOKIE_NAME是否相等来判断是否为我们要删除的登录Cookie
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 设置要删除的Cookie的域名确保与之前设置的域名一致以便准确删除对应的Cookie
ck.setDomain(COOKIE_DOMAIN);
// 设置Cookie的路径为根目录"/"与之前写入时的路径设置保持一致确保删除的是对应的那个Cookie
ck.setPath("/");
ck.setMaxAge(0);//0表示消除此cookie
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的最大存活时间为0表示让浏览器立即删除这个Cookie
ck.setMaxAge(0);
// 记录要删除的Cookie的名称和值的日志信息方便查看操作情况
log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 将设置好要删除状态的Cookie添加到响应对象中浏览器接收到这个响应后就会删除对应的Cookie
response.addCookie(ck);
return;
}
}
}
}
}
}

@ -10,59 +10,99 @@ import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @DESC
* @DESC DateTimeUtilDate便
* Joda-Time便使JavaSimpleDateFormat
*/
public class DateTimeUtil {
//joda-time
//str->Date
//Date->str
// joda-time
// 以下定义了一个常量表示时间格式化的标准格式用于在没有指定特定格式时按照此通用格式进行日期时间的字符串与Date对象之间的转换操作格式为"yyyy-MM-dd HH:mm:ss",符合常见的日期时间展示格式要求。
// str->Date
// Date->str
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static Date strToDate(String dateTimeStr, String formatStr){
/**
* Date
* formatStrDateTimeFormatter使dateTimeStrDateTimeDateTimeJavaDate
* @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();
}
public static String dateToStr(Date date,String formatStr){
if(date == null){
/**
* Date
* DatenullDateDateTimeformatStr
* @param date Datenull
* @param formatStr Date
* @return Datenull
*/
public static String dateToStr(Date date, String formatStr) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(formatStr);
}
//固定好格式
public static Date strToDate(String dateTimeStr){
/**
* "yyyy-MM-dd HH:mm:ss"Date
* STANDARD_FORMATDateTimeFormatter使DateTimeDate
* 便
* @param dateTimeStr "yyyy-MM-dd HH:mm:ss"
* @return DateJoda-Time
*/
public static Date strToDate(String dateTimeStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date){
if(date == null){
/**
* Date"yyyy-MM-dd HH:mm:ss"
* DatenullDateDateTimeSTANDARD_FORMAT
* Date便
* @param date Datenull
* @return Datenull
*/
public static String dateToStr(Date date) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(STANDARD_FORMAT);
}
//Date -> 时间戳
/**
* Date19701100:00:00 UTC
* DatenullnullSimpleDateFormat"yyyy-MM-dd HH:mm:ss"DateDateDate
* 使
* @param date Datenullnull
* @return Datenullnull19701100:00:00 UTC
* @throws ParseException 使SimpleDateFormat
*/
public static Long dateToChuo(Date date) throws ParseException {
if(date == null){
if (date == null) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.parse(String.valueOf(date)).getTime();
}
/**
* SimpleDateFormatDateDate
* maindateToChuo
* @param args 使
* @throws ParseException 使SimpleDateFormatmain
*/
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
String time="1970-01-06 11:45:55";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = "1970-01-06 11:45:55";
Date date = format.parse(time);
System.out.print("Format To times:"+date.getTime());
System.out.print("Format To times:" + date.getTime());
}
}
}

@ -13,114 +13,130 @@ import java.io.IOException;
import java.text.SimpleDateFormat;
/**
* jackson
* @Author
* @Date
* @CONTACT
* @DESC JsonUtilJacksonJackson便JavaJSONJSONJava
* ObjectMapper使@Slf4j
*/
@Slf4j
public class JsonUtil {
// 创建一个ObjectMapper对象它是Jackson库中用于进行JSON序列化和反序列化的核心类后续通过配置它的各种属性来实现特定的序列化和反序列化规则。
private static ObjectMapper objectMapper = new ObjectMapper();
// 静态代码块在类加载时执行用于对ObjectMapper对象进行一系列的配置操作以定制其序列化和反序列化的行为。
static {
//所有字段都列入进行转换
// 设置序列化时包含的字段规则这里设置为JsonSerialize.Inclusion.ALWAYS表示所有字段都列入进行转换即无论字段值是否为null都会包含在序列化生成的JSON字符串中。
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
//取消默认转换timestamp形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
//统一时间的格式
// 取消默认将日期类型转换为时间戳timestamp形式的行为这样在序列化日期对象时会按照后续配置的日期格式进行转换而不是转换为时间戳格式。
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// 忽略在序列化空的Java 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对象不存在属性的错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
// 忽略在反序列化JSON字符串时如果JSON中存在的属性但对应的Java对象不存在该属性的这种错误情况使得即使JSON数据和Java对象结构不完全匹配也能尽量进行反序列化操作避免因属性不匹配而抛出异常。
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
*
* @param obj
* @param <T>
* @return
* JavaJSON
* nullnullString使ObjectMapperJSONIOnull
* @param obj JavaJavaJacksonJacksonGetterSetter
* @param <T> 使
* @return JSONnullnull
*/
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
* obj2StringJavaJSONJSON便
* nullnullString使ObjectMapperJSONIOnull
* @param obj JavaJavaJackson
* @param <T>
* @return JSONnullnull
*/
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
* JSONJava
* JSONJavaclazznullnullString使ObjectMapperJSONJavaIOnull
* @param str JSONJacksonJava
* @param clazz JavaClass便ObjectMapperJSONJava
* @param <T> Java使
* @return JavaJSONnullnull
*/
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
* JSONJavaTypeReference
* JSONTypeReferencenullnullTypeReferenceString使ObjectMapperTypeReferenceJSONJavaIOnull
* @param str JSONJackson
* @param typeReference TypeReferenceJava使ObjectMapper
* @param <T> Java
* @return JavaJSONTypeReferencenullnull
*/
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
* collectionClasselementClassesClassJavaType使ObjectMapperJSONJava
* IOnull
* @param str JSONJackson
* @param collectionClass ClassList.classSet.class
* @param elementClasses ClassList<String>List.classString.class
* @param <T> Java
* @return JavaIOnull
*/
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;
}
}
}
}

@ -12,22 +12,29 @@ import redis.clients.jedis.JedisPool;
* @Date 2019/1/1 15:03
* @CONTACT 317758022@qq.com
* @DESC
* CommonCacheUtilRedis
* JedisPoolWrapperJedisPoolJedisRedis使@Slf4jRedisSnailmallException便
* Spring@Component便使11
*/
@Component
@Slf4j
public class CommonCacheUtil {
// 自动注入JedisPoolWrapper对象用于获取JedisPool连接池以此来建立与Redis服务器的连接并获取Jedis客户端实例从而进行后续的缓存操作。
@Autowired
private JedisPoolWrapper jedisPoolWrapper;
/**
* key
* Redis
* JedisPoolWrapperJedisPoolJedisRedis0Jedis.select(0)使JedissetkeyvalueRedis
* SnailmallExceptionRedis
* @param key Redis
* @param value RedisRedis
*/
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 +47,17 @@ public class CommonCacheUtil {
}
/**
* key
* Rediskey
* JedisPoolWrapperJedisPoolJedisRedis0使Jedisgetvalue
* SnailmallExceptionRedis
* @param key RedisRedisnull
* @return Redisnull
*/
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 +71,19 @@ public class CommonCacheUtil {
}
/**
* key
* Redissetnx
* JedisPoolJedis0使setnx01使expiresetnx
* SnailmallExceptionRedis
* @param key Redissetnx
* @param value RedisRedis
* @param expire RedisRedis
* @return 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 +99,13 @@ public class CommonCacheUtil {
}
/**
* key
* Rediskey
* JedisPoolJedis0使JedisdelSnailmallExceptionRedis
* @param key RedisRedis.
*/
public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
@ -98,7 +117,4 @@ public class CommonCacheUtil {
}
}
}
}
}

@ -12,31 +12,58 @@ import javax.annotation.PostConstruct;
* @Author swg.
* @Date 2019/1/1 15:00
* @CONTACT 317758022@qq.com
* @DESC redisredishash
* @DESC JedisPoolWrapperJedisPoolJedisRedis
* RedisRedisHash
* Spring@Component使JedisPool使@Slf4j便
*/
@Component
@Slf4j
public class JedisPoolWrapper {
// 自动注入Parameters对象该对象应该包含了Redis相关的配置参数例如Redis服务器的主机地址、端口号以及连接池的一些配置参数最大连接数、最大空闲连接数、最大等待时间等用于后续初始化JedisPool连接池。
@Autowired
private Parameters parameters;
// 用于存储创建好的JedisPool对象初始化为null在init方法中会根据配置参数进行实例化后续通过getJedisPool方法向外提供这个连接池对象供其他代码获取并使用来与Redis服务器建立连接。
private JedisPool jedisPool = null;
/**
* @PostConstruct
* initParametersRedisJedisPoolJedisPoolConfigRedisJedisPool
* 便
*/
@PostConstruct
public void init(){
public void init() {
try {
// 创建一个JedisPoolConfig对象用于配置JedisPool连接池的各种属性比如连接池的最大连接数、最大空闲连接数、最大等待时间等以控制连接池的行为和性能。
JedisPoolConfig config = new JedisPoolConfig();
// 从Parameters对象中获取Redis连接池的最大连接数配置参数并设置到JedisPoolConfig对象中用于限制连接池中总共可以创建的最大连接数量。
config.setMaxTotal(parameters.getRedisMaxTotal());
// 从Parameters对象中获取Redis连接池的最大空闲连接数配置参数并设置到JedisPoolConfig对象中用于限制连接池中处于空闲状态的最大连接数量。
config.setMaxIdle(parameters.getRedisMaxIdle());
// 从Parameters对象中获取获取Redis连接时的最大等待时间单位为毫秒配置参数并设置到JedisPoolConfig对象中用于指定当连接池中的连接都被占用时获取连接最多等待的时间超过这个时间则会抛出异常。
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx");
// 使用配置好的JedisPoolConfig对象以及从Parameters对象中获取的Redis服务器的主机地址parameters.getRedisHost()、端口号parameters.getRedisPort()等信息创建JedisPool对象
// 这里的2000表示连接超时时间单位为毫秒"xxx"表示连接Redis服务器的密码实际应用中应替换为真实的密码通过这个构造方法实例化JedisPool连接池完成初始化工作。
jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx");
// 记录初始化Redis连接池成功的日志信息便于在查看日志时确认连接池是否正常初始化完成。
log.info("【初始化redis连接池成功】");
}catch (Exception e){
log.error("【初始化redis连接池失败】",e);
} catch (Exception e) {
// 如果在初始化过程中出现异常记录初始化Redis连接池失败的错误日志信息并将异常对象e传递进去方便查看详细的异常堆栈信息用于排查初始化失败的原因。
log.error("【初始化redis连接池失败】", e);
}
}
/**
* JedisPool使便JedisRedis
* @return JedisPoolinitjedisPoolnullnull
*/
public JedisPool getJedisPool() {
return jedisPool;
}
}
}

@ -9,25 +9,39 @@ import org.springframework.stereotype.Component;
* @Date 2019/1/1 14:27
* @CONTACT 317758022@qq.com
* @DESC
* Parameters使Spring`@Value``application.properties``application.yml`
* Spring`@Component`便使
* 使Lombok`@Data`GetterSetterToStringEqualsAndHashCode便使
*/
@Component
@Data
public class Parameters {
/*****redis config start*******/
// 使用`@Value("${redis.host}")`注解从配置文件中读取`redis.host`属性对应的值,并将其注入到`redisHost`属性中用于存储Redis服务器的主机地址。
@Value("${redis.host}")
private String redisHost;
// 同样通过`@Value("${redis.port}")`注解从配置文件获取`redis.port`属性的值,注入到`redisPort`属性用于存储Redis服务器的端口号。
@Value("${redis.port}")
private int redisPort;
// 此处注解可能存在书写错误,按照语义推测,应该是`@Value("${redis.max-total}")`用于获取Redis连接池的最大连接数配置参数注入到`redisMaxTotal`属性中。
@Value("${redis.max-idle}")
private int redisMaxTotal;
// 按照正确的语义,`@Value("${redis.max-idle}")`应该是用于获取Redis连接池的最大空闲连接数配置参数注入到`redisMaxIdle`属性,这里的注解与语义上的属性对应可能弄反了,实际使用时需留意修正。
@Value("${redis.max-total}")
private int redisMaxIdle;
// 使用`@Value("${redis.max-wait-millis}")`注解从配置文件读取Redis连接时最大等待时间单位为毫秒的配置参数并注入到`redisMaxWaitMillis`属性中,用于控制获取连接的等待时长限制。
@Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis;
/*****redis config end*******/
/*****curator config start*******/
// 通过`@Value("${zk.host}")`注解从配置文件获取`zk.host`属性对应的值,注入到`zkHost`属性中用于存储Zookeeper服务器的主机地址可能用于与Zookeeper相关的操作配置比如分布式相关的协调等场景
@Value("${zk.host}")
private String zkHost;
/*****curator config end*******/
}
}

@ -10,14 +10,28 @@ import org.springframework.web.bind.annotation.RequestParam;
* @Date 2019/1/5 14:57
* @CONTACT 317758022@qq.com
* @DESC
* ProductClientSpring Cloud OpenFeign"product-service"
* 使@FeignClient"product-service"使@RequestMapping@RequestParamHTTP便
*/
@FeignClient("product-service")
public interface ProductClient {
/**
* "product-service""/product/detail.do"
* @RequestParam("productId")"productId"IntegerIDServerResponse
* @param productId Integer
* @return ServerResponse"product-service"
*/
@RequestMapping("/product/detail.do")
ServerResponse getProductDetail(@RequestParam("productId") Integer productId);
ServerResponse getProductDetail(@RequestParam("productId") Integer productId) ;
/**
* "product-service""/product/queryProduct.do"
* @RequestParam("productId")"productId"IntegerIDServerResponse
* @param productId Integer
* @return ServerResponse"product-service"
*/
@RequestMapping("/product/queryProduct.do")
ServerResponse queryProduct(@RequestParam("productId") Integer productId);
}
}

@ -5,38 +5,58 @@ package com.njupt.swg.common.constants;
* @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com
* @DESC
* Constants便使
* Redis
*/
public class Constants {
/**自定义状态码 start**/
// 表示请求成功的状态码通常在服务端成功处理请求并返回正常结果时使用符合HTTP状态码中200表示成功的语义方便在项目中统一表示成功的响应情况。
public static final int RESP_STATUS_OK = 200;
// 表示未授权用户未登录或者权限不足等情况的状态码对应HTTP状态码中的401用于在需要进行权限校验的场景下当用户没有相应权限访问资源时返回该状态码告知客户端需要进行认证授权操作。
public static final int RESP_STATUS_NOAUTH = 401;
// 表示服务器内部错误的状态码等同于HTTP状态码中的500当服务端在处理请求过程中出现了未预期的错误例如代码运行时异常等情况时返回这个状态码给客户端表示服务端出现了问题需要进一步排查和修复。
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
// 表示请求的语法错误或者参数等不符合要求的状态码对应HTTP状态码中的400常用于客户端发送的请求格式不正确、缺少必要参数等情况提示客户端检查并修正请求内容。
public static final int RESP_STATUS_BADREQUEST = 400;
/**自定义状态码 end**/
public interface Cart{
int CHECKED = 1;//即购物车选中状态
int UN_CHECKED = 0;//购物车中未选中状态
// 以下是一个内部接口Cart用于定义与购物车相关的常量将购物车相关的常量集中在这个内部接口中进行管理使得代码结构更清晰方便对购物车相关逻辑中使用的常量进行统一维护。
public interface Cart {
// 表示购物车中商品处于选中状态的常量值为1在处理购物车中商品的选中与否逻辑时可以通过这个常量来判断和设置商品的选中状态。
int CHECKED = 1;
// 表示购物车中商品处于未选中状态的常量值为0与CHECKED常量相对应用于区分商品在购物车中的不同选中情况。
int UN_CHECKED = 0;
// 表示在购物车中对商品数量限制操作失败时的提示信息常量,其值为"LIMIT_NUM_FAIL",可用于在业务逻辑中当商品数量限制相关操作(比如添加商品数量超过限制等情况)失败时,返回给客户端相应的提示信息。
String LIMIT_NUM_FAIL = "LIMIT_NUM_FAIL";
// 表示在购物车中对商品数量限制操作成功时的提示信息常量,值为"LIMIT_NUM_SUCCESS",用于在商品数量限制相关操作成功后,向客户端反馈相应的成功提示消息。
String LIMIT_NUM_SUCCESS = "LIMIT_NUM_SUCCESS";
}
// 内部接口Product用于定义与产品状态相关的常量将产品不同状态对应的常量集中在此处管理便于在涉及产品状态判断、更新等业务逻辑中统一使用和维护这些常量。
/** 产品的状态 **/
public interface Product{
public interface Product {
// 表示产品处于上架、可销售状态的常量值为1在业务逻辑中可以通过这个常量来判断产品是否可供用户购买等情况。
int PRODUCT_ON = 1;
// 表示产品处于下架状态的常量值为2用于标识产品暂时不可销售例如库存不足、商品调整等原因导致下架时使用该常量来表示其状态。
int PRODUCT_OFF = 2;
// 表示产品已被删除的状态常量值为3当产品从系统中彻底删除后通过这个常量来体现其在业务层面的最终状态方便在数据查询、过滤等操作中基于这个状态进行相关处理。
int PRODUCT_DELETED = 3;
}
/***redis product stock**/
// 用于定义在Redis中存储产品库存相关数据时的键key的前缀字符串常量方便在Redis操作中统一管理和区分不同产品的库存相关数据后续实际的键可以在此前缀基础上添加具体的产品标识等信息来构成完整的键。
public static final String PRODUCT_TOKEN_STOCK_PREFIX = "product__stock_";
// 用于定义在Redis中存储产品相关数据除库存外可能还有其他产品相关属性等情况时的键key的前缀字符串常量同样起到统一管理和区分不同产品数据在Redis中的存储的作用便于根据这个前缀构建完整的Redis键来进行数据的读写操作。
public static final String PRODUCT_TOKEN_PREFIX = "product__";
}
}

@ -1,6 +1,5 @@
package com.njupt.swg.common.exception;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ServerResponse;
import lombok.extern.slf4j.Slf4j;
@ -12,22 +11,35 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @Author swg.
* @Date 2019/1/1 13:21
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ExceptionHandlerAdviceSpring
* 使@ControllerAdvice@ResponseBody使JSON便使@Slf4j便
*/
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionHandlerAdvice {
/**
* 使@ExceptionHandler(Exception.class)Exception
* ServerResponseServerResponsecreateByErrorCodeMessage使Constants.RESP_STATUS_INTERNAL_ERROR"系统异常,请稍后再试"
* @param e Exception
* @return ServerResponse
*/
@ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试");
public ServerResponse handleException(Exception e) {
log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试");
}
/**
* 使@ExceptionHandler(SnailmallException.class)SnailmallException
* SnailmallExceptionServerResponseServerResponsecreateByErrorCodeMessage使SnailmallExceptione.getExceptionStatus()e.getMessage()
* @param e SnailmallException
* @return ServerResponseSnailmallException
*/
@ExceptionHandler(SnailmallException.class)
public ServerResponse handleException(SnailmallException e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage());
public ServerResponse handleException(SnailmallException e) {
log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage());
}
}
}

@ -8,18 +8,32 @@ import lombok.Getter;
* @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com
* @DESC
* SnailmallExceptionJavaRuntimeException便
* 使Lombok@GetterexceptionStatusGetter便使
*/
@Getter
public class SnailmallException extends RuntimeException{
public class SnailmallException extends RuntimeException {
// 用于存储该异常对应的状态码默认初始化为ResponseEnum中定义的通用错误状态码ERROR的状态码可以在构造方法中根据具体业务需求重新赋值以此来区分不同类型的业务异常情况便于在统一处理异常时进行针对性的响应。
private int exceptionStatus = ResponseEnum.ERROR.getCode();
public SnailmallException(String msg){
/**
* msgSnailmallExceptionRuntimeExceptionmsg使ResponseEnum.ERROR.getCode()
*
* @param msg 使
*/
public SnailmallException(String msg) {
super(msg);
}
public SnailmallException(int code,String msg){
/**
* codemsgSnailmallExceptionRuntimeExceptionmsgcodeexceptionStatus
* 便
* @param code ResponseEnum
* @param msg 使
*/
public SnailmallException(int code, String msg) {
super(msg);
exceptionStatus = code;
}
}
}

@ -6,20 +6,38 @@ import lombok.Getter;
* @Author swg.
* @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ResponseEnum使便使
* 使Lombok@Gettercodedesc便
*/
@Getter
public enum ResponseEnum {
SUCCESS(0,"SUCCESS"),
ERROR(1,"ERROR"),
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"),
NEED_LOGIN(10,"NEED_LOGIN");
// 表示操作成功的枚举值状态码为0对应的描述信息是"SUCCESS",通常在业务逻辑处理成功,需要向客户端返回成功标识及相关提示时使用这个枚举值来代表成功状态。
SUCCESS(0, "SUCCESS"),
// 表示出现错误的通用枚举值状态码为1描述信息为"ERROR",用于在各种未详细区分的错误场景下,作为一个笼统的错误返回状态,告知客户端请求处理出现了问题,但没有具体指明是哪种错误类型。
ERROR(1, "ERROR"),
// 代表请求参数不合法的枚举值状态码为2描述信息是"ILLEGAL_ARGUMENTS",常用于客户端发送的请求参数不符合业务规则、格式不正确等情况时,通过返回这个枚举值对应的状态码和描述来提示客户端参数存在问题,需要进行修正后重新请求。
ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"),
// 表示需要用户登录的枚举值状态码为10描述信息为"NEED_LOGIN",当客户端尝试访问需要登录认证才能访问的资源,但未提供有效登录凭证时,服务端可以返回这个枚举值对应的状态码和描述信息,提示客户端需要先进行登录操作,然后再发起请求。
NEED_LOGIN(10, "NEED_LOGIN");
// 用于存储每个枚举值对应的状态码,不同的枚举值有不同的状态码,以此来区分不同的返回状态情况,方便在业务逻辑中根据状态码进行不同的处理逻辑分支判断。
private int code;
// 用于存储每个枚举值对应的文字描述信息,直观地展示该返回状态所代表的具体含义,便于客户端或者其他开发人员理解返回结果具体表示的情况,辅助进行相应的后续操作(如提示用户、进行日志记录等)。
private String desc;
ResponseEnum(int code,String desc){
/**
*
* SUCCESSERROR使
* @param code
* @param desc 使便
*/
ResponseEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
}

@ -10,68 +10,158 @@ import java.io.Serializable;
* @Author swg.
* @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ServerResponse使便
* Serializable便
* Lombok@Getter@JsonSerializeJSONnull
*/
@Getter
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ServerResponse<T> implements Serializable {
// 用于存储返回结果的状态码通过不同的状态码来标识请求处理的结果情况例如成功、失败以及不同类型的错误等方便客户端根据状态码来判断后续的操作逻辑一般会与项目中定义的状态码枚举如ResponseEnum相对应。
private int status;
// 用于存储返回结果的提示消息,是对状态码含义的文字描述,更直观地向客户端传达请求处理的相关情况,比如操作成功的提示语或者出现错误时的具体原因说明等内容,有助于客户端更好地向用户展示相应的信息。
private String msg;
// 用于存储具体的业务数据其类型为泛型参数T意味着可以根据不同的业务场景返回不同类型的数据例如可以是用户信息对象、商品列表、订单详情等各种与业务相关的实体数据灵活性较高能够满足多样化的业务需求。
private T data;
public ServerResponse(){}
// 默认的无参构造方法主要用于在一些特定情况下如反序列化等创建对象实例虽然当前类中暂时没有复杂的初始化逻辑依赖于这个构造方法但遵循Java Bean规范保留了它。
public ServerResponse() {}
private ServerResponse(int status){
/**
* ServerResponse使
*
* @param status status
*/
private ServerResponse(int status) {
this.status = status;
}
private ServerResponse(int status,String msg){
/**
* ServerResponse
* 使便
* @param status status
* @param msg msg
*/
private ServerResponse(int status, String msg) {
this.status = status;
this.msg = msg;
}
private ServerResponse(int status,T data){
/**
* ServerResponse
* 使
* @param status status
* @param data Tdata
*/
private ServerResponse(int status, T data) {
this.status = status;
this.data = data;
}
private ServerResponse(int status,String msg,T data){
/**
* ServerResponse
*
* @param status status
* @param msg msg
* @param data Tdata
*/
private ServerResponse(int status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
/**
* 使@JsonIgnoreJSON
* ServerResponseResponseEnumSUCCESS便
* @return truefalse
*/
@JsonIgnore
public boolean isSuccess(){
public boolean isSuccess() {
return this.status == ResponseEnum.SUCCESS.getCode();
}
/**
*
* 便ServerResponse使
* ServerResponse使ResponseEnum
*
* @param <T>
* @return ServerResponse
*/
public static <T>ServerResponse<T> createBySuccess(){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
}
public static <T>ServerResponse<T> createBySuccessMessage(String message){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message);
}
public static <T>ServerResponse<T> createBySuccess(T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data);
}
public static <T>ServerResponse<T> createBySuccess(String message,T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data);
public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
}
/**
*
* ServerResponse使ResponseEnum
* 使
* @param <T>
* @param message ServerResponsemsg
* @return ServerResponse
*/
public static <T>ServerResponse<T> createByError(){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc());
public static <T> ServerResponse<T> createBySuccessMessage(String message) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message);
}
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
/**
* ServerResponse使ResponseEnum
* 便
* @param <T> dataServerResponsedata
* @param data T
* @return ServerResponse
*/
public static <T> ServerResponse<T> createBySuccess(T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data);
}
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
/**
* ServerResponse使ResponseEnum
* 使
* @param <T> dataServerResponsedata
* @param message ServerResponsemsg
* @param data T
* @return ServerResponse
*/
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
}
/**
* 便ServerResponse便使
* ServerResponse使ResponseEnum
*
* @param <T>
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByError() {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
}
/**
* ServerResponse使ResponseEnum
* msg便
* @param <T>
* @param msg ServerResponsemsg
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
}
/**
* ServerResponse
* codemsg使便
* @param <T>
* @param code ServerResponsestatus
* @param msg ServerResponsemsg
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
return new ServerResponse<>(code, msg);
}
}

@ -6,37 +6,67 @@ import java.math.BigDecimal;
* @Author swg.
* @Date 2019/1/5 15:20
* @CONTACT 317758022@qq.com
* @DESC BigDecimal
* @DESC BigDecimalUtildoubleBigDecimal
* 便使
*/
public class BigDecimalUtil {
private BigDecimalUtil(){
// 将构造方法私有化这样外部就不能通过new关键字来创建这个类的实例因为这个类作为工具类只提供静态方法供使用不需要实例化对象来调用方法从而保证了工具类使用方式的规范性。
private BigDecimalUtil() {
}
public static BigDecimal add(double v1, double v2){
/**
* double
* doublev1v2使BigDecimalb1b2BigDecimaladdBigDecimal
* @param v1 double
* @param v2 doublev1
* @return BigDecimalv1v2
*/
public static BigDecimal add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2);
}
public static BigDecimal sub(double v1,double v2){
/**
* double
* doublev1v2BigDecimalb1b2BigDecimalsubtract
* @param v1 double使
* @param v2 doublev1
* @return BigDecimalv1v2
*/
public static BigDecimal sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2);
}
public static BigDecimal mul(double v1,double v2){
/**
* double
* doublev1v2BigDecimalb1b2BigDecimalmultiply
* @param v1 double
* @param v2 doublev1
* @return BigDecimalv1v2
*/
public static BigDecimal mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2);
}
public static BigDecimal div(double v1,double v2){
/**
* double2
* doublev1v2BigDecimalb1b2BigDecimaldivide2ROUND_HALF_UP
* @param v1 double使
* @param v2 double00v12
* @return BigDecimalv1v22
*/
public static BigDecimal div(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);//四舍五入,保留2位小数
return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP); //四舍五入,保留2位小数
//除不尽的情况
}
}
}

@ -8,67 +8,107 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* cookie
* @Author
* @Date
* @CONTACT
* @DESC CookieUtilCookieCookie便Cookie便
*/
@Slf4j
public class CookieUtil {
// 定义了Cookie的域名限定了该Cookie在哪个域名下能够被识别和使用这里设置为"oursnail.cn"意味着只有当访问该域名下的网页时浏览器才会发送对应的Cookie到服务器端以此来确保Cookie的作用范围符合项目要求。
private final static String COOKIE_DOMAIN = "oursnail.cn";
private final static String COOKIE_NAME = "snailmall_login_token";
// 定义了登录相关Cookie的名称在整个项目中通过这个固定的名称来识别和操作对应的登录Cookie这里将其命名为"snailmall_login_token"方便统一管理与登录认证相关的Cookie操作。
private final static String COOKIE_NAME = "snailmall_login_token";
/**
* cookie
* @param response
* @param token
* Cookie
* Cookie访Cookie使Cookie便访Cookie
* @param response HttpServletResponseCookieCookie
* @param token CookieCookie
*/
public static void writeLoginToken(HttpServletResponse response,String token){
Cookie ck = new Cookie(COOKIE_NAME,token);
public static void writeLoginToken(HttpServletResponse response, String token) {
// 创建一个新的Cookie对象其名称为预先定义的登录Cookie名称COOKIE_NAME值为传入的token参数以此构建一个符合项目要求的登录Cookie实例。
Cookie ck = new Cookie(COOKIE_NAME, token);
// 设置Cookie的域名属性使其与预先定义的域名COOKIE_DOMAIN一致这样浏览器就能根据该域名来判断何时发送这个Cookie到服务器确保只有访问指定域名的页面时才会携带该Cookie保障了Cookie的作用范围准确性。
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//设值在根目录
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的路径为根目录"/"这意味着该Cookie在整个域名下的所有页面路径中都有效无论用户在该域名下访问哪个具体页面浏览器都会自动携带这个Cookie发送给服务器方便在项目的各个页面中都能基于这个Cookie进行相关的登录验证等操作。
ck.setPath("/");
// 将Cookie的HttpOnly属性设置为true这样可以防止通过JavaScript等脚本语言在客户端对该Cookie进行访问和操作有效地避免了脚本攻击带来的安全风险提高了Cookie存储用户信息的安全性。
ck.setHttpOnly(true);
// 设置Cookie的最大存活时间这里设置为一年换算成秒即60秒 * 60分钟 * 24小时 * 365天表示该Cookie在客户端浏览器上能够保存的时长超过这个时间后浏览器会自动删除该Cookie。如果设置为 -1 则表示永久有效若不设置该属性maxAge不设置Cookie就只会保存在内存中不会写入硬盘且只在当前页面有效关闭页面后就会失效。
ck.setMaxAge(60 * 60 * 24 * 365);
// 记录要写入的Cookie的名称和值的日志信息方便在查看日志时了解具体的Cookie写入情况对于调试或者跟踪Cookie相关操作具有重要作用比如排查是否正确写入了期望的登录Cookie等问题。
log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue());
// 将设置好的Cookie添加到响应对象中浏览器在接收到服务器的响应时就会根据响应中的这个设置来保存相应的Cookie信息从而完成登录Cookie的写入操作。
response.addCookie(ck);
}
/**
* cookie
* @param request
* @return
* HTTPCookie
* CookieCookieCookieCOOKIE_NAMECookieCookieCookienull
* @param request HttpServletRequestCookie便
* @return CookieCookienull
*/
public static String readLoginToken(HttpServletRequest request){
public static String readLoginToken(HttpServletRequest request) {
// 获取请求中的所有Cookie数组如果请求中没有携带任何Cookie则返回null否则得到一个包含所有Cookie的数组用于后续遍历查找登录Cookie的操作。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks){
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if (cks!= null) {
for (Cookie ck : cks) {
// 记录每个Cookie的名称和值的日志信息有助于在查看日志时了解请求中具体携带了哪些Cookie方便排查与Cookie相关的问题比如确认是否正确接收到了期望的登录Cookie等情况。
log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 通过比较当前Cookie的名称与预先定义的登录Cookie名称COOKIE_NAME是否相等来判断该Cookie是否为我们要找的登录Cookie如果相等则表示找到了对应的登录Cookie。
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 记录找到的登录Cookie的名称和值的详细日志信息方便后续查看具体找到了哪个登录Cookie及其对应的值然后返回该Cookie的值用于后续的业务逻辑处理比如验证用户身份等操作。
log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
return ck.getValue();
}
}
}
return null;
}
/**
*
* @param request
* @param response
* Cookie
* CookieCookieCOOKIE_NAMECookie0CookieCookieCookie
* @param request HttpServletRequestCookie便Cookie
* @param response HttpServletResponseCookieCookieCookie
*/
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
// 获取请求中的所有Cookie数组如果请求中没有携带Cookie则返回null否则得到包含所有Cookie的数组用于后续遍历查找要删除的登录Cookie的操作。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks) {
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
if (cks!= null) {
for (Cookie ck : cks) {
// 通过比较当前Cookie的名称与预先定义的登录Cookie名称COOKIE_NAME是否相等来判断该Cookie是否为我们要删除的登录Cookie如果相等则表示找到了对应的登录Cookie接下来进行删除操作。
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 设置要删除的Cookie的域名属性使其与预先定义的域名COOKIE_DOMAIN一致确保能够准确地删除对应的登录Cookie避免误删其他同名但不同域名下的Cookie情况发生。
ck.setDomain(COOKIE_DOMAIN);
// 设置Cookie的路径为根目录"/"与之前写入该Cookie时设置的路径保持一致这样才能准确地找到并删除对应的登录Cookie确保删除操作的准确性。
ck.setPath("/");
ck.setMaxAge(0);//0表示消除此cookie
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的最大存活时间为0表示让浏览器立即删除这个Cookie从而实现从客户端清除该登录Cookie的目的完成注销时的Cookie删除操作。
ck.setMaxAge(0);
// 记录要删除的Cookie的名称和值的日志信息方便在查看日志时了解具体的Cookie删除情况有助于排查与Cookie删除相关的问题比如确认是否正确地向浏览器发送了删除登录Cookie的指令等情况。
log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 将设置好要删除状态的Cookie添加到响应对象中浏览器在接收到服务器的这个响应后就会根据设置删除对应的Cookie从而完成从客户端删除登录Cookie的操作。
response.addCookie(ck);
return;
}
}
}
}
}
}

@ -4,65 +4,99 @@ import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @DESC
* @DESC DateTimeUtilJoda-TimeJavaDate便使
*/
public class DateTimeUtil {
//joda-time
//str->Date
//Date->str
// 引入Joda-Time库来进行时间相关的操作相比Java原生的日期时间处理方式Joda-Time提供了更方便、灵活且不易出错的接口和功能适用于各种复杂的时间处理场景。
// str->Date 表示可以将表示时间的字符串转换为Date对象Date->str表示能把Date对象转换为对应的表示时间的字符串以下定义了一些相关的方法来实现这些转换功能。
// 定义了一个表示标准时间格式的常量,格式为"yyyy-MM-dd HH:mm:ss",符合常见的日期时间展示格式,在没有特别指定其他格式的情况下,很多时间转换操作会默认使用这个格式,保证时间表示的规范性和统一性。
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static Date strToDate(String dateTimeStr, String formatStr){
/**
* Date
* formatStrDateTimeFormatter使dateTimeStrDateTimeDateTimeJavaDateDate
* @param dateTimeStr formatStrformatStr"yyyy-MM-dd"dateTimeStr
* @param formatStr "yyyy-MM-dd HH:mm:ss""yyyy-MM-dd"DateTimeFormatter
* @return 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){
if(date == null){
/**
* Date
* DatenullnullDatenullDateDateTimeformatStrDate
* @param date Datenull
* @param formatStr Date"yyyy-MM-dd"--
* @return Datenull
*/
public static String dateToStr(Date date, String formatStr) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(formatStr);
}
//固定好格式
public static Date strToDate(String dateTimeStr){
/**
* "yyyy-MM-dd HH:mm:ss"Date
* STANDARD_FORMATDateTimeFormatter使DateTimeDate便
* @param dateTimeStr "yyyy-MM-dd HH:mm:ss""2024-12-18 10:20:30"
* @return DateJoda-Time
*/
public static Date strToDate(String dateTimeStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date){
if(date == null){
/**
* Date"yyyy-MM-dd HH:mm:ss"
* DatenullDateDateTimeSTANDARD_FORMATDate便
* @param date Datenull
* @return Datenull
*/
public static String dateToStr(Date date) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(STANDARD_FORMAT);
}
//Date -> 时间戳
/**
* Date19701100:00:00 UTC
* DatenullnullSimpleDateFormat"yyyy-MM-dd HH:mm:ss"DateDateDate使
* @param date Datenullnull
* @return Datenullnull19701100:00:00 UTC
* @throws ParseException 使SimpleDateFormat使try-catch
*/
public static Long dateToChuo(Date date) throws ParseException {
if(date == null){
if (date == null) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.parse(String.valueOf(date)).getTime();
}
/**
* SimpleDateFormatDateDate
* maindateToChuo
* @param args 使使
* @throws ParseException 使SimpleDateFormatmain使try-catch
*/
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
String time="1970-01-06 11:45:55";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = "1970-01-06 11:45:55";
Date date = format.parse(time);
System.out.print("Format To times:"+date.getTime());
System.out.print("Format To times:" + date.getTime());
}
}
}

@ -8,119 +8,134 @@ import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import java.io.IOException;
import java.text.SimpleDateFormat;
/**
* jackson
* @Author
* @Date
* @CONTACT
* @DESC JsonUtilJackson
* JacksonObjectMapper使JavaJSONJSONJava便
*/
@Slf4j
public class JsonUtil {
// 创建一个ObjectMapper对象它是Jackson库中核心的对象负责实际执行序列化和反序列化的具体操作后续通过配置它的各种属性来定制序列化和反序列化的行为规则。
private static ObjectMapper objectMapper = new ObjectMapper();
// 静态代码块在类加载时执行用于对ObjectMapper对象进行一系列的配置操作使得它符合项目在序列化和反序列化方面的特定要求。
static {
//所有字段都列入进行转换
// 设置序列化时包含的字段规则这里指定为JsonSerialize.Inclusion.ALWAYS表示在将Java对象序列化为JSON字符串时无论对象的字段值是否为null都会把所有字段都包含进转换后的JSON字符串中确保完整地表示对象的结构和数据。
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
//取消默认转换timestamp形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
//统一时间的格式
// 取消默认将日期类型转换为时间戳timestamp形式的行为。通常情况下Jackson默认会把日期对象转换为时间戳进行序列化通过设置这个配置为false就可以按照后续指定的日期格式进行序列化更符合常见的日期展示需求。
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// 忽略在序列化空的Java Bean即没有属性值或者属性值都为null的对象转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对象不存在属性的错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
// 忽略在反序列化JSON字符串时如果JSON中存在的属性但对应的Java对象不存在该属性的这种错误情况。这样即使JSON数据和Java对象结构不完全匹配例如JSON有多余的字段也能尽量进行反序列化操作避免因属性不匹配而直接抛出异常增强了反序列化的兼容性。
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
*
* @param obj
* @param <T>
* @return
* JavaJSON
* nullnullStringJSON使ObjectMapperJSONIOnull
* @param obj JavaJacksonJavaGetterSetter<T>
* @param <T> 使Java
* @return JSONnullnullJSONJSON
*/
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
* obj2StringJavaJSONJSON便
* 使JSONnullnullString使ObjectMapperJSONIOnull
* @param obj JavaJacksonJava<T>JSON
* @param <T> 使JSON便使
* @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
* JSONJava
* JSONJavaclazznullnullString使ObjectMapperJSONJavaIOnull
* @param str JSONJacksonJavaJSON
* @param clazz JavaClass便ObjectMapperJSONJava<T>
* @param <T> Java使JSONJava便JSON
* @return JavaJSONnullnullJava
*/
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
* JSONJavaTypeReference
* JSONTypeReferencenullnullTypeReferenceString使ObjectMapperTypeReferenceJSONJavaIOnull
* @param str JSONJacksonJSONJSON
* @param typeReference TypeReferenceJavaList<String>Map<Integer, User>使ObjectMapper<T>Java
* @param <T> Java使JSONJava
* @return JavaJSONTypeReferencenullnullJava
*/
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
* collectionClasselementClassesClassJavaType使ObjectMapperJSONJava
* List<User>JSONList.classUser.classIOnull
* @param str JSONJacksonJSONJSON
* @param collectionClass ClassList.classSet.classMap.class<T>
* @param elementClasses ClassList<User>List.classUser.class便ObjectMapper
* @param <T> Java使便使
* @return JavaIOnullJava
*/
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;
}
}
}
}

@ -3,9 +3,19 @@ package com.njupt.swg.common.utils;
import java.security.MessageDigest;
/**
* MD5
* @Author
* @Date
* @CONTACT
* @DESC MD5UtilMD5JavaMessageDigestMD5便UTF-8MD5
*/
public class MD5Util {
/**
*
* byteToHexStringMD5便使
* @param b MD5MessageDigestdigestMD5
* @return [10, 20, 30]"0a141e"
*/
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
@ -14,6 +24,12 @@ public class MD5Util {
return resultSb.toString();
}
/**
*
* Java256161644hexDigitsbyteArrayToHexString
* @param b -128127Java10"0a"
* @return 2byteArrayToHexString
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
@ -24,11 +40,11 @@ public class MD5Util {
}
/**
* MD5
*
* @param origin
* @param charsetname
* @return
* MD5使null
* MessageDigest使MD5MessageDigestdigestbyteArrayToHexStringnullMD5
* @param origin MD5MD5
* @param charsetname "utf-8""gbk"null使使UTF-8
* @return MD5MessageDigestnull使
*/
private static String MD5Encode(String origin, String charsetname) {
String resultString = null;
@ -44,16 +60,26 @@ public class MD5Util {
return resultString.toUpperCase();
}
/**
* 便UTF-8MD5
* MD5Encode"utf-8"使UTF-8MD5便MD5Encode便使UTF-8MD5
* @param origin MD5UTF-8
* @return UTF-8MD5null
*/
public static String MD5EncodeUtf8(String origin) {
//这里可以加盐
return MD5Encode(origin, "utf-8");
}
/**
* MD5EncodeUtf8"123456"MD5便MD5
* @param args 使使
*/
public static void main(String[] args) {
System.out.println(MD5EncodeUtf8("123456"));
}
// 预先定义的十六进制数字字符数组用于在将字节转换为十六进制字符串时根据字节对应的十六进制数字索引获取相应的字符表示其中包含了0到9以及a到f的十六进制数字字符按照顺序排列方便在byteToHexString等方法中进行十六进制转换操作。
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}
}

@ -12,35 +12,58 @@ import java.util.Properties;
* @Date 2018/1/10 14:56
* @DESC
* @CONTACT 317758022@qq.com
* PropertiesUtilparameter.properties便便
*/
@Slf4j
public class PropertiesUtil {
// 用于存储从配置文件中读取的属性信息是一个Properties对象它以键值对的形式保存配置文件中的各项配置内容后续通过键来获取对应的值实现对配置信息的访问和使用。
private static Properties props;
// 静态代码块在类加载时执行主要用于加载配置文件parameter.properties到Properties对象中以便后续可以获取其中的配置属性值。
static {
// 指定要加载的配置文件的名称,这里固定为"parameter.properties",意味着该工具类默认会从类路径下查找这个名称的配置文件进行加载操作,如果实际配置文件名称不同或者路径有变化,需要相应地修改此处。
String fileName = "parameter.properties";
// 创建一个新的Properties对象用于后续存放从配置文件中读取的键值对信息它提供了加载配置文件以及获取属性值等相关方法是Java中处理配置文件属性的常用类。
props = new Properties();
try {
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
// 通过类加载器获取指定配置文件fileName的输入流然后使用InputStreamReader将其包装为字符流并指定字符编码为"UTF-8"最后调用Properties对象的load方法将配置文件的内容加载到props对象中完成配置文件的读取操作。
// 如果在这个过程中出现IO异常例如配置文件不存在、无法正确读取等情况则会捕获该异常并通过日志记录错误信息方便后续查看和排查配置文件读取失败的原因。
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName), "UTF-8"));
} catch (IOException e) {
log.error("配置文件读取异常",e);
log.error("配置文件读取异常", e);
}
}
public static String getProperty(String key){
/**
*
* Propertiespropsnullnull便使
* @param key null
* @return null
*/
public static String getProperty(String key) {
String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){
if (StringUtils.isBlank(value)) {
return null;
}
return value.trim();
}
public static String getProperty(String key,String defaultValue){
/**
* getProperty(String key)
* PropertiespropsnulldefaultValuevaluevalue使便使
* @param key 使
* @param defaultValue "DEBUG"
* @return
*/
public static String getProperty(String key, String defaultValue) {
String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){
if (StringUtils.isBlank(value)) {
value = defaultValue;
}
return value.trim();
}
}
}

@ -10,7 +10,6 @@ import com.njupt.swg.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
/**
@ -18,23 +17,40 @@ import javax.servlet.http.HttpServletRequest;
* @Date 2019/1/5 16:19
* @CONTACT 317758022@qq.com
* @DESC
* BaseController
* CommonCacheUtilCookieUtilJsonUtilCookieJSONUser
*/
@Slf4j
public class BaseController {
// 通过Spring的依赖注入机制自动注入CommonCacheUtil对象这个对象通常用于和缓存进行交互比如从缓存中获取存储的用户相关数据等操作方便在后续获取当前用户信息时使用缓存来提高效率、减少数据库查询等操作。
@Autowired
private CommonCacheUtil commonCacheUtil;
User getCurrentUser(HttpServletRequest httpServletRequest){
/**
* HTTP
* CookieUtilHttpServletRequestCookienullSnailmallException
* 使CommonCacheUtilJSONJSONnullResponseEnum.NEED_LOGIN.getCode()ResponseEnum.NEED_LOGIN.getDesc()SnailmallException
* JSONJsonUtilJSONUserUser
* @param httpServletRequest HTTPCookie
* @return UserBaseController使
* @throws SnailmallException
*/
User getCurrentUser(HttpServletRequest httpServletRequest) {
// 从请求中读取登录令牌Cookie中的值用于后续判断用户是否登录以及从缓存中获取对应的用户信息若返回为空则表示没有找到登录相关的Cookie即用户未登录。
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)){
if (StringUtils.isEmpty(loginToken)) {
throw new SnailmallException("用户未登陆,无法获取当前用户信息");
}
// 根据获取到的登录令牌从缓存中获取对应的用户信息的JSON字符串表示形式缓存中通常会预先存储用户登录成功后放入的相关用户信息方便后续快速获取若返回为null则表示缓存中不存在该用户信息可能是登录状态失效等原因导致。
String userJsonStr = commonCacheUtil.getCacheValue(loginToken);
if(userJsonStr == null){
throw new SnailmallException(ResponseEnum.NEED_LOGIN.getCode(),ResponseEnum.NEED_LOGIN.getDesc());
if (userJsonStr == null) {
throw new SnailmallException(ResponseEnum.NEED_LOGIN.getCode(), ResponseEnum.NEED_LOGIN.getDesc());
}
User user = JsonUtil.Str2Obj(userJsonStr,User.class);
// 利用JsonUtil工具类将从缓存中获取到的用户信息的JSON字符串反序列化为User对象使得可以在后续业务逻辑中直接使用User对象的属性和方法进行相关操作比如获取用户权限、展示用户详情等操作完成获取当前用户信息的关键步骤。
User user = JsonUtil.Str2Obj(userJsonStr, User.class);
return user;
}
}
}

@ -1,19 +1,19 @@
package com.njupt.swg.controller;
import com.njupt.swg.cache.CommonCacheUtil;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ResponseEnum;
import com.njupt.swg.common.resp.ServerResponse;
import com.njupt.swg.common.utils.JsonUtil;
import com.njupt.swg.entity.User;
import com.njupt.swg.service.ICartService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import com.njupt.swg.cache.CommonCacheUtil;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ResponseEnum;
import com.njupt.swg.common.resp.ServerResponse;
import com.njupt.swg.common.utils.JsonUtil;
import com.njupt.swg.entity.User;
import com.njupt.swg.service.ICartService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
* @Author swg.
@ -23,168 +23,277 @@ import java.util.Enumeration;
*/
@RestController
@RequestMapping("/cart")
public class CartController extends BaseController{
// CartController类继承自BaseController类这意味着它可以继承BaseController中定义的通用方法和属性
// 例如获取当前用户信息的相关逻辑等,主要负责处理购物车模块相关的各种业务请求,是购物车功能在后端的具体控制器实现。
public class CartController extends BaseController {
// 通过Spring框架的依赖注入功能自动将实现了ICartService接口的实例注入到这个属性中。
// ICartService接口应该定义了一系列用于处理购物车业务逻辑的方法例如添加商品、更新商品数量等操作对应的方法
// 在本类的各个请求处理方法中会调用该服务层接口的方法来完成具体的业务功能。
@Autowired
private ICartService cartService;
// 同样通过Spring的依赖注入机制注入CommonCacheUtil实例。
// CommonCacheUtil通常用于与缓存系统进行交互比如从缓存中获取用户相关的数据等操作
// 在某些购物车相关业务处理中(例如需要获取缓存中的用户信息来确定购物车归属等情况)会起到重要作用。
@Autowired
private CommonCacheUtil commonCacheUtil;
/**
* 1.
* "limitQuantity"
* LIMIT_NUM_FAIL LIMIT_NUM_SUCCESS
* 1.
* HTTP
* "limitQuantity"
* LIMIT_NUM_FAILLIMIT_NUM_SUCCESS便
* BaseControllergetCurrentUser
* user.getId()productIdcountcartServiceadd
* cartServiceServerResponse
*
* @param httpServletRequest HTTP
* productIdcountCookie
* @param productId Integer
* cartServiceadd
* @param count
*
* @return ServerResponse
* LIMIT_NUM_SUCCESS
* LIMIT_NUM_FAIL
*/
@RequestMapping("add.do")
public ServerResponse add(HttpServletRequest httpServletRequest, Integer productId, Integer count){
public ServerResponse add(HttpServletRequest httpServletRequest, Integer productId, Integer count) {
User user = getCurrentUser(httpServletRequest);
return cartService.add(user.getId(),productId,count);
return cartService.add(user.getId(), productId, count);
}
/**
* 2.
* "limitQuantity"
* LIMIT_NUM_FAIL LIMIT_NUM_SUCCESS
* 2.
* HTTP
* "limitQuantity"
* LIMIT_NUM_FAILLIMIT_NUM_SUCCESS便
* user.getId()productIdcountcartServiceupdate
* cartServiceServerResponse
*
* @param httpServletRequest HTTP
* @param productId
* cartServiceproductIdcount
* @param count
*
* @return ServerResponse
* LIMIT_NUM_SUCCESS
* LIMIT_NUM_FAIL
*/
@RequestMapping("update.do")
public ServerResponse update(HttpServletRequest httpServletRequest,Integer productId,Integer count){
public ServerResponse update(HttpServletRequest httpServletRequest, Integer productId, Integer count) {
User user = getCurrentUser(httpServletRequest);
return cartService.update(user.getId(),productId,count);
return cartService.update(user.getId(), productId, count);
}
/**
* 3.
* 3.
* HTTP
* user.getId()productIdscartServicedelete
* productIds
* cartServiceproductIdsServerResponse
*
* @param httpServletRequest HTTPproductIds
* @param productIds String
* ID"123"ID"123,456,789"cartService
* @return ServerResponse
*
*/
@RequestMapping("delete_product.do")
public ServerResponse delete_product(HttpServletRequest httpServletRequest,String productIds){
public ServerResponse delete_product(HttpServletRequest httpServletRequest, String productIds) {
User user = getCurrentUser(httpServletRequest);
return cartService.delete(user.getId(),productIds);
return cartService.delete(user.getId(), productIds);
}
/**
* 4.List
* ,2
* 4. List
* HTTP
* user.getId()cartServicelist
* cartServicelistServerResponse
* 2便
*
* @param httpServletRequest HTTP
* @return ServerResponse
* 2便
*/
@RequestMapping("list.do")
public ServerResponse list(HttpServletRequest httpServletRequest){
public ServerResponse list(HttpServletRequest httpServletRequest) {
User user = getCurrentUser(httpServletRequest);
return cartService.list(user.getId());
}
/**
* 5.
*/
/**
* 5.
* HTTP
* BaseController`getCurrentUser``httpServletRequest`
*
* `user.getId()``Constants.Cart.CHECKED``null``cartService``selectOrUnSelect`
* `cartService``ServerResponse`
*
* @param httpServletRequest HTTPCookie
* @return `ServerResponse`便
*/
@RequestMapping("select_all.do")
public ServerResponse select_all(HttpServletRequest httpServletRequest){
public ServerResponse select_all(HttpServletRequest httpServletRequest) {
User user = getCurrentUser(httpServletRequest);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED,null);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED, null);
}
/**
* 6.
* 6.
* HTTP
* `getCurrentUser``httpServletRequest`
* `user.getId()``Constants.Cart.UN_CHECKED``null``cartService``selectOrUnSelect`
* `cartService``ServerResponse`
*
* @param httpServletRequest HTTP
* @return `ServerResponse`便
*/
@RequestMapping("un_select_all.do")
public ServerResponse un_select_all(HttpServletRequest httpServletRequest){
public ServerResponse un_select_all(HttpServletRequest httpServletRequest) {
User user = getCurrentUser(httpServletRequest);
return cartService.selectOrUnSelect(user.getId(),Constants.Cart.UN_CHECKED,null);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.UN_CHECKED, null);
}
/**
* 7.
* 7.
* HTTP
* `getCurrentUser``httpServletRequest`
* `user.getId()``Constants.Cart.CHECKED``productId``cartService``selectOrUnSelect`
* `cartService``ServerResponse`
*
* @param httpServletRequest HTTP`productId`
* @param productId `cartService`
* @return `ServerResponse`便
*/
@RequestMapping("select.do")
public ServerResponse select(HttpServletRequest httpServletRequest,Integer productId){
public ServerResponse select(HttpServletRequest httpServletRequest, Integer productId) {
User user = getCurrentUser(httpServletRequest);
return cartService.selectOrUnSelect(user.getId(),Constants.Cart.CHECKED,productId);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED, productId);
}
/**
* 8.
* 8.
* HTTP
* `getCurrentUser``httpServletRequest`
* `user.getId()``Constants.Cart.UN_CHECKED``productId``cartService``selectOrUnSelect`
* `cartService``productId``ServerResponse`
*
* @param httpServletRequest HTTP`productId`
* @param productId `cartService`
* @return `ServerResponse`便
*/
@RequestMapping("un_select.do")
public ServerResponse un_select(HttpServletRequest httpServletRequest,Integer productId){
public ServerResponse un_select(HttpServletRequest httpServletRequest, Integer productId) {
User user = getCurrentUser(httpServletRequest);
return cartService.selectOrUnSelect(user.getId(),Constants.Cart.UN_CHECKED,productId);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.UN_CHECKED, productId);
}
/**
* 9.
* 9.
* HTTP
* `getCurrentUser``httpServletRequest`
* `user.getId()``cartService``get_cart_product_count`
* `cartService``get_cart_product_count``ServerResponse<Integer>`
* `ServerResponse<Integer>``<Integer>``ServerResponse`便
*
* @param httpServletRequest HTTP
* @return `ServerResponse<Integer>`
*/
@RequestMapping("get_cart_product_count.do")
public ServerResponse<Integer> get_cart_product_count(HttpServletRequest httpServletRequest){
public ServerResponse<Integer> get_cart_product_count(HttpServletRequest httpServletRequest) {
User user = getCurrentUser(httpServletRequest);
return cartService.get_cart_product_count(user.getId());
}
/**
* feignuserId
* feignuserId
* FeignHTTP
* `User``null`
* `httpServletRequest``headerNames``null`
* "snailmall_login_token"`value``StringUtils.isBlank(value)``true``ServerResponse`
* `commonCacheUtil``getCacheValue`JSON`userJsonStr`JSON`null``ServerResponse`
* JSON使`JsonUtil``Str2Obj``User``user`
* `user``null``user.getId()``cartService``list``ServerResponse``user``null``ServerResponse`
*
* @param httpServletRequest HTTPFeign
* @return `ServerResponse`
*/
@RequestMapping("getCartList.do")
public ServerResponse getCartList(HttpServletRequest httpServletRequest){
public ServerResponse getCartList(HttpServletRequest httpServletRequest) {
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")){
if (name.equalsIgnoreCase("snailmall_login_token")) {
String value = httpServletRequest.getHeader(name);
if(StringUtils.isBlank(value)){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (StringUtils.isBlank(value)) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
String userJsonStr = commonCacheUtil.getCacheValue(value);
if(userJsonStr == null){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (userJsonStr == null) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
user = JsonUtil.Str2Obj(userJsonStr,User.class);
user = JsonUtil.Str2Obj(userJsonStr, User.class);
}
}
}
if (user == null){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (user == null) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
return cartService.list(user.getId());
}
/**
* feign
* feign
* FeignHTTP
* `User``null`
* `headerNames``null`"snailmall_login_token"`value`
* `ServerResponse`
* `commonCacheUtil`JSON`userJsonStr`JSON`null``ServerResponse`
* JSON使`JsonUtil``Str2Obj``User``user`
* `user``null``user.getId()``cartService``removeCart``ServerResponse``user``null``ServerResponse`
*
* @param httpServletRequest HTTPFeign
* @return `ServerResponse`
*/
@RequestMapping("removeCart.do")
public ServerResponse removeCart(HttpServletRequest httpServletRequest){
public ServerResponse removeCart(HttpServletRequest httpServletRequest) {
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")){
if (name.equalsIgnoreCase("snailmall_login_token")) {
String value = httpServletRequest.getHeader(name);
if(StringUtils.isBlank(value)){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (StringUtils.isBlank(value)) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
String userJsonStr = commonCacheUtil.getCacheValue(value);
if(userJsonStr == null){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (userJsonStr == null) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
user = JsonUtil.Str2Obj(userJsonStr,User.class);
user = JsonUtil.Str2Obj(userJsonStr, User.class);
}
}
}
if (user == null){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (user == null) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
return cartService.removeCart(user.getId());
}
}
}

@ -3,34 +3,64 @@ package com.njupt.swg.dao;
import com.njupt.swg.entity.Cart;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// @Mapper注解用于标识这个接口是MyBatis的Mapper接口MyBatis框架会自动扫描并生成该接口的代理实现类
// 从而可以与数据库进行交互执行对应的SQL操作这个接口主要定义了针对购物车Cart相关数据的数据库操作方法。
@Mapper
public interface CartMapper {
// 根据购物车记录的主键通常是一个唯一标识该记录的整数型ID删除对应的购物车记录返回值为受影响的行数
// 如果成功删除一条记录返回值通常为1如果没有匹配的记录可删除则返回0。
int deleteByPrimaryKey(Integer id);
// 向数据库中插入一条完整的购物车记录Cart对象这个方法会将Cart对象中的所有属性对应的字段值插入到数据库表中
// 返回值同样为受影响的行数若插入成功一般返回1表示成功插入了一条记录。
int insert(Cart record);
// 插入购物车记录但允许只插入Cart对象中部分非空的属性对应的字段值到数据库表中
// 相比insert方法更加灵活当Cart对象某些属性为null时只会插入那些非null属性对应的字段返回值为受影响的行数。
int insertSelective(Cart record);
// 根据购物车记录的主键id从数据库中查询并返回对应的购物车记录以Cart对象形式返回
// 如果找不到对应的记录则返回null。
Cart selectByPrimaryKey(Integer id);
// 根据购物车记录的主键id来有选择性地更新购物车记录只会更新Cart对象中那些非null属性对应的数据库字段值
// 返回值为受影响的行数用于指示实际更新了几条记录若没有任何字段值被更新比如传入的Cart对象属性都是null或者和数据库中原有值相同则返回0。
int updateByPrimaryKeySelective(Cart record);
// 根据购物车记录的主键id更新购物车记录会将Cart对象中的所有属性对应的字段值更新到数据库表中对应的记录里
// 不管属性值是否为null返回值为受影响的行数用来表示实际更新了几条记录。
int updateByPrimaryKey(Cart record);
// 根据用户的IDuserId和商品的IDproductId联合查询购物车中对应的记录
// 通过@Param注解明确指定了参数的名称方便在对应的SQL语句中使用这些参数进行准确的条件查询
// 返回值是匹配条件的购物车记录以Cart对象形式返回若找不到符合条件的记录则返回null。
Cart selectByUserIdProductId(@Param("userId") Integer userId, @Param("productId") Integer productId);
// 根据用户的IDuserId查询该用户购物车中的所有记录返回一个包含多个Cart对象的List集合
// 如果该用户没有购物车记录则返回一个空的List集合。
List<Cart> selectCartByUserId(Integer userId);
// 根据用户的IDuserId查询该用户购物车中已选中商品的状态数量具体含义可能根据业务逻辑而定比如返回已选中商品的记录条数等
// 返回一个整数类型的结果,例如返回已选中商品的数量或者代表选中状态的某种统计数值等。
int selectCartCheckedStatusByUserId(Integer userId);
// 根据用户的IDuserId以及要删除商品的ID列表productIdList从购物车中删除对应的商品记录
// 通过@Param注解分别指定了参数名称便于在对应的SQL语句中准确使用这些参数进行批量删除操作
// 返回值为受影响的行数,代表实际删除了几条记录。
int deleteByProductIds(@Param("userId") Integer userId, @Param("productIdList") List<String> productIdList);
// 根据用户的IDuserId、商品的选中状态checked可能是用整数表示选中或未选中比如1表示选中0表示未选中等具体由业务定义以及商品的IDproductId
// 来设置商品在购物车中的选中或未选中状态此方法通常用于更新购物车中某个商品的选中情况无返回值void类型
// 对应的SQL操作会根据传入的参数对数据库表中的相应字段进行更新操作。
void selectOrUnSelectProduct(@Param("userId") Integer userId, @Param("checked") int checked, @Param("productId") Integer productId);
// 根据用户的IDuserId查询该用户购物车中商品的总数量返回一个整数类型的结果用于统计该用户购物车中一共有多少件商品。
int selectCartProductCount(Integer userId);
// 根据用户的IDuserId查询该用户购物车中所有已选中商品的记录返回一个包含多个已选中Cart对象的List集合
// 方便后续对已选中商品进行相关业务处理比如计算总价等操作若没有已选中商品则返回一个空的List集合。
List<Cart> selectCheckedCartByUserId(Integer userId);
}

@ -4,26 +4,44 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
// 使用Lombok的 @Data注解会自动为这个类生成常用的方法比如Getter、Setter方法
// 以及toString、hashCode、equals等方法简化了代码编写让实体类的属性访问和操作更加便捷。
@Data
// 使用 @AllArgsConstructor注解Lombok会为这个类自动生成一个包含所有参数的构造函数
// 方便在创建对象时一次性传入所有属性的值进行初始化。
@AllArgsConstructor
// 使用 @NoArgsConstructor注解Lombok会为这个类自动生成一个无参构造函数
// 这在很多框架(如一些序列化、反序列化的场景或者通过反射创建对象的情况)中是必要的,确保类有默认的构造方式。
@NoArgsConstructor
public class Cart {
// 购物车记录的唯一标识符,通常在数据库中作为主键使用,用于区分不同的购物车记录,类型为整数类型。
private Integer id;
// 关联的用户的唯一标识符,用于表明这个购物车记录属于哪个用户,通过这个字段可以将购物车与具体的用户关联起来,方便查询某个用户的购物车相关信息,类型为整数类型。
private Integer userId;
// 购物车中商品的唯一标识符用于确定购物车中存放的是哪个具体商品方便在业务逻辑中根据商品ID查找商品详情、库存等信息类型为整数类型。
private Integer productId;
// 商品在购物车中的数量代表用户添加该商品到购物车的数量情况例如用户添加了3件某商品到购物车这个字段的值就会是3类型为整数类型。
private Integer quantity;
// 用于表示商品在购物车中的选中状态具体取值含义可能由业务逻辑定义比如可以用1表示选中0表示未选中等方便在购物车相关操作如全选、取消全选、单独选中某个商品等操作中标记商品的状态类型为整数类型。
private Integer checked;
// 使用 @JsonFormat注解来指定日期类型字段的序列化格式这里将createTime字段在序列化为JSON字符串时
// 按照指定的格式("yyyy-MM-dd HH:mm:ss.SSS",即年-月-日 时:分:秒.毫秒)进行格式化,方便在前后端数据交互或者数据存储时,日期格式能够统一且符合预期的展示需求。
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date createTime;
// 与createTime类似也是使用 @JsonFormat注解来指定日期类型字段的序列化格式
// updateTime字段在序列化为JSON字符串时按照"yyyy-MM-dd HH:mm:ss.SSS"格式进行格式化,
// 通常用于记录购物车记录最后一次更新的时间,方便跟踪数据的变化情况以及进行一些基于时间的业务逻辑处理(如判断数据是否过期等情况)。
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date updateTime;
}

@ -3,35 +3,70 @@ package com.njupt.swg.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
// 使用Lombok的 @Data注解它会自动帮我们生成这个类的一系列常用方法包括所有属性的Getter和Setter方法
// 以及重写的toString方法、hashCode方法和equals方法等。这样可以减少大量样板代码的编写让代码看起来更简洁
// 同时也方便在其他地方对类的属性进行访问和操作。
@Data
// @AllArgsConstructor注解指示Lombok为这个类生成一个包含所有参数的构造函数。
// 这意味着我们可以通过传入所有属性的值来便捷地创建一个Product类的实例例如
// Product product = new Product(1, 2, "商品名称", "商品副标题", "主图路径", "子图路径列表", "商品详情", new BigDecimal("9.99"), 100, 1, new Date(), new Date());
@AllArgsConstructor
// @NoArgsConstructor注解让Lombok为这个类生成一个无参构造函数。
// 在很多情况下,比如通过反射创建对象、一些序列化和反序列化的场景中,无参构造函数是必不可少的,
// 确保类有默认的创建实例的方式。
@NoArgsConstructor
public class Product {
// 商品的唯一标识符,在整个系统中用于区分不同的商品,通常会作为数据库表中的主键字段,类型为整数类型,
// 其他业务逻辑或者数据库操作往往会基于这个ID来对特定的商品进行查询、更新、删除等操作。
private Integer id;
// 商品所属的分类的标识符,用于将商品归类到不同的类别下,方便进行分类查询、展示等操作,比如按照不同的品类展示商品列表,类型为整数类型,
// 通过这个字段可以关联到对应的商品分类信息,了解商品所属的大类别或者小类别等情况。
private Integer categoryId;
// 商品的名称,用于展示给用户,让用户直观地知道商品是什么,是一个字符串类型的属性,例如"华为P50手机"等,
// 在商品列表展示、详情页展示等场景中会用到这个字段来呈现商品的基本信息。
private String name;
// 商品的副标题,通常可以用来补充说明商品的一些特点、卖点或者优惠信息等内容,也是字符串类型,
// 比如"华为P50手机超感知徕卡四摄限时优惠",可以进一步吸引用户关注该商品。
private String subtitle;
// 商品的主图片的路径或者标识,一般用于在前端页面展示商品的主要图片,字符串类型,
// 这个路径可以是相对路径或者绝对路径,指向存储商品主图的位置(可能是服务器本地存储、云存储等),方便前端根据这个路径来加载并展示图片。
private String mainImage;
// 商品的子图片的路径或者标识列表,以字符串形式存储,通常用特定的分隔符(如逗号等)将多个子图片的路径分隔开,
// 用于展示商品的多角度、多细节图片,让用户更全面地了解商品外观、功能等方面的情况,方便前端加载并展示商品的多张图片。
private String subImages;
// 商品的详细描述信息,包含了商品的各种详细参数、功能介绍、使用说明等内容,是一个比较长的字符串类型,
// 在商品详情页中会完整地展示这些信息,帮助用户深入了解商品的具体情况,以便做出购买决策。
private String detail;
// 商品的价格使用BigDecimal类型来精确表示金额避免使用浮点数类型如double、float带来的精度丢失问题
// 例如可以表示为new BigDecimal("9.99"),准确地存储商品的售价信息,在购物车计算总价、下单结算等涉及金额计算的业务场景中会频繁使用这个字段。
private BigDecimal price;
// 商品的库存数量,用于记录当前商品还有多少件可供销售,整数类型,
// 在添加商品到购物车、下单扣减库存等业务操作中,会根据这个字段的值来判断操作是否可行,比如库存不足时不能继续添加商品到购物车等情况。
private Integer stock;
// 商品的状态标识具体含义由业务逻辑定义可能用不同的整数值来表示不同的状态比如1表示上架、0表示下架等
// 用于控制商品是否在前端展示、是否可购买等情况,方便后台管理人员对商品的售卖状态进行管理和操作。
private Integer status;
// 商品的创建时间记录商品信息首次被录入系统的时间点使用Date类型来准确表示时间
// 在数据统计、历史记录查询等业务场景中可能会用到这个字段,例如查看某个时间段内新上架的商品等情况。
private Date createTime;
// 商品的更新时间,每当商品的相关信息(如名称、价格、库存等属性发生变化时)被修改后,会更新这个时间字段,
// 同样是Date类型可用于跟踪商品信息的变更情况比如查看商品最近一次的价格调整时间等业务需求。
private Date updateTime;
}

@ -4,7 +4,6 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
@ -12,32 +11,52 @@ import java.util.Date;
* @Author swg.
* @Date 2018/12/31 21:01
* @CONTACT 317758022@qq.com
* @DESC
* @DESC
* 便
*/
@Data
// 使用Lombok的 @Data注解会自动为这个类生成常用的方法包括所有属性的Getter和Setter方法以及重写的toString、hashCode和equals方法等减少了手动编写这些样板代码的工作量使代码更加简洁便于对类中属性进行操作和使用。
@NoArgsConstructor
// 通过 @NoArgsConstructor注解Lombok会为这个类生成一个无参构造函数。在很多场景下比如通过反射创建对象、进行序列化和反序列化操作时无参构造函数是必要的确保类有默认的创建实例的方式。
@AllArgsConstructor
// @AllArgsConstructor注解则指示Lombok生成一个包含所有参数的构造函数方便在创建对象时一次性传入所有属性的值进行初始化例如
// User user = new User(1, "testUser", "123456", "test@example.com", "13812345678", "你的爱好是什么?", "看书", 1, new Date(), new Date());
@ToString
// @ToString注解会让Lombok自动重写toString方法使得在打印对象或者将对象转换为字符串表示形式时能够直观地展示对象的各个属性值方便调试和查看对象的状态。
// 实现Serializable接口表示这个类的对象可以被序列化和反序列化这在很多场景下是很重要的比如将用户对象存储到文件、在网络中传输用户对象等情况确保对象能够以字节流的形式进行持久化和传递并且在反序列化后能正确还原对象状态。
public class User implements Serializable {
// 用户的唯一标识符通常作为数据库表中的主键字段来区分不同的用户在整个系统中其他与用户相关的业务操作如查询、更新、删除用户信息等大多会基于这个ID来进行类型为整数类型。
private Integer id;
// 用户登录使用的用户名,是一个字符串类型的属性,要求在系统中具有唯一性(一般会有相应的业务逻辑和数据库约束来保证),用于用户在登录界面输入,以标识自己的身份,例如"admin"、"user123"等。
private String username;
// 用户登录的密码同样为字符串类型存储用户设置的密码信息出于安全考虑在实际应用中通常会对密码进行加密存储而不是以明文形式保存例如使用MD5、SHA等加密算法对密码进行处理后再存储到数据库中。
private String password;
// 用户的电子邮箱地址,字符串类型,可用于用户注册验证、找回密码等功能,例如"test@example.com",通过向该邮箱发送验证链接或者重置密码的相关信息来完成相应的操作,同时也方便系统与用户进行一些重要通知的邮件沟通。
private String email;
// 用户的手机号码,字符串类型,常用于短信验证码验证、手机号登录等功能场景,例如"13812345678",在如今的很多应用中,手机号已经成为一种常用且重要的用户身份验证和联系的方式。
private String phone;
// 密保问题,字符串类型,是用户在设置账号安全相关信息时所填写的用于找回密码等情况的验证问题,例如"你的爱好是什么?",当用户忘记密码时,可以通过回答正确的密保问题来重置密码,增加账号的安全性。
private String question;
// 密保问题的答案与question属性相对应用户填写的用于验证密保问题的正确答案也是字符串类型例如"看书",只有回答正确这个答案,才能进行后续的密码重置等安全相关操作。
private String answer;
//角色0-管理员,1-普通用户
// 用户的角色标识整数类型通过不同的整数值来区分用户在系统中的不同角色在这里定义了0表示管理员角色1表示普通用户角色不同角色在系统中通常具有不同的权限例如管理员可能具有管理所有用户、系统配置等高级权限而普通用户只能进行与自身相关的常规操作如查看购物车、下单等
private Integer role;
// 用户账号的创建时间使用Date类型准确记录用户首次在系统中注册账号的时间点在数据统计如统计每日新增用户数量等、历史记录查询查看某个时间段内注册的用户等等业务场景中会用到这个字段。
private Date createTime;
// 用户账号信息的最后更新时间每当用户修改了自己的用户名、密码、邮箱等相关信息后会更新这个时间字段同样为Date类型方便跟踪用户信息的变更情况例如查看用户最近一次修改密码的时间等。
private Date updateTime;
}

@ -9,31 +9,51 @@ import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Author swg.
* @Date 2019/1/7 13:23
* @CONTACT 317758022@qq.com
* @DESC
* @DESC MessageReceiverRabbitMQ
*
*/
@Component
// 使用 @Component注解将这个类标记为Spring的一个组件这样Spring容器会自动扫描并管理这个类的实例使其可以参与依赖注入等Spring相关的功能。
@Slf4j
// 通过 @Slf4j注解使用Lombok的功能自动为这个类添加一个名为log的日志记录器方便在类中记录各种运行时的日志信息有助于调试、监控和追踪代码的执行情况。
public class MessageReceiver {
// 通过Spring的依赖注入机制自动注入ICartService接口的实现类对象用于调用购物车相关的业务逻辑方法
// 在这里具体用于执行清除购物车的操作依赖注入使得代码的耦合性更低方便替换不同的购物车服务实现类并且符合Spring的面向接口编程的理念。
@Autowired
private ICartService cartService;
/**
* @RabbitListener"cart-queue"
*
*
* @param message ID
* userId
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue("cart-queue"),
exchange = @Exchange("cart-exchange")
))
public void proess(String message){
log.info("接收到的消息为:{}",message);
public void proess(String message) {
// 使用日志记录器记录接收到的消息内容,方便在运行时查看接收到的消息具体是什么,有助于调试和排查问题,例如查看消息是否符合预期格式等情况。
log.info("接收到的消息为:{}", message);
// 将接收到的消息预期是一个字符串形式的整数转换为实际的整数类型userId这里假设发送过来的消息内容就是有效的用户ID字符串表示形式
// 如果消息格式不符合要求可能会抛出NumberFormatException异常在实际应用中可能需要更完善的错误处理机制来应对这种情况。
Integer userId = Integer.parseInt(message);
log.info("【MQ解析数据,前者为userId,后者为productId{}】",userId);
//清除购物车
// 再次使用日志记录器记录解析后的用户ID信息便于在日志中清晰地看到处理的用户相关数据方便后续查看消息处理的具体情况以及追踪问题比如查看是否处理了正确的用户的购物车清除操作等。
log.info("【MQ解析数据,前者为userId,后者为productId{}】", userId);
// 调用ICartService的removeCart方法传入解析得到的用户IDuserId执行清除对应用户购物车的业务操作
// 这个操作可能涉及到数据库中购物车相关记录的删除等具体的持久层操作具体由ICartService的实现类中对应的方法逻辑来决定。
cartService.removeCart(userId);
}
}
}

@ -31,211 +31,452 @@ import java.util.List;
*/
@Service
@Slf4j
public class CartServiceImpl implements ICartService{
// CartServiceImpl类实现了ICartService接口意味着它需要实现该接口中定义的所有方法
// 这个类主要负责处理购物车相关的具体业务逻辑,例如添加商品到购物车、更新购物车中商品数量等操作,
// 通过调用其他组件如CartMapper、ProductClient、CommonCacheUtil等来协同完成业务功能并将结果以ServerResponse的形式返回给上层调用者如控制层
public class CartServiceImpl implements ICartService {
// 通过Spring的依赖注入机制自动注入CartMapper接口的实现类对象。
// CartMapper用于与数据库进行交互执行如查询、插入、更新、删除购物车相关记录等数据库操作
// 在这里的各个购物车业务方法中会频繁调用它来完成持久化层面的工作,实现业务逻辑与数据访问的分离。
@Autowired
private CartMapper cartMapper;
// 同样通过依赖注入注入ProductClient对象。
// ProductClient通常是用于和外部的商品服务进行通信的客户端比如在购物车业务中需要获取商品的详细信息是否下架、库存情况等
// 就会通过这个客户端去调用商品服务提供的接口来获取相应数据,实现不同微服务之间的协作。
@Autowired
private ProductClient productClient;
// 注入CommonCacheUtil对象用于与缓存系统进行交互操作。
// 在处理购物车业务时,可能会频繁用到商品相关信息,通过缓存可以减少重复查询数据库的次数,提高系统性能。
// 例如先从缓存中查找商品信息,如果缓存中不存在再去查询数据库,并将查询到的数据存入缓存供后续使用。
@Autowired
private CommonCacheUtil commonCacheUtil;
/**
* ICartService
*
* @param userId null
* SnailmallException
* @param productId null
* ServerResponse
* @param count nullnull
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse add(Integer userId, Integer productId, Integer count) {
//1.校验参数
if(userId == null){
// 1. 校验参数
// 首先检查用户ID是否为null如果为null意味着用户没有登录这不符合添加商品到购物车的业务要求所以抛出SnailmallException异常
// 异常信息为"用户未登陆",后续在合适的地方(比如控制层)可以捕获这个异常并返回相应提示给客户端,告知用户需要先登录才能进行添加操作。
if (userId == null) {
throw new SnailmallException("用户未登陆");
}
if(productId == null || count == null){
// 接着检查商品ID和商品数量是否为null如果这两个参数中有任何一个为null说明传入的参数不符合要求无法准确执行添加商品的操作
// 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示参数不正确的响应对象给客户端提示用户检查并修正传入的参数。
if (productId == null || count == null) {
return ServerResponse.createByErrorMessage("参数不正确");
}
//2.校验商品
String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+productId);
// 2. 校验商品
// 尝试从缓存中获取商品信息缓存的键是通过Constants.PRODUCT_TOKEN_PREFIX加上商品ID拼接而成的
// 这样可以根据不同的商品ID来准确地在缓存中查找对应的商品缓存数据提高获取商品信息的效率避免频繁访问数据库。
String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + productId);
Product product = null;
if(productStr == null){
// 如果从缓存中获取到的商品信息字符串为null说明缓存中不存在该商品的相关信息此时需要通过ProductClient去远程调用商品服务来查询商品详情。
if (productStr == null) {
// 调用ProductClient的queryProduct方法传入商品ID作为参数向商品服务发起查询请求获取包含商品信息的ServerResponse对象。
ServerResponse response = productClient.queryProduct(productId);
// 从ServerResponse对象中获取其包含的商品数据部分可能是一个Object类型的对象具体内容由商品服务返回的数据结构决定
Object object = response.getData();
// 使用JsonUtil工具类的obj2String方法将获取到的商品数据对象转换为字符串形式方便后续进行反序列化操作。
String objStr = JsonUtil.obj2String(object);
product = (Product) JsonUtil.Str2Obj(objStr,Product.class);
}else {
product = (Product) JsonUtil.Str2Obj(productStr,Product.class);
// 再通过JsonUtil的Str2Obj方法将字符串形式的商品信息反序列化为Product对象以便后续在业务逻辑中使用商品的各个属性进行相关判断和操作。
product = (Product) JsonUtil.Str2Obj(objStr, Product.class);
} else {
// 如果缓存中存在商品信息字符串则直接使用JsonUtil的Str2Obj方法将其反序列化为Product对象避免了再次远程查询商品服务的开销。
product = (Product) JsonUtil.Str2Obj(productStr, Product.class);
}
if(product == null){
// 如果经过上述步骤获取到的商品对象为null说明商品不存在可能是商品ID有误或者商品已经被彻底删除等原因
// 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示商品不存在的响应对象给客户端告知用户无法添加不存在的商品到购物车。
if (product == null) {
return ServerResponse.createByErrorMessage("商品不存在");
}
if(!product.getStatus().equals(Constants.Product.PRODUCT_ON)){
// 检查商品的状态是否为Constants.Product.PRODUCT_ON这个常量应该是在业务中定义好的表示商品处于正常可售卖状态的标识
// 如果商品状态不等于这个正常可售卖状态标识,说明商品可能已经下架或者被删除了,不应该再添加到购物车中,
// 所以返回一个表示商品下架或者删除的错误提示响应对象给客户端。
if (!product.getStatus().equals(Constants.Product.PRODUCT_ON)) {
return ServerResponse.createByErrorMessage("商品下架或者删除");
}
//3.根据商品或者购物车,购物车存在则增加商品数量即可,不存在则创建新的购物车,一个用户对应一个购物车
Cart cart = cartMapper.selectByUserIdProductId(userId,productId);
if (cart == null){
// 3. 根据商品或者购物车,购物车存在则增加商品数量即可,不存在则创建新的购物车,一个用户对应一个购物车
// 通过CartMapper的selectByUserIdProductId方法根据传入的用户ID和商品ID去数据库中查询购物车中是否已经存在该商品的记录
// 如果不存在即查询返回的Cart对象为null则表示该用户的购物车中还没有添加过这个商品需要创建一个新的购物车记录。
Cart cart = cartMapper.selectByUserIdProductId(userId, productId);
if (cart == null) {
// 创建一个新的Cart对象用于表示要插入到数据库中的购物车记录信息。
Cart cartItem = new Cart();
// 设置新购物车记录的用户ID关联到对应的用户表示这个购物车是属于哪个用户的。
cartItem.setUserId(userId);
// 设置商品ID明确购物车中存放的是哪个商品。
cartItem.setProductId(productId);
// 设置商品数量,即此次要添加到购物车的商品数量。
cartItem.setQuantity(count);
// 设置商品的选中状态这里设置为Constants.Cart.CHECKED应该是在业务中定义好的表示选中的常量表示默认添加到购物车后商品是选中状态具体选中含义可根据业务逻辑确定比如是否参与总价计算等。
cartItem.setChecked(Constants.Cart.CHECKED);
// 通过CartMapper的insert方法将新创建的购物车记录插入到数据库中该方法会返回受影响的行数正常插入成功应该返回1
// 如果返回值为0表示插入操作失败可能是数据库出现问题或者其他原因导致插入不成功此时返回一个表示添加购物车失败的错误提示响应对象给客户端。
int resultCount = cartMapper.insert(cartItem);
if(resultCount == 0){
if (resultCount == 0) {
return ServerResponse.createByErrorMessage("添加购物车失败");
}
}else {
cart.setQuantity(cart.getQuantity()+count);
} else {
// 如果购物车中已经存在该商品的记录,说明之前已经添加过这个商品到购物车了,此时只需要更新商品的数量即可,
// 将原有的商品数量加上此次要添加的数量更新Cart对象中的quantity属性。
cart.setQuantity(cart.getQuantity() + count);
// 通过CartMapper的updateByPrimaryKeySelective方法根据更新后的Cart对象去更新数据库中对应的购物车记录
// 这个方法会根据Cart对象中不为null的属性来更新数据库相应字段返回受影响的行数若返回0则表示更新失败同样返回添加购物车失败的提示响应给客户端。
int resultCount = cartMapper.updateByPrimaryKeySelective(cart);
if(resultCount == 0){
if (resultCount == 0) {
return ServerResponse.createByErrorMessage("添加购物车失败");
}
}
//构建购物车信息,返回给前端,并且要检查库存
CartVo cartVo = getCartVoLimit(userId,true);
// 构建购物车信息,返回给前端,并且要检查库存
// 调用getCartVoLimit方法来构建包含购物车详细信息的CartVo对象这个方法内部会进一步处理购物车中商品的各种信息
// 比如获取商品的详细信息(可能再次从缓存或远程查询商品服务获取更详细数据)、判断库存情况、计算购物车商品的总价等操作,
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户添加商品到购物车操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = getCartVoLimit(userId, true);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId null
*
* @param productId null
* ServerResponseID
* @param count nullnull
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse update(Integer userId, Integer productId, Integer count) {
if(productId == null || count == null){
// 首先对传入的参数进行校验如果商品ID或者商品数量为null说明参数不符合要求无法进行更新操作
// 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示参数错误的响应对象给客户端提示用户修正传入的参数。
if (productId == null || count == null) {
return ServerResponse.createByErrorMessage("参数错误");
}
Cart cart = cartMapper.selectByUserIdProductId(userId,productId);
if(cart == null){
// 通过CartMapper的selectByUserIdProductId方法根据传入的用户ID和商品ID去数据库中查询购物车中是否存在对应的商品记录
// 如果不存在即查询返回的Cart对象为null说明要更新数量的商品在购物车中不存在无法进行更新操作
// 此时返回一个表示购物车不存在的错误提示响应对象给客户端,告知用户无法进行更新。
Cart cart = cartMapper.selectByUserIdProductId(userId, productId);
if (cart == null) {
return ServerResponse.createByErrorMessage("购物车不存在");
}
// 如果找到了对应的购物车商品记录将Cart对象中的quantity属性设置为新传入的商品数量值准备更新数据库中的对应记录。
cart.setQuantity(count);
// 通过CartMapper的updateByPrimaryKeySelective方法根据更新后的Cart对象去更新数据库中对应的购物车记录
// 该方法会根据Cart对象中不为null的属性来更新数据库相应字段返回受影响的行数若返回0则表示更新失败返回一个表示更新购物车失败的错误提示响应给客户端。
int updateCount = cartMapper.updateByPrimaryKeySelective(cart);
if(updateCount == 0){
if (updateCount == 0) {
return ServerResponse.createByErrorMessage("更新购物车失败");
}
CartVo cartVo = this.getCartVoLimit(userId,true);
// 调用getCartVoLimit方法构建包含更新后购物车详细信息的CartVo对象这个方法会进一步处理购物车商品的相关信息如获取商品详情、判断库存、计算总价等
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户更新购物车商品数量操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = this.getCartVoLimit(userId, true);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId null
*
* @param productIds "1,2,3"ID123
* ID
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse delete(Integer userId, String productIds) {
// 使用Google Guava库的Splitter工具类按照逗号","将传入的商品ID字符串productIds分割成一个字符串列表
// 这样就能方便地获取到要删除的各个商品的ID以便后续批量操作数据库中对应的购物车商品记录。
List<String> productIdList = Splitter.on(",").splitToList(productIds);
if(CollectionUtils.isEmpty(productIdList)){
// 通过Apache Commons Collections的CollectionUtils工具类检查分割后的商品ID列表是否为空
// 如果为空说明传入的参数不符合要求无法准确执行删除操作此时返回一个表示参数错误的ServerResponse对象给客户端提示用户修正传入的参数。
if (CollectionUtils.isEmpty(productIdList)) {
return ServerResponse.createByErrorMessage("参数错误");
}
int rowCount = cartMapper.deleteByProductIds(userId,productIdList);
if(rowCount == 0){
// 调用CartMapper的deleteByProductIds方法根据用户ID和要删除的商品ID列表从数据库中删除对应的购物车商品记录
// 该方法会返回受影响的行数即实际删除的记录条数正常情况下如果成功删除了对应的商品记录返回值应该大于0。
int rowCount = cartMapper.deleteByProductIds(userId, productIdList);
// 如果返回值为0表示没有实际删除任何记录可能是因为要删除的商品已经不存在于购物车中了
// 此时返回一个表示商品已经不存在于购物车中请勿重复删除的ServerResponse对象给客户端告知用户当前操作情况。
if (rowCount == 0) {
return ServerResponse.createByErrorMessage("此商品已经不存在于购物车中,请勿重复删除");
}
CartVo cartVo = this.getCartVoLimit(userId,false);
// 调用getCartVoLimit方法构建包含删除商品后购物车详细信息的CartVo对象这个方法会进一步处理购物车商品的相关信息如获取剩余商品详情、计算总价等
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户删除购物车商品操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = this.getCartVoLimit(userId, false);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse list(Integer userId) {
CartVo cartVo = this.getCartVoLimit(userId,false);
// 直接调用getCartVoLimit方法构建包含购物车详细信息的CartVo对象这个方法内部会处理购物车商品的相关信息如获取商品详情、判断库存、计算总价等
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户查询购物车列表操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = this.getCartVoLimit(userId, false);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId
* @param checked Constants.Cart.CHECKEDConstants.Cart.UN_CHECKED
* @param productId null
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse selectOrUnSelect(Integer userId, int checked, Integer productId) {
cartMapper.selectOrUnSelectProduct(userId,checked,productId);
CartVo cartVo = this.getCartVoLimit(userId,false);
// 调用CartMapper的selectOrUnSelectProduct方法根据用户ID、要设置的选中状态以及商品ID如果不为null则针对特定商品为null则针对所有商品具体看业务逻辑实现来更新数据库中购物车商品的选中状态记录
// 此方法无返回值它直接执行数据库更新操作将对应的购物车商品记录的选中状态字段更新为传入的checked值。
cartMapper.selectOrUnSelectProduct(userId, checked, productId);
// 调用getCartVoLimit方法构建包含更新后购物车详细信息的CartVo对象这个方法会进一步处理购物车商品的相关信息如获取商品详情、判断库存、计算总价等
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户设置商品选中状态操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = this.getCartVoLimit(userId, false);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId null0
* @return ServerResponse<Integer>
* ServerResponse
* 便
*/
@Override
public ServerResponse<Integer> get_cart_product_count(Integer userId) {
if(userId == null){
// 首先判断用户ID是否为null如果为null按照业务逻辑直接返回一个表示操作成功且数据部分为0的ServerResponse对象
// 意味着如果用户未登录或者传入的用户ID无效等情况默认购物车商品数量为0告知调用者当前情况。
if (userId == null) {
return ServerResponse.createBySuccess(0);
}
// 调用CartMapper的selectCartProductCount方法传入用户ID作为参数去查询数据库中对应用户购物车中商品的总数量
// 然后返回一个表示操作成功且数据部分为查询到的商品数量的ServerResponse<Integer>对象给客户端,方便调用者获取并使用这个数量信息。
return ServerResponse.createBySuccess(cartMapper.selectCartProductCount(userId));
}
/**
* ICartService
*
* @param userId
* @return ServerResponse
* "清除购物车成功"便
*/
@Override
public ServerResponse removeCart(Integer userId) {
// 首先通过CartMapper的selectCartByUserId方法根据用户ID去查询数据库中该用户购物车中的所有商品记录返回一个包含多个Cart对象的列表代表购物车中的所有商品信息。
List<Cart> cartList = cartMapper.selectCartByUserId(userId);
for(Cart cart:cartList){
// 遍历查询到的购物车商品记录列表对于每一条购物车记录调用CartMapper的deleteByPrimaryKey方法根据购物车记录的主键ID从数据库中删除对应的记录
// 这样就实现了逐个删除购物车中的所有商品记录,达到清空购物车的目的。
for (Cart cart : cartList) {
cartMapper.deleteByPrimaryKey(cart.getId());
}
// 返回一个表示操作成功且包含提示消息"清除购物车成功"的ServerResponse对象给客户端告知用户购物车已成功清空方便调用者如控制层将这个消息返回给前端展示给用户。
return ServerResponse.createBySuccessMessage("清除购物车成功");
}
/**
*
* @param userId
* @return
* CartVo
*
* @param userId
* @param isJudgeStock
* truefalse
* @return CartVoCartProductVo
* 便使
*/
private CartVo getCartVoLimit(Integer userId,boolean isJudgeStock) {
private CartVo getCartVoLimit(Integer userId, boolean isJudgeStock) {
// 创建一个新的CartVo对象用于组装最终要返回的购物车详细信息这个对象将包含购物车的各种关键信息如商品列表、总价、全选状态等内容。
CartVo cartVo = new CartVo();
// 创建一个空的CartProductVo列表用于存放购物车中各个商品的详细信息对象后续会遍历购物车记录将每个商品的详细信息封装成CartProductVo对象后添加到这个列表中。
List<CartProductVo> cartProductVoList = Lists.newArrayList();
// 通过CartMapper的selectCartByUserId方法根据传入的用户ID去查询数据库中该用户购物车中的所有商品记录返回一个包含多个Cart对象的列表代表购物车中的所有商品信息。
List<Cart> cartList = cartMapper.selectCartByUserId(userId);
// 初始化购物车总价为0使用BigDecimal类型来精确表示金额避免浮点数运算的精度问题后续会根据购物车中选中商品的价格和数量来累加计算总价。
BigDecimal cartTotalPrice = new BigDecimal("0");
if(CollectionUtils.isNotEmpty(cartList)){
//1.遍历购物车一条购物车记录对应一个商品这些购物车共同对应到一个用户userId
for(Cart cart:cartList){
// 判断查询到的购物车商品记录列表是否不为空,如果不为空,说明该用户购物车中有商品,需要进一步处理这些商品的详细信息,进行购物车信息的构建。
if (CollectionUtils.isNotEmpty(cartList)) {
// 1. 遍历购物车一条购物车记录对应一个商品这些购物车共同对应到一个用户userId
// 开始遍历购物车商品记录列表对于每一条购物车记录代表一个商品在购物车中的信息进行如下操作构建对应的CartProductVo对象来封装商品详细信息并添加到购物车商品列表中。
for (Cart cart : cartList) {
CartProductVo cartProductVo = new CartProductVo();
// 设置CartProductVo对象的ID一般可以使用购物车记录的主键ID作为其唯一标识方便后续在一些业务逻辑中对特定商品信息进行定位和操作。
cartProductVo.setId(cart.getId());
// 设置CartProductVo对象的用户ID关联到对应的用户确保商品信息所属的用户明确与传入的参数userId对应。
cartProductVo.setUserId(cart.getUserId());
// 设置CartProductVo对象的商品ID明确这个商品信息对应的具体商品与购物车记录中的商品ID保持一致。
cartProductVo.setProductId(cart.getProductId());
//2.从redis中获取商品获取不到则feign获取并且重置进redis中
String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+cart.getProductId());
// 2. 从redis中获取商品获取不到则feign获取并且重置进redis中
// 尝试从缓存这里假设使用Redis缓存通过CommonCacheUtil工具类操作中获取商品信息缓存的键是通过Constants.PRODUCT_TOKEN_PREFIX加上购物车记录中商品的ID拼接而成的
// 这样可以根据不同的商品ID准确地在缓存中查找对应的商品缓存数据提高获取商品信息的效率避免频繁访问数据库。
String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + cart.getProductId());
Product product = null;
if(productStr == null){
// 如果从缓存中获取到的商品信息字符串为null说明缓存中不存在该商品的相关信息此时需要通过ProductClient去远程调用商品服务来查询商品详情。
if (productStr == null) {
ServerResponse response = productClient.queryProduct(cart.getProductId());
Object object = response.getData();
String objStr = JsonUtil.obj2String(object);
product = (Product) JsonUtil.Str2Obj(objStr,Product.class);
}else {
product = (Product) JsonUtil.Str2Obj(productStr,Product.class);
product = (Product) JsonUtil.Str2Obj(objStr, Product.class);
} else {
// 如果缓存中存在商品信息字符串则直接使用JsonUtil的Str2Obj方法将其反序列化为Product对象避免了再次远程查询商品服务的开销。
product = (Product) JsonUtil.Str2Obj(productStr, Product.class);
}
if(product != null){
// 如果成功获取到了商品对象即product不为null说明获取到了商品的详细信息接下来可以设置CartProductVo对象中与商品相关的各种属性值。
if (product!= null) {
// 设置CartProductVo对象的商品主图片路径从获取到的商品对象中获取主图片路径属性值方便前端根据这个路径展示商品的主图片。
cartProductVo.setProductMainImage(product.getMainImage());
// 设置CartProductVo对象的商品名称从商品对象中获取商品名称属性值用于在前端展示商品的名称信息。
cartProductVo.setProductName(product.getName());
// 设置CartProductVo对象的商品副标题从商品对象中获取副标题属性值可在前端展示商品的补充说明信息如卖点、优惠等相关内容。
cartProductVo.setProductSubtitle(product.getSubtitle());
// 设置CartProductVo对象的商品状态从商品对象中获取商品状态属性值用于前端展示商品是否可购买等状态信息例如是否下架等情况
cartProductVo.setProductStatus(product.getStatus());
// 设置CartProductVo对象的商品价格从商品对象中获取商品价格属性值用于后续计算商品总价等操作注意这里价格使用BigDecimal类型保证精度。
cartProductVo.setProductPrice(product.getPrice());
// 设置CartProductVo对象的商品库存从商品对象中获取商品库存属性值用于判断库存是否足够等相关业务逻辑处理。
cartProductVo.setProductStock(product.getStock());
//3.判断这个商品的库存,有些接口不需要再去判断库存了所以根据传进来的isJudgeStock这个boolean参数来决定是否判断库存
// 3. 判断这个商品的库存有些接口不需要再去判断库存了所以根据传进来的isJudgeStock这个boolean参数来决定是否判断库存
int buyLimitCount = 0;
if (isJudgeStock){
if(product.getStock() > cart.getQuantity()){
//4.库存是够的
if (isJudgeStock) {
// 如果需要判断库存即isJudgeStock为true则比较商品的库存数量和购物车中该商品的数量判断库存是否足够。
if (product.getStock() > cart.getQuantity()) {
// 4. 库存是够的
// 如果商品库存大于购物车中该商品的数量,说明库存充足,购买数量可以按照购物车中记录的数量来确定,将购买数量设置为购物车中的商品数量。
buyLimitCount = cart.getQuantity();
// 设置CartProductVo对象的限制数量标识为Constants.Cart.LIMIT_NUM_SUCCESS应该是业务中定义好的表示数量限制成功即库存足够的
// 在构建购物车信息的方法中,针对每个购物车商品记录,根据库存情况设置相关属性,并计算商品总价等信息,以下是继续添加注释后的详细内容。
// 如果库存足够设置CartProductVo对象的限制数量标识为表示库存足够的常量值意味着当前商品在购物车中的数量是可购买的数量没有受到库存限制。
cartProductVo.setLimitQuantity(Constants.Cart.LIMIT_NUM_SUCCESS);
}else {
//5.库存不够了,则返回当前最大库存
} else {
// 5. 库存不够了,则返回当前最大库存
// 当商品库存小于或等于购物车中该商品的数量时,说明库存不足,此时将购买数量设置为商品当前的库存数量,即用户最多只能购买库存剩余的数量。
buyLimitCount = product.getStock();
// 设置CartProductVo对象的限制数量标识为Constants.Cart.LIMIT_NUM_FAIL应该是业务中定义好的表示数量限制失败即库存不足的标识
// 用于前端展示等场景告知用户该商品库存不足,可能无法按照购物车中原本设置的数量购买。
cartProductVo.setLimitQuantity(Constants.Cart.LIMIT_NUM_FAIL);
// 创建一个新的Cart对象用于更新购物车中该商品的记录因为库存不足需要将购物车中记录的商品数量更新为实际可购买的库存数量。
Cart cartItem = new Cart();
// 设置新Cart对象的ID为当前购物车商品记录的ID确保更新的是正确的购物车商品记录。
cartItem.setId(cart.getId());
// 设置新Cart对象的商品数量为前面计算得到的可购买的库存数量buyLimitCount准备更新数据库中的购物车记录。
cartItem.setQuantity(buyLimitCount);
// 通过CartMapper的updateByPrimaryKeySelective方法根据更新后的Cart对象去更新数据库中对应的购物车记录
// 这个方法会根据Cart对象中不为null的属性来更新数据库相应字段以确保购物车中的商品数量与实际库存情况相符。
cartMapper.updateByPrimaryKeySelective(cartItem);
}
}else {
} else {
// 如果不需要判断库存即isJudgeStock为false则直接将购买数量设置为购物车中原本记录的商品数量不考虑库存是否足够的情况可能适用于某些特定业务场景比如仅查看购物车信息而不涉及购买操作时。
buyLimitCount = cart.getQuantity();
}
//6.购买的数量已经是确定的了,下面就可以直接计算价格了
// 6. 购买的数量已经是确定的了,下面就可以直接计算价格了
// 设置CartProductVo对象的商品数量为前面确定好的购买数量buyLimitCount这个数量将用于后续计算该商品在购物车中的总价。
cartProductVo.setQuantity(buyLimitCount);
cartProductVo.setProductTotalPrice(BigDecimalUtil.mul(product.getPrice().doubleValue(),buyLimitCount));
// 使用BigDecimalUtil工具类的mul方法应该是自定义的用于BigDecimal类型乘法运算的工具方法确保精度
// 传入商品价格的double值通过调用product.getPrice().doubleValue()获取和购买数量buyLimitCount计算得到该商品在购物车中的总价
// 并设置到CartProductVo对象的ProductTotalPrice属性中方便后续汇总购物车总价以及展示给用户查看商品的价格明细。
cartProductVo.setProductTotalPrice(BigDecimalUtil.mul(product.getPrice().doubleValue(), buyLimitCount));
// 设置CartProductVo对象的商品选中状态从购物车记录中获取商品的选中状态cart.getChecked()并赋值给CartProductVo对象
// 用于前端展示商品是否被选中以及参与购物车总价计算等相关业务逻辑(例如只有选中的商品总价才会累加到购物车总价中)。
cartProductVo.setProductChecked(cart.getChecked());
}
//7.选中的,就加入到总价中
if(cart.getChecked() == Constants.Cart.CHECKED){
cartTotalPrice = BigDecimalUtil.add(cartTotalPrice.doubleValue(),cartProductVo.getProductTotalPrice().doubleValue());
// 7. 选中的,就加入到总价中
// 判断购物车记录中该商品的选中状态是否为Constants.Cart.CHECKED表示已选中如果是则将该商品的总价累加到购物车总价cartTotalPrice中。
// 通过BigDecimalUtil工具类的add方法同样是自定义的用于BigDecimal类型加法运算的工具方法保证精度
// 传入当前购物车总价的double值cartTotalPrice.doubleValue()和该商品的总价cartProductVo.getProductTotalPrice().doubleValue())进行累加计算,
// 更新购物车总价,以得到所有选中商品的总价信息。
if (cart.getChecked() == Constants.Cart.CHECKED) {
cartTotalPrice = BigDecimalUtil.add(cartTotalPrice.doubleValue(), cartProductVo.getProductTotalPrice().doubleValue());
}
// 将构建好的包含该商品详细信息的CartProductVo对象添加到购物车商品列表cartProductVoList完成一个商品信息的处理后续继续遍历其他购物车商品记录进行同样的操作。
cartProductVoList.add(cartProductVo);
}
}
// 设置CartVo对象的购物车总价属性CartTotalPrice为前面计算得到的购物车总价cartTotalPrice方便将购物车总价信息传递给上层调用者如控制层展示给用户查看整个购物车的商品总价情况。
cartVo.setCartTotalPrice(cartTotalPrice);
// 设置CartVo对象的购物车商品列表属性CartProductVoList为前面构建好的包含所有商品详细信息的列表cartProductVoList
// 这样在前端展示购物车信息时,可以遍历这个列表展示每个商品的各项信息(如名称、价格、数量、选中状态等)。
cartVo.setCartProductVoList(cartProductVoList);
// 调用getAllCheckedStatus方法下面定义的用于判断购物车中商品是否全选的方法传入用户ID作为参数获取购物车中商品的全选状态是否所有商品都被选中
// 并设置到CartVo对象的AllChecked属性中方便前端展示购物车全选按钮的状态以及进行全选、取消全选等相关业务逻辑操作。
cartVo.setAllChecked(this.getAllCheckedStatus(userId));
cartVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.oursnail.cn/"));
log.info("购物车列表内容为:{}",cartVo);
// 通过PropertiesUtil工具类的getProperty方法应该是用于读取配置文件中属性值的工具方法尝试获取配置文件中名为"ftp.server.http.prefix"的属性值,
// 如果获取不到,则使用默认值"http://img.oursnail.cn/"作为图片主机地址,这个地址通常用于前端展示购物车中商品图片时拼接图片的具体路径,
// 例如商品主图片路径可能是相对路径通过拼接这个图片主机地址可以得到完整的可访问的图片URL方便前端正确展示商品图片。
cartVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix", "http://img.oursnail.cn/"));
// 使用日志记录器记录购物车列表的详细内容,方便在开发、调试以及运行过程中查看购物车信息的构建情况,排查可能出现的问题,例如购物车总价计算错误、商品信息缺失等情况。
log.info("购物车列表内容为:{}", cartVo);
// 最后返回构建好的包含完整购物车详细信息的CartVo对象供上层调用者如在其他业务方法中使用例如将其包装在ServerResponse对象中返回给控制层进而展示给前端页面。
return cartVo;
}
/**
* 0-1-
* 0 - 1 -
*
* 0false
* true
*
* @param userId
* @return truefalse
*/
private Boolean getAllCheckedStatus(Integer userId) {
if(userId == null){
// 首先判断用户ID是否为null如果为null按照业务逻辑直接返回false表示无法判断全选状态或者默认不是全选状态可根据实际业务含义调整
// 例如在未登录或者传入无效用户ID的情况下不认为购物车是全选状态。
if (userId == null) {
return false;
}
// 调用CartMapper的selectCartCheckedStatusByUserId方法传入用户ID作为参数去查询数据库中该用户购物车中已选中商品的状态数量
// 这里返回值的具体含义可能根据业务逻辑而定例如返回的是已选中商品的记录条数等只要返回值不为0就表示有商品被选中。
return cartMapper.selectCartCheckedStatusByUserId(userId) == 0;
}
}
}

@ -3,7 +3,6 @@ package com.njupt.swg.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
@ -11,24 +10,69 @@ import java.math.BigDecimal;
* @Author swg.
* @Date 2019/1/5 15:18
* @CONTACT 317758022@qq.com
* @DESC
* @DESC CartProductVoValue Object VO
* 便
* 使VO
* 使Serializable便使
*/
@NoArgsConstructor
// 通过 @NoArgsConstructor注解Lombok会为这个类自动生成一个无参构造函数。在很多情况下比如通过反射创建对象、进行序列化和反序列化操作时无参构造函数是必要的确保类有默认的创建实例的方式。
@AllArgsConstructor
// @AllArgsConstructor注解则指示Lombok生成一个包含所有参数的构造函数方便在创建对象时一次性传入所有属性的值进行初始化例如
// CartProductVo cartProductVo = new CartProductVo(1, 1001, 2001, 2, "华为手机", "高性能智能手机", "http://img/phone.jpg", new BigDecimal("3999"), 1, new BigDecimal("7998"), 10, 1, "库存充足");
@Data
// 使用Lombok的 @Data注解会自动为这个类生成常用的方法包括所有属性的Getter和Setter方法以及重写的toString、hashCode和equals方法等减少了手动编写这些样板代码的工作量使代码更加简洁便于对类中属性进行操作和使用。
public class CartProductVo implements Serializable {
// 购物车商品记录的唯一标识符,通常与数据库中购物车表内对应商品记录的主键相关联,用于在系统中唯一标识这条购物车商品记录,方便进行查询、更新、删除等操作,类型为整数类型。
private Integer id;
// 用户的唯一标识符关联到具体是哪个用户的购物车中的商品通过这个字段可以明确该商品所属的用户与系统中用户模块的用户ID相对应类型为整数类型。
private Integer userId;
// 商品的唯一标识符用于确定购物车中存放的是哪个具体商品可通过这个ID去查询商品的详细信息如商品详情、库存等在整个商品管理和购物流程中用于区分不同的商品类型为整数类型。
private Integer productId;
private Integer quantity;//购物车中此商品的数量
// 购物车中此商品的数量代表用户添加该商品到购物车的数量情况例如用户添加了3件某商品到购物车这个字段的值就会是3类型为整数类型
// 在购物车相关业务逻辑中,这个数量会参与到总价计算、库存判断等操作中。
private Integer quantity;
// 商品的名称,用于展示给用户,让用户直观地知道商品是什么,是一个字符串类型的属性,例如"华为P50手机"等,
// 在购物车列表展示、商品详情页展示等场景中会用到这个字段来呈现商品的基本信息。
private String productName;
// 商品的副标题,通常可以用来补充说明商品的一些特点、卖点或者优惠信息等内容,也是字符串类型,
// 比如"华为P50手机超感知徕卡四摄限时优惠",可以进一步吸引用户关注该商品,在前端展示购物车商品时丰富商品的展示信息。
private String productSubtitle;
// 商品的主图片的路径或者标识,一般用于在前端页面展示商品的主要图片,字符串类型,
// 这个路径可以是相对路径或者绝对路径,指向存储商品主图的位置(可能是服务器本地存储、云存储等),方便前端根据这个路径来加载并展示图片,使购物车中的商品展示更加直观形象。
private String productMainImage;
// 商品的价格使用BigDecimal类型来精确表示金额避免使用浮点数类型如double、float带来的精度丢失问题
// 例如可以表示为new BigDecimal("9.99"),准确地存储商品的售价信息,在购物车计算总价、下单结算等涉及金额计算的业务场景中会频繁使用这个字段。
private BigDecimal productPrice;
// 商品的状态标识具体含义由业务逻辑定义可能用不同的整数值来表示不同的状态比如1表示上架、0表示下架等
// 用于控制商品是否在前端展示、是否可购买等情况,方便后台管理人员对商品的售卖状态进行管理和操作,在购物车中展示商品时也需要根据这个状态来决定是否显示商品等操作。
private Integer productStatus;
// 商品在购物车中的总价通过商品的单价productPrice乘以购物车中该商品的数量quantity计算得出同样使用BigDecimal类型保证金额计算的精度
// 用于在购物车中展示每个商品的总价情况,方便用户清楚了解购买该商品所需的费用,以及参与整个购物车总价的汇总计算等业务操作。
private BigDecimal productTotalPrice;
// 商品的库存数量,用于记录当前商品还有多少件可供销售,整数类型,
// 在购物车相关业务中,如添加商品到购物车、更新商品数量等操作时,会根据这个字段的值来判断操作是否可行,比如库存不足时不能继续添加商品到购物车或者需要调整购物车中商品数量等情况,同时在前端展示时也可以告知用户商品的库存剩余情况。
private Integer productStock;
private Integer productChecked;//此商品是否勾选
private String limitQuantity;//限制数量的一个返回结果
}
// 此商品是否勾选用于表示商品在购物车中的选中状态具体取值含义可能由业务逻辑定义比如可以用1表示选中0表示未选中等方便在购物车相关操作如全选、取消全选、单独选中某个商品等操作中标记商品的状态类型为整数类型
// 在计算购物车总价时,通常只会将勾选状态的商品总价进行累加,并且前端展示购物车时也会根据这个状态来展示商品是否被选中的视觉效果(如打勾图标等)。
private Integer productChecked;
// 限制数量的一个返回结果,这个字段的具体含义可能根据业务逻辑来定,比如在判断商品库存与购物车中商品数量关系后,根据库存情况返回相应的提示信息,
// 像"库存充足"、"库存不足仅剩X件"等类似表示数量限制情况的内容,以字符串形式存储,方便前端展示给用户,让用户了解商品数量相关的限制情况。
private String limitQuantity;
}

@ -1,7 +1,6 @@
package com.njupt.swg.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@ -9,12 +8,29 @@ import java.util.List;
* @Author swg.
* @Date 2019/1/5 15:18
* @CONTACT 317758022@qq.com
* @DESC
* @DESC CartVoValue ObjectVO
* 便
* 使VO使
*/
@Data
// 使用Lombok的 @Data注解会自动为这个类生成常用的方法包括所有属性的Getter和Setter方法以及重写的toString方法等这样可以减少手动编写这些样板代码的工作量使代码更加简洁便于对类中属性进行操作和使用。
public class CartVo {
// 购物车中商品信息列表是一个包含CartProductVo对象的List集合每个CartProductVo对象代表购物车中的一个商品的详细信息如商品名称、价格、数量、选中状态等
// 通过这个列表可以完整地展示购物车中所有商品的各项具体情况,方便前端遍历并展示购物车中的商品列表,以及在后端进行基于购物车商品列表的各种业务逻辑处理(如计算总价、判断全选状态等)。
private List<CartProductVo> cartProductVoList;
// 购物车的总价使用BigDecimal类型来精确表示金额避免浮点数运算带来的精度丢失问题这个总价是通过计算购物车中所有已选中商品的价格总和得到的
// 它准确地反映了用户当前选择购买的商品的总费用情况,在购物车页面展示以及下单结算等业务场景中是一个非常关键的信息,方便用户清楚知晓购买这些商品需要支付的金额总数。
private BigDecimal cartTotalPrice;
private Boolean allChecked;//是否已经都勾选
// 是否已经都勾选是一个布尔类型的属性用于表示购物车中的所有商品是否都处于被选中的状态其取值为true表示购物车中的商品全部都被勾选了
// false则表示存在至少一个商品未被勾选这个属性方便前端展示购物车全选按钮的状态如全选按钮是否应该被勾选等视觉效果
// 同时在后端进行一些批量操作(如批量删除选中商品、批量结算等)时也可以根据这个属性来判断是否符合操作条件。
private Boolean allChecked;
// 图片主机地址,是一个字符串类型的属性,通常用于存放服务器上存储商品图片的主机地址或者前缀路径,例如"http://img.oursnail.cn/"
// 在前端展示购物车中商品的图片时需要将商品图片的相对路径与这个图片主机地址进行拼接从而形成完整的可访问的图片URL以正确地加载并展示商品的图片信息使购物车中的商品展示更加直观形象。
private String imageHost;
}
}

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

@ -128,6 +128,16 @@
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
@ -148,4 +158,5 @@
</plugins>
</build>
</project>

@ -12,35 +12,50 @@ import redis.clients.jedis.JedisPool;
* @Date 2019/1/1 15:03
* @CONTACT 317758022@qq.com
* @DESC
* Redis
* Spring@Component便Spring使Slf4j
*/
@Component
@Slf4j
public class CommonCacheUtil {
@Autowired
// 依赖注入JedisPoolWrapper通过它来获取JedisPool进而操作Redis
private JedisPoolWrapper jedisPoolWrapper;
/**
* key
* RedisRedis0
*
* - keyRedis
* - valueRedis
*/
public void cache(String key, String value) {
try {
// 通过JedisPoolWrapper获取JedisPool对象它是管理Jedis连接的资源池
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
// 从资源池中获取一个Jedis实例使用try-with-resources语句确保使用完后自动关闭连接释放资源
try (Jedis Jedis = pool.getResource()) {
// 选择Redis的0号数据库
Jedis.select(0);
// 将键值对存入Redis中
Jedis.set(key, value);
}
}
} catch (Exception e) {
// 如果出现异常,使用日志记录错误信息,记录的内容为"redis存值失败"以及具体的异常堆栈信息
log.error("redis存值失败", e);
// 抛出一个自定义的业务异常SnailmallException提示"redis报错",方便上层调用者统一处理异常情况
throw new SnailmallException("redis报错");
}
}
/**
* key
* Redis
*
* - keyRedis
*
* Redisnull
*/
public String getCacheValue(String key) {
String value = null;
@ -49,6 +64,7 @@ public class CommonCacheUtil {
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
// 从Redis中获取指定键的值并赋值给value变量
value = Jedis.get(key);
}
}
@ -61,6 +77,13 @@ public class CommonCacheUtil {
/**
* key
* Redis使SETNX
*
* - keyRedis
* - valueRedis
* - expire
*
* SETNX10
*/
public long cacheNxExpire(String key, String value, int expire) {
long result = 0;
@ -69,7 +92,9 @@ public class CommonCacheUtil {
if (pool != null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
// 使用SETNX命令向Redis中设置键值对只有键不存在时才设置成功返回结果赋值给result
result = jedis.setnx(key, value);
// 如果设置成功SETNX返回1则设置该键的过期时间
jedis.expire(key, expire);
}
}
@ -83,6 +108,9 @@ public class CommonCacheUtil {
/**
* key
* Redis
*
* - keyRedis
*/
public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool();
@ -90,6 +118,7 @@ public class CommonCacheUtil {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
// 调用Jedis的del方法从Redis中删除指定的键
jedis.del(key);
} catch (Exception e) {
log.error("从redis中删除失败", e);

@ -13,29 +13,54 @@ import javax.annotation.PostConstruct;
* @Date 2019/1/1 15:00
* @CONTACT 317758022@qq.com
* @DESC redisredishash
* JedisPoolJedisJedis
* Spring@Component便Spring使Slf4j
*/
@Component
@Slf4j
public class JedisPoolWrapper {
@Autowired
// 这里应该是自定义的一个类用于存放Redis相关的配置参数比如最大连接数、主机地址、端口等信息
// 但从当前代码来看没有看到其具体定义假设它有对应的getter方法来获取相关配置值。
private Parameters parameters;
// 用于存放初始化后的JedisPool对象初始值为null代表还未进行初始化操作
private JedisPool jedisPool = null;
/**
* @PostConstruct
* JedisJedisPoolJedisPool
*/
@PostConstruct
public void init(){
try {
// 创建一个JedisPoolConfig对象它用于配置Jedis连接池的各种属性。
JedisPoolConfig config = new JedisPoolConfig();
// 设置连接池允许的最大连接数通过Parameters类的对应方法获取配置值这个值决定了连接池最多能创建多少个Jedis连接。
config.setMaxTotal(parameters.getRedisMaxTotal());
// 设置连接池中空闲连接的最大数量即当连接池中闲置的连接达到这个数量时新的空闲连接将被释放同样通过Parameters获取配置值。
config.setMaxIdle(parameters.getRedisMaxIdle());
// 设置获取连接时的最大等待时间单位是毫秒如果超过这个时间还获取不到连接将会抛出异常也是从Parameters获取对应配置。
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
// 使用配置好的JedisPoolConfig以及其他必要的参数如Redis主机地址、端口、超时时间、密码等来创建JedisPool实例。
// 这里的参数 "xxx" 推测是Redis的密码实际应用中应避免硬编码密码可通过配置文件等更安全的方式传入超时时间设置为2000毫秒。
jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx");
// 如果初始化成功使用日志记录一条信息提示初始化redis连接池成功方便查看运行状态和排查问题。
log.info("【初始化redis连接池成功】");
}catch (Exception e){
// 如果在初始化过程中出现任何异常使用日志记录错误信息提示初始化redis连接池失败并打印出具体的异常堆栈信息方便定位问题所在。
log.error("【初始化redis连接池失败】",e);
}
}
/**
* JedisPoolJedisJedisPool
* JedisRedis
*
* JedisPoolinitnull
*/
public JedisPool getJedisPool() {
return jedisPool;
}

@ -9,24 +9,56 @@ import org.springframework.stereotype.Component;
* @Date 2019/1/1 14:27
* @CONTACT 317758022@qq.com
* @DESC
* ParametersRedisCurator
* Spring@ComponentSpring
* 使Lombok@DataGetterSettertoStringLombok便
*/
@Component
@Data
public class Parameters {
// Redis配置相关部分开始
/*****redis config start*******/
/**
* @Value("${redis.host}")Springapplication.propertiesapplication.yml"redis.host"
* redisHostRedis
*/
@Value("${redis.host}")
private String redisHost;
/**
* @Value"redis.port"Redis
*/
@Value("${redis.port}")
private int redisPort;
/**
* 'redis.max-total''redisMaxTotal'@Value
* "redis.max-total"Redis
*/
@Value("${redis.max-idle}")
private int redisMaxTotal;
/**
* "redis.max-idle"Redis
*/
@Value("${redis.max-total}")
private int redisMaxIdle;
/**
* @Value"redis.max-wait-millis"
* Redis
*/
@Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis;
/*****redis config end*******/
// Curator可能用于和ZooKeeper交互相关的组件配置相关部分开始
/*****curator config start*******/
/**
* Spring"zk.host"ZooKeeper
*/
@Value("${zk.host}")
private String zkHost;
/*****curator config end*******/

@ -9,12 +9,29 @@ import org.springframework.web.bind.annotation.RequestMapping;
* @Date 2019/1/6 20:06
* @CONTACT 317758022@qq.com
* @DESC
* CartClient使Spring Cloud OpenFeign"cart-service"
* FeignHTTP便
*/
@FeignClient("cart-service")
public interface CartClient {
/**
* getCartListFeign"cart-service"HTTP
* "/cart/getCartList.do"ServerResponse
* ServerResponse
*
* @return ServerResponse
*/
@RequestMapping("/cart/getCartList.do")
ServerResponse getCartList();
/**
* Feign"cart-service"HTTP
* "/cart/removeCart.do"
* ServerResponse
*
* @return ServerResponse
*/
@RequestMapping("/cart/removeCart.do")
ServerResponse removeCart();

@ -8,9 +8,19 @@ import org.springframework.web.bind.annotation.RequestMapping;
* @Date 2019/1/7 16:35
* @CONTACT 317758022@qq.com
* @DESC
* KeyGenClientSpring Cloud OpenFeign"KEYGEN-SERVICE"
* 使FeignClient
*/
@FeignClient("KEYGEN-SERVICE")
public interface KeyGenClient {
/**
* generateKeyFeign"KEYGEN-SERVICE"HTTP
* "/keygen""/keygen"
* String"KEYGEN-SERVICE"
*
* @return "KEYGEN-SERVICE"
*/
@RequestMapping("/keygen")
String generateKey();
}

@ -10,9 +10,26 @@ import org.springframework.web.bind.annotation.RequestParam;
* @Date 2019/1/6 20:16
* @CONTACT 317758022@qq.com
* @DESC
* ProductClientSpring Cloud OpenFeign"product-service"
* Feign便
*/
@FeignClient("product-service")
public interface ProductClient {
/**
* queryProductFeign"product-service"HTTP
* "/product/queryProduct.do"
*
* @RequestParam("productId")"productId"
* IntegerID"product-service"ID
*
* ServerResponse
* "product-service"
*
* @param productId ID便ID
* @return ServerResponse"product-service"
*/
@RequestMapping("/product/queryProduct.do")
ServerResponse queryProduct(@RequestParam("productId") Integer productId);
}

@ -10,10 +10,26 @@ import org.springframework.web.bind.annotation.RequestParam;
* @Date 2019/1/5 22:01
* @CONTACT 317758022@qq.com
* @DESC
* ShippingClientSpring Cloud OpenFeign"shipping-service"
* 使FeignClient使便
*/
@FeignClient("shipping-service")
public interface ShippingClient {
/**
* getShippingFeign"shipping-service"HTTP
*
* @RequestMapping"/shipping/getShipping.do""shipping-service"
*
* @RequestParam("shippingId")shippingId"shippingId"Integer
* "shipping-service"ID
*
* ServerResponse
* "shipping-service"
*
* @param shippingId "shipping-service"便
* @return ServerResponse"shipping-service"
*/
@RequestMapping("/shipping/getShipping.do")
ServerResponse getShipping(@RequestParam("shippingId") Integer shippingId);

@ -15,23 +15,41 @@ import javax.servlet.http.HttpServletRequest;
* @Date 2019/1/6 17:05
* @CONTACT 317758022@qq.com
* @DESC
* FeignConfigurationSpringFeign
* 使 @Configuration Spring
* 使 @Slf4j 便Cookie
*/
@Configuration
@Slf4j
public class FeignConfiguration {
/**
* @BeanSpringBeanFeignRequestInterceptor
* FeignFeign
* ServletCookieCookieFeign
* 使
*
* @return RequestInterceptorFeignSpringFeign
*/
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
// 通过RequestContextHolder获取当前线程绑定的请求相关的属性信息这里尝试获取ServletRequestAttributes类型的属性
// 它包含了与Servlet请求相关的详细信息比如请求对象、响应对象等。如果能获取到说明当前处于一个有效的Servlet请求上下文中。
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs != null) {
// 从ServletRequestAttributes中获取HttpServletRequest对象它代表了当前的Servlet请求后续可以通过它来获取请求中的各种信息比如Cookie等。
HttpServletRequest request = attrs.getRequest();
// 如果在Cookie内通过如下方式取
// 尝试从HttpServletRequest对象中获取所有的Cookie信息返回的是一个Cookie数组。
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
// 遍历获取到的Cookie数组对于每一个Cookie对象将其名称cookie.getName()作为请求头的键其值cookie.getValue())作为请求头的值,
// 通过requestTemplate对象添加到Feign后续发起的请求的请求头中。这样在Feign调用其他微服务时这些Cookie信息就会随着请求一起发送过去。
for (Cookie cookie : cookies) {
requestTemplate.header(cookie.getName(), cookie.getValue());
}
} else {
// 如果没有获取到Cookie即cookies为null或者长度为0则记录一条警告日志提示获取Cookie失败方便排查可能出现的与Cookie传递相关的问题。
log.warn("FeignHeadConfiguration", "获取Cookie失败");
}
}

@ -5,33 +5,84 @@ package com.njupt.swg.common.constants;
* @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com
* @DESC
* Constants便
*
*/
public class Constants {
// 自定义状态码相关部分开始
/**自定义状态码 start**/
/**
* 200使便
*/
public static final int RESP_STATUS_OK = 200;
/**
* 401
*/
public static final int RESP_STATUS_NOAUTH = 401;
/**
* 500使
*/
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
/**
* 400
*/
public static final int RESP_STATUS_BADREQUEST = 400;
/**自定义状态码 end**/
// 产品状态相关部分,使用接口(一种特殊的抽象类型)来定义产品相关的状态常量,这里只是一种常量分组的方式,方便代码逻辑中针对产品状态的判断和使用。
/** 产品的状态 **/
/** 产品的状态 **/
public interface Product{
/**
* 1
*/
int PRODUCT_ON = 1;
/**
* 2
*/
int PRODUCT_OFF = 2;
/**
* 3便
*/
int PRODUCT_DELETED = 3;
}
// 订单状态枚举部分使用枚举Enum类型来定义订单可能出现的各种状态枚举类型本身具有更强的类型安全性和可读性并且可以方便地进行相关逻辑处理。
public enum OrderStatusEnum{
/**
*
* 0 "已取消"便
*/
CANCELED(0,"已取消"),
/**
* 10"未支付"
*/
NO_PAY(10,"未支付"),
/**
* 20"已付款"
*/
PAID(20,"已付款"),
/**
* 40"已发货"便
*/
SHIPPED(40,"已发货"),
/**
* 50"订单完成"
*/
ORDER_SUCCESS(50,"订单完成"),
/**
* 60"订单关闭"
*/
ORDER_CLOSE(60,"订单关闭");
@ -39,16 +90,37 @@ public class Constants {
this.code = code;
this.value = value;
}
// 用于存储订单状态对应的描述信息,比如"已取消"等,是枚举值的一个属性,方便获取状态的文字描述内容。
private String value;
// 用于存储订单状态对应的代码值如0、10等也是枚举值的一个属性便于通过代码值来识别和处理不同的订单状态。
private int code;
/**
*
*
* @return "已取消"
*/
public String getValue() {
return value;
}
/**
*
*
* @return 010
*/
public int getCode() {
return code;
}
/**
*
* values()
* 便
*
* @param code
* @return
*/
public static OrderStatusEnum codeOf(int code){
for(OrderStatusEnum orderStatusEnum : values()){
if(orderStatusEnum.getCode() == code){
@ -59,34 +131,68 @@ public class Constants {
}
}
// 支付宝回调相关常量部分,使用接口来定义支付宝回调过程中涉及的一些关键字符串常量,方便在处理支付宝回调逻辑时统一使用和判断。
public interface AlipayCallback{
/**
*
*/
String TRADE_STATUS_WAIT_BUYER_PAY = "WAIT_BUYER_PAY";
/**
*
*/
String TRADE_STATUS_TRADE_SUCCESS = "TRADE_SUCCESS";
/**
*
*/
String RESPONSE_SUCCESS = "success";
/**
*
*/
String RESPONSE_FAILED = "failed";
}
// 支付平台枚举部分,通过枚举类型定义支付平台相关的常量信息,这里只列举了支付宝这一种支付平台示例,可根据实际业务扩展更多支付平台的枚举值。
public enum PayPlatformEnum{
/**
* 1"支付宝"便
*/
ALIPAY(1,"支付宝");
PayPlatformEnum(int code,String value){
this.code = code;
this.value = value;
}
// 存储支付平台的名称描述信息,如"支付宝",方便获取支付平台的文字名称。
private String value;
// 存储支付平台对应的代码值如1便于通过代码值来识别和区分不同的支付平台。
private int code;
/**
*
*
* @return "支付宝"
*/
public String getValue() {
return value;
}
/**
*
*
* @return 1
*/
public int getCode() {
return code;
}
}
// 支付类型枚举部分,同样使用枚举来定义支付类型相关的常量信息,这里以在线支付为例,可按需扩展其他支付类型的枚举值。
public enum PaymentTypeEnum{
/**
* 线1"在线支付"
*/
ONLINE_PAY(1,"在线支付");
PaymentTypeEnum(int code,String value){
@ -104,7 +210,13 @@ public class Constants {
return code;
}
/**
* OrderStatusEnum
* 便
*
* @param code
* @return
*/
public static PaymentTypeEnum codeOf(int code){
for(PaymentTypeEnum paymentTypeEnum : values()){
if(paymentTypeEnum.getCode() == code){
@ -115,10 +227,17 @@ public class Constants {
}
}
// Redis中与产品库存相关的键的前缀常量部分用于在使用Redis缓存产品相关数据特别是库存数据方便构建统一规范的键名。
/***redis product stock**/
/**
* Redis便
*/
/***redis product stock**/
public static final String PRODUCT_TOKEN_PREFIX = "product__";
/**
* Redis便
*/
public static final String PRODUCT_TOKEN_STOCK_PREFIX = "product__stock_";
}

@ -13,17 +13,35 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @Date 2019/1/1 13:21
* @CONTACT 317758022@qq.com
* @DESC
* ExceptionHandlerAdvice使Spring
* Controller使
*/
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionHandlerAdvice {
/**
* @ExceptionHandler(Exception.class)handleExceptionException
*
*
* @param e
* @return ServerResponse使Constants.RESP_STATUS_INTERNAL_ERROR"系统异常,请稍后再试"
*/
@ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){
// 使用日志记录异常的详细信息包括异常消息e.getMessage()以及完整的异常堆栈信息e方便后续排查问题确定异常出现的原因和位置。
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试");
}
/**
* 使@ExceptionHandlerSnailmallException
* SnailmallException
*
* @param e SnailmallException
* @return ServerResponse使SnailmallExceptione.getExceptionStatus()e.getMessage()
*/
@ExceptionHandler(SnailmallException.class)
public ServerResponse handleException(SnailmallException e){
log.error(e.getMessage(),e);

@ -8,17 +8,39 @@ import lombok.Getter;
* @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com
* @DESC
* SnailmallExceptionJavaRuntimeException
* Checked Exception使便
* 使Lombok@GetterLombokexceptionStatusGetter便
*/
@Getter
public class SnailmallException extends RuntimeException{
// 用于存储异常对应的状态码初始值被设置为ResponseEnum.ERROR.getCode()这里推测ResponseEnum是一个枚举类型
// ERROR是其中的一个枚举值通过调用其getCode()方法获取对应的状态码作为默认值,后续可根据不同的构造方法进行修改。
private int exceptionStatus = ResponseEnum.ERROR.getCode();
/**
* msgSnailmallException
* RuntimeExceptionmsg便
* 使ResponseEnum.ERROR.getCode()
*
* @param msg
*/
public SnailmallException(String msg){
super(msg);
}
/**
* codemsg
* RuntimeExceptionmsgcodeexceptionStatus
* 便
*
* @param code 便
* @param msg
*/
public SnailmallException(int code,String msg){
super(msg);
exceptionStatus = code;
}

@ -7,17 +7,45 @@ import lombok.Getter;
* @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com
* @DESC
* ResponseEnum便使
* 使Lombok@GetterLombokcodedescGetter便
*/
@Getter
public enum ResponseEnum {
/**
*
* 0使
* "SUCCESS"使便
*/
SUCCESS(0,"SUCCESS"),
/**
* 1
* "ERROR"
*/
ERROR(1,"ERROR"),
/**
* 2使
* "ILLEGAL_ARGUMENTS"
*/
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"),
/**
* 10访访
* "NEED_LOGIN"
*/
NEED_LOGIN(10,"NEED_LOGIN");
// 用于存储每个枚举值对应的状态代码方便通过Getter方法获取在业务逻辑判断、数据传输等场景中可以依据这个代码值来识别不同的返回状态。
private int code;
// 用于存储每个枚举值对应的状态描述文本同样可通过Getter方法获取用于向客户端展示、日志记录等场景更直观地体现返回状态的含义。
private String desc;
/**
* codedesc
* SUCCESSERROR使
*
* @param code
* @param desc
*/
ResponseEnum(int code,String desc){
this.code = code;
this.desc = desc;

@ -11,32 +11,53 @@ import java.io.Serializable;
* @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com
* @DESC
* ServerResponseSerializable便使
* 使
* 使Lombok@GetterGetter便使JacksonJSON
*/
@Getter
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ServerResponse<T> implements Serializable {
// 用于存储服务端返回的状态码,不同的状态码代表不同的返回情况,可对照项目中定义的相关状态码枚举来理解其具体含义。
private int status;
// 用于存储服务端返回给客户端的提示消息,通常用于向客户端传达一些简要的说明信息,比如操作成功的提示、出现错误的原因等内容。
private String msg;
// 泛型属性,用于存储具体的业务数据,类型可以是任意的,比如查询到的用户信息列表、商品详情等具体的业务相关数据。
private T data;
// 默认构造方法,主要用于在一些需要创建空对象,后续再通过其他方式设置属性值的场景,不过从当前类的设计来看,更多是通过其他特定的构造方法或静态工厂方法来创建对象。
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;
}
/**
* @JsonIgnoreJacksonJSONJavaServerResponse
* this.statusResponseEnum.SUCCESS.getCode()
*
* @return trueServerResponsefalse
*/
@JsonIgnore
public boolean isSuccess(){
return this.status == ResponseEnum.SUCCESS.getCode();
@ -44,16 +65,53 @@ public class ServerResponse<T> implements Serializable {
/**
*
* 便ServerResponse使
*/
/**
* ServerResponse使ResponseEnumResponseEnum.SUCCESS.getCode()ResponseEnum.SUCCESS.getDesc()
* 使ServerResponse
*
* @param <T> <Integer>
* @return ServerResponse
*/
public static <T>ServerResponse<T> createBySuccess(){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
}
/**
* ServerResponse使ResponseEnumResponseEnum.SUCCESS.getCode()message
* ServerResponse
*
* @param <T>
* @param message
* @return ServerResponse
*/
public static <T>ServerResponse<T> createBySuccessMessage(String message){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message);
}
/**
* ServerResponse使ResponseEnumResponseEnum.SUCCESS.getCode()data
* ServerResponse使
*
* @param <T>
* @param data
* @return ServerResponse
*/
public static <T>ServerResponse<T> createBySuccess(T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data);
}
/**
* ServerResponse使ResponseEnumResponseEnum.SUCCESS.getCode()messagedata
* ServerResponse
*
* @param <T>
* @param message
* @param data
* @return ServerResponse
*/
public static <T>ServerResponse<T> createBySuccess(String message,T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data);
}
@ -61,16 +119,40 @@ public class ServerResponse<T> implements Serializable {
/**
*
*/
/**
* ServerResponse使ResponseEnumResponseEnum.ERROR.getCode()ResponseEnum.ERROR.getDesc()
* 使ServerResponse
*
* @param <T>
* @return ServerResponse
*/
public static <T>ServerResponse<T> createByError(){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc());
}
/**
* ServerResponse使ResponseEnumResponseEnum.ERROR.getCode()msg
* ServerResponse
*
* @param <T>
* @param msg
* @return ServerResponse
*/
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
}
/**
* ServerResponse使codemsg
* ServerResponse
*
* @param <T>
* @param code
* @param msg
* @return ServerResponse
*/
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
}
}

@ -7,31 +7,71 @@ import java.math.BigDecimal;
* @Date 2019/1/5 15:20
* @CONTACT 317758022@qq.com
* @DESC BigDecimal
* BigDecimalUtil
* doubleBigDecimal
*
*/
public class BigDecimalUtil {
// 将构造方法私有化这样外部代码就无法通过new关键字来创建BigDecimalUtil类的实例因为这个类的功能都是通过静态方法来提供的不需要实例化对象去调用方法。
private BigDecimalUtil(){
}
/**
* double
* BigDecimalBigDecimaladd
* 使
*
* @param v1
* @param v2
* @return BigDecimal
*/
public static BigDecimal add(double v1, double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2);
}
/**
* double
* BigDecimalBigDecimalsubtract
*
*
* @param v1
* @param v2
* @return BigDecimal
*/
public static BigDecimal sub(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2);
}
/**
* double
* BigDecimalBigDecimalmultiply
*
*
* @param v1
* @param v2
* @return BigDecimal
*/
public static BigDecimal mul(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2);
}
/**
* double
* BigDecimalBigDecimaldivide
* 2BigDecimal.ROUND_HALF_UP
*
*
* @param v1
* @param v2
* @return BigDecimal2
*/
public static BigDecimal div(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));

@ -9,40 +9,65 @@ import javax.servlet.http.HttpServletResponse;
/**
* cookie
* CookieUtilCookieCookieCookieCookie
* Cookie便使Slf4j便Cookie
*/
@Slf4j
public class CookieUtil {
// 定义Cookie的域名用于指定该Cookie在哪个域名下有效这里固定设置为"oursnail.cn"意味着这个Cookie在访问该域名及其子域名下的资源时都可以被携带和使用
private final static String COOKIE_DOMAIN = "oursnail.cn";
// 定义Cookie的名称这里取名为"snailmall_login_token"推测是用于存储和用户登录相关的标识信息方便在后续的业务逻辑中通过这个名称来识别和获取对应的Cookie值。
private final static String COOKIE_NAME = "snailmall_login_token";
/**
* cookie
* @param response
* @param token
* writeLoginTokenCookie
* Cookie访HttpServletResponse使Cookie
*
* @param response CookieCookie
* @param token CookieCookie
*/
public static void writeLoginToken(HttpServletResponse response,String token){
// 创建一个名为COOKIE_NAME即"snailmall_login_token"值为传入的token的Cookie对象这个Cookie将用于存储和传递登录相关的信息。
Cookie ck = new Cookie(COOKIE_NAME,token);
// 设置Cookie的域名使其在指定的域名COOKIE_DOMAIN即"oursnail.cn"及其子域名下都有效这样在访问该域名相关的网页时浏览器会自动带上这个Cookie。
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//设值在根目录
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
// 设置Cookie的路径为根目录"/"表示这个Cookie在该域名下的所有页面请求中都会被发送给服务器覆盖范围较广如果设置为其他具体路径则只在访问该路径及子路径下的页面时才会携带这个Cookie。
ck.setPath("/");
//不允许通过脚本访问cookie,避免脚本攻击
ck.setHttpOnly(true);
//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
ck.setMaxAge(60*60*24*365);
// 使用日志记录要写入的Cookie的名称和值方便后续查看Cookie写入操作的相关情况比如排查写入的值是否正确等问题利于调试和跟踪。
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
// 将创建并设置好属性的Cookie添加到响应对象中这样浏览器在接收到响应后就会根据设置来保存这个Cookie以便后续请求时携带它。
response.addCookie(ck);
}
/**
* cookie
* @param request
* @return
* readLoginTokenHttpServletRequestCookieCOOKIE_NAMECookienull
* CookieCookie
*
* @param request CookieCookie
* @return "snailmall_login_token"CookieCookienull
*/
public static String readLoginToken(HttpServletRequest request){
// 从请求对象中获取所有的Cookie数组这些Cookie是客户端随请求一起发送过来的可能包含了之前服务器设置的各种Cookie信息。
Cookie[] cks = request.getCookies();
if(cks != null){
// 遍历获取到的Cookie数组对每个Cookie进行检查
for(Cookie ck:cks){
// 使用日志记录每个Cookie的名称和值方便查看请求中携带的Cookie情况比如排查是否有预期的Cookie等问题便于调试和跟踪。
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 通过StringUtils工具类的equals方法它能更好地处理null情况等比较当前Cookie的名称和预定义的登录相关Cookie名称COOKIE_NAME是否相等如果相等则说明找到了目标Cookie。
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
// 使用日志记录找到的目标Cookie的名称和值方便确认读取到的内容是否正确等情况利于调试和跟踪。
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 返回找到的目标Cookie的值也就是获取到的登录相关的Cookie中存储的具体信息比如登录凭证等内容
return ck.getValue();
}
}
@ -52,18 +77,28 @@ public class CookieUtil {
/**
*
* @param request
* @param response
* delLoginTokenCookieCOOKIE_NAMECookie
* CookieCookie0CookieCookie
*
* @param request CookieCookie
* @param response CookieCookie
*/
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
// 从请求对象中获取客户端发送过来的所有Cookie数组以便从中查找要删除的登录相关Cookie。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks) {
// 通过比较Cookie名称查找是否有名为COOKIE_NAME"snailmall_login_token"的Cookie找到了就进行删除相关的操作。
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
// 设置Cookie的域名保持和之前设置一致COOKIE_DOMAIN即"oursnail.cn"确保操作的是同一个域名下的对应Cookie。
ck.setDomain(COOKIE_DOMAIN);
// 设置Cookie的路径为根目录"/"同样要和之前设置相符确保准确删除对应的Cookie。
ck.setPath("/");
ck.setMaxAge(0);//0表示消除此cookie
// 设置Cookie的有效期为0表示让客户端立即删除这个Cookie下次请求时就不会再携带它了实现了注销时清除登录相关Cookie的功能。
ck.setMaxAge(0);
// 使用日志记录要删除的Cookie的名称和值方便查看注销操作时删除Cookie的相关情况利于调试和跟踪。
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 将设置好属性主要是有效期为0的Cookie添加到响应对象中通知客户端删除这个Cookie这样客户端接收到响应后就会按照要求删除对应的Cookie。
response.addCookie(ck);
return;
}

@ -4,65 +4,110 @@ import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @DESC
* DateTimeUtil便DateDate
* 使Joda-TimeJoda-TimeJava便使JavaSimpleDateFormat
*/
public class DateTimeUtil {
//joda-time
//str->Date
//Date->str
// joda-time
// 定义一个常量字符串,表示日期时间的标准格式,这里采用"yyyy-MM-dd HH:mm:ss"的格式,后续很多方法中如果没有指定特定格式时,就会默认使用这个标准格式进行转换操作。
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static Date strToDate(String dateTimeStr, String formatStr){
/**
* DateformatStr
* Joda-TimeDateTimeFormatter使DateTimeDateTimeJavaDate
*
* @param dateTimeStr "2024-12-10 15:30:00"formatStr
* @param formatStr "yyyy-MM-dd HH:mm:ss" dateTimeStr
* @return 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){
if(date == null){
/**
* Date
* DatenullnullStringUtils.EMPTY
* DatenullJoda-TimeDateTimeDateDateTimetoStringformatStrDate
*
* @param date Datenullnull
* @param formatStr "yyyy-MM-dd HH:mm:ss"
* @return datenull
*/
public static String dateToStr(Date date, String formatStr) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(formatStr);
}
//固定好格式
public static Date strToDate(String dateTimeStr){
/**
* Date使STANDARD_FORMAT"yyyy-MM-dd HH:mm:ss"
* strToDate(String dateTimeStr, String formatStr)使DateTimeDate
*
* @param dateTimeStr STANDARD_FORMAT"yyyy-MM-dd HH:mm:ss"
* @return DateJoda-Time
*/
public static Date strToDate(String dateTimeStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date){
if(date == null){
/**
* Date使STANDARD_FORMAT"yyyy-MM-dd HH:mm:ss"
* DatenullnullnullDateTimedateToStr(Date date, String formatStr)
*
* @param date Datenullnull
* @return "yyyy-MM-dd HH:mm:ss"datenull
*/
public static String dateToStr(Date date) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(STANDARD_FORMAT);
}
//Date -> 时间戳
/**
* Date19701100:00:00 UTC
* DatenullnullnullnullSimpleDateFormat使"yyyy-MM-dd HH:mm:ss"
* Date使SimpleDateFormatparseDateDategetTime197011
* ParseException
*
* @param date Datenullnullnull
* @return Long19701100:00:00 UTCdatenullnullParseException
* @throws ParseException 使SimpleDateFormat
*/
public static Long dateToChuo(Date date) throws ParseException {
if(date == null){
if (date == null) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.parse(String.valueOf(date)).getTime();
}
/**
* Date
* 使
* ParseExceptionSimpleDateFormatparsemain
*
* @param args 使
* @throws ParseException 使SimpleDateFormatmain
*/
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
String time="1970-01-06 11:45:55";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = "1970-01-06 11:45:55";
Date date = format.parse(time);
System.out.print("Format To times:"+date.getTime());
System.out.print("Format To times:" + date.getTime());
}
}
}

@ -3,7 +3,6 @@ package com.njupt.swg.common.utils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@ -14,77 +13,141 @@ import java.util.List;
* @Date 2018/1/11 14:32
* @DESC
* @CONTACT 317758022@qq.com
* FtpUtilFTPFTP
* 使Apache Commons NetFTPClientFTP便FTP
* 使Lombok@DataGetterSetter@Slf4j便便FTP
*/
@Data
@Slf4j
public class FtpUtil {
// 通过PropertiesUtil工具类这里未展示其代码但推测是用于读取配置文件属性的类从配置文件中读取FTP服务器的IP地址并赋值给这个静态变量用于后续连接FTP服务器时指定IP。
private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip");
// 同样通过PropertiesUtil从配置文件中读取FTP服务器的用户名并赋值给这个静态变量用于登录FTP服务器时提供用户名。
private static String ftpUser = PropertiesUtil.getProperty("ftp.user");
// 通过PropertiesUtil从配置文件中读取FTP服务器的密码并赋值给这个静态变量用于登录FTP服务器时提供密码。
private static String ftpPass = PropertiesUtil.getProperty("ftp.pass");
public FtpUtil(String ip, int port, String user, String pwd){
/**
* FtpUtilFTPIPipportuserpwd
* 便FTP使使
*
* @param ip FTPIP
* @param port FTPFTP21
* @param user FTP
* @param pwd FTP
*/
public FtpUtil(String ip, int port, String user, String pwd) {
this.ip = ip;
this.port = port;
this.user = user;
this.pwd = pwd;
}
/**
* fileListFTP
* FtpUtil使FTPIP使21
* uploadFile
* IOExceptionFTPI/O
*
* @param fileList FTPList<File>FTP
* @return truefalse
* @throws IOException FTPFTPI/O
*/
public static boolean uploadFile(List<File> fileList) throws IOException {
FtpUtil ftpUtil = new FtpUtil(ftpIp,21,ftpUser,ftpPass);
FtpUtil ftpUtil = new FtpUtil(ftpIp, 21, ftpUser, ftpPass);
log.info("开始连接ftp服务器");
boolean result = ftpUtil.uploadFile("img",fileList);
boolean result = ftpUtil.uploadFile("img", fileList);
log.info("开始连接ftp服务器,结束上传,上传结果:{}");
return result;
}
private boolean uploadFile(String remotePath,List<File> fileList) throws IOException {
/**
* FTPremotePath
* uploadedtrueFileInputStreamFTP
* FTPFTP
* IOExceptionuploadedfalseFTP
*
* @param remotePath FTPFTP "/img" FTPimg
* @param fileList FTP
* @return truefalsefalse
* @throws IOException FTPFTPI/O
*/
private boolean uploadFile(String remotePath, List<File> fileList) throws IOException {
boolean uploaded = true;
FileInputStream fis = null;
//连接FTP服务器
if(connectServer(this.ip,this.port,this.user,this.pwd)){
// 连接FTP服务器调用connectServer方法进行连接操作如果连接成功则继续后续的文件上传相关配置和操作否则根据异常处理逻辑设置uploaded为false等情况。
if (connectServer(this.ip, this.port, this.user, this.pwd)) {
try {
// 切换FTP客户端的工作目录到指定的远程路径下确保后续上传的文件会存放在这个目录中例如将工作目录切换到FTP服务器的img目录下方便文件分类存放等管理。
ftpClient.changeWorkingDirectory(remotePath);
// 设置FTP客户端的缓冲区大小为1024字节缓冲区用于临时存储数据合适的缓冲区大小可以在一定程度上提高文件传输效率等性能表现。
ftpClient.setBufferSize(1024);
// 设置FTP客户端的控制编码为"UTF-8"确保在与FTP服务器交互过程中命令和相关文本信息的编码统一避免出现乱码等问题特别是处理包含中文等多字节字符的文件名等情况。
ftpClient.setControlEncoding("UTF-8");
// 设置FTP客户端传输的文件类型为二进制文件类型FTPClient.BINARY_FILE_TYPE适用于上传各种类型的文件如图像、视频、文档等与ASCII文件类型区分开确保文件内容准确传输不会出现格式损坏等情况。
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
// 使FTP客户端进入本地被动模式这是一种常见的FTP数据传输模式在很多网络环境下特别是客户端处于防火墙或NAT之后等情况可以更好地保证数据传输的顺利进行避免连接问题。
ftpClient.enterLocalPassiveMode();
for(File fileItem : fileList){
for (File fileItem : fileList) {
// 为每个要上传的文件创建一个文件输入流用于读取本地文件的内容以便后续通过FTP客户端将文件内容发送到FTP服务器上进行存储。
fis = new FileInputStream(fileItem);
ftpClient.storeFile(fileItem.getName(),fis);
// 使用FTP客户端将本地文件上传到FTP服务器以文件的名称fileItem.getName()作为在FTP服务器上存储的文件名通过文件输入流fis将文件内容传输过去进行存储。
ftpClient.storeFile(fileItem.getName(), fis);
}
} catch (IOException e) {
log.error("上传文件异常",e);
// 如果在文件上传过程中出现I/O相关的异常情况使用日志记录异常信息方便后续排查问题确定是哪个环节出现了错误导致文件上传失败。
log.error("上传文件异常", e);
uploaded = false;
e.printStackTrace();
} finally {
fis.close();
// 无论文件上传是否成功,都需要关闭文件输入流,释放相关的系统资源,避免资源泄漏等问题。
if (fis!= null) {
fis.close();
}
// 断开与FTP服务器的连接释放与FTP服务器连接相关的资源结束本次文件上传操作对应的FTP连接会话。
ftpClient.disconnect();
}
}
return uploaded;
}
private boolean connectServer(String ip,int port,String user,String pwd){
/**
* FTPIPipportuserpwd
* FTPClient使IPFTP使
* IOException便FTP
*
* @param ip FTPIP
* @param port FTPFTP21
* @param user FTP
* @param pwd FTP
* @return FTPtruefalse
*/
private boolean connectServer(String ip, int port, String user, String pwd) {
boolean isSuccess = false;
ftpClient = new FTPClient();
try {
// 使用FTPClient对象的connect方法尝试连接指定IP地址的FTP服务器连接的端口号通过传入的port参数指定。
ftpClient.connect(ip);
isSuccess = ftpClient.login(user,pwd);
// 使用FTPClient对象的login方法传入用户名user和密码pwd进行登录操作登录成功则将isSuccess设置为true否则保持为false。
isSuccess = ftpClient.login(user, pwd);
} catch (IOException e) {
log.error("连接FTP服务器异常",e);
// 如果在连接FTP服务器或者登录过程中出现I/O相关的异常情况使用日志记录异常信息方便后续排查问题确定是网络连接问题还是用户名密码等认证问题导致无法连接FTP服务器。
log.error("连接FTP服务器异常", e);
}
return isSuccess;
}
// 用于存储FTP服务器的IP地址通过构造方法或者默认配置赋值在连接和操作FTP服务器时使用这个IP地址进行网络连接。
private String ip;
// 用于存储FTP服务器的端口号通过构造方法传入或者使用默认值如常见的21端口在连接FTP服务器时指定要连接的端口。
private int port;
// 用于存储登录FTP服务器的用户名通过构造方法传入或者使用从配置文件中读取的默认用户名用于在连接FTP服务器时进行身份验证获取操作权限。
private String user;
// 用于存储登录FTP服务器的密码通过构造方法传入或者使用从配置文件中读取的默认密码与用户名配合进行身份验证确保有权限进行文件上传等操作。
private String pwd;
// FTPClient对象用于与FTP服务器进行实际的交互操作比如连接服务器、切换目录、上传文件等功能都是通过这个对象来实现的它是Apache Commons Net库中提供的用于操作FTP服务器的核心类。
private FTPClient ftpClient;
}
}

@ -8,119 +8,162 @@ import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import java.io.IOException;
import java.text.SimpleDateFormat;
/**
* jackson
* JsonUtilJSONJavaJSONJSONJava
* Jackson使CodehausJacksonObjectMapper使JSON便
* 使Slf4j便便
*/
@Slf4j
public class JsonUtil {
// 创建一个ObjectMapper对象它是Jackson库中用于进行JSON序列化和反序列化的核心类后续的各种配置以及序列化、反序列化操作都是基于这个对象来完成的。
// 由于ObjectMapper是线程安全的所以整个应用中可以共享使用这一个实例对象来进行JSON处理提高性能和资源利用率。
private static ObjectMapper objectMapper = new ObjectMapper();
// 静态代码块在类加载时执行用于对ObjectMapper对象进行一系列的配置以定制JSON序列化和反序列化的行为确保符合项目的需求和规范。
static {
//所有字段都列入进行转换
// 所有字段都列入进行转换
// 设置序列化时包含所有的字段即无论字段的值是否为null都会被包含在生成的JSON字符串中通过指定JsonSerialize.Inclusion.ALWAYS来实现这一配置
// 这样可以保证对象的完整结构都能在JSON表示中体现出来避免遗漏某些字段信息。
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
//取消默认转换timestamp形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
//统一时间的格式
// 取消默认转换timestamp形式
// 配置ObjectMapper在序列化日期类型的属性时不将其转换为时间戳的形式默认情况下Jackson可能会把日期转换为时间戳而是按照后续设置的日期格式进行转换
// 通过将SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS设置为false来取消默认的时间戳转换行为使得日期在JSON中以更易读的格式化字符串形式呈现。
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// 忽略空bean转json的错误
// 配置ObjectMapper在尝试将一个空的Java Bean即没有任何属性值的对象转换为JSON字符串时忽略可能出现的错误直接返回一个空的JSON对象{})或者空数组([])等合适的表示形式,
// 通过将SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS设置为false来避免因为空Bean转换导致的序列化失败情况增强程序的健壮性。
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
// 统一时间的格式
// 设置日期的格式化格式使用了项目中可能自定义的标准日期时间格式DateTimeUtil.STANDARD_FORMAT推测是类似"yyyy-MM-dd HH:mm:ss"这样的格式),
// 通过创建SimpleDateFormat对象并将其设置给ObjectMapper使得在序列化和反序列化日期类型数据时都按照这个统一的格式进行处理便于数据的一致性和可读性。
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
//忽略json存在属性但是java对象不存在属性的错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
// 忽略json存在属性但是java对象不存在属性的错误
// 配置ObjectMapper在进行反序列化时如果JSON字符串中存在一些Java对象中没有对应的属性比如JSON数据多了一些额外字段忽略这样的属性不匹配错误正常进行反序列化操作
// 通过将DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES设置为false来实现避免因为JSON数据结构和Java对象不完全匹配而导致反序列化失败提高对不同来源JSON数据的兼容性。
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
*
* @param obj
* @param <T>
* @return
* obj2StringJavaJSON
* nullnullinstanceof使ObjectMapperJSON
* IOExceptionnull
*
* @param obj JSONJavaJavaPOJOJavaMapJackson
* @param <T> JSONJava
* @return JSONJavaJSONnullnull
*/
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
* obj2StringPrettyobj2StringJavaJSONJSON便JSON
* nullnull使ObjectMapperwriterWithDefaultPrettyPrinterJSON
* IOExceptionnull
*
* @param obj JSONJavaobj2StringobjJava
* @param <T> JSONJava
* @return JSONJavaJSONnullnullJSON便
*/
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
* String2ObjJSONclazzJava
* JSONJavaclazznullnull
* JSONnullStringequalsJSON
* 使ObjectMapperJSONJavaclazzIOExceptionnull
*
* @param str JSONJava
* @param clazz JavaJSONJavaPOJO
* @param <T> JavaJava
* @return clazzJavaJSONJSONnullnull
*/
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
* Str2ObjJavaString2ObjJSONJava
* TypeReferenceJSONTypeReferencenullnull
* JSONTypeReferenceTypeReferenceStringgetTypeJSON
* 使ObjectMapperJSONTypeReferenceIOExceptionnull
*
* @param str JSONTypeReference
* @param typeReference TypeReferenceList<SomeClass>Map<String, AnotherClass>便JSON
* @param <T> TypeReferenceJava
* @return TypeReferenceJavaJSONJSONTypeReferencenullnull
*/
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
* Str2ObjcollectionClasselementClassesJavaType使ObjectMapper
* 使ObjectMappergetTypeFactoryconstructParametricTypeJavaTypeJavaType
* 使ObjectMapperJSONJavaTypeIOExceptionnull
* List<SomeClass>Set<AnotherClass>Java
*
* @param str JSONcollectionClasselementClasses
* @param collectionClass List.classSet.class
* @param elementClasses List<List<SomeClass>>List.classSomeClass.classJavaType
* @param <T> Java
* @return collectionClasselementClassesJavaJSONIOExceptionnull
*/
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;
}
}
}
}

@ -4,8 +4,19 @@ import java.security.MessageDigest;
/**
* MD5
* MD5UtilMD5MD5
* MD5便UTF-8MD5
*/
public class MD5Util {
/**
*
* byteToHexString
* MD5便
*
* @param b MD5MessageDigestdigest
* @return MD5
*/
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
@ -14,6 +25,15 @@ public class MD5Util {
return resultSb.toString();
}
/**
*
* 0Java -128 127256使0255便
* 16hexDigits
* byteArrayToHexString
*
* @param b MD5
* @return 10"0a"
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
@ -24,11 +44,15 @@ public class MD5Util {
}
/**
* MD5
* MD5MD5
* originJavaMessageDigestMD5MD5
* charsetnamenull使MD5byteArrayToHexString
* "utf-8"MD5toUpperCase便
* MessageDigestnull
*
* @param origin
* @param charsetname
* @return
* @param origin MD5
* @param charsetname "utf-8""gbk"null使MD5
* @return MD5null
*/
private static String MD5Encode(String origin, String charsetname) {
String resultString = null;
@ -44,16 +68,31 @@ public class MD5Util {
return resultString.toUpperCase();
}
/**
* 使UTF-8MD5便MD5
* MD5Encodeorigin"utf-8"MD5UTF-8MD5UTF-8Web
*
* @param origin MD5UTF-8UTF-8
* @return UTF-8MD5null
*/
public static String MD5EncodeUtf8(String origin) {
//这里可以加盐
return MD5Encode(origin, "utf-8");
}
/**
* "123456"MD5UTF-8MD5MD5EncodeUtf8
* 使MD5
*
* @param args 使
*/
public static void main(String[] args) {
System.out.println(MD5EncodeUtf8("123456"));
}
/**
* 10"a"10
*/
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}
}

@ -2,7 +2,6 @@ package com.njupt.swg.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;
@ -12,35 +11,61 @@ import java.util.Properties;
* @Date 2018/1/10 14:56
* @DESC
* @CONTACT 317758022@qq.com
* PropertiesUtilproperties
* "parameter.properties"Properties便便使Slf4j便
*/
@Slf4j
public class PropertiesUtil {
// 定义一个静态的Properties对象用于存储从配置文件中读取的键值对信息Properties类是Java中用于处理属性文件.properties的常用类它可以方便地获取、设置各种配置属性的值。
private static Properties props;
// 静态代码块在类被加载到内存时执行主要用于初始化配置文件的读取操作加载配置文件中的属性信息到props对象中。
static {
// 指定要读取的配置文件的名称,这里固定设置为"parameter.properties",意味着程序会默认从这个文件名对应的配置文件中读取属性信息,如果需要读取其他配置文件,可能需要对这里进行修改或者添加相应的参数传递机制来指定不同的文件名。
String fileName = "parameter.properties";
props = new Properties();
try {
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
// 通过类加载器PropertiesUtil.class.getClassLoader()获取指定配置文件的输入流getResourceAsStream方法并将其包装为InputStreamReader同时指定字符编码为"UTF-8",以确保正确读取配置文件内容(特别是包含中文等多字节字符的情况)。
// 然后使用Properties对象的load方法将配置文件中的键值对信息加载到props对象中这样后续就可以通过键来获取相应的值了。
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName), "UTF-8"));
} catch (IOException e) {
log.error("配置文件读取异常",e);
// 如果在读取配置文件过程中出现I/O相关的异常比如文件不存在、权限不足、编码错误等情况使用日志记录异常信息方便后续排查问题确定是哪个环节导致配置文件读取失败影响程序获取配置属性的功能。
log.error("配置文件读取异常", e);
}
}
public static String getProperty(String key){
/**
* nullnull
* PropertiespropskeytrimStringUtils.isBlanknull
* null
*
* @param key "键=值"Properties"server.port=8080""server.port"
* @return nullnull
*/
public static String getProperty(String key) {
String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){
if (StringUtils.isBlank(value)) {
return null;
}
return value.trim();
}
public static String getProperty(String key,String defaultValue){
/**
* nulldefaultValue
* PropertiespropskeyStringUtils.isBlank
* defaultValuevaluevalue
*
* @param key getProperty
* @param defaultValue null8080
* @return null
*/
public static String getProperty(String key, String defaultValue) {
String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){
if (StringUtils.isBlank(value)) {
value = defaultValue;
}
return value.trim();
}
}
}

@ -15,22 +15,34 @@ import java.util.Collections;
* @Date 2019/1/7 12:23
* @CONTACT 317758022@qq.com
* @DESC
* RedisUtilsRedisSpringRedis
* Spring@Component便SpringRedis
*/
@Component
public class RedisUtils {
// 通过Spring的依赖注入机制自动注入一个RedisTemplate对象该对象是Spring Data Redis提供的用于操作Redis的核心模板类
// 这里限定了操作的键Key和值Value的类型都是字符串类型String通过它可以方便地执行各种Redis操作如读写数据等。
@Autowired
private RedisTemplate<String,String> redisTemplate;
private RedisTemplate<String, String> redisTemplate;
// 通过Spring的依赖注入机制自动注入一个JedisPoolWrapper对象这里未展示其具体代码但推测是对Jedis连接池进行封装的类
// 用于获取Jedis连接对象等相关操作可能在与Redis底层交互时发挥作用比如获取原生的Jedis连接来执行一些特定的Redis命令等情况。
@Autowired
private JedisPoolWrapper jedisPoolWrapper;
/**
*
* -2
* -1
* >=00
* STOCK_REDUCE_LUALuaRedis
* KEYS[1]ARGV[1]RedisEXISTS
* EXISTS1DECRBY00INCRBY -1
* 0EXISTS0 -2
* RedisLua
*/
public static final String STOCK_REDUCE_LUA=
public static final String STOCK_REDUCE_LUA =
"local stock = KEYS[1] " +
"local stock_change = tonumber(ARGV[1]) " +
"local is_exists = redis.call(\"EXISTS\", stock) " +
@ -47,16 +59,21 @@ public class RedisUtils {
"end";
/**
*
* reduceStockRedisTemplateRedisRedisCallbackRedisJedis使LuaSTOCK_REDUCE_LUA
* stockKeystockChangeLuaCollections.unmodifiableListArrays.asListLua
* Lua -2 -10
*
* @Description
* @param stockKey RedisID
* @param stockChange Lua
* @return ObjectLua -2 -10
*/
public Object reduceStock(String stockKey,String stockChange){
Object result = redisTemplate.execute((RedisCallback<Object>) redisConnection -> {
Jedis jedis = (Jedis)redisConnection.getNativeConnection();
return jedis.eval(STOCK_REDUCE_LUA, Collections.unmodifiableList(Arrays.asList(stockKey))
,Collections.unmodifiableList(Arrays.asList(stockChange)));
public Object reduceStock(String stockKey, String stockChange) {
Object result = redisTemplate.execute((RedisCallback<Object>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
return jedis.eval(STOCK_REDUCE_LUA, Collections.unmodifiableList(Arrays.asList(stockKey)),
Collections.unmodifiableList(Arrays.asList(stockChange)));
});
return result;
}
}
}

@ -17,23 +17,39 @@ import javax.servlet.http.HttpServletRequest;
* @Date 2019/1/5 16:19
* @CONTACT 317758022@qq.com
* @DESC
* BaseController
* 使Spring@AutowiredCommonCacheUtilCookieUtilJsonUtilUser
* 使Slf4j便便
*/
@Slf4j
public class BaseController {
// 通过Spring的依赖注入机制自动注入一个CommonCacheUtil对象这里未展示其具体代码但推测是用于操作缓存的工具类比如操作Redis缓存等用于存储和获取一些常用数据
// 后续会使用这个对象从缓存中获取与当前登录用户相关的信息(比如存储在缓存中的用户对象的序列化字符串等内容)。
@Autowired
private CommonCacheUtil commonCacheUtil;
User getCurrentUser(HttpServletRequest httpServletRequest){
/**
* User
* HttpServletRequestHTTPloginTokenCookieCookieUtil.readLoginToken
* StringUtils.isEmpty(loginToken)trueSnailmallException
* 使CommonCacheUtilgetCacheValueuserJsonStrJSON
* nullSnailmallException使ResponseEnum.NEED_LOGIN
* 使JsonUtil.Str2ObjUserUser.classUser便使
*
* @param httpServletRequest HTTPCookie
* @return UserUser
*/
User getCurrentUser(HttpServletRequest httpServletRequest) {
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)){
if (StringUtils.isEmpty(loginToken)) {
throw new SnailmallException("用户未登陆,无法获取当前用户信息");
}
String userJsonStr = commonCacheUtil.getCacheValue(loginToken);
if(userJsonStr == null){
throw new SnailmallException(ResponseEnum.NEED_LOGIN.getCode(),ResponseEnum.NEED_LOGIN.getDesc());
if (userJsonStr == null) {
throw new SnailmallException(ResponseEnum.NEED_LOGIN.getCode(), ResponseEnum.NEED_LOGIN.getDesc());
}
User user = JsonUtil.Str2Obj(userJsonStr,User.class);
User user = JsonUtil.Str2Obj(userJsonStr, User.class);
return user;
}
}
}

@ -182,4 +182,4 @@ public class OrderController extends BaseController{
return Constants.AlipayCallback.RESPONSE_FAILED;
}
}
}

@ -14,35 +14,76 @@ import org.springframework.web.bind.annotation.RestController;
* @Date 2019/1/5 21:44
* @CONTACT 317758022@qq.com
* @DESC
* OrderManageControllerSpring RESTful
* @RestControllerJSONAPI
* @RequestMapping"/manage/order"
* IOrderServiceSpringServerResponse便
*/
@RestController
@RequestMapping("/manage/order")
public class OrderManageController {
// 通过Spring的依赖注入机制自动注入一个IOrderService接口的实现类对象IOrderService应该定义了一系列与订单业务相关的方法比如获取订单列表、查询订单详情等操作的抽象方法
// 具体的业务逻辑实现在其对应的实现类中由Spring根据配置去查找并注入合适的实现类实例这个控制器类中的各个方法会调用IOrderService的相应方法来完成具体的订单业务处理。
@Autowired
private IOrderService orderService;
/**
* "/manage/order/list.do"GET@RequestMappingGETmethod
* pageNum@RequestParam"1"pageSize"10"10
* orderServicemanageListIOrderService
* orderService.manageListServerResponse<PageInfo>PageInfoJSON
*
* @param pageNum 1
* @param pageSize 便1010
* @return ServerResponse<PageInfo>ServerResponsePageInfo
*/
@RequestMapping("list.do")
public ServerResponse<PageInfo> orderList(@RequestParam(value = "pageNum",defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize",defaultValue = "10") int pageSize){
return orderService.manageList(pageNum,pageSize);
public ServerResponse<PageInfo> orderList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {
return orderService.manageList(pageNum, pageSize);
}
/**
* "/manage/order/detail.do"GET
* orderNoLongorderServicemanageDetailIOrderService
* orderService.manageDetailServerResponse<OrderVo>OrderVoJSON
*
* @param orderNo Long便
* @return ServerResponse<OrderVo>ServerResponseOrderVo
*/
@RequestMapping("detail.do")
public ServerResponse<OrderVo> orderDetail(Long orderNo){
public ServerResponse<OrderVo> orderDetail(Long orderNo) {
return orderService.manageDetail(orderNo);
}
/**
* "/manage/order/search.do"GET
* orderNoLongpageNum"1"pageSize"10"
* orderServicemanageSearchIOrderService
* orderService.manageSearchServerResponse<PageInfo>PageInfoJSON
*
* @param orderNo 便Long
* @param pageNum 1
* @param pageSize 便1010
* @return ServerResponse<PageInfo>ServerResponsePageInfo
*/
@RequestMapping("search.do")
public ServerResponse<PageInfo> orderSearch(Long orderNo,@RequestParam(value = "pageNum",defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize",defaultValue = "10")int pageSize){
return orderService.manageSearch(orderNo,pageNum,pageSize);
public ServerResponse<PageInfo> orderSearch(Long orderNo, @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {
return orderService.manageSearch(orderNo, pageNum, pageSize);
}
/**
* "/manage/order/send_goods.do"GET
* orderNoLongorderServicemanageSendGoodsIOrderService
* orderService.manageSendGoodsServerResponse<String>JSON
*
* @param orderNo 便Long
* @return ServerResponse<String>ServerResponseString
*/
@RequestMapping("send_goods.do")
public ServerResponse<String> orderSendGoods(Long orderNo){
public ServerResponse<String> orderSendGoods(Long orderNo) {
return orderService.manageSendGoods(orderNo);
}
}
}

@ -3,26 +3,108 @@ package com.njupt.swg.dao;
import com.njupt.swg.entity.OrderItem;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @MapperMyBatisMapperMyBatisSQL
* MyBatis
* OrderItemMapperOrderItemOrderItem
* IDMyBatisSQLSQLXMLSQL
*/
@Mapper
public interface OrderItemMapper {
/**
* idDELETE
* IntegeridSQLMyBatis
* 10
*
* @param id Integer
* @return 10
*/
int deleteByPrimaryKey(Integer id);
/**
* INSERT
* OrderItemrecord
* MyBatisSQLINSERT1
*
* @param record OrderItemMyBatisSQL
* @return 1
*/
int insert(OrderItem record);
/**
* insertINSERTSQL
* OrderItemrecordSQLMyBatisrecordnull
* 1null
*
* @param record OrderItemMyBatisnullSQL
* @return 1
*/
int insertSelective(OrderItem record);
/**
* idSELECT
* IntegeridMyBatisSQLSELECT
* OrderItemnull便
*
* @param id Integernull
* @return OrderItemnull使
*/
OrderItem selectByPrimaryKey(Integer id);
/**
* idUPDATESQL
* OrderItemrecordMyBatisrecordnullSQLnull
* 1null
*
* @param record OrderItemMyBatisnullSQL
* @return 1
*/
int updateByPrimaryKeySelective(OrderItem record);
/**
* idUPDATE
* OrderItemrecordnull
* MyBatisSQLUPDATErecord1
*
* @param record OrderItemMyBatisSQL
* @return 1
*/
int updateByPrimaryKey(OrderItem record);
/**
* orderNoIDuserIdSELECT
* LongorderNoIntegerIDuserIdMyBatisSQLSELECTID
* List<OrderItem>OrderItem便
* 使@ParamMyBatisSQLSQL
*
* @param orderNo LonguserId
* @param userId IDIntegerorderNo
* @return List<OrderItem>ID使
*/
List<OrderItem> getByOrderNoUserId(@Param("orderNo") Long orderNo, @Param("userId") Integer userId);
/**
* INSERT
* List<OrderItem>orderItemListOrderItem
* MyBatisSQLINSERT便
* void
* 使@ParamMyBatisSQLSQL
*
* @param orderItemList List<OrderItem>MyBatisSQL
*/
void batchInsert(@Param("orderItemList") List<OrderItem> orderItemList);
/**
* orderNoSELECT
* LongorderNoMyBatisSQLSELECT
* List<OrderItem>OrderItem便
* 使@ParamMyBatisSQLSQL
*
* @param orderNo Long便使
* @return List<OrderItem>使
*/
List<OrderItem> getByOrderNo(@Param("orderNo") Long orderNo);
}

@ -4,36 +4,151 @@ import com.njupt.swg.entity.Order;
import com.njupt.swg.entity.OrderItem;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @MapperMyBatisMapperMyBatis
* SQLOrderMapperOrderOrderItem
* OrderIDSQLXMLSQL
*/
@Mapper
public interface OrderMapper {
/**
* idDELETE
* IntegeridMyBatisSQLMyBatis
* 10
*
* @param id Integer
* @return 10
*/
int deleteByPrimaryKey(Integer id);
/**
* INSERT
* OrderrecordOrderID
* MyBatisSQLINSERT1
*
* @param record OrderMyBatisSQL
* @return 1
*/
int insert(Order record);
/**
* insertINSERTSQL
* OrderrecordSQLMyBatisrecordnull
* 1null
*
* @param record OrderMyBatisnullSQL
* @return 1
*/
int insertSelective(Order record);
/**
* idSELECT
* IntegeridMyBatisSQLSELECT
* OrderIDnull便
*
* @param id Integernull
* @return Ordernull使
*/
Order selectByPrimaryKey(Integer id);
/**
* idUPDATESQL
* OrderrecordMyBatisrecordnullSQLnull
* 1null
*
* @param record OrderMyBatisnullSQL
* @return 1
*/
int updateByPrimaryKeySelective(Order record);
/**
* idUPDATE
* Orderrecordnull
* MyBatisSQLUPDATErecord1
*
* @param record OrderMyBatisSQL
* @return 1
*/
int updateByPrimaryKey(Order record);
/**
* IDuserIdorderNoSELECT
* IntegerIDuserIdLongorderNoMyBatisSQLSELECTID
* OrderIDnull便
* 使@ParamMyBatisSQLSQL
*
* @param userId IDIntegerorderNo
* @param orderNo LonguserIdnull
* @return OrderIDnull使
*/
Order selectByUserIdOrderNo(@Param("userId") Integer userId, @Param("orderNo") Long orderNo);
/**
* orderNoSELECT
* LongorderNoMyBatisSQLSELECT
* OrderIDnull便
*
* @param orderNo Longnull
* @return Ordernull使
*/
Order selectByOrderNo(Long orderNo);
/**
* IDuserIdSELECT
* IntegeruserIdIDMyBatisSQLSELECT
* List<Order>OrderID便
*
* @param userId IDInteger
* @return List<Order>ID使
*/
List<Order> selectByUserId(Integer userId);
/**
* orderNoOrderItemSELECT
* LongorderNoMyBatisSQLSELECT
* List<OrderItem>OrderItem便
*
* @param orderNo Long
* @return List<OrderItem>使
*/
List<OrderItem> getByOrderNo(Long orderNo);
/**
* SELECT
* MyBatisSQLSELECT
* List<Order>OrderID便
*
* @return List<Order>使
*/
List<Order> selectAllOrder();
//定时关单
/**
* statusdateSELECT
* IntegerstatusStringdateMyBatisSQLSELECTdateSQL
* List<Order>OrderID便
* 使@ParamMyBatisSQLSQL
*
* @param status Integerdate
* @param date String"yyyy-MM-dd HH:mm:ss"SQLstatus
* @return List<Order>使
*/
List<Order> selectOrderStatusByCreateTime(@Param("status") Integer status, @Param("date") String date);
//关闭订单
int closeOrderByOrderId(Integer id);
/**
* UPDATEIDidSQLSQL
* IntegeridMyBatisSQLUPDATE
* 1
*
* @param id Integer
* @return
*/
int closeOrderByOrderId(Integer id);
//关闭订单
}

@ -4,22 +4,71 @@ import com.njupt.swg.entity.PayInfo;
import org.apache.ibatis.annotations.Mapper;
/**
* @Author swg.
* @Date 2019/1/8 10:58
* @CONTACT 317758022@qq.com
* @DESC
* @MapperMyBatisMapperMyBatis
* MyBatisSQL
* PayInfoMapperPayInfoPayInfo
* SQLXMLSQL
*/
@Mapper
public interface PayInfoMapper {
/**
* idDELETE
* IntegeridMyBatisSQLMyBatis
* 10
*
* @param id Integer
* @return 10
*/
int deleteByPrimaryKey(Integer id);
/**
* INSERT
* PayInforecordPayInfo
* MyBatisSQLINSERT1
*
* @param record PayInfoMyBatisSQL
* @return 1
*/
int insert(PayInfo record);
/**
* insertINSERTSQL
* PayInforecordSQLMyBatisrecordnull
* 1null
*
* @param record PayInfoMyBatisnullSQL
* @return 1
*/
int insertSelective(PayInfo record);
/**
* idSELECT
* IntegeridMyBatisSQLSELECT
* PayInfonull便
*
* @param id Integernull
* @return PayInfonull使
*/
PayInfo selectByPrimaryKey(Integer id);
/**
* idUPDATESQL
* PayInforecordMyBatisrecordnullSQLnull
* 1null
*
* @param record PayInfoMyBatisnullSQL
* @return 1
*/
int updateByPrimaryKeySelective(PayInfo record);
/**
* idUPDATE
* PayInforecordnull
* MyBatisSQLUPDATErecord1
*
* @param record PayInfoMyBatisSQL
* @return 1
*/
int updateByPrimaryKey(PayInfo record);
}
}

@ -4,26 +4,55 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* CartJava
* 使Lombok @Data GetterSettertoStringequalshashCode便
* @AllArgsConstructor 便
* @NoArgsConstructor JSON
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Cart {
/**
* Integer便
*/
private Integer id;
/**
* IDIntegerID
*/
private Integer userId;
/**
* IDIntegerID便
*/
private Integer productId;
/**
* Integer
*/
private Integer quantity;
/**
* 01Integer便
*/
private Integer checked;
/**
* 使 @JsonFormat JSONWeb API"yyyy-MM-dd HH:mm:ss.SSS"
* 便Date
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date createTime;
/**
* 使 @JsonFormat createTime"yyyy-MM-dd HH:mm:ss.SSS"
* 便Date
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date updateTime;
}

@ -4,40 +4,89 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
/**
* OrderJava
* Lombok @Data GetterSettertoStringequalshashCode使便
* @AllArgsConstructor 便
* @NoArgsConstructor JSON使
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
/**
* Integer便
*/
private Integer id;
/**
* Long便
*/
private Long orderNo;
/**
* IDIntegerID
*/
private Integer userId;
/**
* IDInteger便
*/
private Integer shippingId;
/**
* 使BigDecimal
*/
private BigDecimal payment;
/**
* 123Integer便
*/
private Integer paymentType;
/**
* 使Integer使BigDecimal
*/
private Integer postage;
/**
* 01234Integer
*/
private Integer status;
/**
* 使Date @JsonFormat "yyyy-MM-dd HH:mm:ss.SSS"JSONWeb API便
*/
private Date paymentTime;
/**
* Date @JsonFormat JSON
*/
private Date sendTime;
/**
* Date使 @JsonFormat 便
*/
private Date endTime;
/**
* Date @JsonFormat 便
*/
private Date closeTime;
/**
* Date @JsonFormat "yyyy-MM-dd HH:mm:ss.SSS"便使
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date createTime;
/**
* Date @JsonFormat "yyyy-MM-dd HH:mm:ss.SSS"便
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date updateTime;
}

@ -4,34 +4,74 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
/**
* OrderItemJava
* Lombok @Data GetterSettertoStringequalshashCode便
* @AllArgsConstructor
* @NoArgsConstructor JSON使
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderItem {
/**
* 便Integer
*/
private Integer id;
/**
* ID便IntegerID
*/
private Integer userId;
/**
* Long
*/
private Long orderNo;
/**
* IDInteger便
*/
private Integer productId;
/**
* 便StringT使
*/
private String productName;
/**
* 使String便
*/
private String productImage;
/**
* 使BigDecimalBigDecimalBigDecimal
*/
private BigDecimal currentUnitPrice;
/**
* Integer
*/
private Integer quantity;
/**
* 使BigDecimalcurrentUnitPricequantityBigDecimal
*/
private BigDecimal totalPrice;
/**
* 使 @JsonFormat "yyyy-MM-dd HH:mm:ss.SSS"JSONWeb API便Date
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date createTime;
/**
* @JsonFormat "yyyy-MM-dd HH:mm:ss.SSS"Date
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date updateTime;
}

@ -3,32 +3,57 @@ package com.njupt.swg.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @Author swg.
* @Date 2019/1/8 10:56
* @CONTACT 317758022@qq.com
* @DESC
* PayInfoJava
* Lombok
* - @Data GetterSettertoStringequalshashCode便使
* - @NoArgsConstructor JSON访
* - @AllArgsConstructor 便PayInfo
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PayInfo {
/**
* 便Integer
*/
private Integer id;
/**
* ID便IntegerID
*/
private Integer userId;
/**
* Long
*/
private Long orderNo;
/**
* 123Integer便
*/
private Integer payPlatform;
/**
* String退
*/
private String platformNumber;
/**
* "SUCCESS""FAIL""PROCESSING"String便
*/
private String platformStatus;
/**
* Date
*/
private Date createTime;
/**
* Date
*/
private Date updateTime;
}
}

@ -3,35 +3,78 @@ package com.njupt.swg.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
/**
* ProductJava
* Lombok
* - @Data GetterSettertoStringequalshashCode使便便使
* - @AllArgsConstructor
* - @NoArgsConstructor JSON访使
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
/**
* 便Integer
*/
private Integer id;
/**
* ID便IntegerID
*/
private Integer categoryId;
/**
* 便String
*/
private String name;
/**
* String
*/
private String subtitle;
/**
* 使String便
*/
private String mainImage;
/**
* String
*/
private String subImages;
/**
* 使便String
*/
private String detail;
/**
* 使BigDecimalBigDecimalBigDecimal
*/
private BigDecimal price;
/**
* Integer
*/
private Integer stock;
/**
* 012Integer
*/
private Integer status;
/**
* Date
*/
private Date createTime;
/**
* Date
*/
private Date updateTime;
}

@ -4,35 +4,80 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* ShippingJava
*
* Lombok
* - @Data GetterSettertoStringequalshashCode使便便使
* - @NoArgsConstructor JSON访使
* - @AllArgsConstructor 便
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Shipping {
/**
* 便Integer
*/
private Integer id;
/**
* ID便IntegerID
*/
private Integer userId;
/**
* String
*/
private String receiverName;
/**
* 使String
*/
private String receiverPhone;
/**
* String
*/
private String receiverMobile;
/**
* String广线
*/
private String receiverProvince;
/**
* String便
*/
private String receiverCity;
/**
* 使String
*/
private String receiverDistrict;
/**
* StringXXXXXXXXX
*/
private String receiverAddress;
/**
* 使String
*/
private String receiverZip;
/**
* 使 @JsonFormat yyyy-MM-dd HH:mm:ss.SSSJSONWeb API便Date
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date createTime;
/**
* @JsonFormat yyyy-MM-dd HH:mm:ss.SSSDate
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date updateTime;
}

@ -4,7 +4,6 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
@ -13,31 +12,68 @@ import java.util.Date;
* @Date 2018/12/31 21:01
* @CONTACT 317758022@qq.com
* @DESC
* UserJava
* 使Lombok
* - @Data GetterSettertoStringequalshashCode便使
* - @NoArgsConstructor JSON访使
* - @AllArgsConstructor 便
* - @ToString toString使
* Serializable便
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Serializable {
/**
* 便Integer
*/
private Integer id;
/**
* String
*/
private String username;
/**
* String访
*/
private String password;
/**
* String
*/
private String email;
/**
* String便便
*/
private String phone;
/**
* String
*/
private String question;
/**
* Stringquestion
*/
private String answer;
/**
* 01Integer访
*/
//角色0-管理员,1-普通用户
private Integer role;
/**
* Date
*/
private Date createTime;
/**
* Date
*/
private Date updateTime;
}

@ -18,26 +18,48 @@ import java.util.List;
* @Date 2019/1/7 13:23
* @CONTACT 317758022@qq.com
* @DESC
* MessageReceiverRabbitMQ
* Spring便
*/
@Component
@Slf4j
public class MessageReceiver {
// 通过Spring的依赖注入机制自动注入一个IOrderService接口的实现类对象IOrderService应该定义了一系列与订单业务相关的方法
// 比如库存扣减与下订单的关联操作等方法这里推测是stockAndOrderprocess方法实现了相关逻辑具体的业务逻辑实现在其对应的实现类中由Spring根据配置去查找并注入合适的实现类实例
// 这个对象会在接收到消息后被调用,用于执行后续与订单相关的业务处理操作。
@Autowired
private IOrderService orderService;
/**
* 使Spring AMQP @RabbitListener
* @QueueBinding
* - @Queue("order-queue") "order-queue"
* - @Exchange("order-exchange") "order-exchange""order-queue"
* message
*
* @param message ID
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue("order-queue"),
exchange = @Exchange("order-exchange")
))
public void proess(String message){
log.info("接收到的消息为:{}",message);
public void proess(String message) {
// 使用日志记录接收到的消息内容,方便后续查看消息的原始情况,进行调试或者问题排查等操作,这里通过 {} 占位符的方式将message变量的值填充到日志信息中以一种格式化的方式输出消息内容。
log.info("接收到的消息为:{}", message);
// 调用JsonUtil工具类的Str2Obj方法尝试将接收到的消息字符串message反序列化为一个List集合集合中的元素类型为MessageVo这里假设MessageVo是一个自定义的用于承载消息中具体业务数据的视图对象包含了如用户ID、商品相关信息等字段
// 通过指定List.class和MessageVo.class来明确反序列化的目标类型以便后续能够从解析后的对象中获取具体的业务数据进行相应的业务操作如果反序列化失败可能会抛出异常具体的异常处理逻辑可能在工具类内部或者外层调用处有相应设置
List<MessageVo> result = JsonUtil.Str2Obj(message, List.class, MessageVo.class);
log.info("【MQ解析数据,前者为userId,后者为product信息{}】",result);
//扣减库存、下订单
//是先扣减库存,扣减成功才可以下订单,但是这是两个数据库,那么属于跨库的事务,所以如何解决呢?
//一种方案是:利用消息队列,订单服务订阅扣减库存服务,一旦发现数据库的库存扣减成功,就去扣减插入订单;
//如果库存扣减不成功,那么订单也不会写入
// 使用日志记录解析后的数据情况,方便查看消息解析出来的数据是否符合预期,同样通过 {} 占位符的方式将result变量的值填充到日志信息中进行格式化输出这里简单描述了一下数据大概包含的内容假设MessageVo中包含userId和product信息相关字段便于在日志中快速了解解析结果。
log.info("【MQ解析数据,前者为userId,后者为product信息{}】", result);
// 以下是业务逻辑相关的注释:
// 这里涉及到的业务需求是要先扣减库存,只有在库存扣减成功的情况下才可以下订单,但是库存和订单可能存储在不同的数据库中(属于跨库的情况),这种跨库操作在事务处理上存在一定复杂性,需要合适的解决方案来保证数据的一致性和业务的正确性。
// 一种可行的方案描述如下:
// 利用消息队列的机制,让订单服务订阅扣减库存服务相关的消息,一旦通过消息机制发现数据库中的库存扣减成功(可能是扣减库存服务在完成扣减操作后发送了相应的成功消息到消息队列中,这里未展示发送消息部分代码),
// 那么订单服务就会执行后续的操作,即去进行订单的插入等相关操作;如果库存扣减不成功(同样可能通过消息或者其他机制得知),那么订单就不会被写入,以此来保证在跨库情况下库存和订单操作的业务逻辑完整性和数据一致性。
// 这里调用orderService的stockAndOrderprocess方法将解析后的包含业务数据的result对象传入这个方法内部应该实现了上述的根据库存扣减情况来决定是否插入订单的具体业务逻辑具体的逻辑实现在IOrderService的实现类中。
orderService.stockAndOrderprocess(result);
}
}
}

@ -3,7 +3,6 @@ package com.njupt.swg.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
@ -12,23 +11,77 @@ import java.math.BigDecimal;
* @Date 2019/1/5 15:18
* @CONTACT 317758022@qq.com
* @DESC
* CartProductVoValue ObjectVO
* 便Serializable使便
* LombokGetterSetter便
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CartProductVo implements Serializable {
/**
* Integer
*/
private Integer id;
/**
* ID便IntegerID
*/
private Integer userId;
/**
* Integer便ID
*/
private Integer productId;
private Integer quantity;//购物车中此商品的数量
/**
* Integer
*/
private Integer quantity;
/**
* 便String
*/
private String productName;
/**
* String
*/
private String productSubtitle;
/**
* 使String便
*/
private String productMainImage;
/**
* 使BigDecimalBigDecimalBigDecimal
*/
private BigDecimal productPrice;
/**
* 012Integer
*/
private Integer productStatus;
/**
* 使BigDecimalproductPricequantityBigDecimal
*/
private BigDecimal productTotalPrice;
/**
* Integer
*/
private Integer productStock;
private Integer productChecked;//此商品是否勾选
private String limitQuantity;//限制数量的一个返回结果
}
/**
* 01Integer
*/
private Integer productChecked;
/**
* 5String便
*/
private String limitQuantity;
}

@ -1,7 +1,6 @@
package com.njupt.swg.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@ -10,11 +9,33 @@ import java.util.List;
* @Date 2019/1/5 15:18
* @CONTACT 317758022@qq.com
* @DESC
* CartVoValue ObjectVO便
* Lombok @Data GetterSetter使便
*/
@Data
public class CartVo {
/**
* CartProductVoCartProductVo
* 便使
*/
private List<CartProductVo> cartProductVoList;
/**
* 使BigDecimalBigDecimal
*
*/
private BigDecimal cartTotalPrice;
private Boolean allChecked;//是否已经都勾选
/**
* Booleantruefalse
*
*/
private Boolean allChecked;
/**
* IP访
* CartProductVo访便
*/
private String imageHost;
}
}

@ -7,11 +7,32 @@ import lombok.Data;
* @Date 2019/1/7 16:17
* @CONTACT 317758022@qq.com
* @DESC
* MessageVoValue ObjectVO
* Lombok @Data GetterSetter便使便
*/
@Data
public class MessageVo {
/**
* IntegerID
*/
private Integer userId;
/**
* ID使
* 使便Integer
*/
private Integer shippingId;
/**
* CartVo
* 便使CartVo
*/
private CartVo cartVo;
/**
*
* 便long
*/
private long orderNo;
}
}

@ -1,7 +1,6 @@
package com.njupt.swg.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
@ -9,24 +8,49 @@ import java.math.BigDecimal;
* @Date 2019/1/5 21:47
* @CONTACT 317758022@qq.com
* @DESC
* OrderItemVoValue ObjectVO便
* Lombok @Data GetterSetter使便
*/
@Data
public class OrderItemVo {
/**
* Long便
*/
private Long orderNo;
/**
* Integer便
*/
private Integer productId;
/**
* 便StringT使
*/
private String productName;
/**
* 使String便
*/
private String productImage;
/**
* 使BigDecimalBigDecimalBigDecimal
*/
private BigDecimal currentUnitPrice;
/**
* Integer
*/
private Integer quantity;
/**
* 使BigDecimalcurrentUnitPricequantityBigDecimal
*/
private BigDecimal totalPrice;
/**
* String便
*/
private String createTime;
}
}

@ -1,7 +1,6 @@
package com.njupt.swg.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@ -10,10 +9,27 @@ import java.util.List;
* @Date 2019/1/8 10:20
* @CONTACT 317758022@qq.com
* @DESC
* OrderProductVoValue ObjectVO便
* Lombok @Data GetterSetter使便
*/
@Data
public class OrderProductVo {
/**
* OrderItemVoOrderItemVo
* 便使
*/
private List<OrderItemVo> orderItemVoList;
/**
* 使BigDecimalOrderItemVoBigDecimal
*
*/
private BigDecimal productTotalPrice;
/**
* IP访
* OrderItemVo访便
*/
private String imageHost;
}

@ -1,7 +1,6 @@
package com.njupt.swg.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@ -10,42 +9,99 @@ import java.util.List;
* @Date 2019/1/5 21:47
* @CONTACT 317758022@qq.com
* @DESC
* OrderVoValue ObjectVO便
* Lombok @Data GetterSetter使便
*/
@Data
public class OrderVo {
/**
*
* 便Long
*/
private Long orderNo;
/**
* 使BigDecimalBigDecimalBigDecimal
*/
private BigDecimal payment;
/**
* 123Integer便
*/
private Integer paymentType;
/**
* paymentType使String
*/
private String paymentTypeDesc;
/**
* 使Integer使BigDecimalInteger
*/
private Integer postage;
/**
* 01234Integer
*/
private Integer status;
/**
* status便String
*/
private String statusDesc;
/**
* 便String
*/
private String paymentTime;
/**
* String
*/
private String sendTime;
/**
* 使String
*/
private String endTime;
/**
* 便String
*/
private String closeTime;
/**
* String
*/
private String createTime;
/**
* OrderItemVoOrderItemVo
* 便退使List<OrderItemVo>
*/
//订单的明细
private List<OrderItemVo> orderItemVoList;
/**
* IP访
* OrderItemVo访便String
*/
private String imageHost;
/**
* ID使
* 使便Integer
*/
private Integer shippingId;
/**
* StringString
*/
private String receiverName;
/**
* ShippingVo便使ShippingVo
*/
private ShippingVo shippingVo;
}
}

@ -7,22 +7,49 @@ import lombok.Data;
* @Date 2019/1/5 21:48
* @CONTACT 317758022@qq.com
* @DESC
* ShippingVoValue ObjectVO便
* Lombok @Data GetterSetter使便
*/
@Data
public class ShippingVo {
/**
* String便
*/
private String receiverName;
/**
* 使String
*/
private String receiverPhone;
/**
* String
*/
private String receiverMobile;
/**
* String广线
*/
private String receiverProvince;
/**
* String便便
*/
private String receiverCity;
/**
* 使String便
*/
private String receiverDistrict;
/**
* StringXXXXXXXXX
*/
private String receiverAddress;
/**
* 使String
*/
private String receiverZip;
}
}

@ -122,6 +122,10 @@
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
<build>

@ -10,6 +10,327 @@ import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* @Author swg.
* @Date 2019/1/3 19:13
* @CONTACT 317758022@qq.com
* @DESC
*/
@Service
@Slf4j
public class FileServiceImpl implements IFileService{
@Override
public String upload(MultipartFile file, String path) {
String fileName = file.getOriginalFilename();
//扩展名
//abc.jpg
String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);
String uploadFileName = UUID.randomUUID().toString()+"."+fileExtensionName;
log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}",fileName,path,uploadFileName);
File fileDir = new File(path);
if(!fileDir.exists()){
fileDir.setWritable(true);
fileDir.mkdirs();
}
log.info("【文件上传路径为:{}】",fileDir);
File targetFile = new File(path,uploadFileName);
try {
file.transferTo(targetFile);
//文件已经上传成功了
log.info("【文件上传本地服务器成功】");
FtpUtil.uploadFile(Lists.newArrayList(targetFile));
//已经上传到ftp服务器上
log.info("【文件上传到文件服务器成功】");
targetFile.delete();
log.info("【删除本地文件】");
} catch (IOException e) {
log.error("上传文件异常",e);
return null;
}
//A:abc.jpg
//B:abc.jpg
return targetFile.getName();
}
}
package com.njupt.swg.service;
import com.google.common.collect.Lists;
import com.njupt.swg.common.utils.FtpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* @Author swg.
* @Date 2019/1/3 19:13
* @CONTACT 317758022@qq.com
* @DESC
*/
@Service
@Slf4j
public class FileServiceImpl implements IFileService{
@Override
public String upload(MultipartFile file, String path) {
String fileName = file.getOriginalFilename();
//扩展名
//abc.jpg
String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);
String uploadFileName = UUID.randomUUID().toString()+"."+fileExtensionName;
log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}",fileName,path,uploadFileName);
File fileDir = new File(path);
if(!fileDir.exists()){
fileDir.setWritable(true);
fileDir.mkdirs();
}
log.info("【文件上传路径为:{}】",fileDir);
File targetFile = new File(path,uploadFileName);
try {
file.transferTo(targetFile);
//文件已经上传成功了
log.info("【文件上传本地服务器成功】");
FtpUtil.uploadFile(Lists.newArrayList(targetFile));
//已经上传到ftp服务器上
log.info("【文件上传到文件服务器成功】");
targetFile.delete();
log.info("【删除本地文件】");
} catch (IOException e) {
log.error("上传文件异常",e);
return null;
}
//A:abc.jpg
//B:abc.jpg
return targetFile.getName();
}
}
package com.njupt.swg.service;
import com.google.common.collect.Lists;
import com.njupt.swg.common.utils.FtpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* @Author swg.
* @Date 2019/1/3 19:13
* @CONTACT 317758022@qq.com
* @DESC IFileService
* FTP
*/
@Service
@Slf4j
public class FileServiceImpl implements IFileService {
/**
* MultipartFile
*
* @param file
* @param path FTP
* @return FTPnull
*/
@Override
public String upload(MultipartFile file, String path) {
// 获取上传文件的原始文件名例如abc.jpg
String fileName = file.getOriginalFilename();
// 提取文件的扩展名,即从文件名中获取最后一个点(.之后的部分比如对于abc.jpg获取到的是jpg。
// 作用是后续生成新文件名时用于保持文件类型一致。
// abc.jpg
String fileExtensionName = fileName.substring(fileName.lastIndexOf(".") + 1);
// 使用UUID通用唯一识别码生成一个唯一的字符串并拼接上文件扩展名形成新的文件名。
// 这样可以保证每次上传的文件名都是唯一的,避免文件名冲突问题。
String uploadFileName = UUID.randomUUID().toString() + "." + fileExtensionName;
log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}", fileName, path, uploadFileName);
// 根据传入的路径创建一个File对象代表本地的文件目录。
File fileDir = new File(path);
// 判断该目录是否存在,如果不存在则进行创建目录操作。
// 先设置目录可写权限有些系统可能需要显式设置然后创建多层级目录mkdirs会创建所有不存在的父目录
if (!fileDir.exists()) {
fileDir.setWritable(true);
fileDir.mkdirs();
}
log.info("【文件上传路径为:{}】", fileDir);
// 创建一个代表目标文件的File对象其路径是由前面传入的path和新生成的文件名组成
// 这个文件就是即将要把上传的文件保存到本地的目标文件。
File targetFile = new File(path, uploadFileName);
try {
// 将上传的MultipartFile文件内容传输并保存到本地的目标文件中完成本地临时文件的保存。
file.transferTo(targetFile);
// 文件已经上传成功保存到本地临时服务器了,记录日志信息。
log.info("【文件上传本地服务器成功】");
// 调用FtpUtil工具类的uploadFile方法将本地保存好的文件这里包装成一个包含该文件的列表上传到FTP服务器上。
// 具体FtpUtil类内部如何实现FTP上传逻辑这里暂不详细展示但它完成了向FTP服务器传输文件的功能。
FtpUtil.uploadFile(Lists.newArrayList(targetFile));
// 文件已经成功上传到FTP文件服务器了记录日志信息。
log.info("【文件上传到文件服务器成功】");
// 删除本地已经上传到FTP服务器的临时文件释放本地磁盘空间因为已经不需要在本地保留该文件副本了。
targetFile.delete();
log.info("【删除本地文件】");
} catch (IOException e) {
// 如果在文件上传过程包括保存到本地、上传到FTP服务器等操作涉及到的IO操作出现异常
// 则记录错误日志信息并返回null表示上传失败。
log.error("上传文件异常", e);
return null;
}
// 返回上传到FTP服务器后的文件名这个文件名可能在后续业务中用于记录、关联等操作。
// 例如在数据库中记录该文件在FTP服务器上的存储名称等情况。
return targetFile.getName();
}
}
package com.njupt.swg.service;
import com.google.common.collect.Lists;
import com.njupt.swg.common.utils.FtpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* FileServiceImplIFileService
* MultipartFileFTPFtpUtil
* 访便
*
* @Author swg.
* @Date 2019/1/3 19:13
* @CONTACT 317758022@qq.com
* @DESC
*/
@Service
// @Service注解用于标记该类是Spring框架中的一个服务层组件意味着这个类会被Spring容器管理在其他地方如控制层可以通过依赖注入的方式使用这个类的实例
// 同时Spring会对其进行相关的生命周期管理以及一些配置和增强操作如AOP切面等若有配置的话方便业务逻辑的组织和复用。
@Slf4j
// 使用Lombok的 @Slf4j注解自动生成名为log的SLF4J日志记录器用于在文件上传的各个步骤中记录关键信息如文件名称、路径等以及出现异常情况时记录详细的错误信息
// 方便后续查看日志来了解文件上传的执行情况排查可能出现的问题例如文件上传失败的原因、是否成功传输到FTP服务器等情况。
public class FileServiceImpl implements IFileService {
/**
* MultipartFile
*
* @param file MultipartFileSpring
* 便
* @param path FTP
* "/upload"
* @return String
* 访null
*/
@Override
public String upload(MultipartFile file, String path) {
String fileName = file.getOriginalFilename();
// 获取上传文件的原始文件名,这个文件名是客户端上传文件时原本的名称,例如客户端上传了一个名为 "abc.jpg" 的图片文件,这里获取到的就是 "abc.jpg"
// 通过这个原始文件名可以提取文件的扩展名等信息,用于后续生成新的文件名以及进行相关的文件操作判断等情况。
//扩展名
//abc.jpg
String fileExtensionName = fileName.substring(fileName.lastIndexOf(".") + 1);
// 从原始文件名中提取文件的扩展名,通过查找文件名中最后一个 "." 出现的位置,然后取其后面的字符串部分,得到文件的扩展名,
// 例如对于文件名 "abc.jpg",通过该操作获取到的扩展名就是 "jpg",用于后续构建新的文件名(确保新文件名保留正确的文件类型扩展名),便于识别文件类型以及正确的访问和处理文件。
String uploadFileName = UUID.randomUUID().toString() + "." + fileExtensionName;
// 使用Java的UUID通用唯一识别码生成一个随机的字符串并与获取到的文件扩展名拼接起来组成新的文件名
// 这样做的目的是为了避免文件名冲突(特别是在多用户同时上传文件或者重复上传同名文件的情况下),确保每个上传的文件在服务器上有一个唯一的标识名称,
// 例如生成的新文件名可能类似 "550e8400-e29b-41d4-a716-446655440000.jpg",方便后续文件的存储、管理以及访问操作。
log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}", fileName, path, uploadFileName);
// 使用日志记录器记录文件上传操作开始时的关键信息,包括原始文件名、上传的目标路径以及生成的新文件名,方便后续查看日志了解文件上传的初始情况,
// 同时在出现问题时也可以通过这些记录来排查是哪个文件在哪个路径下上传出现了异常等情况。
File fileDir = new File(path);
if (!fileDir.exists()) {
fileDir.setWritable(true);
fileDir.mkdirs();
}
// 根据传入的文件上传路径创建一个File对象用于表示对应的目录如果该目录不存在则先设置其可写权限确保后续可以创建文件等操作
// 然后通过mkdirs方法创建该目录及其所有必要的父目录如果不存在的话保证文件上传时有对应的本地存储目录可用避免因目录不存在导致文件保存失败的情况。
log.info("【文件上传路径为:{}】", fileDir);
// 使用日志记录创建好的文件上传路径对应的File对象信息方便后续查看实际使用的文件存储目录情况确认目录是否创建正确以及是否符合预期等情况
// 也有助于排查可能因目录问题导致的文件上传异常情况。
File targetFile = new File(path, uploadFileName);
// 根据文件上传路径和新生成的文件名创建一个用于保存上传文件的目标File对象这个对象代表了文件在本地服务器上最终要保存的位置和对应的文件名
// 后续会将上传的文件内容写入到这个目标文件中,完成文件在本地服务器的临时存储操作。
try {
file.transferTo(targetFile);
// 将上传的MultipartFile对象中的文件内容传输并保存到之前创建的本地目标文件targetFile
// 这个操作会将客户端上传的文件数据实际写入到服务器本地的文件系统中完成文件在本地服务器的临时存储如果这个过程出现I/O异常等问题会抛出IOException异常
// 例如文件权限不足、磁盘空间不足等原因可能导致传输失败需要在catch块中进行相应的异常处理。
//文件已经上传成功了
log.info("【文件上传本地服务器成功】");
// 使用日志记录文件成功上传到本地服务器的信息,方便后续查看文件上传的执行进度以及确认本地存储这一步是否成功完成,
// 若后续出现问题如无法上传到FTP服务器等情况可以通过这个记录来判断是否是本地存储环节之后出现的问题。
FtpUtil.uploadFile(Lists.newArrayList(targetFile));
// 调用FtpUtil工具类的uploadFile方法将包含目标文件targetFile的列表传递进去执行将文件上传到FTP服务器的操作
// FtpUtil类应该是专门用于处理FTP文件传输相关逻辑的工具类通过它实现与FTP服务器的连接、文件上传等功能若这个过程出现异常如FTP连接失败、权限问题等
// 会在FtpUtil内部进行相应的处理可能记录日志、抛出异常等情况具体取决于FtpUtil的实现这里只是调用其方法来触发上传到FTP服务器的操作。
//已经上传到ftp服务器上
log.info("【文件上传到文件服务器成功】");
// 使用日志记录文件成功上传到FTP服务器的信息表明文件已经从本地服务器进一步传输到了FTP服务器上完成了整个文件上传流程中的关键步骤
// 通过这个记录可以方便后续查看文件是否完整地按照预期上传到了指定的FTP服务器若出现问题如文件在FTP服务器上不可访问等情况可以据此排查是FTP上传环节出现的问题。
targetFile.delete();
log.info("【删除本地文件】");
// 在文件成功上传到FTP服务器后删除本地临时保存的文件以释放本地服务器的磁盘空间避免不必要的文件冗余存储
// 通过这个操作本地服务器只起到一个临时中转存储的作用最终文件存储在FTP服务器上而本地只保留相关的文件上传记录如文件名等信息用于后续访问等操作
} catch (IOException e) {
log.error("上传文件异常", e);
// 如果在文件上传过程包括本地保存或者上传到FTP服务器等操作中出现IOException异常使用日志记录器记录详细的错误信息包括异常堆栈信息
// 方便后续查看日志来排查具体是哪个环节出现了I/O相关的问题例如是文件传输失败、FTP连接异常还是其他文件操作异常等情况同时返回null表示文件上传失败。
return null;
}
//A:abc.jpg
//B:abc.jpg
return targetFile.getName();
// 返回上传后文件在服务器上对应的文件名这里是经过重命名后的新文件名即之前生成的uploadFileName
// 这个文件名可以在其他地方如数据库中记录、返回给前端用于构建文件访问链接等使用用于标识和访问已经上传到FTP服务器上的文件完成文件上传操作并返回相应的文件名结果。
}
}
package com.njupt.swg.service;
import com.google.common.collect.Lists;
import com.njupt.swg.common.utils.FtpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* FileServiceImplIFileService
* MultipartFileFTPFtpUtil

@ -2,6 +2,89 @@ package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author swg.
* @Date 2019/1/3 19:12
* @CONTACT 317758022@qq.com
* @DESC
*/
public interface IFileService {
String upload(MultipartFile file, String path);
}
package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author swg.
* @Date 2019/1/3 19:12
* @CONTACT 317758022@qq.com
* @DESC
*/
public interface IFileService {
String upload(MultipartFile file, String path);
}
package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author swg.
* @Date 2019/1/3 19:12
* @CONTACT 317758022@qq.com
* @DESC
*
* 便
*/
public interface IFileService {
/**
*
*
* @param file SpringMultipartFile
*
* @param path 使
*
* @return
* null
*/
String upload(MultipartFile file, String path);
}
package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* IFileService
* 使
* 便
*
* @Author swg.
* @Date 2019/1/3 19:12
* @CONTACT 317758022@qq.com
* @DESC
*/
public interface IFileService {
/**
*
* MultipartFile
*
*
* @param file MultipartFileSpring
* 便
*
* @param path
*
* "/upload" "upload" FTP访
* @return String
* 访
* null
*/
String upload(MultipartFile file, String path);
}
package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* IFileService
* 使

@ -1,30 +1,87 @@
package com.njupt.swg.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* @Author swg.
* @Date 2018/1/11 12:34
* @DESC
* @DESC Value Object VO
* 便
* 使Lombok @Data GetterSettertoStringhashCodeequals
* @CONTACT 317758022@qq.com
*/
@Data
public class ProductDetailVo {
/**
*
*/
private Integer id;
/**
* IDID
*/
private Integer categoryId;
/**
* 便
*/
private String name;
/**
*
*/
private String subtitle;
/**
* URL
*/
private String mainImage;
/**
* URL
*
*/
private String subImages;
/**
* 使
*/
private String detail;
/**
* 使BigDecimal
*/
private BigDecimal price;
/**
*
*/
private Integer stock;
/**
* 便
*/
private Integer status;
/**
*
*/
private String createTime;
/**
*
*/
private String updateTime;
/**
* mainImagesubImages使
* 访URL使
*/
private String imageHost;
/**
* ID
*/
private Integer parentCategoryId;
}
}

@ -1,30 +1,58 @@
package com.njupt.swg.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* @Author swg.
* @Date 2019/1/3 12:06
* @CONTACT 317758022@qq.com
* @DESC
* @DESC Value ObjectVO
*
* 便Lombok @Data
* GetterSettertoStringhashCodeequals
*/
@Data
public class ProductListVo {
/**
* 便ID
*/
private Integer id;
/**
* IDID便
*
*/
private Integer categoryId;
/**
*
*/
private String name;
/**
*
*/
private String subtitle;
/**
* URL
*/
private String mainImage;
/**
* BigDecimal
*/
private BigDecimal price;
/**
* 便
*/
private Integer status;
/**
* mainImage使
* 访URL
*/
private String imageHost;
}
}

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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save