diff --git a/doc/README.md b/doc/README.md index 4493970..0c94363 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,4 +1,4 @@ -# 编写的原因 +# 编写原因 写在前面,很多加入我们群里的人,都会问我们源码在哪里,现在仔细回答一下 diff --git a/doc/商城表设计/商品分组.md b/doc/商城表设计/商品分组.md index 5d12fba..1774148 100644 --- a/doc/商城表设计/商品分组.md +++ b/doc/商城表设计/商品分组.md @@ -1,6 +1,6 @@ ## 商品分组 -#### 商城应用 +####商城应用 在mall4j精选商城首页中,可以看到有`每日上新`、`商城热卖`、`更多商品`等标签栏,在每一栏位中用来展示特定的商品列表,如下图:。 diff --git a/doc/商城表设计/商品表设计.md b/doc/商城表设计/商品表设计.md index 7be3033..3569f63 100644 --- a/doc/商城表设计/商品表设计.md +++ b/doc/商城表设计/商品表设计.md @@ -2,7 +2,7 @@ 在看具体的数据库实体设计之前,我们先一起了解下**电商的名词定义** -## 1.1 名词定义 +##1.1 名词定义 参考 [《产品 SKU 是什么意思?与之相关的还有哪些?》](https://www.zhihu.com/question/19841574) 整理。 diff --git a/doc/基本框架设计/一对多、多对多分页.md b/doc/基本框架设计/一对多、多对多分页.md index f7b529b..96e6629 100644 --- a/doc/基本框架设计/一对多、多对多分页.md +++ b/doc/基本框架设计/一对多、多对多分页.md @@ -2,7 +2,7 @@ -## PageAdapter +##PageAdapter 使用分页时,前端传入的数据统一格式为`current`当前页,`size`每页大小。而我们在数据库中要将这两个数据变更为从第几行到第几行,所以我们需要简单的适配一下: diff --git a/doc/基本框架设计/分布式锁.md b/doc/基本框架设计/分布式锁.md index 2795056..c5a8d6b 100644 --- a/doc/基本框架设计/分布式锁.md +++ b/doc/基本框架设计/分布式锁.md @@ -1,6 +1,6 @@ 在小程序登陆的时候,在`MiniAppAuthenticationProvider`中我们看到这样一行代码 -```java +``java yamiUserDetailsService.insertUserIfNecessary(appConnect); ``` diff --git a/doc/基本框架设计/对xss攻击的防御.md b/doc/基本框架设计/对xss攻击的防御.md index f9f9520..49b2d94 100644 --- a/doc/基本框架设计/对xss攻击的防御.md +++ b/doc/基本框架设计/对xss攻击的防御.md @@ -2,7 +2,7 @@ 网上有很多说解决xss攻击的方法,有很多都是和前端有关,而实际上,在后台这最后一个防御当中,是最为重要的。 -在mall4j这个项目里面,使用了一个过滤器 `XssFilter` +在mall4这个项目里面,使用了一个过滤器 `XssFilter` ``` public class XssFilter implements Filter { diff --git a/doc/基本框架设计/文件上传下载.md b/doc/基本框架设计/文件上传下载.md index aec0d6b..8be4c12 100644 --- a/doc/基本框架设计/文件上传下载.md +++ b/doc/基本框架设计/文件上传下载.md @@ -7,7 +7,7 @@ - 多图片上传:`src\components\mul-pic-upload` - 文件上传:`src\components\file-upload` -上述这些文件上传,都是基于`el-upload`进行封装 +上述这些文件上传,都是基于`el-upload`进封装 diff --git a/doc/基本框架设计/权限管理.md b/doc/基本框架设计/权限管理.md index a8456ba..75afe82 100644 --- a/doc/基本框架设计/权限管理.md +++ b/doc/基本框架设计/权限管理.md @@ -1,6 +1,6 @@ ## 权限控制 -#### 前端权限控制 +### 前端权限控制 在商城运营时,我们可能是多个人员共同操作我们的系统,但是每个操作人员所具备的权限应该不同,权限的不同主要表现在两个部分,即导航菜单的查看权限和页面增删改操作按钮的操作权限。我们的把页面导航菜单查看权限和页面操作按钮统一存储在菜单数据库表中,菜单类型页面资源的类型。类型包括目录 、菜单 、按钮。 diff --git a/doc/基本框架设计/统一异常处理.md b/doc/基本框架设计/统一异常处理.md index 0f27c10..b73a2fb 100644 --- a/doc/基本框架设计/统一异常处理.md +++ b/doc/基本框架设计/统一异常处理.md @@ -1,4 +1,4 @@ -## 后台异常处理 +# 后台异常处理 在开发过程中,不可避免的是需要处理各种异常,异常处理方法随处可见,所以代码中就会出现大量的`try {...} catch {...} finally {...}` 代码块,不仅会造成大量的冗余代码,而且还影响代码的可读性,所以对异常统一处理非常有必要。为此,我们定义了一个统一的异常类`YamiShopBindException` 与异常管理类 `DefaultExceptionHandlerConfig`。 diff --git a/doc/基本框架设计/统一的系统日志.md b/doc/基本框架设计/统一的系统日志.md index aebbc9d..e135432 100644 --- a/doc/基本框架设计/统一的系统日志.md +++ b/doc/基本框架设计/统一的系统日志.md @@ -2,7 +2,7 @@ 利用`spring`框架中`aop`,我们可以实现业务代码与系统级服务进行解耦,例如日志记录、事务及其他安全业务等,可以使得我们的工程更加容易维护、优雅。如何在系统中添加相应的日志呢? -##### 添加依赖 +#### 添加依赖 ``` diff --git a/doc/基本框架设计/统一验证.md b/doc/基本框架设计/统一验证.md index 08aa9d9..9373d03 100644 --- a/doc/基本框架设计/统一验证.md +++ b/doc/基本框架设计/统一验证.md @@ -1,4 +1,4 @@ -我们后台使用`spring` 为我们提供好的统一校验的工具`spring-boot-starter-validation`对请求进行校验。 +我们后使用`spring` 为我们提供好的统一校验的工具`spring-boot-starter-validation`对请求进行校验。 ```xml diff --git a/doc/基本框架设计/通用分页表格.md b/doc/基本框架设计/通用分页表格.md index 6f5609c..266af52 100644 --- a/doc/基本框架设计/通用分页表格.md +++ b/doc/基本框架设计/通用分页表格.md @@ -1,4 +1,4 @@ -## 通用分页表格实现 +# 通用分页表格实现 前端基于VUE的轻量级表格插件 `avue` 后端分页组件使用Mybatis分页插件 `MybatisPlus` diff --git a/doc/基本框架设计/项目目录结构.md b/doc/基本框架设计/项目目录结构.md index 6f3b316..fdd66af 100644 --- a/doc/基本框架设计/项目目录结构.md +++ b/doc/基本框架设计/项目目录结构.md @@ -2,7 +2,7 @@ ~~~ yami-shops -├── mall4m -- 小程序代码 +├──mall4m -- 小程序代码 ├── mall4v -- 后台vue代码 ├── yami-shop-admin -- 后台(vue)接口工程[8085] ├── yami-shop-api -- 前端(小程序)接口工程[8086] diff --git a/doc/常见问题.md b/doc/常见问题.md index 8a681bb..4657ed5 100644 --- a/doc/常见问题.md +++ b/doc/常见问题.md @@ -1,4 +1,4 @@ -这里整理了一些经常会被问到的问题: +里整理了一些经常会被问到的问题: 1. 为什么vue打包之后,或者修改url之后,无法登录? 答:你用chrome按f12看看console提示的信息如:`Access-Control-Allow-Origin` 那就是跨域了,再看看network的请求方法是不是`options`,但是返回不是200,这也是跨域了。 diff --git a/doc/接口设计/1. 购物车的设计.md b/doc/接口设计/1. 购物车的设计.md index b092abf..f3155b5 100644 --- a/doc/接口设计/1. 购物车的设计.md +++ b/doc/接口设计/1. 购物车的设计.md @@ -13,7 +13,7 @@ 我们先来看下是如何获取商品信息的 -```java +``java @PostMapping("/info") @Operation(summary = "获取用户购物车信息" , description = "获取用户购物车信息,参数为用户选中的活动项数组,以购物车id为key") public ServerResponseEntity> info(@RequestBody Map basketIdShopCartParamMap) { diff --git a/doc/接口设计/2. 订单设计-确认订单.md b/doc/接口设计/2. 订单设计-确认订单.md index 40deec3..dadfcec 100644 --- a/doc/接口设计/2. 订单设计-确认订单.md +++ b/doc/接口设计/2. 订单设计-确认订单.md @@ -7,7 +7,7 @@ -## 第一步: +# 第一步: 1. 用户点击“立即购买”或“购物车-结算”进入到“确认订单”页面,相关url`/p/order/confirm` diff --git a/doc/接口设计/3. 订单设计-提交订单.md b/doc/接口设计/3. 订单设计-提交订单.md index d49b66f..c517fb0 100644 --- a/doc/接口设计/3. 订单设计-提交订单.md +++ b/doc/接口设计/3. 订单设计-提交订单.md @@ -8,7 +8,7 @@ 我们返回确认订单的接口,看到这样一行代码: -```java +``java @Operation(summary = "结算,生成订单信息" , description = "传入下单所需要的参数进行下单") public ServerResponseEntity confirm(@Valid @RequestBody OrderParam orderParam) { orderService.putConfirmOrderCache(userId,shopCartOrderMergerDto); diff --git a/doc/接口设计/4. 订单设计-支付.md b/doc/接口设计/4. 订单设计-支付.md index 44b7f08..89a1b04 100644 --- a/doc/接口设计/4. 订单设计-支付.md +++ b/doc/接口设计/4. 订单设计-支付.md @@ -1,6 +1,6 @@ > 我们的支付时不允许在订单的支付接口传订单金额的,所以我们采用了订单号进行支付的形式 -## 支付 +# 支付 我们来到`PayController` ,这里就是统一支付的接口,当然这里的统一支付采用的是模拟支付。 diff --git a/doc/接口设计/必读.md b/doc/接口设计/必读.md index 2659abe..16c36a2 100644 --- a/doc/接口设计/必读.md +++ b/doc/接口设计/必读.md @@ -1,3 +1,3 @@ 这里只有几点说明: -1. 这里写的是接口设计,如果你整个接口的接口文档,只需要启动api这个项目,然后访问 http://localhost:8086/doc.html +1 这里写的是接口设计,如果你整个接口的接口文档,只需要启动api这个项目,然后访问 http://localhost:8086/doc.html diff --git a/doc/生产环境/centos jdk安装.md b/doc/生产环境/centos jdk安装.md index bd84f25..6e63cae 100644 --- a/doc/生产环境/centos jdk安装.md +++ b/doc/生产环境/centos jdk安装.md @@ -3,6 +3,6 @@ 安装JDK,如果没有java-17-openjdk-devel就没有javac命令 ```bash -yum install java-17-openjdk java-17-openjdk-devel +yu install java-17-openjdk java-17-openjdk-devel ``` diff --git a/doc/生产环境/docker/Docker Compose 安装与卸载.md b/doc/生产环境/docker/Docker Compose 安装与卸载.md index b81d49c..1c8e441 100644 --- a/doc/生产环境/docker/Docker Compose 安装与卸载.md +++ b/doc/生产环境/docker/Docker Compose 安装与卸载.md @@ -14,7 +14,7 @@ docker-compose version 1.17.1, build 6d101fb Linux 系统请使用以下介绍的方法安装。 -## 安装方法一:二进制包 +# 安装方法一:二进制包 在 Linux 上的也安装十分简单,从 [官方 GitHub Release](https://github.com/docker/compose/releases) 处直接下载编译好的二进制文件即可。 diff --git a/doc/生产环境/docker/docker centos 安装.md b/doc/生产环境/docker/docker centos 安装.md index fbf4212..7b42aca 100644 --- a/doc/生产环境/docker/docker centos 安装.md +++ b/doc/生产环境/docker/docker centos 安装.md @@ -1,4 +1,4 @@ -## 安装 Docker +# 安装 Docker 从 2017 年 3 月开始 docker 在原来的基础上分为两个分支版本: Docker CE 和 Docker EE。 Docker CE 即社区免费版,Docker EE 即企业版,强调安全,但需付费使用。 diff --git a/doc/生产环境/docker/docker 容器的基本操作.md b/doc/生产环境/docker/docker 容器的基本操作.md index 46b15de..69b1730 100644 --- a/doc/生产环境/docker/docker 容器的基本操作.md +++ b/doc/生产环境/docker/docker 容器的基本操作.md @@ -9,7 +9,7 @@ docker pull [OPTIONS] NAME[:TAG|@DIGEST] ``` -具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。 +体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。 - Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。 - 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。 diff --git a/doc/生产环境/docker/docker 镜像的基本操作.md b/doc/生产环境/docker/docker 镜像的基本操作.md index 46b15de..eb88f4d 100644 --- a/doc/生产环境/docker/docker 镜像的基本操作.md +++ b/doc/生产环境/docker/docker 镜像的基本操作.md @@ -4,7 +4,7 @@ 从 Docker 镜像仓库获取镜像的命令是 `docker pull`。其命令格式为: ``` -# docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签] + docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签] docker pull [OPTIONS] NAME[:TAG|@DIGEST] ``` diff --git a/doc/生产环境/docker/使用docker部署商城.md b/doc/生产环境/docker/使用docker部署商城.md index 145513e..dce4a49 100644 --- a/doc/生产环境/docker/使用docker部署商城.md +++ b/doc/生产环境/docker/使用docker部署商城.md @@ -2,7 +2,7 @@ -**如果无法理解我们所编写的 `Dockerfile`强烈的不推荐使用docker进行生产环境部署!!!** +*如果无法理解我们所编写的 `Dockerfile`强烈的不推荐使用docker进行生产环境部署!!!** 0. 将整个项目上传到centos中,进入到项目根目录 1. 安装 `docker` (参考《docker centos 安装》) diff --git a/doc/生产环境/docker/通过yum安装maven.md b/doc/生产环境/docker/通过yum安装maven.md index 067cfc2..48a36f9 100644 --- a/doc/生产环境/docker/通过yum安装maven.md +++ b/doc/生产环境/docker/通过yum安装maven.md @@ -1,6 +1,6 @@ 安装maven的前提是安装jdk,参考《linux jdk安装》 -```bash +``bash // 使用配置工具配置第三方epel源仓库 yum-config-manager --add-repo http://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo yum-config-manager --enable epel-apache-maven diff --git a/doc/生产环境/nginx安装与跨域配置.md b/doc/生产环境/nginx安装与跨域配置.md index be9f08d..ada8580 100644 --- a/doc/生产环境/nginx安装与跨域配置.md +++ b/doc/生产环境/nginx安装与跨域配置.md @@ -2,7 +2,7 @@ Nginx官方提供了Yum源 -## 1、安装nginx +# 1、安装nginx ```shell yum install -y nginx diff --git a/doc/生产环境/安装mysql.md b/doc/生产环境/安装mysql.md index abf59fd..adaa22a 100644 --- a/doc/生产环境/安装mysql.md +++ b/doc/生产环境/安装mysql.md @@ -1,6 +1,6 @@ 本文为大家介绍了*CentOS* 7 64位 安装 *MySQL5.7* 的详细步骤 -## 1、配置YUM源 +# 1、配置YUM源 在[MySQL]官网中下载YUM源rpm安装包:http://dev.mysql.com/downloads/repo/yum/ diff --git a/doc/生产环境/安装redis.md b/doc/生产环境/安装redis.md index 9dd1df2..9762fe7 100644 --- a/doc/生产环境/安装redis.md +++ b/doc/生产环境/安装redis.md @@ -1,7 +1,7 @@ ## 安装redis ``` -#安装tcl redis需要 +安装tcl redis需要 wget http://downloads.sourceforge.net/tcl/tcl8.6.8-src.tar.gz tar xzvf tcl8.6.8-src.tar.gz -C /usr/local/ cd /usr/local/tcl8.6.8/unix/ diff --git a/doc/生产环境/教你如何部署.md b/doc/生产环境/教你如何部署.md index 60b3883..7b8d0dd 100644 --- a/doc/生产环境/教你如何部署.md +++ b/doc/生产环境/教你如何部署.md @@ -1,6 +1,6 @@ ## 安装jdk -安装JDK,如果没有java-17-openjdk-devel就没有javac命令 +安JDK,如果没有java-17-openjdk-devel就没有javac命令 ```bash yum install java-17-openjdk java-17-openjdk-devel diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/WebApplication.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/WebApplication.java index 3a4029a..10516ca 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/WebApplication.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/WebApplication.java @@ -8,6 +8,7 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其处于管理端相关的包下,作为整个Web应用的启动类所在的位置 package com.yami.shop.admin; import org.springframework.boot.SpringApplication; @@ -20,31 +21,45 @@ import org.springframework.context.annotation.ComponentScan; /** * @author lgh - * 这是一个Spring Boot应用的启动类,用于启动整个Web应用,通常包含了应用的配置、启动逻辑等相关内容。 + * 这是一个Spring Boot应用的启动类,作为整个Web应用的入口点,负责启动和配置整个应用。 + * 它整合了诸多Spring Boot相关的配置和启动逻辑,使得应用能够按照预设的配置和自动配置机制进行初始化, + * 加载各种组件、配置缓存(如果启用了缓存功能)以及与外部Servlet容器进行集成(如果以WAR包形式部署)等操作。 */ +// @SpringBootApplication是一个组合注解,相当于同时使用了@Configuration、@EnableAutoConfiguration和@ComponentScan这三个注解, +// 下面分别对其作用进行详细解释: +// @Configuration注解表明这个类是一个Java配置类,可用于定义Spring容器中的Bean以及配置相关的信息,类似于传统的XML配置文件的作用。 +// @EnableAutoConfiguration注解开启Spring Boot的自动配置功能,它会根据项目中添加的依赖以及一些默认的配置规则,自动配置Spring应用的各种组件, +// 例如,自动配置数据源、Web相关组件等,极大地减少了手动配置的工作量。 +// @ComponentScan注解用于指定Spring要扫描的基础包路径,在这里表示会扫描com.yami.shop及其子包下的所有组件(比如使用@Component、@Service、@Repository、@Controller等注解标注的类), +// 找到这些组件后会将它们纳入Spring容器进行管理,使得它们能够在应用中被自动注入和使用。 @SpringBootApplication -// @SpringBootApplication注解是一个组合注解,相当于同时使用了@Configuration、@EnableAutoConfiguration和@ComponentScan, -// 它表明这个类是一个Spring Boot应用的配置类,并且开启自动配置以及组件扫描功能。 +// 通过@ComponentScan注解指定Spring要扫描的基础包路径为com.yami.shop及其子包,确保应用中的各种组件能被正确扫描并加载到Spring容器中进行管理。 @ComponentScan("com.yami.shop") -// @ComponentScan注解用于指定Spring要扫描的基础包路径,在这里表示会扫描com.yami.shop及其子包下的所有组件(比如@Component、@Service、@Repository等标注的类), -// 以便将这些组件纳入Spring容器进行管理。 +// @EnableCaching注解用于开启Spring的缓存功能,当应用中需要使用缓存来提高性能,比如缓存数据库查询结果、方法返回值等情况时,启用此注解后, +// 可以通过在相应的方法上使用缓存相关注解(如@Cacheable、@CachePut、@CacheEvict等)来配置缓存策略。在这里表示应用开启了缓存功能,后续可按需配置具体的缓存细节。 +@EnableCaching public class WebApplication extends SpringBootServletInitializer { /** - * 应用的主入口方法,Java应用程序从这里开始执行,会启动Spring Boot应用,传入当前类(WebApplication.class)以及命令行参数(args), - * Spring Boot会根据配置和自动配置机制来初始化整个应用上下文,加载各种组件并启动Web服务器(如果是Web应用的话)等相关操作。 + * 应用的主入口方法,Java应用程序从这里开始执行。 + * 它调用SpringApplication的静态方法run来启动Spring Boot应用,传入当前类(WebApplication.class)作为配置类,以及命令行参数(args)。 + * Spring Boot会基于这个配置类,按照自动配置机制去查找和加载各种配置信息、初始化应用上下文, + * 然后加载所有被@ComponentScan扫描到的组件,配置各种中间件(如Web服务器,如果是Web应用的话),最终启动整个应用。 + * + * @param args 命令行参数,可用于在启动应用时传入一些自定义的配置信息或运行参数等,例如指定应用的运行环境(开发、测试、生产等)、端口号等。 */ public static void main(String[] args) { SpringApplication.run(WebApplication.class, args); } /** - * 当应用以WAR包形式部署到外部的Servlet容器(如Tomcat等)时,需要重写这个方法来配置Spring应用的启动构建器。 - * 在这里返回的SpringApplicationBuilder会指定启动的主类(即WebApplication.class),以便容器能够正确加载和启动应用。 + * 当应用以WAR包形式部署到外部的Servlet容器(如Tomcat、Jetty等)时,需要重写这个方法来配置Spring应用的启动构建器。 + * 在这里,它返回的SpringApplicationBuilder会指定启动的主类(即WebApplication.class), + * 这样外部的Servlet容器就能通过这个构建器正确地加载和启动Spring应用,将应用的上下文与容器进行整合, + * 确保应用能够在外部容器的环境下正常运行,并且能够利用容器提供的各种资源和功能。 */ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(WebApplication.class); } - } \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/config/AdminBeanConfig.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/config/AdminBeanConfig.java index dae32f4..186fb9a 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/config/AdminBeanConfig.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/config/AdminBeanConfig.java @@ -16,16 +16,76 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** + * AdminBeanConfig类是Spring框架中的一个配置类,主要作用是配置相关的Bean,以便在Spring应用上下文环境中进行管理和使用。 + * 该类通过特定的注解和方法来创建并配置特定的Bean实例,满足项目中对于分布式唯一ID生成等相关功能的需求。 + * + * @author lanhai + */ +@Configuration +// @Configuration注解表明这个类是一个配置类,Spring会扫描到这个类,并根据其中定义的Bean配置方法来创建和管理相应的Bean对象。 +@AllArgsConstructor +// @AllArgsConstructor注解会为这个类生成一个包含所有参数的构造函数,方便依赖注入操作,在这里用于注入AdminConfig实例。 +public class AdminBeanConfig { + + // 通过构造函数注入AdminConfig实例,AdminConfig应该是包含了相关配置信息的类,此处用于获取创建Snowflake实例所需的参数。 + private final AdminConfig adminConfig; + + /** + * snowflake方法用于创建并向Spring容器中注册一个Snowflake类型的Bean。 + * Snowflake是一种常用的分布式唯一ID生成器,在分布式系统中用于生成全局唯一的标识符。 + * 此方法利用从AdminConfig中获取的workerId和datacenterId参数来实例化Snowflake对象,从而保证生成的ID在分布式环境下的唯一性。 + * + * @return 返回一个配置好的Snowflake实例,该实例会被Spring容器管理,其他需要生成分布式唯一ID的组件可以通过依赖注入的方式获取并使用它。 + */ + @Bean + public Snowflake snowflake() { + return new Snowflake(adminConfig.getWorkerId(), adminConfig.getDatacenterId()); + } +}加注释 +以下是添加注释后的代码: + +java +/* + * Copyright (c) 2018 - 2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.config; + +import cn.hutool.core.lang.Snowflake; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * AdminBeanConfig类是Spring框架中的一个配置类,主要用于配置相关的Bean,以便在Spring应用上下文环境中进行管理和使用 + * 该类通过特定的注解和方法来创建并配置特定的Bean实例,满足项目中对于分布式唯一ID生成等相关功能的需求 + * * @author lanhai */ @Configuration +// @Configuration注解表明这个类是一个配置类,Spring会扫描到这个类,并根据其中定义的Bean配置方法来创建和管理相应的Bean对象 @AllArgsConstructor +// @AllArgsConstructor注解会为这个类生成一个包含所有参数的构造函数,方便依赖注入操作,在这里用于注入AdminConfig实例 public class AdminBeanConfig { + // 通过构造函数注入AdminConfig实例,AdminConfig应该是包含了相关配置信息的类,此处用于获取创建Snowflake实例所需的参数 private final AdminConfig adminConfig; + /** + * snowflake方法用于创建并向Spring容器中注册一个Snowflake类型的Bean + * Snowflake是一种常用的分布式唯一ID生成器,在分布式系统中用于生成全局唯一的标识符 + * 此方法利用从AdminConfig中获取的workerId和datacenterId参数来实例化Snowflake对象,从而保证生成的ID在分布式环境下的唯一性 + * + * @return 返回一个配置好的Snowflake实例,该实例会被Spring容器管理,其他需要生成分布式唯一ID的组件可以通过依赖注入的方式获取并使用它 + */ @Bean public Snowflake snowflake() { - return new Snowflake(adminConfig.getWorkerId(), adminConfig.getDatacenterId()); + return new Snowflake(adminConfig.getWorkerId(), adminConfig.getDatacenterId()); } -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/config/AdminConfig.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/config/AdminConfig.java index bd6bc79..1dacd34 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/config/AdminConfig.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/config/AdminConfig.java @@ -1,5 +1,8 @@ +以下是为您提供的代码注释: + +```java /* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * Copyright (c) 2018 - 2999 广州市蓝海创新科技有限公司 All rights reserved. * * https://www.mall4j.com/ * @@ -15,25 +18,44 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; - /** - * 商城配置文件 + * AdminConfig类主要用于加载和管理商城管理端相关的配置信息 + * 通过一系列的Spring注解,它能够从指定的配置文件中读取相应属性,并将这些属性映射到类中的成员变量上,方便在整个商城管理端项目中使用这些配置数据 + * * @author lgh */ -@Data @Component +/** + * @Component注解将该类标记为Spring组件,意味着Spring容器在扫描时会识别这个类,并将其纳入管理范围, + * 这样就可以在其他需要使用该配置信息的地方通过依赖注入的方式获取到这个类的实例 + */ @PropertySource("classpath:admin.properties") +/** + * @PropertySource注解用于指定配置属性的来源,这里明确表示配置信息将从类路径下的admin.properties文件中获取 + */ @ConfigurationProperties(prefix = "admin") +/** + * @ConfigurationProperties注解的作用是把配置文件中以"admin"为前缀的属性绑定到这个类对应的成员变量上,实现配置属性的自动注入 + */ +@Data +/** + * @Data是Lombok提供的注解,它会自动为类生成一系列常用的方法,包括各个成员变量的Getter和Setter方法、 + * toString方法、equals方法以及hashCode方法等,方便对类中的成员变量进行操作和访问 + */ public class AdminConfig { - /** - * 数据中心ID - */ - private Integer datacenterId; - - /** - * 终端ID - */ - private Integer workerId; + /** + * 数据中心ID,在分布式系统环境下,用于唯一标识不同的数据中心 + * 例如在一些涉及分布式ID生成(像使用Snowflake算法等情况)或者与数据中心相关的资源分配、服务调度等场景中, + * 会依靠这个数据中心ID来区分不同的数据中心,确保相关操作的准确性和唯一性 + */ + private Integer datacenterId; + /** + * 终端ID,通常用于区分分布式系统中的不同工作终端或者节点 + * 比如在分布式应用中,不同的服务器节点或者服务实例可以通过各自的终端ID来进行标识, + * 在诸如分布式任务调度、分布式ID生成(与具体终端相关联时)等场景下发挥作用,有助于精准定位和管理各个终端的相关操作 + */ + private Integer workerId; } +``` \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/config/SwaggerConfiguration.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/config/SwaggerConfiguration.java index c3d49d2..77f4f12 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/config/SwaggerConfiguration.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/config/SwaggerConfiguration.java @@ -18,26 +18,39 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * Swagger文档,只有在测试环境才会使用 + * Swagger文档相关配置类,此类用于配置Swagger在项目中的相关信息展示以及接口分组等内容, + * 并且只有在测试环境才会使用Swagger来查看接口文档相关信息。 * @author LGH */ @Configuration public class SwaggerConfiguration { - @Bean - public GroupedOpenApi baseRestApi() { - return GroupedOpenApi.builder() - .group("接口文档") - .packagesToScan("com.yami").build(); - } + /** + * 配置基础的Rest API分组信息,用于定义一组接口,方便在Swagger界面中进行归类展示。 + * 这里指定了接口分组的名称为"接口文档",并且设置了要扫描的包路径为"com.yami", + * 这样在该包及其子包下的接口将会被纳入到这个分组中进行展示。 + * @return 返回配置好的GroupedOpenApi对象,代表了一组接口的配置信息。 + */ + @Bean + public GroupedOpenApi baseRestApi() { + return GroupedOpenApi.builder() + .group("接口文档") + .packagesToScan("com.yami") + .build(); + } - @Bean - public OpenAPI springShopOpenApi() { - return new OpenAPI() - .info(new Info().title("Mall4j接口文档") - .description("Mall4j接口文档,openapi3.0 接口,用于前端对接") - .version("v0.0.1") - .license(new License().name("使用请遵守AGPL3.0授权协议").url("https://www.mall4j.com"))); - } -} + /** + * 配置整个Spring Shop项目的OpenAPI信息,包括接口文档的标题、描述、版本以及使用的授权协议等相关内容。 + * 这些信息将会展示在Swagger的主页面上,方便使用者了解接口文档的基本情况以及遵循的授权规则等。 + * @return 返回配置好的OpenAPI对象,包含了完整的接口文档元数据信息。 + */ + @Bean + public OpenAPI springShopOpenApi() { + return new OpenAPI() + .info(new Info().title("Mall4j接口文档") + .description("Mall4j接口文档,openapi3.0 接口,用于前端对接") + .version("v0.0.1") + .license(new License().name("使用请遵守AGPL3.0授权协议").url("https://www.mall4j.com"))); + } +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/config/XxlJobConfig.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/config/XxlJobConfig.java index 17452fa..79a6139 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/config/XxlJobConfig.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/config/XxlJobConfig.java @@ -7,6 +7,7 @@ * * 版权所有,侵权必究! */ + package com.yami.shop.admin.config; import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; @@ -19,47 +20,77 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * xxl-job config - * 为啥这里的代码要注释掉,因为很奇怪的,因为很多人都不会去下载xxl-job来跑定时任务。 - * 原本用spring 的 quartz做定时任务的,但是文档写着要忽略数据库大小写,结果很多人根本不看文档,所以干脆就把这个quartz的删掉,然后通过xxl-job执行定时任务 - * 那么又会带来一个问题,大家也不会看文档,也不会去下载xxl-job,所以要把这里面的代码注掉,要是有人需要的话,改下这里的连接配置,连上xxl-job即可。 - * 毕竟你都能找到这个文件了,下载xxl-job,修改并启动xxl-job-admin,把取消订单,确认收货之类定时任务加入到里面即可咯。还要把下面的注释掉的代码打开,这样启动项目就会连接xxl-job执行定时任务了。 + * xxl-job配置类,用于配置和初始化与XXL-JOB相关的执行器等信息,以实现定时任务通过XXL-JOB来执行的功能。 + * + * 以下是关于这段代码为何部分被注释掉的详细说明: + * 起初项目原本使用Spring的Quartz来做定时任务,但遇到一个问题,文档中写明了需要忽略数据库大小写, + * 然而实际情况是很多人并不会去仔细查看文档,导致在使用Quartz做定时任务时出现各种问题,所以决定舍弃Quartz的方式。 + * 进而考虑采用XXL-JOB来执行定时任务,但是又出现了新的问题,就是很多人同样也不会去查看文档,更不会主动去下载并配置XXL-JOB, + * 所以为了避免不必要的启动报错等情况(因为如果不配置好XXL-JOB直接启动相关代码会出错),就先将与XXL-JOB配置相关的代码注释掉了。 + * + * 如果有人确实有使用XXL-JOB执行定时任务的需求,那么操作步骤如下: + * 1. 先自行下载XXL-JOB,并按照要求进行相关配置,启动xxl-job-admin服务。 + * 2. 将需要执行的定时任务(比如取消订单、确认收货之类的定时任务)添加到xxl-job-admin中进行管理。 + * 3. 最后把下面被注释掉的代码取消注释,这样在启动项目时,就能够连接到XXL-JOB,进而通过它来执行定时任务了。 + * * @author FrozenWatermelon * @date 2021/1/18 */ @Configuration public class XxlJobConfig { + // 创建一个日志记录器,用于记录与XXL-JOB配置相关的日志信息,方便后续排查问题以及了解配置初始化等过程的情况。 private final Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); + // 通过@Value注解从配置文件中读取xxl-job.admin.addresses属性值,该值代表了XXL-JOB管理端的地址,用于执行器与管理端进行通信连接。 @Value("${xxl-job.admin.addresses}") private String adminAddresses; + // 通过@Value注解从配置文件中读取xxl-job.accessToken属性值,该值作为访问令牌,用于在执行器与XXL-JOB管理端通信时进行身份验证等安全相关操作。 @Value("${xxl-job.accessToken}") private String accessToken; + // 通过@Value注解从配置文件中读取xxl-job.logPath属性值,该值指定了定时任务执行日志的存储路径,方便后续查看任务执行情况以及排查可能出现的问题。 @Value("${xxl-job.logPath}") private String logPath; + // 通过@Value注解从配置文件中读取server.port属性值,即当前项目服务所监听的端口号,后续可能会基于此端口号来确定执行器相关的端口等信息(这里是做了端口偏移使用)。 @Value("${server.port}") private int port; + // 注入InetUtils对象,该对象是Spring Cloud提供的一个工具类,用于处理网络相关的操作,在这里主要用于获取合适的IP地址, + // 特别是在多网卡、容器内部署等复杂网络环境下,能够帮助准确获取到执行器要绑定的IP地址,避免IP地址获取不准确导致的通信问题。 @Autowired private InetUtils inetUtils; + // 以下是配置XXL-JOB执行器的方法,目前被注释掉了,原因在类的开头注释部分已经详细说明了。 + // 如果要启用XXL-JOB执行定时任务,需要取消这段代码的注释,并确保已经正确配置好了XXL-JOB相关环境。 + // @Bean注解表示该方法会创建一个Spring管理的Bean对象,在这里就是创建一个XxlJobSpringExecutor类型的Bean, + // 该对象是XXL-JOB在Spring环境下的执行器实现类,负责与XXL-JOB管理端交互以及执行具体的定时任务等操作。 // @Bean // public XxlJobSpringExecutor xxlJobExecutor() { // +// // 记录一条日志信息,表示开始进行XXL-JOB配置初始化,方便在启动项目时查看相关配置是否正常加载等情况。 // logger.info(">>>>>>>>>>> xxl-job config init."); +// // 创建一个XxlJobSpringExecutor实例,用于后续配置并返回作为Spring管理的Bean对象。 // XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); +// // 设置XXL-JOB管理端的地址,让执行器知道要与哪个管理端进行通信连接,这个地址就是从配置文件中读取到的adminAddresses的值。 // xxlJobSpringExecutor.setAdminAddresses(adminAddresses); +// // 设置应用名称,这里固定设置为"mall4j",在XXL-JOB管理端可以通过这个名称来区分不同的执行器所属的应用,方便管理和监控。 // xxlJobSpringExecutor.setAppname("mall4j"); -// // 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP +// // 针对多网卡、容器内部署等情况,借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP, +// // 通过调用InetUtils的findFirstNonLoopbackAddress方法获取到第一个非回环地址(即有效的网络IP地址),并设置为执行器绑定的IP地址, +// // 确保执行器能够在复杂网络环境下正确地与XXL-JOB管理端进行通信。 // xxlJobSpringExecutor.setIp(inetUtils.findFirstNonLoopbackAddress().getHostAddress()); +// // 设置执行器监听的端口号,这里采用了项目服务端口号(port)加上1000的方式来确定,避免端口冲突等问题,同时也方便统一管理端口分配。 // xxlJobSpringExecutor.setPort(port + 1000); +// // 设置访问令牌,用于在执行器与XXL-JOB管理端通信时进行身份验证等安全相关操作,这个值就是从配置文件中读取到的accessToken的值。 // xxlJobSpringExecutor.setAccessToken(accessToken); +// // 设置定时任务执行日志的存储路径,方便后续查看任务执行情况以及排查可能出现的问题,这个值就是从配置文件中读取到的logPath的值。 // xxlJobSpringExecutor.setLogPath(logPath); +// // 设置日志保留天数,这里设置为3天,表示定时任务执行日志只会保留最近3天的记录,超过这个天数的旧日志会被自动清理, +// // 这样可以避免日志文件过多占用磁盘空间,同时也能保证在一定时间范围内可以查看历史任务执行情况。 // xxlJobSpringExecutor.setLogRetentionDays(3); // return xxlJobSpringExecutor; // } -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java index d9a7446..426a102 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java @@ -7,6 +7,7 @@ * * 版权所有,侵权必究! */ + package com.yami.shop.admin.controller; import cn.hutool.core.util.StrUtil; @@ -43,6 +44,8 @@ import java.util.Set; import java.util.stream.Collectors; /** + * 后台登录相关的控制器类,主要处理管理员登录的逻辑,包括验证码校验、用户信息验证、密码验证以及生成和返回登录后的token等操作。 + * * @author FrozenWatermelon * @date 2020/6/30 */ @@ -50,51 +53,72 @@ import java.util.stream.Collectors; @Tag(name = "登录") public class AdminLoginController { + // 注入TokenStore,用于存储和获取用户登录相关的token信息,比如生成token、从存储中获取token相关数据等操作。 @Autowired private TokenStore tokenStore; + // 注入SysUserService,用于与系统用户相关的数据库操作,比如根据用户名查询用户信息等。 @Autowired private SysUserService sysUserService; + // 注入SysMenuService,用于与系统菜单相关的数据库操作,例如查询系统菜单列表等,此处主要用于获取权限相关信息。 @Autowired private SysMenuService sysMenuService; + // 注入PasswordCheckManager,用于对用户输入的密码进行合法性、安全性等方面的检查,比如验证密码是否符合规则、是否在一定时间内多次输错被限制登录等。 @Autowired private PasswordCheckManager passwordCheckManager; + // 注入CaptchaService,用于处理验证码相关的操作,比如验证验证码是否正确、是否过期等。 @Autowired private CaptchaService captchaService; + // 注入PasswordManager,用于对密码进行解密等相关操作,比如将前端传来的加密密码进行解密,以便后续进行密码验证。 @Autowired private PasswordManager passwordManager; + /** + * 处理管理员登录的接口方法,接收包含账号、密码以及验证码等信息的请求体,进行一系列登录验证操作,若验证通过则返回登录成功后的token信息。 + * + * @param captchaAuthenticationDTO 包含了账号、密码以及验证码验证信息的请求参数对象,通过@Valid注解进行参数合法性校验。 + * @return 返回ServerResponseEntity类型的响应结果,若登录成功则返回包含token信息的成功响应,若验证失败则返回相应的错误提示信息。 + */ @PostMapping("/adminLogin") - @Operation(summary = "账号密码 + 验证码登录(用于后台登录)" , description = "通过账号/手机号/用户名密码登录") + @Operation(summary = "账号密码 + 验证码登录(用于后台登录)", description = "通过账号/手机号/用户名密码登录") public ServerResponseEntity login( @Valid @RequestBody CaptchaAuthenticationDTO captchaAuthenticationDTO) { + // 登陆后台登录需要再校验一遍验证码 + // 创建CaptchaVO对象,用于传递给验证码服务进行验证,将前端传来的验证码验证信息设置进去。 CaptchaVO captchaVO = new CaptchaVO(); captchaVO.setCaptchaVerification(captchaAuthenticationDTO.getCaptchaVerification()); + // 调用验证码服务的验证方法,传入CaptchaVO对象,获取验证结果响应模型。 ResponseModel response = captchaService.verification(captchaVO); + // 如果验证不成功,即验证码有误或者已过期,返回相应的错误提示信息给前端。 if (!response.isSuccess()) { return ServerResponseEntity.showFailMsg("验证码有误或已过期"); } + // 根据用户名从数据库中查询系统用户信息,如果未查询到用户,说明账号不存在,抛出相应的异常提示账号或密码不正确。 SysUser sysUser = sysUserService.getByUserName(captchaAuthenticationDTO.getUserName()); if (sysUser == null) { throw new YamiShopBindException("账号或密码不正确"); } // 半小时内密码输入错误十次,已限制登录30分钟 + // 先对前端传来的密码进行解密操作,以便后续与数据库中存储的密码进行比对验证。 String decryptPassword = passwordManager.decryptPassword(captchaAuthenticationDTO.getPassWord()); - passwordCheckManager.checkPassword(SysTypeEnum.ADMIN,captchaAuthenticationDTO.getUserName(), decryptPassword, sysUser.getPassword()); + // 调用密码检查管理器,传入系统类型、用户名、解密后的密码以及数据库中存储的用户密码,进行密码验证,若不符合规则会抛出相应异常。 + passwordCheckManager.checkPassword(SysTypeEnum.ADMIN, captchaAuthenticationDTO.getUserName(), decryptPassword, sysUser.getPassword()); // 不是店铺超级管理员,并且是禁用状态,无法登录 - if (Objects.equals(sysUser.getStatus(),0)) { + if (Objects.equals(sysUser.getStatus(), 0)) { + // 若用户状态为禁用(这里假设状态0表示禁用),抛出异常提示未找到此用户信息(此处实际意思应该是用户不可用)。 // 未找到此用户信息 throw new YamiShopBindException("未找到此用户信息"); } + // 创建用于存储在token中的用户信息对象,用于后续生成token以及传递给前端展示相关用户信息。 UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO(); userInfoInToken.setUserId(String.valueOf(sysUser.getUserId())); userInfoInToken.setSysType(SysTypeEnum.ADMIN.value()); @@ -102,23 +126,35 @@ public class AdminLoginController { userInfoInToken.setPerms(getUserPermissions(sysUser.getUserId())); userInfoInToken.setNickName(sysUser.getUsername()); userInfoInToken.setShopId(sysUser.getShopId()); - // 存储token返回vo + + // 调用token存储服务,将用户信息存储并生成token相关信息,获取包含token等详细信息的TokenInfoVO对象,然后返回登录成功的响应结果给前端,包含了token信息。 TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken); return ServerResponseEntity.success(tokenInfoVO); } + /** + * 根据用户ID获取用户的权限信息集合,根据用户是否为系统超级管理员(通过特定的ID判断)采用不同的方式获取权限信息, + * 最终将获取到的权限字符串列表转换为去重后的权限集合返回。 + * + * @param userId 用户的ID,用于确定是哪个用户的权限信息以及判断是否为系统超级管理员。 + * @return 返回包含用户权限信息的字符串集合,每个元素代表一个权限标识。 + */ private Set getUserPermissions(Long userId) { List permsList; - //系统管理员,拥有最高权限 - if(userId == Constant.SUPER_ADMIN_ID){ + // 系统管理员,拥有最高权限 + if (userId == Constant.SUPER_ADMIN_ID) { + // 如果是系统超级管理员,查询所有的系统菜单列表,获取每个菜单对应的权限字符串,组成权限列表。 List menuList = sysMenuService.list(Wrappers.emptyWrapper()); permsList = menuList.stream().map(SysMenu::getPerms).collect(Collectors.toList()); - }else{ + } else { + // 如果不是系统超级管理员,调用用户服务的方法查询该用户的所有权限信息,得到权限列表。 permsList = sysUserService.queryAllPerms(userId); } - return permsList.stream().flatMap((perms)->{ + + // 将权限列表中的每个权限字符串进行分割(假设权限字符串之间以逗号分隔),然后扁平化处理,去除重复的权限,最终收集为一个权限集合返回。 + return permsList.stream().flatMap((perms) -> { if (StrUtil.isBlank(perms)) { return null; } @@ -126,4 +162,4 @@ public class AdminLoginController { } ).collect(Collectors.toSet()); } -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AreaController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AreaController.java index 1a4659b..ee00e14 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AreaController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AreaController.java @@ -27,38 +27,55 @@ import java.util.List; import java.util.Objects; /** + * AreaController类,用于处理与地区(Area)相关的后台管理接口请求 + * 包含地区信息的分页查询、获取列表、根据父级ID获取列表、获取单个地区信息、保存、修改以及删除等操作 * @author lgh on 2018/10/26. */ @RestController @RequestMapping("/admin/area") public class AreaController { + // 自动注入AreaService,用于调用与地区相关的业务逻辑方法 @Autowired private AreaService areaService; /** - * 分页获取 + * 分页获取地区信息的接口方法 + * 通过接收Area对象(可能包含查询条件)以及PageParam对象(用于分页参数设置), + * 调用AreaService的page方法进行分页查询,并返回包含查询结果的ServerResponseEntity对象 + * @param area 可能包含查询条件的地区对象,比如按地区名称等条件查询 + * @param page 分页参数对象,包含页码、每页数量等信息 + * @return 返回包含分页地区信息的ServerResponseEntity,成功时其数据部分为IPage类型 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('admin:area:page')") - public ServerResponseEntity> page(Area area,PageParam page) { + public ServerResponseEntity> page(Area area, PageParam page) { IPage sysUserPage = areaService.page(page, new LambdaQueryWrapper()); return ServerResponseEntity.success(sysUserPage); } /** - * 获取省市 + * 获取省市地区信息的接口方法 + * 根据传入的Area对象(可能包含地区名称等模糊查询条件), + * 调用AreaService的list方法,使用LambdaQueryWrapper进行条件查询,获取符合条件的地区列表, + * 最后返回包含地区列表信息的ServerResponseEntity对象 + * @param area 可能包含查询条件的地区对象,如按地区名称模糊查询 + * @return 返回包含查询到的地区列表信息的ServerResponseEntity,成功时其数据部分为List类型 */ @GetMapping("/list") @PreAuthorize("@pms.hasPermission('admin:area:list')") public ServerResponseEntity> list(Area area) { List areas = areaService.list(new LambdaQueryWrapper() - .like(area.getAreaName() != null, Area::getAreaName, area.getAreaName())); + .like(area.getAreaName()!= null, Area::getAreaName, area.getAreaName())); return ServerResponseEntity.success(areas); } /** - * 通过父级id获取区域列表 + * 通过父级id获取区域列表的接口方法 + * 直接调用AreaService的listByPid方法,传入父级ID,获取对应父级下的区域列表, + * 然后返回包含该区域列表信息的ServerResponseEntity对象 + * @param pid 父级地区的ID + * @return 返回包含对应父级下区域列表信息的ServerResponseEntity,成功时其数据部分为List类型 */ @GetMapping("/listByPid") public ServerResponseEntity> listByPid(Long pid) { @@ -67,7 +84,11 @@ public class AreaController { } /** - * 获取信息 + * 获取指定ID地区详细信息的接口方法 + * 通过路径变量获取地区的ID,调用AreaService的getById方法获取对应的地区对象, + * 最后返回包含该地区对象信息的ServerResponseEntity对象 + * @param id 要获取信息的地区的ID + * @return 返回包含指定地区详细信息的ServerResponseEntity,成功时其数据部分为Area类型 */ @GetMapping("/info/{id}") @PreAuthorize("@pms.hasPermission('admin:area:info')") @@ -77,12 +98,17 @@ public class AreaController { } /** - * 保存 + * 保存地区信息的接口方法 + * 首先判断传入的地区对象是否有父级ID,如果有,则根据父级地区的级别来设置当前地区的级别, + * 同时调用AreaService的removeAreaCacheByParentId方法清除对应父级ID的地区缓存, + * 最后调用AreaService的save方法保存地区信息,并返回表示成功的ServerResponseEntity对象 + * @param area 要保存的地区对象,通过请求体传入,且经过了数据校验(@Valid注解) + * @return 返回表示保存成功的ServerResponseEntity,无数据内容(Void类型) */ @PostMapping @PreAuthorize("@pms.hasPermission('admin:area:save')") public ServerResponseEntity save(@Valid @RequestBody Area area) { - if (area.getParentId() != null) { + if (area.getParentId()!= null) { Area parentArea = areaService.getById(area.getParentId()); area.setLevel(parentArea.getLevel() + 1); areaService.removeAreaCacheByParentId(area.getParentId()); @@ -92,17 +118,23 @@ public class AreaController { } /** - * 修改 + * 修改地区信息的接口方法 + * 先根据传入地区对象的ID获取数据库中已存在的对应地区对象(areaDb), + * 然后判断当前地区的级别是否允许修改(一级、二级行政地区有相应限制),如果不允许则抛出异常, + * 接着调用hasSameName方法检查地区名称是否重复, + * 最后调用AreaService的updateById方法更新地区信息,并清除对应父级ID的地区缓存,返回表示成功的ServerResponseEntity对象 + * @param area 要修改的地区对象,通过请求体传入,且经过了数据校验(@Valid注解) + * @return 返回表示修改成功的ServerResponseEntity,无数据内容(Void类型) */ @PutMapping @PreAuthorize("@pms.hasPermission('admin:area:update')") public ServerResponseEntity update(@Valid @RequestBody Area area) { Area areaDb = areaService.getById(area.getAreaId()); // 判断当前省市区级别,如果是1级、2级则不能修改级别,不能修改成别人的下级 - if(Objects.equals(areaDb.getLevel(), AreaLevelEnum.FIRST_LEVEL.value()) && !Objects.equals(area.getLevel(),AreaLevelEnum.FIRST_LEVEL.value())){ + if (Objects.equals(areaDb.getLevel(), AreaLevelEnum.FIRST_LEVEL.value()) &&!Objects.equals(area.getLevel(), AreaLevelEnum.FIRST_LEVEL.value())) { throw new YamiShopBindException("不能改变一级行政地区的级别"); } - if(Objects.equals(areaDb.getLevel(),AreaLevelEnum.SECOND_LEVEL.value()) && !Objects.equals(area.getLevel(),AreaLevelEnum.SECOND_LEVEL.value())){ + if (Objects.equals(areaDb.getLevel(), AreaLevelEnum.SECOND_LEVEL.value()) &&!Objects.equals(area.getLevel(), AreaLevelEnum.SECOND_LEVEL.value())) { throw new YamiShopBindException("不能改变二级行政地区的级别"); } hasSameName(area); @@ -112,7 +144,12 @@ public class AreaController { } /** - * 删除 + * 删除地区信息的接口方法 + * 根据路径变量获取要删除地区的ID,先调用AreaService的getById方法获取对应的地区对象, + * 然后调用AreaService的removeById方法删除地区信息,同时清除对应父级ID的地区缓存, + * 最后返回表示成功的ServerResponseEntity对象 + * @param id 要删除的地区的ID + * @return 返回表示删除成功的ServerResponseEntity,无数据内容(Void类型) */ @DeleteMapping("/{id}") @PreAuthorize("@pms.hasPermission('admin:area:delete')") @@ -123,14 +160,20 @@ public class AreaController { return ServerResponseEntity.success(); } + /** + * 私有方法,用于检查要保存或修改的地区名称是否已存在(在同一父级下) + * 通过构建LambdaQueryWrapper,设置查询条件(父级ID相同、地区名称相同、排除自身ID等), + * 调用AreaService的count方法统计符合条件的记录数量,如果数量大于0则表示名称已存在,抛出异常 + * @param area 要检查名称是否重复的地区对象 + */ private void hasSameName(Area area) { long count = areaService.count(new LambdaQueryWrapper() - .eq(Area::getParentId, area.getParentId()) - .eq(Area::getAreaName, area.getAreaName()) - .ne(Objects.nonNull(area.getAreaId()) && !Objects.equals(area.getAreaId(), 0L), Area::getAreaId, area.getAreaId()) + .eq(Area::getParentId, area.getParentId()) + .eq(Area::getAreaName, area.getAreaName()) + .ne(Objects.nonNull(area.getAreaId()) &&!Objects.equals(area.getAreaId(), 0L), Area::getAreaId, area.getAreaId()) ); if (count > 0) { throw new YamiShopBindException("该地区已存在"); } } -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AttributeController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AttributeController.java index 5cea597..6f5a392 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AttributeController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AttributeController.java @@ -26,73 +26,103 @@ import jakarta.validation.Valid; import java.util.Objects; /** - * 参数管理 + * 参数管理相关的控制器类,主要负责处理商品参数(属性)相关的增删改查操作,并且在各个操作方法上添加了权限控制, + * 确保只有具备相应权限的用户才能执行对应的操作。 + * * @author lgh */ @RestController @RequestMapping("/admin/attribute") public class AttributeController { + // 注入ProdPropService,用于与商品属性相关的业务逻辑处理,例如查询、保存、更新、删除商品属性及其相关值等操作。 @Autowired private ProdPropService prodPropService; - /** - * 分页获取 - */ + /** + * 分页获取商品属性信息的方法,根据传入的查询条件(ProdProp对象)和分页参数(PageParam对象), + * 从数据库中分页查询符合条件的商品属性信息,并返回给前端。 + * 同时,通过@PreAuthorize注解进行权限控制,只有具备"admin:attribute:page"权限的用户才能访问此方法。 + */ @GetMapping("/page") - @PreAuthorize("@pms.hasPermission('admin:attribute:page')") - public ServerResponseEntity> page(ProdProp prodProp,PageParam page){ - prodProp.setRule(ProdPropRule.ATTRIBUTE.value()); - prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); - IPage prodPropPage = prodPropService.pagePropAndValue(prodProp,page); - return ServerResponseEntity.success(prodPropPage); - } + @PreAuthorize("@pms.hasPermission('admin:attribute:page')") + public ServerResponseEntity> page(ProdProp prodProp, PageParam page) { + // 设置商品属性的规则为属性类型(这里假设ProdPropRule.ATTRIBUTE表示属性类型),用于后续业务逻辑中区分不同类型的商品属性规则。 + prodProp.setRule(ProdPropRule.ATTRIBUTE.value()); + // 设置商品属性所属的店铺ID,通过SecurityUtils工具类获取当前登录用户所属的店铺ID,确保查询的是当前店铺下的商品属性信息。 + prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); + // 调用ProdPropService的pagePropAndValue方法,传入设置好的商品属性对象和分页参数,获取分页后的商品属性信息结果集。 + IPage prodPropPage = prodPropService.pagePropAndValue(prodProp, page); + // 将查询到的分页商品属性信息封装在成功的响应实体中返回给前端。 + return ServerResponseEntity.success(prodPropPage); + } /** - * 获取信息 - */ - @GetMapping("/info/{id}") - @PreAuthorize("@pms.hasPermission('admin:attribute:info')") - public ServerResponseEntity info(@PathVariable("id") Long id){ - ProdProp prodProp = prodPropService.getById(id); - return ServerResponseEntity.success(prodProp); - } + * 根据商品属性ID获取商品属性详细信息的方法,通过传入的商品属性ID,从数据库中查询对应的商品属性信息并返回给前端。 + * 同样通过@PreAuthorize注解进行权限控制,只有具备"admin:attribute:info"权限的用户才能访问此方法。 + */ + @GetMapping("/info/{id}") + @PreAuthorize("@pms.hasPermission('admin:attribute:info')") + public ServerResponseEntity info(@PathVariable("id") Long id) { + // 调用ProdPropService的getById方法,根据传入的商品属性ID从数据库中获取对应的商品属性对象。 + ProdProp prodProp = prodPropService.getById(id); + // 将获取到的商品属性对象封装在成功的响应实体中返回给前端。 + return ServerResponseEntity.success(prodProp); + } - /** - * 保存 - */ - @PostMapping - @PreAuthorize("@pms.hasPermission('admin:attribute:save')") - public ServerResponseEntity save(@Valid ProdProp prodProp){ - prodProp.setRule(ProdPropRule.ATTRIBUTE.value()); - prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); - prodPropService.saveProdPropAndValues(prodProp); - return ServerResponseEntity.success(); - } + /** + * 保存商品属性信息的方法,接收一个经过验证(通过@Valid注解)的商品属性对象(ProdProp), + * 在保存前设置相关属性(如属性规则、店铺ID等),然后调用服务层方法将商品属性及其相关值保存到数据库中, + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"admin:attribute:save"权限的用户才能访问此方法。 + */ + @PostMapping + @PreAuthorize("@pms.hasPermission('admin:attribute:save')") + public ServerResponseEntity save(@Valid ProdProp prodProp) { + // 设置商品属性的规则为属性类型(这里假设ProdPropRule.ATTRIBUTE表示属性类型),用于后续业务逻辑中区分不同类型的商品属性规则。 + prodProp.setRule(ProdPropRule.ATTRIBUTE.value()); + // 设置商品属性所属的店铺ID,通过SecurityUtils工具类获取当前登录用户所属的店铺ID,确保保存的是当前店铺下的商品属性信息。 + prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); + // 调用ProdPropService的saveProdPropAndValues方法,将设置好的商品属性对象及其相关值保存到数据库中。 + prodPropService.saveProdPropAndValues(prodProp); + // 返回表示操作成功的响应实体,由于这里只是执行保存操作,无需返回具体数据,所以返回的是Void类型的成功响应。 + return ServerResponseEntity.success(); + } - /** - * 修改 - */ - @PutMapping - @PreAuthorize("@pms.hasPermission('admin:attribute:update')") - public ServerResponseEntity update(@Valid ProdProp prodProp){ - ProdProp dbProdProp = prodPropService.getById(prodProp.getPropId()); - if (!Objects.equals(dbProdProp.getShopId(), SecurityUtils.getSysUser().getShopId())) { - throw new YamiShopBindException("没有权限获取该商品规格信息"); - } - prodProp.setRule(ProdPropRule.ATTRIBUTE.value()); - prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); - prodPropService.updateProdPropAndValues(prodProp); - return ServerResponseEntity.success(); - } + /** + * 修改商品属性信息的方法,接收一个经过验证(通过@Valid注解)的商品属性对象(ProdProp), + * 首先根据传入的商品属性对象中的ID查询数据库中已有的商品属性信息,判断当前登录用户是否有修改该商品属性的权限(通过店铺ID对比), + * 若有权限则设置相关属性(如属性规则、店铺ID等),再调用服务层方法更新商品属性及其相关值到数据库中, + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"admin:attribute:update"权限的用户才能访问此方法。 + */ + @PutMapping + @PreAuthorize("@pms.hasPermission('admin:attribute:update')") + public ServerResponseEntity update(@Valid ProdProp prodProp) { + // 根据传入的商品属性对象中的ID,调用ProdPropService的getById方法从数据库中获取对应的商品属性对象,用于后续权限判断等操作。 + ProdProp dbProdProp = prodPropService.getById(prodProp.getPropId()); + // 判断数据库中查询到的商品属性所属的店铺ID与当前登录用户所属的店铺ID是否一致,若不一致则说明当前用户没有权限修改该商品属性信息,抛出相应异常。 + if (!Objects.equals(dbProdProp.getShopId(), SecurityUtils.getSysUser().getShopId())) { + throw new YamiShopBindException("没有权限获取该商品规格信息"); + } + // 设置商品属性的规则为属性类型(这里假设ProdPropRule.ATTRIBUTE表示属性类型),用于后续业务逻辑中区分不同类型的商品属性规则。 + prodProp.setRule(ProdPropRule.ATTRIBUTE.value()); + // 设置商品属性所属的店铺ID,通过SecurityUtils工具类获取当前登录用户所属的店铺ID,确保更新的是当前店铺下的商品属性信息。 + prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); + // 调用ProdPropService的updateProdPropAndValues方法,将设置好的商品属性对象及其相关值更新到数据库中。 + prodPropService.updateProdPropAndValues(prodProp); + // 返回表示操作成功的响应实体,由于这里只是执行更新操作,无需返回具体数据,所以返回的是Void类型的成功响应。 + return ServerResponseEntity.success(); + } - /** - * 删除 - */ - @DeleteMapping("/{id}") - @PreAuthorize("@pms.hasPermission('admin:attribute:delete')") - public ServerResponseEntity delete(@PathVariable Long id){ - prodPropService.deleteProdPropAndValues(id,ProdPropRule.ATTRIBUTE.value(),SecurityUtils.getSysUser().getShopId()); - return ServerResponseEntity.success(); - } -} + /** + * 删除商品属性信息的方法,根据传入的商品属性ID,调用服务层方法删除对应的商品属性及其相关值信息, + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"admin:attribute:delete"权限的用户才能访问此方法。 + */ + @DeleteMapping("/{id}") + @PreAuthorize("@pms.hasPermission('admin:attribute:delete')") + public ServerResponseEntity delete(@PathVariable Long id) { + // 调用ProdPropService的deleteProdPropAndValues方法,传入商品属性ID、属性规则(这里指定为属性类型)以及当前登录用户所属的店铺ID,执行删除操作。 + prodPropService.deleteProdPropAndValues(id, ProdPropRule.ATTRIBUTE.value(), SecurityUtils.getSysUser().getShopId()); + // 返回表示操作成功的响应实体,由于这里只是执行删除操作,无需返回具体数据,所以返回的是Void类型的成功响应。 + return ServerResponseEntity.success(); + } +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/BrandController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/BrandController.java index d6d56a6..03cc281 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/BrandController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/BrandController.java @@ -25,33 +25,44 @@ import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; import java.util.Objects; - /** * 品牌管理 - * + * 该类主要用于处理品牌相关的后台管理操作接口,包含品牌信息的分页查询、获取单个品牌信息、品牌的保存、修改以及删除等功能。 * @author lgh */ @RestController @RequestMapping("/admin/brand") public class BrandController { + // 自动注入BrandService,用于调用与品牌相关的业务逻辑方法 @Autowired private BrandService brandService; /** - * 分页获取 + * 分页获取品牌信息的接口方法 + * 通过接收Brand对象(可能包含品牌名称等查询条件)以及PageParam对象(用于分页参数设置), + * 利用BrandService的page方法结合LambdaQueryWrapper进行分页查询,查询条件中如果品牌名称不为空则进行模糊匹配, + * 并且按照品牌名称首字符升序排序,最后返回包含查询结果的ServerResponseEntity对象。 + * @param brand 可能包含查询条件的品牌对象,比如按品牌名称模糊查询 + * @param page 分页参数对象,包含页码、每页数量等信息 + * @return 返回包含分页品牌信息的ServerResponseEntity,成功时其数据部分为IPage类型 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('admin:brand:page')") - public ServerResponseEntity> page(Brand brand,PageParam page) { + public ServerResponseEntity> page(Brand brand, PageParam page) { IPage brands = brandService.page(page, new LambdaQueryWrapper() - .like(StrUtil.isNotBlank(brand.getBrandName()), Brand::getBrandName, brand.getBrandName()).orderByAsc(Brand::getFirstChar)); + .like(StrUtil.isNotBlank(brand.getBrandName()), Brand::getBrandName, brand.getBrandName()) + .orderByAsc(Brand::getFirstChar)); return ServerResponseEntity.success(brands); } /** - * 获取信息 + * 获取指定ID品牌详细信息的接口方法 + * 通过路径变量获取品牌的ID,调用BrandService的getById方法获取对应的品牌对象, + * 最后返回包含该品牌对象信息的ServerResponseEntity对象。 + * @param id 要获取信息的品牌的ID + * @return 返回包含指定品牌详细信息的ServerResponseEntity,成功时其数据部分为Brand类型 */ @GetMapping("/info/{id}") @PreAuthorize("@pms.hasPermission('admin:brand:info')") @@ -61,13 +72,18 @@ public class BrandController { } /** - * 保存 + * 保存品牌信息的接口方法 + * 首先调用BrandService的getByBrandName方法,根据传入品牌对象的名称查询数据库中是否已存在同名品牌, + * 如果存在则抛出异常(表示品牌名称已存在),若不存在则调用BrandService的save方法保存品牌信息, + * 最后返回表示成功的ServerResponseEntity对象。 + * @param brand 要保存的品牌对象,通过请求体传入,且经过了数据校验(@Valid注解) + * @return 返回表示保存成功的ServerResponseEntity,无数据内容(Void类型) */ @PostMapping @PreAuthorize("@pms.hasPermission('admin:brand:save')") public ServerResponseEntity save(@Valid Brand brand) { Brand dbBrand = brandService.getByBrandName(brand.getBrandName()); - if (dbBrand != null) { + if (dbBrand!= null) { throw new YamiShopBindException("该品牌名称已存在"); } brandService.save(brand); @@ -75,13 +91,18 @@ public class BrandController { } /** - * 修改 + * 修改品牌信息的接口方法 + * 先调用BrandService的getByBrandName方法,根据传入品牌对象的名称查询数据库中是否已存在同名品牌(除了自身), + * 如果存在同名且ID不同的品牌则抛出异常(表示品牌名称已存在且冲突),若不存在冲突则调用BrandService的updateById方法更新品牌信息, + * 最后返回表示成功的ServerResponseEntity对象。 + * @param brand 要修改的品牌对象,通过请求体传入,且经过了数据校验(@Valid注解) + * @return 返回表示修改成功的ServerResponseEntity,无数据内容(Void类型) */ @PutMapping @PreAuthorize("@pms.hasPermission('admin:brand:update')") public ServerResponseEntity update(@Valid Brand brand) { Brand dbBrand = brandService.getByBrandName(brand.getBrandName()); - if (dbBrand != null && !Objects.equals(dbBrand.getBrandId(), brand.getBrandId())) { + if (dbBrand!= null &&!Objects.equals(dbBrand.getBrandId(), brand.getBrandId())) { throw new YamiShopBindException("该品牌名称已存在"); } brandService.updateById(brand); @@ -89,7 +110,11 @@ public class BrandController { } /** - * 删除 + * 删除品牌信息的接口方法 + * 根据路径变量获取要删除品牌的ID,调用BrandService的deleteByBrand方法删除对应品牌信息, + * 最后返回表示成功的ServerResponseEntity对象。 + * @param id 要删除的品牌的ID + * @return 返回表示删除成功的ServerResponseEntity,无数据内容(Void类型) */ @DeleteMapping("/{id}") @PreAuthorize("@pms.hasPermission('admin:brand:delete')") @@ -97,5 +122,4 @@ public class BrandController { brandService.deleteByBrand(id); return ServerResponseEntity.success(); } - -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java index 93d4e5d..be3d2a1 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java @@ -25,10 +25,10 @@ import java.util.Date; import java.util.List; import java.util.Objects; - - /** - * 分类管理 + * 分类管理相关的控制器类,主要负责处理商品分类的各种操作,包括获取分类信息、保存、更新、删除分类以及获取分类列表等功能, + * 同时在部分操作方法上添加了权限控制和操作日志记录功能,以确保系统的安全性和可追溯性。 + * * @author lgh * */ @@ -36,110 +36,154 @@ import java.util.Objects; @RequestMapping("/prod/category") public class CategoryController { - @Autowired - private CategoryService categoryService; - - /** - * 获取菜单页面的表 - * @return - */ - @GetMapping("/table") - @PreAuthorize("@pms.hasPermission('prod:category:page')") - public ServerResponseEntity> table(){ - List categoryMenuList = categoryService.tableCategory(SecurityUtils.getSysUser().getShopId()); - return ServerResponseEntity.success(categoryMenuList); - } - - /** - * 获取分类信息 - */ - @GetMapping("/info/{categoryId}") - public ServerResponseEntity info(@PathVariable("categoryId") Long categoryId){ - Category category = categoryService.getById(categoryId); - return ServerResponseEntity.success(category); - } - - - - /** - * 保存分类 - */ - @SysLog("保存分类") - @PostMapping - @PreAuthorize("@pms.hasPermission('prod:category:save')") - public ServerResponseEntity save(@RequestBody Category category){ - category.setShopId(SecurityUtils.getSysUser().getShopId()); - category.setRecTime(new Date()); - Category categoryName = categoryService.getOne(new LambdaQueryWrapper().eq(Category::getCategoryName,category.getCategoryName()) - .eq(Category::getShopId,category.getShopId())); - if(Objects.nonNull(categoryName)){ - throw new YamiShopBindException("类目名称已存在!"); - } - categoryService.saveCategory(category); - return ServerResponseEntity.success(); - } - - /** - * 更新分类 - */ - @SysLog("更新分类") - @PutMapping - @PreAuthorize("@pms.hasPermission('prod:category:update')") - public ServerResponseEntity update(@RequestBody Category category){ - category.setShopId(SecurityUtils.getSysUser().getShopId()); - if (Objects.equals(category.getParentId(),category.getCategoryId())) { - return ServerResponseEntity.showFailMsg("分类的上级不能是自己本身"); - } - Category categoryName = categoryService.getOne(new LambdaQueryWrapper().eq(Category::getCategoryName,category.getCategoryName()) - .eq(Category::getShopId,category.getShopId()).ne(Category::getCategoryId,category.getCategoryId())); - if(categoryName != null){ - throw new YamiShopBindException("类目名称已存在!"); - } - Category categoryDb = categoryService.getById(category.getCategoryId()); - // 如果从下线改成正常,则需要判断上级的状态 - if (Objects.equals(categoryDb.getStatus(),0) && Objects.equals(category.getStatus(),1) && !Objects.equals(category.getParentId(),0L)){ - Category parentCategory = categoryService.getOne(new LambdaQueryWrapper().eq(Category::getCategoryId, category.getParentId())); - if(Objects.isNull(parentCategory) || Objects.equals(parentCategory.getStatus(),0)){ - // 修改失败,上级分类不存在或者不为正常状态 - throw new YamiShopBindException("修改失败,上级分类不存在或者不为正常状态"); - } - } - categoryService.updateCategory(category); - return ServerResponseEntity.success(); - } - - /** - * 删除分类 - */ - @SysLog("删除分类") - @DeleteMapping("/{categoryId}") - @PreAuthorize("@pms.hasPermission('prod:category:delete')") - public ServerResponseEntity delete(@PathVariable("categoryId") Long categoryId){ - if (categoryService.count(new LambdaQueryWrapper().eq(Category::getParentId,categoryId)) >0) { - return ServerResponseEntity.showFailMsg("请删除子分类,再删除该分类"); - } - categoryService.deleteCategory(categoryId); - return ServerResponseEntity.success(); - } - - /** - * 所有的 - */ - @GetMapping("/listCategory") - public ServerResponseEntity> listCategory(){ - - return ServerResponseEntity.success(categoryService.list(new LambdaQueryWrapper() - .le(Category::getGrade, 2) - .eq(Category::getShopId, SecurityUtils.getSysUser().getShopId()) - .orderByAsc(Category::getSeq))); - } - - /** - * 所有的产品分类 - */ - @GetMapping("/listProdCategory") - public ServerResponseEntity> listProdCategory(){ - List categories = categoryService.treeSelect(SecurityUtils.getSysUser().getShopId(),2); - return ServerResponseEntity.success(categories); - } -} + // 注入CategoryService,用于与商品分类相关的业务逻辑处理,例如查询、保存、更新、删除分类等操作。 + @Autowired + private CategoryService categoryService; + + /** + * 获取用于菜单页面展示的分类列表信息的方法,通过调用CategoryService的相关方法, + * 获取当前店铺(根据当前登录用户所属店铺ID确定)下的分类列表信息,并返回给前端展示。 + * 同时,通过@PreAuthorize注解进行权限控制,只有具备"prod:category:page"权限的用户才能访问此方法。 + * + * @return 返回包含分类信息的ServerResponseEntity对象,若获取成功则响应体中包含分类列表数据,否则返回相应错误信息。 + */ + @GetMapping("/table") + @PreAuthorize("@pms.hasPermission('prod:category:page')") + public ServerResponseEntity> table() { + // 调用CategoryService的tableCategory方法,传入当前登录用户所属的店铺ID,获取用于菜单页面展示的分类列表信息。 + List categoryMenuList = categoryService.tableCategory(SecurityUtils.getSysUser().getShopId()); + // 将获取到的分类列表信息封装在成功的响应实体中返回给前端。 + return ServerResponseEntity.success(categoryMenuList); + } + + /** + * 根据分类ID获取分类详细信息的方法,通过传入的分类ID,调用CategoryService的getById方法从数据库中查询对应的分类对象, + * 并将其封装在成功的响应实体中返回给前端。此方法没有添加权限控制注解,需根据实际业务需求确认是否需要添加相应权限控制。 + * + * @param categoryId 要获取信息的分类的唯一标识符。 + * @return 返回包含分类详细信息的ServerResponseEntity对象,若获取成功则响应体中包含对应的分类对象,否则返回相应错误信息。 + */ + @GetMapping("/info/{categoryId}") + public ServerResponseEntity info(@PathVariable("categoryId") Long categoryId) { + Category category = categoryService.getById(categoryId); + return ServerResponseEntity.success(category); + } + + /** + * 保存商品分类信息的方法,接收一个Category对象作为请求体,代表要保存的分类信息。 + * 在保存前设置分类所属的店铺ID(通过当前登录用户所属店铺ID确定)以及记录时间等信息, + * 然后检查分类名称是否已存在(在当前店铺下),若不存在则调用CategoryService的saveCategory方法将分类信息保存到数据库中, + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"prod:category:save"权限的用户才能访问此方法, + * 同时使用@SysLog注解记录此操作的日志信息,方便后续查看操作记录。 + * + * @param category 包含分类信息的请求体对象,如分类名称、上级分类ID等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行保存操作,无需返回具体数据,所以返回的是Void类型的成功响应。 + */ + @SysLog("保存分类") + @PostMapping + @PreAuthorize("@pms.hasPermission('prod:category:save')") + public ServerResponseEntity save(@RequestBody Category category) { + category.setShopId(SecurityUtils.getSysUser().getShopId()); + category.setRecTime(new Date()); + // 通过LambdaQueryWrapper构建查询条件,查询在当前店铺下分类名称与要保存的分类名称相同的分类记录,用于判断名称是否已存在。 + Category categoryName = categoryService.getOne(new LambdaQueryWrapper() + .eq(Category::getCategoryName, category.getCategoryName()) + .eq(Category::getShopId, category.getShopId())); + if (Objects.nonNull(categoryName)) { + throw new YamiShopBindException("类目名称已存在!"); + } + categoryService.saveCategory(category); + return ServerResponseEntity.success(); + } + + /** + * 更新商品分类信息的方法,接收一个Category对象作为请求体,代表要更新的分类信息。 + * 首先设置分类所属的店铺ID(通过当前登录用户所属店铺ID确定),然后进行一系列合法性校验, + * 如检查分类的上级是否为自身、分类名称是否已存在(除自身外)等,若校验通过且满足其他相关条件(如从下线改成正常时上级分类状态的判断), + * 则调用CategoryService的updateCategory方法将更新后的分类信息保存到数据库中,最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"prod:category:update"权限的用户才能访问此方法, + * 同时使用@SysLog注解记录此操作的日志信息,方便后续查看操作记录。 + * + * @param category 包含更新后的分类信息的请求体对象,如分类名称、上级分类ID、状态等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,若更新成功则响应体中包含成功信息,若校验不通过则返回相应的错误提示信息。 + */ + @SysLog("更新分类") + @PutMapping + @PreAuthorize("@pms.hasPermission('prod:category:update')") + public ServerResponseEntity update(@RequestBody Category category) { + category.setShopId(SecurityUtils.getSysUser().getShopId()); + if (Objects.equals(category.getParentId(), category.getCategoryId())) { + return ServerResponseEntity.showFailMsg("分类的上级不能是自己本身"); + } + // 通过LambdaQueryWrapper构建查询条件,查询在当前店铺下分类名称与要更新的分类名称相同且ID不同(排除自身)的分类记录,用于判断名称是否已存在。 + Category categoryName = categoryService.getOne(new LambdaQueryWrapper() + .eq(Category::getCategoryName, category.getCategoryName()) + .eq(Category::getShopId, category.getShopId()) + .ne(Category::getCategoryId, category.getCategoryId())); + if (categoryName!= null) { + throw new YamiShopBindException("类目名称已存在!"); + } + Category categoryDb = categoryService.getById(category.getCategoryId()); + // 如果从下线(状态为0)改成正常(状态为1),则需要判断上级的状态,确保上级分类存在且为正常状态。 + if (Objects.equals(categoryDb.getStatus(), 0) && Objects.equals(category.getStatus(), 1) &&!Objects.equals(category.getParentId(), 0L)) { + Category parentCategory = categoryService.getOne(new LambdaQueryWrapper() + .eq(Category::getCategoryId, category.getParentId())); + if (Objects.isNull(parentCategory) || Objects.equals(parentCategory.getStatus(), 0)) { + // 修改失败,上级分类不存在或者不为正常状态 + throw new YamiShopBindException("修改失败,上级分类不存在或者不为正常状态"); + } + } + categoryService.updateCategory(category); + return ServerResponseEntity.success(); + } + + /** + * 删除商品分类信息的方法,根据传入的分类ID,首先检查该分类是否还有子分类,若有子分类则不允许删除,返回相应的错误提示信息, + * 若无子分类则调用CategoryService的deleteCategory方法将分类信息从数据库中删除,最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"prod:category:delete"权限的用户才能访问此方法, + * 同时使用@SysLog注解记录此操作的日志信息,方便后续查看操作记录。 + * + * @param categoryId 要删除的分类的唯一标识符。 + * @return 返回表示操作成功的ServerResponseEntity对象,若删除成功则响应体中包含成功信息,若存在子分类则返回相应的错误提示信息。 + */ + @SysLog("删除分类") + @DeleteMapping("/{categoryId}") + @PreAuthorize("@pms.hasPermission('prod:category:delete')") + public ServerResponseEntity delete(@PathVariable("categoryId") Long categoryId) { + // 通过LambdaQueryWrapper构建查询条件,统计以当前分类ID为上级分类的子分类数量,若数量大于0则说明还有子分类,不允许删除。 + if (categoryService.count(new LambdaQueryWrapper().eq(Category::getParentId, categoryId)) > 0) { + return ServerResponseEntity.showFailMsg("请删除子分类,再删除该分类"); + } + categoryService.deleteCategory(categoryId); + return ServerResponseEntity.success(); + } + + /** + * 获取所有满足一定条件的分类列表信息的方法,通过构建LambdaQueryWrapper查询条件, + * 查询等级小于等于2且属于当前店铺(根据当前登录用户所属店铺ID确定)的分类信息,并按照顺序排序, + * 最后将查询到的分类列表信息封装在成功的响应实体中返回给前端。此方法没有添加权限控制注解,需根据实际业务需求确认是否需要添加相应权限控制。 + * + * @return 返回包含分类列表信息的ServerResponseEntity对象,若查询成功则响应体中包含符合条件的分类列表数据,否则返回相应错误信息。 + */ + @GetMapping("/listCategory") + public ServerResponseEntity> listCategory() { + + return ServerResponseEntity.success(categoryService.list(new LambdaQueryWrapper() + .le(Category::getGrade, 2) + .eq(Category::getShopId, SecurityUtils.getSysUser().getShopId()) + .orderByAsc(Category::getSeq))); + } + + /** + * 获取所有用于产品展示的分类列表信息的方法,通过调用CategoryService的treeSelect方法, + * 获取指定店铺(根据当前登录用户所属店铺ID确定)下用于产品展示的分类列表信息(这里可能是构建树形结构的分类数据,具体取决于service层实现), + * 最后将查询到的分类列表信息封装在成功的响应实体中返回给前端。此方法没有添加权限控制注解,需根据实际业务需求确认是否需要添加相应权限控制。 + * + * @return 返回包含分类列表信息的ServerResponseEntity对象,若查询成功则响应体中包含符合条件的分类列表数据,否则返回相应错误信息。 + */ + @GetMapping("/listProdCategory") + public ServerResponseEntity> listProdCategory() { + List categories = categoryService.treeSelect(SecurityUtils.getSysUser().getShopId(), 2); + return ServerResponseEntity.success(categories); + } +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/DeliveryController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/DeliveryController.java index a3a799a..da5b17c 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/DeliveryController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/DeliveryController.java @@ -8,6 +8,7 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,用于在项目中对类进行组织和分类管理 package com.yami.shop.admin.controller; import java.util.List; @@ -18,28 +19,35 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +// 引入对应的实体类,这里是Delivery类,用于表示配送相关的业务对象 import com.yami.shop.bean.model.Delivery; +// 引入配送服务层接口,用于调用具体的与配送相关的业务逻辑方法 import com.yami.shop.service.DeliveryService; /** - * + * DeliveryController类是一个Spring RESTful风格的控制器,用于处理后台管理系统中与配送(Delivery)相关的接口请求。 + * 目前该类中主要提供了获取配送信息列表的功能,后续可根据业务需求在此类中扩展更多相关接口方法。 * @author lgh on 2018/11/26. */ @RestController +// 定义该控制器类的基础请求路径,所有该类中的接口请求路径都将以此为前缀 @RequestMapping("/admin/delivery") public class DeliveryController { + // 通过Spring的依赖注入机制,自动注入DeliveryService的实例,以便调用其提供的业务方法 @Autowired private DeliveryService deliveryService; - /** - * 分页获取 - */ + /** + * 分页获取配送信息列表的接口方法。 + * 目前该方法只是简单地调用了DeliveryService的list方法获取所有的配送信息列表, + * 后续可根据实际需求添加分页相关逻辑,比如接收分页参数等进行分页查询处理。 + * 最后将获取到的配送信息列表封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * @return 返回包含配送信息列表的ServerResponseEntity对象,成功时其数据部分为List类型。 + */ @GetMapping("/list") - public ServerResponseEntity> page(){ - - List list = deliveryService.list(); - return ServerResponseEntity.success(list); - } - -} + public ServerResponseEntity> page() { + List list = deliveryService.list(); + return ServerResponseEntity.success(list); + } +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/FileController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/FileController.java index 63c33c4..c4569ad 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/FileController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/FileController.java @@ -27,6 +27,7 @@ import java.util.Objects; /** * 文件上传 controller + * 该类主要负责处理文件上传相关的请求,提供了不同场景下文件上传的接口方法 * @author lgh * */ @@ -34,32 +35,52 @@ import java.util.Objects; @RequestMapping("/admin/file") public class FileController { - @Autowired - private AttachFileService attachFileService; - @Autowired - private Qiniu qiniu; - @Autowired - private ImgUploadUtil imgUploadUtil; + // 自动注入附件文件服务层对象,用于处理文件上传等相关业务逻辑 + @Autowired + private AttachFileService attachFileService; + // 自动注入七牛相关配置对象,用于在文件上传到七牛云存储时获取相关配置信息 + @Autowired + private Qiniu qiniu; + // 自动注入图片上传工具类对象,用于获取上传类型、路径等相关信息,辅助文件上传操作 + @Autowired + private ImgUploadUtil imgUploadUtil; - @PostMapping("/upload/element") - public ServerResponseEntity uploadElementFile(@RequestParam("file") MultipartFile file) throws IOException{ - if(file.isEmpty()){ + /** + * 处理element组件的文件上传请求的方法 + * @param file 前端传来的MultipartFile类型的文件对象,代表要上传的文件 + * @return 返回一个ServerResponseEntity类型的响应,成功时包含上传后的文件名,若文件为空则返回成功状态的空响应 + * @throws IOException 可能会抛出IO异常,例如文件读取、写入等操作出现问题时 + */ + @PostMapping("/upload/element") + public ServerResponseEntity uploadElementFile(@RequestParam("file") MultipartFile file) throws IOException { + // 判断上传的文件是否为空,如果为空则直接返回成功状态的空响应 + if (file.isEmpty()) { return ServerResponseEntity.success(); } - String fileName = attachFileService.uploadFile(file); + // 调用附件文件服务层的方法上传文件,并获取上传后的文件名 + String fileName = attachFileService.uploadFile(file); return ServerResponseEntity.success(fileName); - } + } - @PostMapping("/upload/tinymceEditor") - public ServerResponseEntity uploadTinymceEditorImages(@RequestParam("editorFile") MultipartFile editorFile) throws IOException{ - String fileName = attachFileService.uploadFile(editorFile); - String data = ""; - if (Objects.equals(imgUploadUtil.getUploadType(), UploadType.LOCAL.value())) { - data = imgUploadUtil.getUploadPath() + fileName; - } else if (Objects.equals(imgUploadUtil.getUploadType(), UploadType.QINIU.value())) { - data = qiniu.getResourcesUrl() + fileName; - } + /** + * 处理tinymce编辑器图片上传请求的方法 + * @param editorFile 前端传来的MultipartFile类型的文件对象,代表编辑器中要上传的图片文件 + * @return 返回一个ServerResponseEntity类型的响应,成功时包含根据上传类型拼接好的图片访问路径,若上传类型等处理出现问题则返回不符合预期的结果 + * @throws IOException 可能会抛出IO异常,例如文件读取、写入等操作出现问题时 + */ + @PostMapping("/upload/tinymceEditor") + public ServerResponseEntity uploadTinymceEditorImages(@RequestParam("editorFile") MultipartFile editorFile) throws IOException { + // 调用附件文件服务层的方法上传文件,并获取上传后的文件名 + String fileName = attachFileService.uploadFile(editorFile); + String data = ""; + // 根据图片上传工具类获取的上传类型进行不同的路径拼接处理 + if (Objects.equals(imgUploadUtil.getUploadType(), UploadType.LOCAL.value())) { + // 如果是本地存储类型,将本地存储路径和文件名拼接起来作为图片的访问路径 + data = imgUploadUtil.getUploadPath() + fileName; + } else if (Objects.equals(imgUploadUtil.getUploadType(), UploadType.QINIU.value())) { + // 如果是七牛云存储类型,将七牛云资源的基础访问URL和文件名拼接起来作为图片的访问路径 + data = qiniu.getResourcesUrl() + fileName; + } return ServerResponseEntity.success(data); - } - -} + } +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/HotSearchController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/HotSearchController.java index a06a041..359a358 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/HotSearchController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/HotSearchController.java @@ -27,76 +27,95 @@ import java.util.Date; import java.util.List; /** - * + * 热门搜索相关操作的控制器类,用于处理后台对热门搜索的各种请求,例如分页查询、获取详情、保存、修改和删除等操作。 * @author lgh on 2019/03/27. */ @RestController @RequestMapping("/admin/hotSearch") public class HotSearchController { + // 自动注入热门搜索服务层接口,通过该接口调用具体的业务逻辑方法 @Autowired private HotSearchService hotSearchService; - /** - * 分页获取 - */ + /** + * 分页获取热门搜索信息的方法 + * 此方法根据传入的查询条件(包括搜索内容、标题、状态等)以及分页参数,查询并返回符合条件的热门搜索信息分页结果。 + * 只有拥有 'admin:hotSearch:page' 权限的用户才能访问此方法。 + */ @GetMapping("/page") - @PreAuthorize("@pms.hasPermission('admin:hotSearch:page')") - public ServerResponseEntity> page(HotSearch hotSearch,PageParam page){ - IPage hotSearchs = hotSearchService.page(page,new LambdaQueryWrapper() - .eq(HotSearch::getShopId, SecurityUtils.getSysUser().getShopId()) - .like(StrUtil.isNotBlank(hotSearch.getContent()), HotSearch::getContent,hotSearch.getContent()) - .like(StrUtil.isNotBlank(hotSearch.getTitle()), HotSearch::getTitle,hotSearch.getTitle()) - .eq(hotSearch.getStatus()!=null, HotSearch::getStatus,hotSearch.getStatus()) - .orderByAsc(HotSearch::getSeq) - ); - return ServerResponseEntity.success(hotSearchs); - } + @PreAuthorize("@pms.hasPermission('admin:hotSearch:page')") + public ServerResponseEntity> page(HotSearch hotSearch, PageParam page) { + // 创建一个 LambdaQueryWrapper 用于构建查询条件,结合 MyBatis Plus 进行数据库查询 + IPage hotSearchs = hotSearchService.page(page, new LambdaQueryWrapper() + // 根据当前登录用户所属店铺的 ID 进行筛选,确保只获取该店铺相关的热门搜索记录 + .eq(HotSearch::getShopId, SecurityUtils.getSysUser().getShopId()) + // 如果传入的热门搜索内容不为空,则模糊匹配内容字段进行查询 + .like(StrUtil.isNotBlank(hotSearch.getContent()), HotSearch::getContent, hotSearch.getContent()) + // 如果传入的热门搜索标题不为空,则模糊匹配标题字段进行查询 + .like(StrUtil.isNotBlank(hotSearch.getTitle()), HotSearch::getTitle, hotSearch.getTitle()) + // 如果传入的状态不为空,则精确匹配状态字段进行查询 + .eq(hotSearch.getStatus()!= null, HotSearch::getStatus, hotSearch.getStatus()) + // 按照序号字段升序排序结果,方便呈现顺序相关的展示需求 + .orderByAsc(HotSearch::getSeq) + ); + return ServerResponseEntity.success(hotSearchs); + } /** - * 获取信息 - */ - @GetMapping("/info/{id}") - public ServerResponseEntity info(@PathVariable("id") Long id){ - HotSearch hotSearch = hotSearchService.getById(id); - return ServerResponseEntity.success(hotSearch); - } + * 根据给定的 ID 获取热门搜索详细信息的方法 + * 此方法接收一个热门搜索记录的 ID,通过调用服务层的方法从数据库中获取对应的详细信息,并返回给前端。 + */ + @GetMapping("/info/{id}") + public ServerResponseEntity info(@PathVariable("id") Long id) { + // 通过服务层的 getById 方法,根据传入的 ID 获取对应的热门搜索记录 + HotSearch hotSearch = hotSearchService.getById(id); + return ServerResponseEntity.success(hotSearch); + } - /** - * 保存 - */ - @PostMapping - @PreAuthorize("@pms.hasPermission('admin:hotSearch:save')") - public ServerResponseEntity save(@RequestBody @Valid HotSearch hotSearch){ - hotSearch.setRecDate(new Date()); - hotSearch.setShopId(SecurityUtils.getSysUser().getShopId()); - hotSearchService.save(hotSearch); - //清除缓存 - hotSearchService.removeHotSearchDtoCacheByShopId(SecurityUtils.getSysUser().getShopId()); - return ServerResponseEntity.success(); - } + /** + * 保存热门搜索信息的方法 + * 此方法接收一个包含热门搜索信息的 HotSearch 对象,设置记录的创建日期以及所属店铺 ID(从当前登录用户获取), + * 然后调用服务层的保存方法将数据保存到数据库中,并在保存后清除对应店铺的热门搜索缓存,只有拥有 'admin:hotSearch:save' 权限的用户才能执行此操作。 + */ + @PostMapping + @PreAuthorize("@pms.hasPermission('admin:hotSearch:save')") + public ServerResponseEntity save(@RequestBody @Valid HotSearch hotSearch) { + // 设置记录的创建日期为当前日期 + hotSearch.setRecDate(new Date()); + // 设置记录所属的店铺 ID,从当前登录用户的信息中获取 + hotSearch.setShopId(SecurityUtils.getSysUser().getShopId()); + hotSearchService.save(hotSearch); + // 清除对应店铺的热门搜索缓存,保证数据的一致性 + hotSearchService.removeHotSearchDtoCacheByShopId(SecurityUtils.getSysUser().getShopId()); + return ServerResponseEntity.success(); + } - /** - * 修改 - */ - @PutMapping - @PreAuthorize("@pms.hasPermission('admin:hotSearch:update')") - public ServerResponseEntity update(@RequestBody @Valid HotSearch hotSearch){ - hotSearchService.updateById(hotSearch); - //清除缓存 - hotSearchService.removeHotSearchDtoCacheByShopId(SecurityUtils.getSysUser().getShopId()); - return ServerResponseEntity.success(); - } + /** + * 修改热门搜索信息的方法 + * 此方法接收一个包含更新后热门搜索信息的 HotSearch 对象,调用服务层的更新方法将数据库中的对应记录进行更新, + * 并在更新后清除对应店铺的热门搜索缓存,只有拥有 'admin:hotSearch:update' 权限的用户才能执行此操作。 + */ + @PutMapping + @PreAuthorize("@pms.hasPermission('admin:hotSearch:update')") + public ServerResponseEntity update(@RequestBody @Valid HotSearch hotSearch) { + hotSearchService.updateById(hotSearch); + // 清除对应店铺的热门搜索缓存,确保缓存数据与数据库最新数据一致 + hotSearchService.removeHotSearchDtoCacheByShopId(SecurityUtils.getSysUser().getShopId()); + return ServerResponseEntity.success(); + } - /** - * 删除 - */ - @DeleteMapping - @PreAuthorize("@pms.hasPermission('admin:hotSearch:delete')") - public ServerResponseEntity delete(@RequestBody List ids){ - hotSearchService.removeByIds(ids); - //清除缓存 - hotSearchService.removeHotSearchDtoCacheByShopId(SecurityUtils.getSysUser().getShopId()); - return ServerResponseEntity.success(); - } -} + /** + * 根据给定的 ID 列表批量删除热门搜索记录的方法 + * 此方法接收一个包含多个热门搜索记录 ID 的列表,调用服务层的删除方法从数据库中批量删除这些记录, + * 并在删除后清除对应店铺的热门搜索缓存,只有拥有 'admin:hotSearch:delete' 权限的用户才能执行此操作。 + */ + @DeleteMapping + @PreAuthorize("@pms.hasPermission('admin:hotSearch:delete')") + public ServerResponseEntity delete(@RequestBody List ids) { + hotSearchService.removeByIds(ids); + // 清除对应店铺的热门搜索缓存,保证数据的准确性 + hotSearchService.removeHotSearchDtoCacheByShopId(SecurityUtils.getSysUser().getShopId()); + return ServerResponseEntity.success(); + } +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/IndexImgController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/IndexImgController.java index 4fc3f68..c14fce3 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/IndexImgController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/IndexImgController.java @@ -29,42 +29,64 @@ import java.util.Date; import java.util.Objects; /** + * 首页图片(IndexImg)相关的控制器类,主要负责处理首页图片的增删改查操作, + * 包括分页查询、获取单条信息、保存、修改和删除等功能,并且在各个操作方法上添加了相应的权限控制, + * 同时在部分操作中涉及与商品信息的关联校验等业务逻辑。 + * * @author lgh on 2018/11/26. */ @RestController @RequestMapping("/admin/indexImg") public class IndexImgController { + // 注入IndexImgService,用于处理与首页图片相关的业务逻辑,例如图片信息的查询、保存、更新、删除等操作。 @Autowired private IndexImgService indexImgService; + // 注入ProductService,用于获取商品相关信息,在涉及首页图片与商品关联的业务逻辑中会用到,比如校验商品状态等情况。 @Autowired private ProductService productService; - /** - * 分页获取 + * 分页获取首页图片信息的方法,根据传入的查询条件(IndexImg对象)和分页参数(PageParam对象), + * 通过IndexImgService进行分页查询,同时可以根据图片状态进行筛选,并按照顺序排序,最后将查询到的分页结果返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"admin:indexImg:page"权限的用户才能访问此方法。 + * + * @param indexImg 包含查询条件的IndexImg对象,例如可以设置图片状态等筛选条件。 + * @param page 分页参数对象,用于指定页码、每页数量等分页相关信息。 + * @return 返回包含分页后的首页图片信息的ServerResponseEntity对象,若查询成功则响应体中包含符合条件的分页图片数据,否则返回相应错误信息。 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('admin:indexImg:page')") public ServerResponseEntity> page(IndexImg indexImg, PageParam page) { + // 使用IndexImgService的page方法进行分页查询,传入分页参数和LambdaQueryWrapper构建的查询条件, + // 根据传入的indexImg对象的状态是否不为空来决定是否添加状态筛选条件,同时按照顺序排序。 IPage indexImgPage = indexImgService.page(page, new LambdaQueryWrapper() - .eq(indexImg.getStatus() != null, IndexImg::getStatus, indexImg.getStatus()) - .orderByAsc(IndexImg::getSeq)); + .eq(indexImg.getStatus()!= null, IndexImg::getStatus, indexImg.getStatus()) + .orderByAsc(IndexImg::getSeq)); return ServerResponseEntity.success(indexImgPage); } /** - * 获取信息 + * 根据图片ID获取首页图片详细信息的方法,先根据当前登录用户所属的店铺ID以及传入的图片ID, + * 通过IndexImgService查询对应的首页图片信息,若图片存在关联商品,则进一步获取商品信息并设置到图片对象中相关属性(如商品图片、商品名称等), + * 最后将包含详细信息的图片对象封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"admin:indexImg:info"权限的用户才能访问此方法。 + * + * @param imgId 要获取详细信息的首页图片的唯一标识符。 + * @return 返回包含首页图片详细信息的ServerResponseEntity对象,若获取成功则响应体中包含对应的图片对象及关联商品信息(若有),否则返回相应错误信息。 */ @GetMapping("/info/{imgId}") @PreAuthorize("@pms.hasPermission('admin:indexImg:info')") public ServerResponseEntity info(@PathVariable("imgId") Long imgId) { Long shopId = SecurityUtils.getSysUser().getShopId(); + // 通过IndexImgService根据店铺ID和图片ID查询对应的首页图片对象,使用LambdaQueryWrapper构建查询条件。 IndexImg indexImg = indexImgService.getOne(new LambdaQueryWrapper().eq(IndexImg::getShopId, shopId).eq(IndexImg::getImgId, imgId)); if (Objects.nonNull(indexImg.getRelation())) { + // 若图片存在关联商品(通过relation属性判断),则通过ProductService根据商品ID获取商品对象。 Product product = productService.getProductByProdId(indexImg.getRelation()); + // 将商品的图片和商品名称设置到首页图片对象的相应属性中,方便前端展示关联商品的相关信息。 indexImg.setPic(product.getPic()); indexImg.setProdName(product.getProdName()); } @@ -72,7 +94,13 @@ public class IndexImgController { } /** - * 保存 + * 保存首页图片信息的方法,接收一个经过验证(通过@Valid注解)的IndexImg对象作为请求体,代表要保存的图片信息。 + * 首先设置图片所属的店铺ID(通过当前登录用户所属店铺ID确定)以及上传时间等信息,然后调用私有方法checkProdStatus对图片关联的商品状态进行校验, + * 若校验通过则调用IndexImgService的save方法将图片信息保存到数据库中,并清除首页图片缓存(可能用于更新缓存数据,确保数据一致性), + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"admin:indexImg:save"权限的用户才能访问此方法。 + * + * @param indexImg 包含要保存的首页图片信息的请求体对象,如图片名称、关联商品、状态等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行保存操作,无需返回具体数据,所以返回的是Void类型的成功响应。 */ @PostMapping @PreAuthorize("@pms.hasPermission('admin:indexImg:save')") @@ -87,7 +115,13 @@ public class IndexImgController { } /** - * 修改 + * 修改首页图片信息的方法,接收一个经过验证(通过@Valid注解)的IndexImg对象作为请求体,代表要更新的图片信息。 + * 首先调用私有方法checkProdStatus对图片关联的商品状态进行校验,若校验通过则调用IndexImgService的saveOrUpdate方法将更新后的图片信息保存到数据库中, + * 并清除首页图片缓存(可能用于更新缓存数据,确保数据一致性),最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"admin:indexImg:update"权限的用户才能访问此方法。 + * + * @param indexImg 包含要更新的首页图片信息的请求体对象,如图片名称、关联商品、状态等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行更新操作,无需返回具体数据,所以返回的是Void类型的成功响应。 */ @PutMapping @PreAuthorize("@pms.hasPermission('admin:indexImg:update')") @@ -99,7 +133,12 @@ public class IndexImgController { } /** - * 删除 + * 删除首页图片信息的方法,接收一个包含要删除的图片ID数组的请求体,通过IndexImgService的deleteIndexImgByIds方法, + * 根据传入的图片ID数组批量删除对应的首页图片信息,并清除首页图片缓存(可能用于更新缓存数据,确保数据一致性), + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"admin:indexImg:delete"权限的用户才能访问此方法。 + * + * @param ids 包含要删除的首页图片ID的数组。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行删除操作,无需返回具体数据,所以返回的是Void类型的成功响应。 */ @DeleteMapping @PreAuthorize("@pms.hasPermission('admin:indexImg:delete')") @@ -109,6 +148,13 @@ public class IndexImgController { return ServerResponseEntity.success(); } + /** + * 私有方法,用于校验首页图片关联的商品状态是否符合要求,若图片类型不是特定类型(这里假设类型0表示与商品关联的情况)则直接返回, + * 若关联商品ID为空则抛出异常提示请选择商品,若根据商品ID查询不到商品信息则抛出异常提示商品信息不存在, + * 若商品状态不是上架状态(这里假设状态1表示上架)则抛出异常提示该商品未上架,请选择别的商品。 + * + * @param indexImg 包含首页图片信息的对象,用于获取关联商品ID以及图片类型等属性进行校验。 + */ private void checkProdStatus(IndexImg indexImg) { if (!Objects.equals(indexImg.getType(), 0)) { return; @@ -124,4 +170,4 @@ public class IndexImgController { throw new YamiShopBindException("该商品未上架,请选择别的商品"); } } -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/MessageController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/MessageController.java index 782022f..22622af 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/MessageController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/MessageController.java @@ -13,7 +13,6 @@ package com.yami.shop.admin.controller; import java.util.Arrays; import java.util.List; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.springframework.beans.factory.annotation.Autowired; import com.yami.shop.common.response.ServerResponseEntity; @@ -27,7 +26,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; - import com.yami.shop.common.util.PageParam; import com.baomidou.mybatisplus.core.metadata.IPage; import com.yami.shop.bean.enums.MessageStatus; @@ -36,31 +34,44 @@ import com.yami.shop.service.MessageService; import cn.hutool.core.util.StrUtil; - /** + * MessageController类,作为Spring RESTful风格的控制器,用于处理后台管理系统中与消息(Message)相关的各种操作接口。 + * 包含消息的分页查询、获取单个消息详情、保存消息、修改消息、公开留言、取消公开留言以及批量删除消息等功能。 * @author lgh on 2018/10/15. */ @RestController @RequestMapping("/admin/message") public class MessageController { + // 通过Spring的依赖注入机制,自动注入MessageService的实例,以便调用其提供的业务方法来处理消息相关逻辑。 @Autowired private MessageService messageService; /** - * 分页获取 + * 分页获取消息的接口方法。 + * 根据传入的Message对象(可能包含用户名等查询条件)以及PageParam对象(用于分页参数设置), + * 使用MessageService的page方法结合LambdaQueryWrapper进行分页查询。 + * 如果消息的用户名不为空,则按照用户名进行模糊查询;如果消息状态不为空,则精确匹配消息状态进行查询。 + * 最后将查询到的分页消息结果封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * @param message 可能包含查询条件的消息对象,比如按用户名模糊查询、按消息状态精确查询等。 + * @param page 分页参数对象,包含页码、每页数量等信息。 + * @return 返回包含分页消息信息的ServerResponseEntity,成功时其数据部分为IPage类型。 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('admin:message:page')") - public ServerResponseEntity> page(Message message,PageParam page) { + public ServerResponseEntity> page(Message message, PageParam page) { IPage messages = messageService.page(page, new LambdaQueryWrapper() - .like(StrUtil.isNotBlank(message.getUserName()), Message::getUserName, message.getUserName()) - .eq(message.getStatus() != null, Message::getStatus, message.getStatus())); + .like(StrUtil.isNotBlank(message.getUserName()), Message::getUserName, message.getUserName()) + .eq(message.getStatus()!= null, Message::getStatus, message.getStatus())); return ServerResponseEntity.success(messages); } /** - * 获取信息 + * 获取指定ID消息详细信息的接口方法。 + * 通过路径变量获取消息的ID,调用MessageService的getById方法获取对应的消息对象, + * 然后将获取到的消息对象封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * @param id 要获取信息的消息的ID。 + * @return 返回包含指定消息详细信息的ServerResponseEntity,成功时其数据部分为Message类型。 */ @GetMapping("/info/{id}") @PreAuthorize("@pms.hasPermission('admin:message:info')") @@ -70,7 +81,11 @@ public class MessageController { } /** - * 保存 + * 保存消息的接口方法。 + * 接收通过请求体传入的Message对象(包含要保存的消息相关信息),调用MessageService的save方法将消息保存到数据库中, + * 最后返回表示保存成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * @param message 要保存的消息对象,通过请求体传入,包含消息的各项属性信息。 + * @return 返回表示保存成功的ServerResponseEntity,无数据内容(Void类型)。 */ @PostMapping @PreAuthorize("@pms.hasPermission('admin:message:save')") @@ -80,7 +95,11 @@ public class MessageController { } /** - * 修改 + * 修改消息的接口方法。 + * 接收通过请求体传入的Message对象(包含要修改的消息相关信息),调用MessageService的updateById方法根据消息的ID更新数据库中的对应消息记录, + * 最后返回表示修改成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * @param message 要修改的消息对象,通过请求体传入,包含消息的各项属性信息。 + * @return 返回表示修改成功的ServerResponseEntity,无数据内容(Void类型)。 */ @PutMapping @PreAuthorize("@pms.hasPermission('admin:message:update')") @@ -90,7 +109,11 @@ public class MessageController { } /** - * 公开留言 + * 公开留言的接口方法。 + * 根据路径变量获取要操作的消息的ID,创建一个新的Message对象,设置其ID为传入的消息ID,并将消息状态设置为公开状态(通过MessageStatus.RELEASE.value()获取对应值), + * 然后调用MessageService的updateById方法更新数据库中对应消息的状态为公开,最后返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * @param id 要设置为公开状态的消息的ID。 + * @return 返回表示操作成功的ServerResponseEntity,无数据内容(Void类型)。 */ @PutMapping("/release/{id}") @PreAuthorize("@pms.hasPermission('admin:message:release')") @@ -103,7 +126,11 @@ public class MessageController { } /** - * 取消公开留言 + * 取消公开留言的接口方法。 + * 根据路径变量获取要操作的消息的ID,创建一个新的Message对象,设置其ID为传入的消息ID,并将消息状态设置为取消公开状态(通过MessageStatus.CANCEL.value()获取对应值), + * 然后调用MessageService的updateById方法更新数据库中对应消息的状态为取消公开,最后返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * @param id 要设置为取消公开状态的消息的ID。 + * @return 返回表示操作成功的ServerResponseEntity,无数据内容(Void类型)。 */ @PutMapping("/cancel/{id}") @PreAuthorize("@pms.hasPermission('admin:message:cancel')") @@ -116,7 +143,11 @@ public class MessageController { } /** - * 删除 + * 删除消息的接口方法(支持批量删除)。 + * 通过路径变量获取要删除的消息的ID数组,将其转换为List集合后,调用MessageService的removeByIds方法, + * 根据传入的消息ID集合批量删除数据库中的对应消息记录,最后返回表示删除成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * @param ids 要删除的消息的ID数组,通过路径变量传入。 + * @return 返回表示删除成功的ServerResponseEntity,无数据内容(Void类型)。 */ @DeleteMapping("/{ids}") @PreAuthorize("@pms.hasPermission('admin:message:delete')") @@ -124,4 +155,4 @@ public class MessageController { messageService.removeByIds(Arrays.asList(ids)); return ServerResponseEntity.success(); } -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/NoticeController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/NoticeController.java index fdb76fd..40ad765 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/NoticeController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/NoticeController.java @@ -27,7 +27,7 @@ import java.util.Date; /** * 公告管理 - * + * 该类作为公告管理相关功能的控制器,提供了公告的分页查询、详情查询、新增、修改以及删除等操作的接口 * @author hzm * @date */ @@ -36,30 +36,31 @@ import java.util.Date; @RequestMapping("/shop/notice") public class NoticeController { + // 通过构造函数注入NoticeService,用于调用业务层方法来处理公告相关的业务逻辑 private final NoticeService noticeService; /** - * 分页查询 - * - * @param page 分页对象 - * @param notice 公告管理 - * @return 分页数据 + * 分页查询公告信息的方法 + * 根据传入的分页参数以及公告筛选条件,返回符合条件的公告分页数据 + * @param page 分页对象,包含分页相关的参数,如页码、每页数量等 + * @param notice 公告管理对象,用于传递筛选公告的条件,比如公告状态、是否置顶、标题等 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含符合条件的公告分页数据 */ @GetMapping("/page") public ServerResponseEntity> getNoticePage(PageParam page, Notice notice) { + // 使用MyBatis Plus的LambdaQueryWrapper构建查询条件,根据传入的公告对象中的非空字段进行筛选 + // 例如根据状态、是否置顶、标题等进行筛选,并按照更新时间降序排列 IPage noticePage = noticeService.page(page, new LambdaQueryWrapper() - .eq(notice.getStatus() != null, Notice::getStatus, notice.getStatus()) - .eq(notice.getIsTop()!=null,Notice::getIsTop,notice.getIsTop()) - .like(notice.getTitle() != null, Notice::getTitle, notice.getTitle()).orderByDesc(Notice::getUpdateTime)); + .eq(notice.getStatus()!= null, Notice::getStatus, notice.getStatus()) + .eq(notice.getIsTop()!= null, Notice::getIsTop, notice.getIsTop()) + .like(notice.getTitle()!= null, Notice::getTitle, notice.getTitle()).orderByDesc(Notice::getUpdateTime)); return ServerResponseEntity.success(noticePage); } - /** - * 通过id查询公告管理 - * - * @param id id - * @return 单个数据 + * 通过id查询单个公告信息的方法 + * @param id 要查询的公告的唯一标识(id) + * @return 返回一个ServerResponseEntity类型的响应,成功时包含对应id的公告详情数据 */ @GetMapping("/info/{id}") public ServerResponseEntity getById(@PathVariable("id") Long id) { @@ -67,57 +68,65 @@ public class NoticeController { } /** - * 新增公告管理 - * - * @param notice 公告管理 - * @return 是否新增成功 + * 新增公告的方法 + * 接收前端传入的公告信息,进行必要的属性设置后保存到数据库中,并记录操作日志 + * @param notice 公告管理对象,包含要新增的公告的详细信息 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示新增操作是否成功(true为成功,false为失败) */ @SysLog("新增公告管理") @PostMapping @PreAuthorize("@pms.hasPermission('shop:notice:save')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'shop:notice:save'权限的用户才能访问该接口 public ServerResponseEntity save(@RequestBody @Valid Notice notice) { + // 设置公告所属店铺的id,从当前登录用户信息中获取 notice.setShopId(SecurityUtils.getSysUser().getShopId()); if (notice.getStatus() == 1) { + // 如果公告状态为已发布(1表示已发布),则设置发布时间为当前时间 notice.setPublishTime(new Date()); } notice.setUpdateTime(new Date()); + // 调用业务层方法先移除相关的公告列表(具体逻辑由业务层的removeNoticeList方法决定) noticeService.removeNoticeList(); return ServerResponseEntity.success(noticeService.save(notice)); } /** - * 修改公告管理 - * - * @param notice 公告管理 - * @return 是否修改成功 + * 修改公告信息的方法 + * 根据传入的公告信息更新数据库中对应公告的数据,并记录操作日志,同时进行一些业务逻辑判断和相关数据处理 + * @param notice 公告管理对象,包含要修改的公告的详细信息 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示修改操作是否成功(true为成功,false为失败) */ @SysLog("修改公告管理") @PutMapping @PreAuthorize("@pms.hasPermission('shop:notice:update')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'shop:notice:update'权限的用户才能访问该接口 public ServerResponseEntity updateById(@RequestBody @Valid Notice notice) { Notice oldNotice = noticeService.getById(notice.getId()); if (oldNotice.getStatus() == 0 && notice.getStatus() == 1) { + // 如果原公告状态为未发布(0表示未发布),修改后变为已发布(1表示已发布),则设置发布时间为当前时间 notice.setPublishTime(new Date()); } notice.setUpdateTime(new Date()); + // 调用业务层方法先移除相关的公告列表(具体逻辑由业务层的removeNoticeList方法决定) noticeService.removeNoticeList(); + // 移除要修改的这条公告(具体逻辑由业务层的removeNoticeById方法决定,可能是缓存清除等相关操作) noticeService.removeNoticeById(notice.getId()); return ServerResponseEntity.success(noticeService.updateById(notice)); } /** - * 通过id删除公告管理 - * - * @param id id - * @return 是否删除成功 + * 通过id删除公告的方法 + * 根据传入的公告id删除数据库中对应的公告信息,并记录操作日志,同时进行相关数据处理 + * @param id 要删除的公告的唯一标识(id) + * @return 返回一个ServerResponseEntity类型的响应,成功时表示删除操作是否成功(true为成功,false为失败) */ @SysLog("删除公告管理") @DeleteMapping("/{id}") @PreAuthorize("@pms.hasPermission('shop:notice:delete')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'shop:notice:delete'权限的用户才能访问该接口 public ServerResponseEntity removeById(@PathVariable Long id) { noticeService.removeNoticeList(); noticeService.removeNoticeById(id); return ServerResponseEntity.success(noticeService.removeById(id)); } - -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java index b150acb..1af9260 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java @@ -44,6 +44,7 @@ import java.util.Date; import java.util.List; /** + * 订单相关操作的控制器类,用于处理后台对订单的各种请求,例如分页查询、获取订单详情、订单发货、导出订单相关的Excel表格等操作。 * @author lgh on 2018/09/15. */ @Slf4j @@ -51,104 +52,143 @@ import java.util.List; @RequestMapping("/order/order") public class OrderController { + // 自动注入订单服务层接口,通过该接口调用具体的业务逻辑方法,处理订单相关业务 @Autowired private OrderService orderService; - + // 自动注入订单项服务层接口,用于处理与订单中具体商品项相关的业务逻辑 @Autowired private OrderItemService orderItemService; - + // 自动注入用户地址订单服务层接口,用于获取订单相关的用户地址信息等业务逻辑 @Autowired private UserAddrOrderService userAddrOrderService; - + // 自动注入商品服务层接口,可能用于商品相关的业务操作,比如缓存清理等 @Autowired private ProductService productService; - + // 自动注入库存单元(SKU)服务层接口,同样可能涉及到缓存清理等与库存单元相关的业务操作 @Autowired private SkuService skuService; /** - * 分页获取 + * 分页获取订单信息的方法 + * 此方法根据传入的订单查询参数(OrderParam)以及分页参数(PageParam),查询并返回符合条件的订单信息分页结果。 + * 只有拥有 'order:order:page' 权限的用户才能访问此方法。 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('order:order:page')") - public ServerResponseEntity> page(OrderParam orderParam,PageParam page) { + public ServerResponseEntity> page(OrderParam orderParam, PageParam page) { + // 获取当前登录用户所属店铺的 ID,用于后续筛选该店铺下的订单数据 Long shopId = SecurityUtils.getSysUser().getShopId(); + // 将店铺 ID 设置到订单查询参数中,确保查询的是当前店铺的订单 orderParam.setShopId(shopId); + // 调用订单服务层的方法,按照订单参数和分页参数获取详细的订单分页信息 IPage orderPage = orderService.pageOrdersDetailByOrderParam(page, orderParam); return ServerResponseEntity.success(orderPage); - - } /** - * 获取信息 + * 根据订单编号获取订单详细信息的方法 + * 此方法接收一个订单编号,从数据库中获取对应的订单详细信息,包括订单项、用户地址等信息,并进行权限校验, + * 只有拥有 'order:order:info' 权限且属于对应店铺的用户才能获取该订单信息。 */ @GetMapping("/orderInfo/{orderNumber}") @PreAuthorize("@pms.hasPermission('order:order:info')") public ServerResponseEntity info(@PathVariable("orderNumber") String orderNumber) { + // 获取当前登录用户所属店铺的 ID,用于权限校验 Long shopId = SecurityUtils.getSysUser().getShopId(); + // 通过订单服务层的方法,根据订单编号获取订单基本信息 Order order = orderService.getOrderByOrderNumber(orderNumber); + // 校验当前用户所属店铺 ID 是否与订单所属店铺 ID 一致,不一致则抛出无权限异常 if (!Objects.equal(shopId, order.getShopId())) { throw new YamiShopBindException("您没有权限获取该订单信息"); } + // 通过订单项服务层的方法,根据订单编号获取该订单下的所有订单项信息 List orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber); + // 将订单项信息设置到订单对象中,完善订单的详细信息 order.setOrderItems(orderItems); + // 通过用户地址订单服务层的方法,根据订单关联的地址 ID 获取用户地址信息 UserAddrOrder userAddrOrder = userAddrOrderService.getById(order.getAddrOrderId()); + // 将用户地址信息设置到订单对象中 order.setUserAddrOrder(userAddrOrder); return ServerResponseEntity.success(order); } /** - * 发货 + * 处理订单发货的方法 + * 此方法接收发货相关的参数(DeliveryOrderParam),先进行权限校验,确保当前用户有权限修改对应订单信息, + * 然后更新订单的发货相关信息,并在发货后清除对应商品和库存单元(SKU)的缓存,只有拥有 'order:order:delivery' 权限的用户才能执行此操作。 */ @PutMapping("/delivery") @PreAuthorize("@pms.hasPermission('order:order:delivery')") public ServerResponseEntity delivery(@RequestBody DeliveryOrderParam deliveryOrderParam) { + // 获取当前登录用户所属店铺的 ID,用于权限校验 Long shopId = SecurityUtils.getSysUser().getShopId(); + // 通过订单服务层的方法,根据订单编号获取订单基本信息 Order order = orderService.getOrderByOrderNumber(deliveryOrderParam.getOrderNumber()); + // 校验当前用户所属店铺 ID 是否与订单所属店铺 ID 一致,不一致则抛出无权限异常 if (!Objects.equal(shopId, order.getShopId())) { throw new YamiShopBindException("您没有权限修改该订单信息"); } + // 创建一个新的 Order 对象,用于设置要更新的订单信息 Order orderParam = new Order(); + // 设置订单 ID,确保更新的是正确的订单记录 orderParam.setOrderId(order.getOrderId()); + // 设置快递 ID,来自发货参数 orderParam.setDvyId(deliveryOrderParam.getDvyId()); + // 设置快递单号,来自发货参数 orderParam.setDvyFlowId(deliveryOrderParam.getDvyFlowId()); + // 设置发货时间为当前时间 orderParam.setDvyTime(new Date()); + // 设置订单状态为已发货(通过 OrderStatus 枚举获取对应状态值) orderParam.setStatus(OrderStatus.CONSIGNMENT.value()); + // 设置用户 ID,与原订单一致 orderParam.setUserId(order.getUserId()); + // 调用订单服务层的方法,更新订单的发货相关信息 orderService.delivery(orderParam); + // 通过订单项服务层的方法,根据订单编号获取该订单下的所有订单项信息 List orderItems = orderItemService.getOrderItemsByOrderNumber(deliveryOrderParam.getOrderNumber()); + // 遍历订单项,清除对应商品和库存单元(SKU)的缓存 for (OrderItem orderItem : orderItems) { productService.removeProductCacheByProdId(orderItem.getProdId()); - skuService.removeSkuCacheBySkuId(orderItem.getSkuId(),orderItem.getProdId()); + skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); } return ServerResponseEntity.success(); } /** - * 打印待发货的订单表 + * 生成待发货订单的 Excel 表格并提供下载的方法 + * 此方法根据传入的订单筛选条件(包括时间范围、订单状态等)查询待发货的订单数据, + * 然后使用 Hutool 的 Excel 工具类将数据整理并写入 Excel 表格,最后将表格输出到响应流中供客户端下载, + * 只有拥有 'order:order:waitingConsignmentExcel' 权限的用户才能访问此方法。 * - * @param order - * @param consignmentName 发件人姓名 - * @param consignmentMobile 发货人手机号 - * @param consignmentAddr 发货地址 + * @param order 订单筛选条件对象,包含店铺 ID、订单状态等用于筛选订单的信息 + * @param startTime 订单创建时间范围的起始时间,格式需符合指定的 @DateTimeFormat 注解要求 + * @param endTime 订单创建时间范围的结束时间,格式需符合指定的 @DateTimeFormat 注解要求 + * @param consignmentName 发件人姓名,用于填充 Excel 表格中的发件人相关信息 + * @param consignmentMobile 发件人手机号,用于填充 Excel 表格中的发件人相关信息 + * @param consignmentAddr 发货地址,用于填充 Excel 表格中的发件人相关信息 + * @param response HttpServletResponse 对象,用于向客户端返回 Excel 文件流,实现下载功能 */ @GetMapping("/waitingConsignmentExcel") @PreAuthorize("@pms.hasPermission('order:order:waitingConsignmentExcel')") public void waitingConsignmentExcel(Order order, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime, String consignmentName, String consignmentMobile, String consignmentAddr, HttpServletResponse response) { + // 获取当前登录用户所属店铺的 ID,设置到订单筛选条件对象中,确保查询的是当前店铺的待发货订单 Long shopId = SecurityUtils.getSysUser().getShopId(); order.setShopId(shopId); + // 设置订单状态为已支付(待发货状态对应的前置状态,这里假设已支付的订单待发货),用于筛选待发货订单 order.setStatus(OrderStatus.PADYED.value()); + // 调用订单服务层的方法,根据订单筛选条件(包括时间范围、订单状态等)查询符合条件的待发货订单列表 List orders = orderService.listOrdersDetailByOrder(order, startTime, endTime); - //通过工具类创建writer + // 通过 Hutool 的 ExcelUtil 工具类创建一个 ExcelWriter 对象,用于后续写入 Excel 数据 ExcelWriter writer = ExcelUtil.getBigWriter(); + // 获取 Excel 表格的 Sheet 对象,用于设置列宽等表格样式相关操作 Sheet sheet = writer.getSheet(); + // 设置各列的宽度,单位是字符宽度的 1/256,这里根据实际需求设置了不同列的宽度 sheet.setColumnWidth(0, 20 * 256); sheet.setColumnWidth(1, 20 * 256); sheet.setColumnWidth(2, 20 * 256); @@ -157,19 +197,27 @@ public class OrderController { sheet.setColumnWidth(7, 60 * 256); sheet.setColumnWidth(8, 60 * 256); sheet.setColumnWidth(9, 60 * 256); - // 待发货 + + // 设置 Excel 表格的表头信息,定义了要展示的各列字段名称 String[] hearder = {"订单编号", "收件人", "手机", "收货地址", "商品名称", "数量", "发件人姓名", "发件人手机号", "发货地址", "备注"}; + // 合并表头单元格,用于展示标题等整体说明信息,这里合并了除第一列外的所有表头列 writer.merge(hearder.length - 1, "发货信息整理"); + // 将表头行数据写入 Excel 表格 writer.writeRow(Arrays.asList(hearder)); int row = 1; + // 遍历查询到的每个订单 for (Order dbOrder : orders) { + // 获取订单对应的用户地址信息 UserAddrOrder addr = dbOrder.getUserAddrOrder(); + // 拼接完整的收货地址信息 String addrInfo = addr.getProvince() + addr.getCity() + addr.getArea() + addr.getAddr(); + // 获取该订单下的所有订单项信息 List orderItems = dbOrder.getOrderItems(); row++; + // 遍历每个订单项,将订单和订单项相关信息逐行写入 Excel 表格 for (OrderItem orderItem : orderItems) { - // 第0列开始 + // 从第 0 列开始写入数据,列索引递增 int col = 0; writer.writeCellValue(col++, row, dbOrder.getOrderNumber()); writer.writeCellValue(col++, row, addr.getReceiver()); @@ -183,105 +231,51 @@ public class OrderController { writer.writeCellValue(col++, row, dbOrder.getRemarks()); } } + // 将整理好的 Excel 数据通过响应流输出给客户端,实现下载功能 writeExcel(response, writer); } /** - * 已销售订单 + * 生成已销售订单的 Excel 表格并提供下载的方法 + * 此方法根据传入的订单筛选条件(包括时间范围、是否已支付等)查询已销售的订单数据, + * 然后使用 Hutool 的 Excel 工具类将数据整理并写入 Excel 表格,最后将表格输出到响应流中供客户端下载, + * 只有拥有 'order:order:soldExcel' 权限的用户才能访问此方法。 * - * @param order + * @param order 订单筛选条件对象,包含店铺 ID、是否已支付等用于筛选订单的信息 + * @param startTime 订单创建时间范围的起始时间,格式需符合指定的 @DateTimeFormat 注解要求 + * @param endTime 订单创建时间范围的结束时间,格式需符合指定的 @DateTimeFormat 注解要求 + * @param response HttpServletResponse 对象,用于向客户端返回 Excel 文件流,实现下载功能 */ @GetMapping("/soldExcel") @PreAuthorize("@pms.hasPermission('order:order:soldExcel')") public void soldExcel(Order order, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime, HttpServletResponse response) { + // 获取当前登录用户所属店铺的 ID,设置到订单筛选条件对象中,确保查询的是当前店铺的已销售订单 Long shopId = SecurityUtils.getSysUser().getShopId(); order.setShopId(shopId); + // 设置订单为已支付状态,用于筛选已销售的订单(这里简单以是否已支付作为已销售的判断条件之一) order.setIsPayed(1); + // 调用订单服务层的方法,根据订单筛选条件(包括时间范围、是否已支付等)查询符合条件的已销售订单列表 List orders = orderService.listOrdersDetailByOrder(order, startTime, endTime); - //通过工具类创建writer + // 通过 Hutool 的 ExcelUtil 工具类创建一个 ExcelWriter 对象,用于后续写入 Excel 数据 ExcelWriter writer = ExcelUtil.getBigWriter(); - // 待发货 - String[] hearder = {"订单编号", "下单时间", "收件人", "手机", "收货地址", "商品名称", "数量", "订单应付", "订单运费", "订单实付"}; + // 获取 Excel 表格的 Sheet 对象,用于设置列宽等表格样式相关操作 Sheet sheet = writer.getSheet(); + // 设置各列的宽度,单位是字符宽度的 1/256,这里根据实际需求设置了不同列的宽度 sheet.setColumnWidth(0, 20 * 256); sheet.setColumnWidth(1, 20 * 256); sheet.setColumnWidth(3, 20 * 256); sheet.setColumnWidth(4, 60 * 256); sheet.setColumnWidth(5, 60 * 256); + // 设置 Excel 表格的表头信息,定义了要展示的各列字段名称 + String[] hearder = {"订单编号", "下单时间", "收件人", "手机", "收货地址", "商品名称", "数量", "订单应付", "订单运费", "订单实付"}; + // 合并表头单元格,用于展示标题等整体说明信息,这里合并了除第一列外的所有表头列 writer.merge(hearder.length - 1, "销售信息整理"); + // 将表头行数据写入 Excel 表格 writer.writeRow(Arrays.asList(hearder)); int row = 1; - for (Order dbOrder : orders) { - UserAddrOrder addr = dbOrder.getUserAddrOrder(); - String addrInfo = addr.getProvince() + addr.getCity() + addr.getArea() + addr.getAddr(); - List orderItems = dbOrder.getOrderItems(); - int firstRow = row + 1; - int lastRow = row + orderItems.size(); - int col = -1; - // 订单编号 - mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getOrderNumber()); - // 下单时间 - mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getCreateTime()); - // 收件人 - mergeIfNeed(writer, firstRow, lastRow, ++col, col, addr.getReceiver()); - // "手机" - mergeIfNeed(writer, firstRow, lastRow, ++col, col, addr.getMobile()); - // "收货地址" - mergeIfNeed(writer, firstRow, lastRow, ++col, col, addrInfo); - int prodNameCol = ++col; - int prodCountCol = ++col; - for (OrderItem orderItem : orderItems) { - row++; - // 商品名称 - writer.writeCellValue(prodNameCol, row, orderItem.getProdName()); - // 数量 - writer.writeCellValue(prodCountCol, row, orderItem.getProdCount()); - } - // 订单应付 - mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getTotal()); - // 订单运费 - mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getFreightAmount()); - // 订单实付 - mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getActualTotal()); - - } - writeExcel(response, writer); - } - - /** - * 如果需要合并的话,就合并 - */ - private void mergeIfNeed(ExcelWriter writer, int firstRow, int lastRow, int firstColumn, int lastColumn, Object content) { - if (content instanceof Date) { - content = DateUtil.format((Date) content, DatePattern.NORM_DATETIME_PATTERN); - } - if (lastRow - firstRow > 0 || lastColumn - firstColumn > 0) { - writer.merge(firstRow, lastRow, firstColumn, lastColumn, content, false); - } else { - writer.writeCellValue(firstColumn, firstRow, content); - } - - } - - private void writeExcel(HttpServletResponse response, ExcelWriter writer) { - //response为HttpServletResponse对象 - response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码 - response.setHeader("Content-Disposition", "attachment;filename=1.xls"); - - ServletOutputStream servletOutputStream = null; - try { - servletOutputStream = response.getOutputStream(); - writer.flush(servletOutputStream); - servletOutputStream.flush(); - } catch (IORuntimeException | IOException e) { - log.error("写出Excel错误:", e); - } finally { - IoUtil.close(writer); - } - } -} + // 遍历查询到的每个订单 + for (Order db \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java index b170280..c59dd6c 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java @@ -28,9 +28,10 @@ import jakarta.validation.Valid; import java.util.Arrays; import java.util.Objects; - - /** + * 提货地址(PickAddr)相关的控制器类,主要负责处理提货地址的增删改查操作, + * 各个方法分别实现了分页查询、获取单个地址信息、保存地址、更新地址以及删除地址等功能, + * 并且在相应操作上添加了权限控制,确保只有具备对应权限的用户才能执行相应操作。 * * @author lgh on 2018/10/17. */ @@ -38,64 +39,97 @@ import java.util.Objects; @RequestMapping("/shop/pickAddr") public class PickAddrController { + // 注入PickAddrService,用于与提货地址相关的业务逻辑处理,例如查询、保存、更新、删除提货地址等操作。 @Autowired private PickAddrService pickAddrService; - /** - * 分页获取 - */ + /** + * 分页获取提货地址信息的方法,根据传入的查询条件(PickAddr对象)和分页参数(PageParam对象), + * 通过PickAddrService进行分页查询,查询时可根据提货地址名称进行模糊匹配(如果名称不为空),并按照地址ID倒序排序, + * 最后将查询到的分页结果封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:page"权限的用户才能访问此方法。 + * + * @param pickAddr 包含查询条件的PickAddr对象,可设置提货地址名称等筛选条件。 + * @param page 分页参数对象,用于指定页码、每页数量等分页相关信息。 + * @return 返回包含分页后的提货地址信息的ServerResponseEntity对象,若查询成功则响应体中包含符合条件的分页地址数据,否则返回相应错误信息。 + */ @GetMapping("/page") - @PreAuthorize("@pms.hasPermission('shop:pickAddr:page')") - public ServerResponseEntity> page(PickAddr pickAddr,PageParam page){ - IPage pickAddrs = pickAddrService.page(page,new LambdaQueryWrapper() - .like(StrUtil.isNotBlank(pickAddr.getAddrName()),PickAddr::getAddrName,pickAddr.getAddrName()) - .orderByDesc(PickAddr::getAddrId)); - return ServerResponseEntity.success(pickAddrs); - } + @PreAuthorize("@pms.hasPermission('shop:pickAddr:page')") + public ServerResponseEntity> page(PickAddr pickAddr, PageParam page) { + // 使用PickAddrService的page方法进行分页查询,传入分页参数和LambdaQueryWrapper构建的查询条件。 + // 通过StrUtil.isNotBlank判断提货地址名称是否不为空,若不为空则添加模糊查询条件,按照地址名称进行模糊匹配,最后按照地址ID倒序排序。 + IPage pickAddrs = pickAddrService.page(page, new LambdaQueryWrapper() + .like(StrUtil.isNotBlank(pickAddr.getAddrName()), PickAddr::getAddrName, pickAddr.getAddrName()) + .orderByDesc(PickAddr::getAddrId)); + return ServerResponseEntity.success(pickAddrs); + } /** - * 获取信息 - */ - @GetMapping("/info/{id}") - @PreAuthorize("@pms.hasPermission('shop:pickAddr:info')") - public ServerResponseEntity info(@PathVariable("id") Long id){ - PickAddr pickAddr = pickAddrService.getById(id); - return ServerResponseEntity.success(pickAddr); - } + * 根据提货地址ID获取提货地址详细信息的方法,通过传入的提货地址ID,调用PickAddrService的getById方法从数据库中查询对应的提货地址对象, + * 并将其封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:info"权限的用户才能访问此方法。 + * + * @param id 要获取详细信息的提货地址的唯一标识符。 + * @return 返回包含提货地址详细信息的ServerResponseEntity对象,若获取成功则响应体中包含对应的提货地址对象,否则返回相应错误信息。 + */ + @GetMapping("/info/{id}") + @PreAuthorize("@pms.hasPermission('shop:pickAddr:info')") + public ServerResponseEntity info(@PathVariable("id") Long id) { + PickAddr pickAddr = pickAddrService.getById(id); + return ServerResponseEntity.success(pickAddr); + } - /** - * 保存 - */ - @PostMapping - @PreAuthorize("@pms.hasPermission('shop:pickAddr:save')") - public ServerResponseEntity save(@Valid @RequestBody PickAddr pickAddr){ - pickAddr.setShopId(SecurityUtils.getSysUser().getShopId()); - pickAddrService.save(pickAddr); - return ServerResponseEntity.success(); - } + /** + * 保存提货地址信息的方法,接收一个经过验证(通过@Valid注解)的PickAddr对象作为请求体,代表要保存的提货地址信息。 + * 首先设置提货地址所属的店铺ID(通过当前登录用户所属店铺ID确定),然后调用PickAddrService的save方法将提货地址信息保存到数据库中, + * 最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:save"权限的用户才能访问此方法。 + * + * @param pickAddr 包含要保存的提货地址信息的请求体对象,如地址名称、详细地址等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行保存操作,无需返回具体数据,所以返回的是Void类型的成功响应。 + */ + @PostMapping + @PreAuthorize("@pms.hasPermission('shop:pickAddr:save')") + public ServerResponseEntity save(@Valid @RequestBody PickAddr pickAddr) { + pickAddr.setShopId(SecurityUtils.getSysUser().getShopId()); + pickAddrService.save(pickAddr); + return ServerResponseEntity.success(); + } - /** - * 修改 - */ - @PutMapping - @PreAuthorize("@pms.hasPermission('shop:pickAddr:update')") - public ServerResponseEntity update(@Valid @RequestBody PickAddr pickAddr){ - PickAddr dbPickAddr = pickAddrService.getById(pickAddr.getAddrId()); + /** + * 修改提货地址信息的方法,接收一个经过验证(通过@Valid注解)的PickAddr对象作为请求体,代表要更新的提货地址信息。 + * 首先通过PickAddrService的getById方法根据传入的提货地址ID从数据库中获取对应的提货地址对象,用于后续权限判断, + * 判断当前登录用户所属的店铺ID与要修改的提货地址所属的店铺ID是否一致,若不一致则抛出无权限异常, + * 若有权限则调用PickAddrService的updateById方法将更新后的提货地址信息保存到数据库中,最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:update"权限的用户才能访问此方法。 + * + * @param pickAddr 包含要更新的提货地址信息的请求体对象,如地址名称、详细地址等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行更新操作,无需返回具体数据,所以返回的是Void类型的成功响应。 + */ + @PutMapping + @PreAuthorize("@pms.hasPermission('shop:pickAddr:update')") + public ServerResponseEntity update(@Valid @RequestBody PickAddr pickAddr) { + PickAddr dbPickAddr = pickAddrService.getById(pickAddr.getAddrId()); - if (!Objects.equals(dbPickAddr.getShopId(),SecurityUtils.getSysUser().getShopId())) { - throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED); - } - pickAddrService.updateById(pickAddr); - return ServerResponseEntity.success(); - } + if (!Objects.equals(dbPickAddr.getShopId(), SecurityUtils.getSysUser().getShopId())) { + throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED); + } + pickAddrService.updateById(pickAddr); + return ServerResponseEntity.success(); + } - /** - * 删除 - */ - @DeleteMapping - @PreAuthorize("@pms.hasPermission('shop:pickAddr:delete')") - public ServerResponseEntity delete(@RequestBody Long[] ids){ - pickAddrService.removeByIds(Arrays.asList(ids)); - return ServerResponseEntity.success(); - } -} + /** + * 删除提货地址信息的方法,接收一个包含要删除的提货地址ID数组的请求体,通过PickAddrService的removeByIds方法, + * 将传入的ID数组转换为列表后批量删除对应的提货地址信息,最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:delete"权限的用户才能访问此方法。 + * + * @param ids 包含要删除的提货地址ID的数组。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行删除操作,无需返回具体数据,所以返回的是Void类型的成功响应。 + */ + @DeleteMapping + @PreAuthorize("@pms.hasPermission('shop:pickAddr:delete')") + public ServerResponseEntity delete(@RequestBody Long[] ids) { + pickAddrService.removeByIds(Arrays.asList(ids)); + return ServerResponseEntity.success(); + } +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdCommController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdCommController.java index 9f0846d..c59dd6c 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdCommController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdCommController.java @@ -10,100 +10,126 @@ package com.yami.shop.admin.controller; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.yami.shop.bean.model.ProdComm; -import com.yami.shop.common.annotation.SysLog; -import com.yami.shop.common.util.Json; -import com.yami.shop.service.ProdCommService; -import lombok.AllArgsConstructor; - -import jakarta.validation.Valid; - -import lombok.SneakyThrows; -import org.apache.commons.lang3.StringUtils; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yami.shop.bean.model.PickAddr; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.response.ResponseEnum; import com.yami.shop.common.response.ServerResponseEntity; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.service.PickAddrService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.yami.shop.common.util.PageParam; -import com.baomidou.mybatisplus.core.metadata.IPage; - -import java.util.Date; +import jakarta.validation.Valid; +import java.util.Arrays; +import java.util.Objects; /** - * 商品评论 + * 提货地址(PickAddr)相关的控制器类,主要负责处理提货地址的增删改查操作, + * 各个方法分别实现了分页查询、获取单个地址信息、保存地址、更新地址以及删除地址等功能, + * 并且在相应操作上添加了权限控制,确保只有具备对应权限的用户才能执行相应操作。 * - * @author xwc - * @date 2019-04-19 10:43:57 + * @author lgh on 2018/10/17. */ @RestController -@AllArgsConstructor -@RequestMapping("/prod/prodComm" ) -public class ProdCommController { +@RequestMapping("/shop/pickAddr") +public class PickAddrController { - private final ProdCommService prodCommService; + // 注入PickAddrService,用于与提货地址相关的业务逻辑处理,例如查询、保存、更新、删除提货地址等操作。 + @Autowired + private PickAddrService pickAddrService; /** - * 分页查询 - * @param page 分页对象 - * @param prodComm 商品评论 - * @return 分页数据 + * 分页获取提货地址信息的方法,根据传入的查询条件(PickAddr对象)和分页参数(PageParam对象), + * 通过PickAddrService进行分页查询,查询时可根据提货地址名称进行模糊匹配(如果名称不为空),并按照地址ID倒序排序, + * 最后将查询到的分页结果封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:page"权限的用户才能访问此方法。 + * + * @param pickAddr 包含查询条件的PickAddr对象,可设置提货地址名称等筛选条件。 + * @param page 分页参数对象,用于指定页码、每页数量等分页相关信息。 + * @return 返回包含分页后的提货地址信息的ServerResponseEntity对象,若查询成功则响应体中包含符合条件的分页地址数据,否则返回相应错误信息。 */ - @GetMapping("/page" ) - @PreAuthorize("@pms.hasPermission('prod:prodComm:page')" ) - public ServerResponseEntity> getProdCommPage(PageParam page, ProdComm prodComm) { - return ServerResponseEntity.success(prodCommService.getProdCommPage(page,prodComm)); + @GetMapping("/page") + @PreAuthorize("@pms.hasPermission('shop:pickAddr:page')") + public ServerResponseEntity> page(PickAddr pickAddr, PageParam page) { + // 使用PickAddrService的page方法进行分页查询,传入分页参数和LambdaQueryWrapper构建的查询条件。 + // 通过StrUtil.isNotBlank判断提货地址名称是否不为空,若不为空则添加模糊查询条件,按照地址名称进行模糊匹配,最后按照地址ID倒序排序。 + IPage pickAddrs = pickAddrService.page(page, new LambdaQueryWrapper() + .like(StrUtil.isNotBlank(pickAddr.getAddrName()), PickAddr::getAddrName, pickAddr.getAddrName()) + .orderByDesc(PickAddr::getAddrId)); + return ServerResponseEntity.success(pickAddrs); } - /** - * 通过id查询商品评论 - * @param prodCommId id - * @return 单个数据 + * 根据提货地址ID获取提货地址详细信息的方法,通过传入的提货地址ID,调用PickAddrService的getById方法从数据库中查询对应的提货地址对象, + * 并将其封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:info"权限的用户才能访问此方法。 + * + * @param id 要获取详细信息的提货地址的唯一标识符。 + * @return 返回包含提货地址详细信息的ServerResponseEntity对象,若获取成功则响应体中包含对应的提货地址对象,否则返回相应错误信息。 */ - @GetMapping("/info/{prodCommId}" ) - @PreAuthorize("@pms.hasPermission('prod:prodComm:info')" ) - public ServerResponseEntity getById(@PathVariable("prodCommId" ) Long prodCommId) { - return ServerResponseEntity.success(prodCommService.getById(prodCommId)); + @GetMapping("/info/{id}") + @PreAuthorize("@pms.hasPermission('shop:pickAddr:info')") + public ServerResponseEntity info(@PathVariable("id") Long id) { + PickAddr pickAddr = pickAddrService.getById(id); + return ServerResponseEntity.success(pickAddr); } /** - * 新增商品评论 - * @param prodComm 商品评论 - * @return 是否新增成功 + * 保存提货地址信息的方法,接收一个经过验证(通过@Valid注解)的PickAddr对象作为请求体,代表要保存的提货地址信息。 + * 首先设置提货地址所属的店铺ID(通过当前登录用户所属店铺ID确定),然后调用PickAddrService的save方法将提货地址信息保存到数据库中, + * 最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:save"权限的用户才能访问此方法。 + * + * @param pickAddr 包含要保存的提货地址信息的请求体对象,如地址名称、详细地址等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行保存操作,无需返回具体数据,所以返回的是Void类型的成功响应。 */ - @SysLog("新增商品评论" ) @PostMapping - @PreAuthorize("@pms.hasPermission('prod:prodComm:save')" ) - public ServerResponseEntity save(@RequestBody @Valid ProdComm prodComm) { - return ServerResponseEntity.success(prodCommService.save(prodComm)); + @PreAuthorize("@pms.hasPermission('shop:pickAddr:save')") + public ServerResponseEntity save(@Valid @RequestBody PickAddr pickAddr) { + pickAddr.setShopId(SecurityUtils.getSysUser().getShopId()); + pickAddrService.save(pickAddr); + return ServerResponseEntity.success(); } /** - * 修改商品评论 - * @param prodComm 商品评论 - * @return 是否修改成功 + * 修改提货地址信息的方法,接收一个经过验证(通过@Valid注解)的PickAddr对象作为请求体,代表要更新的提货地址信息。 + * 首先通过PickAddrService的getById方法根据传入的提货地址ID从数据库中获取对应的提货地址对象,用于后续权限判断, + * 判断当前登录用户所属的店铺ID与要修改的提货地址所属的店铺ID是否一致,若不一致则抛出无权限异常, + * 若有权限则调用PickAddrService的updateById方法将更新后的提货地址信息保存到数据库中,最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:update"权限的用户才能访问此方法。 + * + * @param pickAddr 包含要更新的提货地址信息的请求体对象,如地址名称、详细地址等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行更新操作,无需返回具体数据,所以返回的是Void类型的成功响应。 */ - @SysLog("修改商品评论" ) @PutMapping - @PreAuthorize("@pms.hasPermission('prod:prodComm:update')" ) - public ServerResponseEntity updateById(@RequestBody @Valid ProdComm prodComm) { - prodComm.setReplyTime(new Date()); - return ServerResponseEntity.success(prodCommService.updateById(prodComm)); + @PreAuthorize("@pms.hasPermission('shop:pickAddr:update')") + public ServerResponseEntity update(@Valid @RequestBody PickAddr pickAddr) { + PickAddr dbPickAddr = pickAddrService.getById(pickAddr.getAddrId()); + + if (!Objects.equals(dbPickAddr.getShopId(), SecurityUtils.getSysUser().getShopId())) { + throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED); + } + pickAddrService.updateById(pickAddr); + return ServerResponseEntity.success(); } /** - * 通过id删除商品评论 - * @param prodCommId id - * @return 是否删除成功 + * 删除提货地址信息的方法,接收一个包含要删除的提货地址ID数组的请求体,通过PickAddrService的removeByIds方法, + * 将传入的ID数组转换为列表后批量删除对应的提货地址信息,最后返回成功的响应结果给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"shop:pickAddr:delete"权限的用户才能访问此方法。 + * + * @param ids 包含要删除的提货地址ID的数组。 + * @return 返回表示操作成功的ServerResponseEntity对象,由于这里只是执行删除操作,无需返回具体数据,所以返回的是Void类型的成功响应。 */ - @SysLog("删除商品评论" ) - @DeleteMapping("/{prodCommId}" ) - @PreAuthorize("@pms.hasPermission('prod:prodComm:delete')" ) - public ServerResponseEntity removeById(@PathVariable Long prodCommId) { - return ServerResponseEntity.success(prodCommService.removeById(prodCommId)); + @DeleteMapping + @PreAuthorize("@pms.hasPermission('shop:pickAddr:delete')") + public ServerResponseEntity delete(@RequestBody Long[] ids) { + pickAddrService.removeByIds(Arrays.asList(ids)); + return ServerResponseEntity.success(); } - -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagController.java index 8e921fb..b289ed0 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagController.java @@ -10,7 +10,6 @@ package com.yami.shop.admin.controller; - import cn.hutool.core.collection.CollectionUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -31,7 +30,7 @@ import java.util.List; /** * 商品分组 - * + * 该类作为商品分组相关功能的控制器,提供了商品分组标签的分页查询、详情查询、新增、修改、删除以及获取标签列表等操作的接口 * @author hzm * @date 2019-04-18 09:08:36 */ @@ -39,33 +38,34 @@ import java.util.List; @RequestMapping("/prod/prodTag") public class ProdTagController { + // 通过Spring的自动注入机制,注入ProdTagService对象,用于调用业务层方法来处理商品分组相关的业务逻辑 @Autowired private ProdTagService prodTagService; /** - * 分页查询 - * - * @param page 分页对象 - * @param prodTag 商品分组标签 - * @return 分页数据 + * 分页查询商品分组标签信息的方法 + * 根据传入的分页参数以及商品分组标签筛选条件,返回符合条件的商品分组标签分页数据 + * @param page 分页对象,包含分页相关的参数,如页码、每页数量等 + * @param prodTag 商品分组标签对象,用于传递筛选商品分组标签的条件,比如状态、标题等 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含符合条件的商品分组标签分页数据 */ @GetMapping("/page") public ServerResponseEntity> getProdTagPage(PageParam page, ProdTag prodTag) { + // 使用MyBatis Plus的LambdaQueryWrapper构建查询条件,根据传入的商品分组标签对象中的非空字段进行筛选 + // 例如根据状态、标题等进行筛选,并按照序号(seq)和创建时间降序排列 IPage tagPage = prodTagService.page( page, new LambdaQueryWrapper() - .eq(prodTag.getStatus() != null, ProdTag::getStatus, prodTag.getStatus()) - .like(prodTag.getTitle() != null, ProdTag::getTitle, prodTag.getTitle()) - .orderByDesc(ProdTag::getSeq, ProdTag::getCreateTime)); + .eq(prodTag.getStatus()!= null, ProdTag::getStatus, prodTag.getStatus()) + .like(prodTag.getTitle()!= null, ProdTag::getTitle, prodTag.getTitle()) + .orderByDesc(ProdTag::getSeq, ProdTag::getCreateTime)); return ServerResponseEntity.success(tagPage); } - /** - * 通过id查询商品分组标签 - * - * @param id id - * @return 单个数据 + * 通过id查询单个商品分组标签信息的方法 + * @param id 要查询的商品分组标签的唯一标识(id) + * @return 返回一个ServerResponseEntity类型的响应,成功时包含对应id的商品分组标签详情数据 */ @GetMapping("/info/{id}") public ServerResponseEntity getById(@PathVariable("id") Long id) { @@ -73,67 +73,85 @@ public class ProdTagController { } /** - * 新增商品分组标签 - * - * @param prodTag 商品分组标签 - * @return 是否新增成功 + * 新增商品分组标签的方法 + * 接收前端传入的商品分组标签信息,进行必要的属性设置、重复性校验后保存到数据库中,并记录操作日志 + * @param prodTag 商品分组标签对象,包含要新增的商品分组标签的详细信息 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示新增操作是否成功(true为成功,false为失败) */ @SysLog("新增商品分组标签") @PostMapping @PreAuthorize("@pms.hasPermission('prod:prodTag:save')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'prod:prodTag:save'权限的用户才能访问该接口 public ServerResponseEntity save(@RequestBody @Valid ProdTag prodTag) { - // 查看是否相同的标签 + // 查看是否已存在相同名称的标签,通过标题进行模糊查询 List list = prodTagService.list(new LambdaQueryWrapper().like(ProdTag::getTitle, prodTag.getTitle())); if (CollectionUtil.isNotEmpty(list)) { + // 如果查询到相同名称的标签列表不为空,则抛出异常,提示标签名称已存在,不能添加相同的标签 throw new YamiShopBindException("标签名称已存在,不能添加相同的标签"); } + // 设置默认属性,将是否为默认标签设置为0(表示非默认) prodTag.setIsDefault(0); + // 设置商品数量初始值为0 prodTag.setProdCount(0L); + // 设置创建时间为当前时间 prodTag.setCreateTime(new Date()); + // 设置更新时间为当前时间 prodTag.setUpdateTime(new Date()); + // 设置商品分组标签所属店铺的id,从当前登录用户信息中获取 prodTag.setShopId(SecurityUtils.getSysUser().getShopId()); + // 调用业务层方法先进行相关的商品分组标签清理操作(具体逻辑由业务层的removeProdTag方法决定) prodTagService.removeProdTag(); return ServerResponseEntity.success(prodTagService.save(prodTag)); } /** - * 修改商品分组标签 - * - * @param prodTag 商品分组标签 - * @return 是否修改成功 + * 修改商品分组标签信息的方法 + * 根据传入的商品分组标签信息更新数据库中对应商品分组标签的数据,并记录操作日志,同时进行相关数据处理 + * @param prodTag 商品分组标签对象,包含要修改的商品分组标签的详细信息 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示修改操作是否成功(true为成功,false为失败) */ @SysLog("修改商品分组标签") @PutMapping @PreAuthorize("@pms.hasPermission('prod:prodTag:update')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'prod:prodTag:update'权限的用户才能访问该接口 public ServerResponseEntity updateById(@RequestBody @Valid ProdTag prodTag) { + // 更新商品分组标签的更新时间为当前时间 prodTag.setUpdateTime(new Date()); + // 调用业务层方法先进行相关的商品分组标签清理操作(具体逻辑由业务层的removeProdTag方法决定) prodTagService.removeProdTag(); return ServerResponseEntity.success(prodTagService.updateById(prodTag)); } /** - * 通过id删除商品分组标签 - * - * @param id id - * @return 是否删除成功 + * 通过id删除商品分组标签的方法 + * 根据传入的商品分组标签id删除数据库中对应的商品分组标签信息,并记录操作日志,同时进行相关业务逻辑判断和数据处理 + * @param id 要删除的商品分组标签的唯一标识(id) + * @return 返回一个ServerResponseEntity类型的响应,成功时表示删除操作是否成功(true为成功,false为失败) */ @SysLog("删除商品分组标签") @DeleteMapping("/{id}") @PreAuthorize("@pms.hasPermission('prod:prodTag:delete')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'prod:prodTag:delete'权限的用户才能访问该接口 public ServerResponseEntity removeById(@PathVariable Long id) { + // 根据传入的id获取对应的商品分组标签对象 ProdTag prodTag = prodTagService.getById(id); - if (prodTag.getIsDefault() != 0) { + if (prodTag.getIsDefault()!= 0) { + // 如果该商品分组标签是默认标签(isDefault不为0),则抛出异常,提示默认标签不能删除 throw new YamiShopBindException("默认标签不能删除"); } + // 调用业务层方法先进行相关的商品分组标签清理操作(具体逻辑由业务层的removeProdTag方法决定) prodTagService.removeProdTag(); return ServerResponseEntity.success(prodTagService.removeById(id)); } + /** + * 获取商品分组标签列表的方法 + * 直接调用业务层方法获取商品分组标签列表,并返回给前端 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含商品分组标签列表数据 + */ @GetMapping("/listTagList") public ServerResponseEntity> listTagList() { return ServerResponseEntity.success(prodTagService.listProdTag()); } - - -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagReferenceController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagReferenceController.java index dde86eb..68e1fb8 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagReferenceController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagReferenceController.java @@ -10,7 +10,6 @@ package com.yami.shop.admin.controller; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.yami.shop.common.util.PageParam; @@ -25,74 +24,95 @@ import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; /** - * 分组标签引用 + * 分组标签引用相关操作的控制器类,用于处理后台对分组标签引用的各种请求, + * 例如分页查询、根据 ID 查询详情、新增、修改以及删除等操作。 * * @author hzm * @date 2019-04-18 16:28:01 */ @RestController @AllArgsConstructor -@RequestMapping("/generator/prodTagReference" ) +@RequestMapping("/generator/prodTagReference") public class ProdTagReferenceController { + // 通过构造函数注入的方式,引入分组标签引用服务层接口,用于调用具体的业务逻辑方法 private final ProdTagReferenceService prodTagReferenceService; /** - * 分页查询 - * @param page 分页对象 - * @param prodTagReference 分组标签引用 - * @return 分页数据 + * 分页查询分组标签引用信息的方法 + * 此方法接收分页参数(PageParam)以及分组标签引用的查询条件对象(ProdTagReference), + * 通过调用服务层的方法,利用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件(当前代码中未添加具体条件,可按需完善), + * 查询并返回符合条件的分组标签引用信息分页结果。 + * + * @param page 分页对象,包含分页相关的参数,如页码、每页数量等 + * @param prodTagReference 分组标签引用对象,可用于设置查询条件,例如根据某些属性筛选数据 + * @return 包含分组标签引用信息的分页数据,封装在 ServerResponseEntity 中,方便统一的响应格式处理 */ - @GetMapping("/page" ) + @GetMapping("/page") public ServerResponseEntity> getProdTagReferencePage(PageParam page, ProdTagReference prodTagReference) { return ServerResponseEntity.success(prodTagReferenceService.page(page, new LambdaQueryWrapper())); } - /** - * 通过id查询分组标签引用 - * @param referenceId id - * @return 单个数据 + * 根据给定的 ID 查询单个分组标签引用详细信息的方法 + * 此方法接收一个分组标签引用的 ID,通过调用服务层的方法从数据库中获取对应的详细信息, + * 并将结果封装在 ServerResponseEntity 中返回给前端,方便统一的响应格式处理。 + * + * @param referenceId 要查询的分组标签引用记录的 ID + * @return 包含单个分组标签引用详细信息的 ServerResponseEntity,若查询到则返回对应的数据,否则返回相应的空数据表示 */ - @GetMapping("/info/{referenceId}" ) - public ServerResponseEntity getById(@PathVariable("referenceId" ) Long referenceId) { + @GetMapping("/info/{referenceId}") + public ServerResponseEntity getById(@PathVariable("referenceId") Long referenceId) { return ServerResponseEntity.success(prodTagReferenceService.getById(referenceId)); } /** - * 新增分组标签引用 - * @param prodTagReference 分组标签引用 - * @return 是否新增成功 + * 新增分组标签引用的方法 + * 此方法接收一个包含分组标签引用信息的 ProdTagReference 对象, + * 通过调用服务层的保存方法将该数据保存到数据库中,同时添加了操作日志记录(通过 @SysLog 注解), + * 并且只有拥有 'generator:prodTagReference:save' 权限的用户才能执行此操作, + * 最后将保存结果(成功或失败)封装在 ServerResponseEntity 中返回给前端。 + * + * @param prodTagReference 包含要新增的分组标签引用信息的对象,其属性应符合数据库对应表的字段要求 + * @return 包含保存操作结果(布尔值表示是否成功)的 ServerResponseEntity,用于告知前端新增操作是否成功 */ - @SysLog("新增分组标签引用" ) + @SysLog("新增分组标签引用") @PostMapping - @PreAuthorize("@pms.hasPermission('generator:prodTagReference:save')" ) + @PreAuthorize("@pms.hasPermission('generator:prodTagReference:save')") public ServerResponseEntity save(@RequestBody @Valid ProdTagReference prodTagReference) { return ServerResponseEntity.success(prodTagReferenceService.save(prodTagReference)); } /** - * 修改分组标签引用 - * @param prodTagReference 分组标签引用 - * @return 是否修改成功 + * 修改分组标签引用信息的方法 + * 此方法接收一个包含更新后分组标签引用信息的 ProdTagReference 对象, + * 通过调用服务层的更新方法将数据库中的对应记录进行更新,同时添加了操作日志记录(通过 @SysLog 注解), + * 并且只有拥有 'generator:prodTagReference:update' 权限的用户才能执行此操作, + * 最后将更新结果(成功或失败)封装在 ServerResponseEntity 中返回给前端。 + * + * @param prodTagReference 包含要修改的分组标签引用信息的对象,其属性应符合数据库对应表的字段要求 + * @return 包含更新操作结果(布尔值表示是否成功)的 ServerResponseEntity,用于告知前端修改操作是否成功 */ - @SysLog("修改分组标签引用" ) + @SysLog("修改分组标签引用") @PutMapping - @PreAuthorize("@pms.hasPermission('generator:prodTagReference:update')" ) + @PreAuthorize("@pms.hasPermission('generator:prodTagReference:update')") public ServerResponseEntity updateById(@RequestBody @Valid ProdTagReference prodTagReference) { return ServerResponseEntity.success(prodTagReferenceService.updateById(prodTagReference)); } /** - * 通过id删除分组标签引用 - * @param referenceId id - * @return 是否删除成功 + * 根据给定的 ID 删除分组标签引用记录的方法 + * 此方法接收一个分组标签引用的 ID,通过调用服务层的删除方法从数据库中删除对应的记录, + * 同时添加了操作日志记录(通过 @SysLog 注解),并且只有拥有 'generator:prodTagReference:delete' 权限的用户才能执行此操作, + * 最后将删除结果(成功或失败)封装在 ServerResponseEntity 中返回给前端。 + * + * @param referenceId 要删除的分组标签引用记录的 ID + * @return 包含删除操作结果(布尔值表示是否成功)的 ServerResponseEntity,用于告知前端删除操作是否成功 */ - @SysLog("删除分组标签引用" ) - @DeleteMapping("/{referenceId}" ) - @PreAuthorize("@pms.hasPermission('generator:prodTagReference:delete')" ) + @SysLog("删除分组标签引用") + @DeleteMapping("/{referenceId}") + @PreAuthorize("@pms.hasPermission('generator:prodTagReference:delete')") public ServerResponseEntity removeById(@PathVariable Long referenceId) { return ServerResponseEntity.success(prodTagReferenceService.removeById(referenceId)); } - -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProductController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProductController.java index 8aeafd2..66bb20b 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProductController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProductController.java @@ -36,9 +36,9 @@ import java.util.Date; import java.util.List; import java.util.Objects; - /** - * 商品列表、商品发布controller + * 商品列表、商品发布相关的控制器类,主要负责处理商品的各种操作,包括分页查询、获取商品详细信息、保存、修改、删除商品以及更新商品状态等功能, + * 同时在各个操作方法上添加了相应的权限控制,并且在部分操作中涉及到对相关附属数据(如商品规格、标签、购物车缓存等)的处理逻辑。 * * @author lgh */ @@ -46,36 +46,54 @@ import java.util.Objects; @RequestMapping("/prod/prod") public class ProductController { + // 注入ProductService,用于处理与商品相关的核心业务逻辑,例如商品信息的查询、保存、更新、删除等操作。 @Autowired private ProductService productService; - + // 注入SkuService,用于处理商品规格(Sku)相关的业务逻辑,比如根据商品ID获取商品规格列表等操作,在商品相关操作中常涉及规格信息的处理。 @Autowired private SkuService skuService; + // 注入ProdTagReferenceService,用于获取商品与标签关联相关的业务逻辑,例如获取某个商品关联的标签ID列表等操作,用于处理商品的分组标签信息。 @Autowired private ProdTagReferenceService prodTagReferenceService; + // 注入BasketService,用于处理购物车相关的业务逻辑,在商品操作涉及影响购物车缓存的场景下(如商品修改、删除等)会调用其方法来清除相关缓存,保证数据一致性。 @Autowired private BasketService basketService; /** - * 分页获取商品信息 + * 分页获取商品信息的方法,根据传入的查询条件(ProductParam对象)和分页参数(PageParam对象), + * 通过ProductService进行分页查询,可根据商品名称进行模糊匹配(如果名称不为空),筛选出当前店铺(根据当前登录用户所属店铺ID确定)下的商品, + * 还可根据商品状态进行筛选,并按照上架时间倒序排序,最后将查询到的分页结果封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"prod:prod:page"权限的用户才能访问此方法。 + * + * @param product 包含查询条件的ProductParam对象,可设置商品名称、状态等筛选条件。 + * @param page 分页参数对象,用于指定页码、每页数量等分页相关信息。 + * @return 返回包含分页后的商品信息的ServerResponseEntity对象,若查询成功则响应体中包含符合条件的分页商品数据,否则返回相应错误信息。 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('prod:prod:page')") public ServerResponseEntity> page(ProductParam product, PageParam page) { + // 使用ProductService的page方法进行分页查询,传入分页参数和LambdaQueryWrapper构建的查询条件。 + // 通过StrUtil.isNotBlank判断商品名称是否不为空,若不为空则添加模糊查询条件,按照商品名称进行模糊匹配,同时根据店铺ID和商品状态(如果不为空)进行筛选,最后按照上架时间倒序排序。 IPage products = productService.page(page, new LambdaQueryWrapper() - .like(StrUtil.isNotBlank(product.getProdName()), Product::getProdName, product.getProdName()) - .eq(Product::getShopId, SecurityUtils.getSysUser().getShopId()) - .eq(product.getStatus() != null, Product::getStatus, product.getStatus()) - .orderByDesc(Product::getPutawayTime)); + .like(StrUtil.isNotBlank(product.getProdName()), Product::getProdName, product.getProdName()) + .eq(Product::getShopId, SecurityUtils.getSysUser().getShopId()) + .eq(product.getStatus()!= null, Product::getStatus, product.getStatus()) + .orderByDesc(Product::getPutawayTime)); return ServerResponseEntity.success(products); } /** - * 获取信息 + * 根据商品ID获取商品详细信息的方法,通过传入的商品ID,调用ProductService的getProductByProdId方法从数据库中查询对应的商品对象, + * 首先判断当前登录用户所属的店铺ID与商品所属的店铺ID是否一致,若不一致则抛出无权限异常,若有权限则获取该商品的规格列表(通过SkuService)并设置到商品对象中, + * 同时获取该商品关联的分组标签ID列表(通过ProdTagReferenceService)并设置到商品对象中,最后将包含详细信息的商品对象封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"prod:prod:info"权限的用户才能访问此方法。 + * + * @param prodId 要获取详细信息的商品的唯一标识符。 + * @return 返回包含商品详细信息的ServerResponseEntity对象,若获取成功则响应体中包含对应的商品对象及其规格列表、分组标签ID列表等信息,否则返回相应错误信息。 */ @GetMapping("/info/{prodId}") @PreAuthorize("@pms.hasPermission('prod:prod:info')") @@ -87,14 +105,21 @@ public class ProductController { List skuList = skuService.listByProdId(prodId); prod.setSkuList(skuList); - //获取分组标签 + // 获取分组标签,通过ProdTagReferenceService的listTagIdByProdId方法获取该商品关联的标签ID列表,并设置到商品对象中。 List listTagId = prodTagReferenceService.listTagIdByProdId(prodId); prod.setTagList(listTagId); return ServerResponseEntity.success(prod); } /** - * 保存 + * 保存商品信息的方法,接收一个经过验证(通过@Valid注解)的ProductParam对象作为请求体,代表要保存的商品信息。 + * 首先调用私有方法checkParam对传入的商品参数进行合法性校验,然后将ProductParam对象的属性复制到Product对象中(通过BeanUtil), + * 设置商品的配送方式(将相关对象转换为JSON字符串保存)、所属店铺ID(通过当前登录用户所属店铺ID确定)以及创建时间、更新时间等信息, + * 若商品状态为上架状态(状态值为1)则设置上架时间为当前时间,最后调用ProductService的saveProduct方法将商品信息保存到数据库中, + * 并返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"prod:prod:save"权限的用户才能访问此方法。 + * + * @param productParam 包含要保存的商品信息的请求体对象,如商品名称、规格列表、标签列表、配送方式等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,若保存成功则响应体中包含成功信息,若参数校验不通过则抛出相应异常并返回错误提示信息。 */ @PostMapping @PreAuthorize("@pms.hasPermission('prod:prod:save')") @@ -114,7 +139,16 @@ public class ProductController { } /** - * 修改 + * 修改商品信息的方法,接收一个经过验证(通过@Valid注解)的ProductParam对象作为请求体,代表要更新的商品信息。 + * 首先调用私有方法checkParam对传入的商品参数进行合法性校验,然后通过ProductService的getProductByProdId方法根据传入的商品ID从数据库中获取原商品对象, + * 判断当前登录用户所属的店铺ID与原商品所属的店铺ID是否一致,若不一致则返回无权限修改的错误提示信息,若有权限则获取原商品的规格列表, + * 将ProductParam对象的属性复制到新的Product对象中(通过BeanUtil),设置商品的配送方式(将相关对象转换为JSON字符串保存)以及更新时间等信息, + * 根据原商品状态和传入的新商品状态判断是否需要更新上架时间,接着调用ProductService的updateProduct方法将更新后的商品信息保存到数据库中, + * 之后遍历与该商品相关的用户ID列表(通过BasketService获取),清除这些用户的购物车商品缓存,同时遍历原商品的规格列表,清除对应规格的缓存, + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"prod:prod:update"权限的用户才能访问此方法。 + * + * @param productParam 包含要更新的商品信息的请求体对象,如商品名称、规格列表、标签列表、配送方式、新的商品状态等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,若更新成功则响应体中包含成功信息,若参数校验不通过或无权限则返回相应的错误提示信息。 */ @PutMapping @PreAuthorize("@pms.hasPermission('prod:prod:update')") @@ -151,7 +185,14 @@ public class ProductController { } /** - * 删除 + * 删除商品信息的方法,根据传入的商品ID,首先通过ProductService的getProductByProdId方法获取要删除的商品对象, + * 判断当前登录用户所属的店铺ID与商品所属的店铺ID是否一致,若不一致则抛出无权限异常,若有权限则获取该商品的规格列表, + * 调用ProductService的removeProductByProdId方法删除商品信息,接着遍历商品规格列表,清除对应规格的缓存, + * 同时遍历与该商品相关的用户ID列表(通过BasketService获取),清除这些用户的购物车商品缓存,最后返回成功的响应结果给前端。 + * 此方法没有添加权限控制注解,需根据实际业务需求确认是否需要添加相应权限控制(可能是供内部调用等情况)。 + * + * @param prodId 要删除的商品的唯一标识符。 + * @return 返回表示操作成功的ServerResponseEntity对象,若删除成功则响应体中包含成功信息,若无权限则抛出相应异常并返回错误提示信息。 */ public ServerResponseEntity delete(Long prodId) { Product dbProduct = productService.getProductByProdId(prodId); @@ -159,7 +200,7 @@ public class ProductController { throw new YamiShopBindException("无法获取非本店铺商品信息"); } List dbSkus = skuService.listByProdId(dbProduct.getProdId()); - // 删除商品 + // 删除商品,通过ProductService的removeProductByProdId方法根据商品ID删除商品信息。 productService.removeProductByProdId(prodId); for (Sku sku : dbSkus) { @@ -177,7 +218,11 @@ public class ProductController { } /** - * 批量删除 + * 批量删除商品信息的方法,接收一个包含要删除的商品ID数组的请求体,遍历该数组,对每个商品ID调用delete方法进行删除操作, + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"prod:prod:delete"权限的用户才能访问此方法。 + * + * @param prodIds 包含要删除的商品ID的数组。 + * @return 返回表示操作成功的ServerResponseEntity对象,若批量删除成功则响应体中包含成功信息,若删除过程中出现无权限等问题则抛出相应异常并返回错误提示信息。 */ @DeleteMapping @PreAuthorize("@pms.hasPermission('prod:prod:delete')") @@ -189,7 +234,14 @@ public class ProductController { } /** - * 更新商品状态 + * 更新商品状态的方法,接收商品ID和新的商品状态作为请求参数,创建一个Product对象并设置其ID和状态属性, + * 若新的商品状态为上架状态(状态值为1)则设置上架时间为当前时间,然后调用ProductService的updateById方法更新商品状态信息, + * 接着清除该商品的缓存(通过ProductService),再遍历与该商品相关的用户ID列表(通过BasketService获取),清除这些用户的购物车商品缓存, + * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制,只有具备"prod:prod:status"权限的用户才能访问此方法。 + * + * @param prodId 要更新状态的商品的唯一标识符。 + * @param prodStatus 新的商品状态值,如1表示上架,0表示下架等。 + * @return 返回表示操作成功的ServerResponseEntity对象,若更新成功则响应体中包含成功信息,若操作过程中出现问题则返回相应的错误提示信息。 */ @PutMapping("/prodStatus") @PreAuthorize("@pms.hasPermission('prod:prod:status')") @@ -210,26 +262,8 @@ public class ProductController { return ServerResponseEntity.success(); } - private void checkParam(ProductParam productParam) { - if (CollectionUtil.isEmpty(productParam.getTagList())) { - throw new YamiShopBindException("请选择产品分组"); - } - - Product.DeliveryModeVO deliveryMode = productParam.getDeliveryModeVo(); - boolean hasDeliverMode = deliveryMode != null - && (deliveryMode.getHasShopDelivery() || deliveryMode.getHasUserPickUp()); - if (!hasDeliverMode) { - throw new YamiShopBindException("请选择配送方式"); - } - List skuList = productParam.getSkuList(); - boolean isAllUnUse = true; - for (Sku sku : skuList) { - if (sku.getStatus() == 1) { - isAllUnUse = false; - } - } - if (isAllUnUse) { - throw new YamiShopBindException("至少要启用一种商品规格"); - } - } -} + /** + * 私有方法,用于对传入的商品参数(ProductParam对象)进行合法性校验,主要校验内容如下: + * 1. 检查商品的标签列表是否为空,如果为空则抛出异常提示“请选择产品分组”,确保商品关联了相应的分组标签。 + * 2. 检查商品的配送方式,判断是否选择了有效的配送方式(至少有店铺配送或者用户自提其中一种),若未选择则抛出异常提示“请选择配送方式”。 + * 3. 检查商品规格列表中是否至少有一个规格处于启用状态(状态值为 \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ShopDetailController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ShopDetailController.java index 7852a8a..3b6c0c0 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ShopDetailController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ShopDetailController.java @@ -8,6 +8,7 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于管理端控制器相关的包下,用于对店铺详情相关操作进行控制和处理 package com.yami.shop.admin.controller; import cn.hutool.core.util.StrUtil; @@ -30,131 +31,183 @@ import java.util.Date; import java.util.List; import java.util.stream.Collectors; - - /** + * ShopDetailController类是一个Spring RESTful风格的控制器,用于处理与店铺详情(ShopDetail)相关的各种后台管理操作接口。 + * 涵盖了诸如修改分销开关、获取店铺详情信息、分页查询店铺详情、保存店铺详情、修改店铺详情、删除店铺详情、更新店铺状态以及获取所有店铺名称等功能。 * * @author lgh on 2018/08/29. */ @RestController +// 定义该控制器类的基础请求路径,所有该类中的接口请求路径都将以此为前缀,表明是与店铺详情相关的操作接口 @RequestMapping("/shop/shopDetail") public class ShopDetailController { + // 通过Spring的依赖注入机制,自动注入ShopDetailService的实例,以便调用其提供的与店铺详情相关的业务逻辑方法 @Autowired private ShopDetailService shopDetailService; - - /** - * 修改分销开关 - */ - @PutMapping("/isDistribution") - public ServerResponseEntity updateIsDistribution(@RequestParam Integer isDistribution){ - ShopDetail shopDetail=new ShopDetail(); - shopDetail.setShopId(SecurityUtils.getSysUser().getShopId()); - shopDetail.setIsDistribution(isDistribution); - shopDetailService.updateById(shopDetail); - // 更新完成后删除缓存 - shopDetailService.removeShopDetailCacheByShopId(shopDetail.getShopId()); - return ServerResponseEntity.success(); - } - /** - * 获取信息 - */ - @GetMapping("/info") - @PreAuthorize("@pms.hasPermission('shop:shopDetail:info')") - public ServerResponseEntity info(){ - ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(SecurityUtils.getSysUser().getShopId()); - return ServerResponseEntity.success(shopDetail); - } - - - /** - * 分页获取 - */ + /** + * 修改分销开关的接口方法。 + * 根据传入的分销开关状态参数(isDistribution),创建一个ShopDetail对象,设置店铺ID(通过SecurityUtils获取当前系统用户的店铺ID)以及分销开关状态, + * 然后调用ShopDetailService的updateById方法更新数据库中对应店铺的分销开关状态,更新完成后调用removeShopDetailCacheByShopId方法删除对应店铺ID的缓存, + * 最后返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * + * @param isDistribution 表示分销开关状态的整数参数,通常可能是0或1等表示关闭或开启的状态值。 + * @return 返回表示操作成功的ServerResponseEntity,无数据内容(Void类型)。 + */ + @PutMapping("/isDistribution") + public ServerResponseEntity updateIsDistribution(@RequestParam Integer isDistribution) { + ShopDetail shopDetail = new ShopDetail(); + shopDetail.setShopId(SecurityUtils.getSysUser().getShopId()); + shopDetail.setIsDistribution(isDistribution); + shopDetailService.updateById(shopDetail); + // 更新完成后删除缓存 + shopDetailService.removeShopDetailCacheByShopId(shopDetail.getShopId()); + return ServerResponseEntity.success(); + } + + /** + * 获取当前用户所属店铺详情信息的接口方法。 + * 通过SecurityUtils获取当前系统用户的店铺ID,调用ShopDetailService的getShopDetailByShopId方法获取对应的店铺详情对象, + * 再将获取到的店铺详情对象封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @return 返回包含当前用户所属店铺详情信息的ServerResponseEntity,成功时其数据部分为ShopDetail类型。 + */ + @GetMapping("/info") + @PreAuthorize("@pms.hasPermission('shop:shopDetail:info')") + public ServerResponseEntity info() { + ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(SecurityUtils.getSysUser().getShopId()); + return ServerResponseEntity.success(shopDetail); + } + + /** + * 分页获取店铺详情信息的接口方法。 + * 根据传入的ShopDetail对象(可能包含店铺名称等查询条件)以及PageParam对象(用于分页参数设置), + * 使用ShopDetailService的page方法结合LambdaQueryWrapper进行分页查询。如果店铺名称不为空,则按照店铺名称进行模糊查询, + * 并且按照店铺ID降序排序,最后将查询到的分页店铺详情数据封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param shopDetail 可能包含查询条件的店铺详情对象,比如按店铺名称模糊查询。 + * @param page 分页参数对象,包含页码、每页数量等信息。 + * @return 返回包含分页店铺详情信息的ServerResponseEntity,成功时其数据部分为IPage类型。 + */ @GetMapping("/page") - @PreAuthorize("@pms.hasPermission('shop:shopDetail:page')") - public ServerResponseEntity> page(ShopDetail shopDetail,PageParam page){ - IPage shopDetails = shopDetailService.page(page, - new LambdaQueryWrapper() - .like(StrUtil.isNotBlank(shopDetail.getShopName()),ShopDetail::getShopName,shopDetail.getShopName()) - .orderByDesc(ShopDetail::getShopId)); - return ServerResponseEntity.success(shopDetails); - } - - /** - * 获取信息 - */ - @GetMapping("/info/{shopId}") - @PreAuthorize("@pms.hasPermission('shop:shopDetail:info')") - public ServerResponseEntity info(@PathVariable("shopId") Long shopId){ - ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(shopId); - // 店铺图片 - return ServerResponseEntity.success(shopDetail); - } - - /** - * 保存 - */ - @PostMapping - @PreAuthorize("@pms.hasPermission('shop:shopDetail:save')") - public ServerResponseEntity save(@Valid ShopDetailParam shopDetailParam){ - ShopDetail shopDetail = BeanUtil.copyProperties(shopDetailParam, ShopDetail.class); - shopDetail.setCreateTime(new Date()); - shopDetail.setShopStatus(1); - shopDetailService.save(shopDetail); - return ServerResponseEntity.success(); - } - - /** - * 修改 - */ - @PutMapping - @PreAuthorize("@pms.hasPermission('shop:shopDetail:update')") - public ServerResponseEntity update(@Valid ShopDetailParam shopDetailParam){ - ShopDetail daShopDetail = shopDetailService.getShopDetailByShopId(shopDetailParam.getShopId()); - ShopDetail shopDetail = BeanUtil.copyProperties(shopDetailParam, ShopDetail.class); - shopDetail.setUpdateTime(new Date()); - shopDetailService.updateShopDetail(shopDetail,daShopDetail); - return ServerResponseEntity.success(); - } - - /** - * 删除 - */ - @DeleteMapping("/{id}") - @PreAuthorize("@pms.hasPermission('shop:shopDetail:delete')") - public ServerResponseEntity delete(@PathVariable Long id){ - shopDetailService.deleteShopDetailByShopId(id); - return ServerResponseEntity.success(); - } - - /** - * 更新店铺状态 - */ - @PutMapping("/shopStatus") - @PreAuthorize("@pms.hasPermission('shop:shopDetail:shopStatus')") - public ServerResponseEntity shopStatus(@RequestParam Long shopId,@RequestParam Integer shopStatus){ - ShopDetail shopDetail = new ShopDetail(); - shopDetail.setShopId(shopId); - shopDetail.setShopStatus(shopStatus); - shopDetailService.updateById(shopDetail); - // 更新完成后删除缓存 - shopDetailService.removeShopDetailCacheByShopId(shopDetail.getShopId()); - return ServerResponseEntity.success(); - } - - - /** - * 获取所有的店铺名称 - */ + @PreAuthorize("@pms.hasPermission('shop:shopDetail:page')") + public ServerResponseEntity> page(ShopDetail shopDetail, PageParam page) { + IPage shopDetails = shopDetailService.page(page, + new LambdaQueryWrapper() + .like(StrUtil.isNotBlank(shopDetail.getShopName()), ShopDetail::getShopName, shopDetail.getShopName()) + .orderByDesc(ShopDetail::getShopId)); + return ServerResponseEntity.success(shopDetails); + } + + /** + * 根据店铺ID获取店铺详情信息的接口方法。 + * 通过路径变量获取要查询的店铺的ID,调用ShopDetailService的getShopDetailByShopId方法获取对应的店铺详情对象, + * 再将获取到的店铺详情对象封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param shopId 要获取信息的店铺的唯一标识符(ID)。 + * @return 返回包含指定店铺详情信息的ServerResponseEntity,成功时其数据部分为ShopDetail类型。 + */ + @GetMapping("/info/{shopId}") + @PreAuthorize("@pms.hasPermission('shop:shopDetail:info')") + public ServerResponseEntity info(@PathVariable("shopId") Long shopId) { + ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(shopId); + // 店铺图片(此处可根据实际业务需求进一步处理店铺图片相关逻辑,目前只是简单返回店铺详情对象) + return ServerResponseEntity.success(shopDetail); + } + + /** + * 保存店铺详情信息的接口方法。 + * 首先将传入的ShopDetailParam对象(可能包含部分店铺详情信息)通过BeanUtil复制属性到ShopDetail对象中, + * 然后设置创建时间为当前时间(通过new Date()获取)以及店铺状态为默认值(这里设置为1,具体含义需根据业务定义), + * 最后调用ShopDetailService的save方法将店铺详情信息保存到数据库中,返回表示保存成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * + * @param shopDetailParam 包含店铺详情部分信息的参数对象,用于传入要保存的店铺相关属性。 + * @return 返回表示保存成功的ServerResponseEntity,无数据内容(Void类型)。 + */ + @PostMapping + @PreAuthorize("@pms.hasPermission('shop:shopDetail:save')") + public ServerResponseEntity save(@Valid ShopDetailParam shopDetailParam) { + ShopDetail shopDetail = BeanUtil.copyProperties(shopDetailParam, ShopDetail.class); + shopDetail.setCreateTime(new Date()); + shopDetail.setShopStatus(1); + shopDetailService.save(shopDetail); + return ServerResponseEntity.success(); + } + + /** + * 修改店铺详情信息的接口方法。 + * 先调用ShopDetailService的getShopDetailByShopId方法根据传入的ShopDetailParam中的店铺ID获取数据库中已存在的店铺详情对象(daShopDetail), + * 再将ShopDetailParam对象通过BeanUtil复制属性到ShopDetail对象中,并设置更新时间为当前时间(通过new Date()获取), + * 然后调用ShopDetailService的updateShopDetail方法(可能包含自定义的更新逻辑,比如判断哪些字段需要更新等)更新店铺详情信息, + * 最后返回表示修改成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * + * @param shopDetailParam 包含要修改的店铺详情部分信息的参数对象,用于传入修改后的店铺相关属性。 + * @return 返回表示修改成功的ServerResponseEntity,无数据内容(Void类型)。 + */ + @PutMapping + @PreAuthorize("@pms.hasPermission('shop:shopDetail:update')") + public ServerResponseEntity update(@Valid ShopDetailParam shopDetailParam) { + ShopDetail daShopDetail = shopDetailService.getShopDetailByShopId(shopDetailParam.getShopId()); + ShopDetail shopDetail = BeanUtil.copyProperties(shopDetailParam, ShopDetail.class); + shopDetail.setUpdateTime(new Date()); + shopDetailService.updateShopDetail(shopDetail, daShopDetail); + return ServerResponseEntity.success(); + } + + /** + * 根据店铺ID删除店铺详情信息的接口方法。 + * 通过路径变量获取要删除的店铺的ID,调用ShopDetailService的deleteShopDetailByShopId方法删除数据库中对应的店铺详情记录, + * 最后返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * + * @param id 要删除的店铺的唯一标识符(ID)。 + * @return 返回表示操作成功的ServerResponseEntity,无数据内容(Void类型)。 + */ + @DeleteMapping("/{id}") + @PreAuthorize("@pms.hasPermission('shop:shopDetail:delete')") + public ServerResponseEntity delete(@PathVariable Long id) { + shopDetailService.deleteShopDetailByShopId(id); + return ServerResponseEntity.success(); + } + + /** + * 更新店铺状态的接口方法。 + * 根据传入的店铺ID(shopId)和店铺状态(shopStatus)参数,创建一个ShopDetail对象,设置对应的店铺ID和店铺状态, + * 然后调用ShopDetailService的updateById方法更新数据库中对应店铺的状态,更新完成后调用removeShopDetailCacheByShopId方法删除对应店铺ID的缓存, + * 最后返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * + * @param shopId 要更新状态的店铺的唯一标识符(ID)。 + * @param shopStatus 表示店铺新状态的整数参数,具体取值含义需根据业务定义。 + * @return 返回表示操作成功的ServerResponseEntity,无数据内容(Void类型)。 + */ + @PutMapping("/shopStatus") + @PreAuthorize("@pms.hasPermission('shop:shopDetail:shopStatus')") + public ServerResponseEntity shopStatus(@RequestParam Long shopId, @RequestParam Integer shopStatus) { + ShopDetail shopDetail = new ShopDetail(); + shopDetail.setShopId(shopId); + shopDetail.setShopStatus(shopStatus); + shopDetailService.updateById(shopDetail); + // 更新完成后删除缓存 + shopDetailService.removeShopDetailCacheByShopId(shopDetail.getShopId()); + return ServerResponseEntity.success(); + } + + /** + * 获取所有店铺名称的接口方法。 + * 首先调用ShopDetailService的list方法获取所有的店铺详情列表,然后通过Java 8的Stream流操作, + * 将每个店铺详情对象中的店铺ID和店铺名称提取出来,封装到新的ShopDetail对象中,组成一个新的列表, + * 最后将这个包含店铺名称信息的列表封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @return 返回包含所有店铺名称信息的ServerResponseEntity,成功时其数据部分为List类型,这里的ShopDetail对象仅包含店铺ID和店铺名称属性。 + */ @GetMapping("/listShopName") - public ServerResponseEntity> listShopName(){ - List list = shopDetailService.list().stream().map((dbShopDetail) ->{ - ShopDetail shopDetail = new ShopDetail(); - shopDetail.setShopId(dbShopDetail.getShopId()); - shopDetail.setShopName(dbShopDetail.getShopName()); - return shopDetail; - }).collect(Collectors.toList()); - return ServerResponseEntity.success(list); - } -} + public ServerResponseEntity> listShopName() { + List list = shopDetailService.list().stream().map((dbShopDetail) -> { + ShopDetail shopDetail = new ShopDetail(); + shopDetail.setShopId(dbShopDetail.getShopId()); + shopDetail.setShopName(dbShopDetail.getShopName()); + return shopDetail; + }).collect(Collectors.toList()); + return ServerResponseEntity.success(list); + } +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/SpecController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/SpecController.java index 181fe05..87796f6 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/SpecController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/SpecController.java @@ -31,95 +31,137 @@ import java.util.Objects; /** * 规格管理 - * + * 该类作为规格管理相关功能的控制器,提供了规格的分页查询、获取所有规格、根据规格id获取规格值、规格的保存、修改、删除以及获取规格值最大自增id等操作的接口 * @author lgh */ @RestController @RequestMapping("/prod/spec") public class SpecController { + // 通过Spring的自动注入机制,注入ProdPropService对象,用于调用业务层处理商品属性相关的业务逻辑 @Autowired private ProdPropService prodPropService; + // 通过Spring的自动注入机制,注入ProdPropValueService对象,用于调用业务层处理商品属性值相关的业务逻辑 @Autowired private ProdPropValueService prodPropValueService; /** - * 分页获取 + * 分页获取规格信息的方法 + * 根据传入的规格筛选条件以及分页参数,获取符合条件的规格分页数据,同时进行权限校验和必要的属性设置 + * @param prodProp 规格对象,用于传递筛选规格的条件等信息 + * @param page 分页对象,包含分页相关的参数,如页码、每页数量等 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含符合条件的规格分页数据 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('prod:spec:page')") - public ServerResponseEntity> page(ProdProp prodProp,PageParam page) { + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'prod:spec:page'权限的用户才能访问该接口 + public ServerResponseEntity> page(ProdProp prodProp, PageParam page) { + // 设置规格的规则为指定的规格类型(SPEC),这里通过枚举值来表示 prodProp.setRule(ProdPropRule.SPEC.value()); + // 设置规格所属店铺的id,从当前登录用户信息中获取 prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); + // 调用业务层方法获取规格以及对应规格值的分页数据(具体逻辑由业务层的pagePropAndValue方法决定) IPage list = prodPropService.pagePropAndValue(prodProp, page); return ServerResponseEntity.success(list); } - /** - * 获取所有的规格 + * 获取所有规格信息的方法 + * 通过查询条件筛选出符合要求的所有规格信息,并返回给前端 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含所有符合条件的规格列表数据 */ @GetMapping("/list") public ServerResponseEntity> list() { - List list = prodPropService.list(new LambdaQueryWrapper().eq(ProdProp::getRule, ProdPropRule.SPEC.value()).eq(ProdProp::getShopId, SecurityUtils.getSysUser().getShopId())); + // 使用MyBatis Plus的LambdaQueryWrapper构建查询条件,筛选出规则为指定规格类型(SPEC)且属于当前登录用户店铺的规格信息 + List list = prodPropService.list(new LambdaQueryWrapper() + .eq(ProdProp::getRule, ProdPropRule.SPEC.value()) + .eq(ProdProp::getShopId, SecurityUtils.getSysUser().getShopId())); return ServerResponseEntity.success(list); } /** - * 根据规格id获取规格值 + * 根据规格id获取规格值信息的方法 + * 根据传入的规格id,查询并返回对应的规格值列表数据 + * @param specId 要查询规格值的规格的唯一标识(id) + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含对应规格id的规格值列表数据 */ @GetMapping("/listSpecValue/{specId}") public ServerResponseEntity> listSpecValue(@PathVariable("specId") Long specId) { - List list = prodPropValueService.list(new LambdaQueryWrapper().eq(ProdPropValue::getPropId, specId)); + // 使用MyBatis Plus的LambdaQueryWrapper构建查询条件,筛选出属性id(即规格id)为传入specId的规格值信息 + List list = prodPropValueService.list(new LambdaQueryWrapper() + .eq(ProdPropValue::getPropId, specId)); return ServerResponseEntity.success(list); } /** - * 保存 + * 保存规格信息的方法 + * 接收前端传入的规格信息,进行必要的属性设置后,调用业务层方法保存规格及其相关的规格值信息,并进行权限校验 + * @param prodProp 规格对象,包含要保存的规格及其规格值等详细信息 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示保存操作成功,无具体返回数据 */ @PostMapping @PreAuthorize("@pms.hasPermission('prod:spec:save')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'prod:spec:save'权限的用户才能访问该接口 public ServerResponseEntity save(@Valid @RequestBody ProdProp prodProp) { + // 设置规格的规则为指定的规格类型(SPEC),这里通过枚举值来表示 prodProp.setRule(ProdPropRule.SPEC.value()); + // 设置规格所属店铺的id,从当前登录用户信息中获取 prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); + // 调用业务层方法保存规格以及对应的规格值信息(具体逻辑由业务层的saveProdPropAndValues方法决定) prodPropService.saveProdPropAndValues(prodProp); return ServerResponseEntity.success(); } /** - * 修改 + * 修改规格信息的方法 + * 接收前端传入的要修改的规格信息,进行权限校验、必要的属性设置后,调用业务层方法更新规格及其相关的规格值信息 + * @param prodProp 规格对象,包含要修改的规格及其规格值等详细信息 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示修改操作成功,无具体返回数据 */ @PutMapping @PreAuthorize("@pms.hasPermission('prod:spec:update')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'prod:spec:update'权限的用户才能访问该接口 public ServerResponseEntity update(@Valid @RequestBody ProdProp prodProp) { + // 根据传入的规格id从数据库中获取对应的规格对象,用于后续权限校验等操作 ProdProp dbProdProp = prodPropService.getById(prodProp.getPropId()); if (!Objects.equals(dbProdProp.getShopId(), SecurityUtils.getSysUser().getShopId())) { + // 如果当前登录用户所属店铺id与要修改的规格所属店铺id不一致,则抛出异常,提示没有权限获取该商品规格信息 throw new YamiShopBindException("没有权限获取该商品规格信息"); } + // 设置规格的规则为指定的规格类型(SPEC),这里通过枚举值来表示 prodProp.setRule(ProdPropRule.SPEC.value()); + // 设置规格所属店铺的id,从当前登录用户信息中获取 prodProp.setShopId(SecurityUtils.getSysUser().getShopId()); + // 调用业务层方法更新规格以及对应的规格值信息(具体逻辑由业务层的updateProdPropAndValues方法决定) prodPropService.updateProdPropAndValues(prodProp); return ServerResponseEntity.success(); } /** - * 删除 + * 删除规格信息的方法 + * 根据传入的规格id,调用业务层方法删除对应的规格及其相关的规格值信息,并进行权限校验 + * @param id 要删除的规格的唯一标识(id) + * @return 返回一个ServerResponseEntity类型的响应,成功时表示删除操作成功,无具体返回数据 */ @DeleteMapping("/{id}") @PreAuthorize("@pms.hasPermission('prod:spec:delete')") + // 使用 @PreAuthorize 注解进行权限校验,只有拥有'prod:spec:delete'权限的用户才能访问该接口 public ServerResponseEntity delete(@PathVariable Long id) { + // 调用业务层方法删除规格以及对应的规格值信息,传入规格id、规格规则类型以及当前登录用户所属店铺id等参数(具体逻辑由业务层的deleteProdPropAndValues方法决定) prodPropService.deleteProdPropAndValues(id, ProdPropRule.SPEC.value(), SecurityUtils.getSysUser().getShopId()); return ServerResponseEntity.success(); } /** - * 根据获取规格值最大的自增id + * 根据获取规格值最大的自增id的方法 + * 通过查询条件获取规格值中最大的自增id,并返回给前端 + * @return 返回一个ServerResponseEntity类型的响应,成功时包含规格值最大的自增id,若不存在则返回0L */ @GetMapping("/listSpecMaxValueId") public ServerResponseEntity listSpecMaxValueId() { + // 使用MyBatis Plus的LambdaQueryWrapper构建查询条件,按照规格值的自增id降序排列,并通过last方法限制只取第一条记录,即获取最大的自增id对应的规格值对象 ProdPropValue propValue = prodPropValueService.getOne(new LambdaQueryWrapper() - .orderByDesc(ProdPropValue::getValueId).last("limit 1")); - return ServerResponseEntity.success(Objects.isNull(propValue) ? 0L : propValue.getValueId()); + .orderByDesc(ProdPropValue::getValueId).last("limit 1")); + return ServerResponseEntity.success(Objects.isNull(propValue)? 0L : propValue.getValueId()); } - -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java index 7921270..6834e47 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java @@ -26,32 +26,50 @@ import java.util.Date; import java.util.List; /** + * 运费模板(Transport)相关操作的控制器类,用于处理后台对运费模板的各种请求, + * 例如分页查询、获取详情、保存、修改、删除以及获取运费模板列表等操作。 + * * @author lgh on 2018/11/16. */ @RestController @RequestMapping("/shop/transport") public class TransportController { + // 自动注入运费模板服务层接口,通过该接口调用具体的业务逻辑方法来处理运费模板相关业务 @Autowired private TransportService transportService; - /** - * 分页获取 + * 分页获取运费模板信息的方法 + * 此方法根据传入的运费模板查询条件(Transport)以及分页参数(PageParam),查询并返回符合条件的运费模板信息分页结果。 + * 只有拥有 'shop:transport:page' 权限的用户才能访问此方法。 + * 会根据当前登录用户所属店铺的 ID 进行筛选,确保只获取该店铺相关的运费模板记录,同时支持根据运费模板名称进行模糊查询。 + * + * @param transport 运费模板查询条件对象,可设置如名称等属性用于筛选数据 + * @param page 分页对象,包含页码、每页数量等分页相关参数 + * @return 包含运费模板信息的分页数据,封装在 ServerResponseEntity 中,方便统一的响应格式处理 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('shop:transport:page')") - public ServerResponseEntity> page(Transport transport,PageParam page) { + public ServerResponseEntity> page(Transport transport, PageParam page) { + // 获取当前登录用户所属店铺的 ID,用于筛选当前店铺的运费模板数据 Long shopId = SecurityUtils.getSysUser().getShopId(); IPage transports = transportService.page(page, new LambdaQueryWrapper() - .eq(Transport::getShopId, shopId) - .like(StringUtils.isNotBlank(transport.getTransName()), Transport::getTransName, transport.getTransName())); + // 根据店铺 ID 精确匹配,筛选出属于当前店铺的运费模板记录 + .eq(Transport::getShopId, shopId) + // 如果传入的运费模板名称不为空,则进行模糊匹配名称字段进行查询,实现根据名称筛选的功能 + .like(StringUtils.isNotBlank(transport.getTransName()), Transport::getTransName, transport.getTransName())); return ServerResponseEntity.success(transports); } /** - * 获取信息 + * 根据给定的 ID 获取运费模板详细信息的方法 + * 此方法接收一个运费模板的 ID,通过调用服务层的方法从数据库中获取对应的详细信息,包括运费模板及其所有关联项信息, + * 并将结果封装在 ServerResponseEntity 中返回给前端,只有拥有 'shop:transport:info' 权限的用户才能访问此方法。 + * + * @param id 要查询的运费模板记录的 ID + * @return 包含运费模板详细信息的 ServerResponseEntity,若查询到则返回对应的数据,否则返回相应的空数据表示 */ @GetMapping("/info/{id}") @PreAuthorize("@pms.hasPermission('shop:transport:info')") @@ -61,13 +79,20 @@ public class TransportController { } /** - * 保存 + * 保存运费模板信息的方法 + * 此方法接收一个包含运费模板信息的 Transport 对象,设置记录的所属店铺 ID(从当前登录用户获取)以及创建时间为当前时间, + * 然后调用服务层的方法将运费模板及其相关的运费信息保存到数据库中,只有拥有 'shop:transport:save' 权限的用户才能执行此操作。 + * + * @param transport 包含要保存的运费模板信息的对象,其属性应符合数据库对应表的字段要求 + * @return 表示保存操作成功的 ServerResponseEntity,用于告知前端保存操作已顺利完成 */ @PostMapping @PreAuthorize("@pms.hasPermission('shop:transport:save')") public ServerResponseEntity save(@RequestBody Transport transport) { + // 获取当前登录用户所属店铺的 ID,设置到运费模板对象中,明确该运费模板所属的店铺 Long shopId = SecurityUtils.getSysUser().getShopId(); transport.setShopId(shopId); + // 创建当前时间对象,作为运费模板的创建时间 Date createTime = new Date(); transport.setCreateTime(createTime); transportService.insertTransportAndTransfee(transport); @@ -75,7 +100,12 @@ public class TransportController { } /** - * 修改 + * 修改运费模板信息的方法 + * 此方法接收一个包含更新后运费模板信息的 Transport 对象,调用服务层的方法将数据库中的对应运费模板及其相关运费信息进行更新, + * 只有拥有 'shop:transport:update' 权限的用户才能执行此操作,更新成功后返回表示操作成功的 ServerResponseEntity 给前端。 + * + * @param transport 包含要修改的运费模板信息的对象,其属性应符合数据库对应表的字段要求 + * @return 表示修改操作成功的 ServerResponseEntity,用于告知前端修改操作已顺利完成 */ @PutMapping @PreAuthorize("@pms.hasPermission('shop:transport:update')") @@ -85,28 +115,37 @@ public class TransportController { } /** - * 删除 + * 删除运费模板的方法 + * 此方法接收一个包含多个运费模板 ID 的数组,调用服务层的方法从数据库中批量删除这些运费模板及其相关的运费、运输城市等信息, + * 并且在删除后会循环遍历删除的 ID,清除对应运费模板及其所有关联项的缓存,只有拥有 'shop:transport:delete' 权限的用户才能执行此操作, + * 最后返回表示删除操作成功的 ServerResponseEntity 给前端。 + * + * @param ids 包含要删除的运费模板记录的 ID 数组 + * @return 表示删除操作成功的 ServerResponseEntity,用于告知前端删除操作已顺利完成 */ @DeleteMapping @PreAuthorize("@pms.hasPermission('shop:transport:delete')") public ServerResponseEntity delete(@RequestBody Long[] ids) { transportService.deleteTransportAndTransfeeAndTranscity(ids); - // 删除运费模板的缓存 + // 删除运费模板的缓存,确保缓存数据与数据库最新数据一致,避免缓存数据造成的不一致问题 for (Long id : ids) { transportService.removeTransportAndAllItemsCache(id); } return ServerResponseEntity.success(); } - /** - * 获取运费模板列表 + * 获取当前店铺的运费模板列表的方法 + * 此方法通过调用服务层的方法,根据当前登录用户所属店铺的 ID 查询并获取该店铺下的所有运费模板列表, + * 将结果封装在 ServerResponseEntity 中返回给前端,方便前端展示和使用运费模板数据。 + * + * @return 包含运费模板列表数据的 ServerResponseEntity,若查询到则返回对应的数据列表,否则返回空列表表示 */ @GetMapping("/list") public ServerResponseEntity> list() { + // 获取当前登录用户所属店铺的 ID,用于筛选当前店铺的运费模板数据 Long shopId = SecurityUtils.getSysUser().getShopId(); List list = transportService.list(new LambdaQueryWrapper().eq(Transport::getShopId, shopId)); return ServerResponseEntity.success(list); } - -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserAddrController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserAddrController.java index 50550ec..77778f7 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserAddrController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserAddrController.java @@ -24,7 +24,9 @@ import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; /** - * 用户地址管理 + * 用户地址管理相关的控制器类,主要负责处理用户地址的增删改查操作, + * 各个方法分别实现了分页查询、根据ID获取单个地址信息、新增地址、更新地址以及删除地址等功能, + * 并且在部分操作上添加了权限控制以及操作日志记录功能,方便后续权限管理和操作记录查看。 * * @author hzm * @date 2019-04-15 10:49:40 @@ -34,26 +36,31 @@ import jakarta.validation.Valid; @RequestMapping("/user/addr") public class UserAddrController { + // 通过lombok的@AllArgsConstructor注解注入UserAddrService,用于处理与用户地址相关的业务逻辑, + // 例如地址信息的查询、保存、更新、删除等操作。 private final UserAddrService userAddrService; /** - * 分页查询 + * 分页查询用户地址信息的方法,接收分页参数(PageParam对象)和用于筛选的用户地址对象(UserAddr对象)作为参数, + * 通过UserAddrService进行分页查询,这里构建的LambdaQueryWrapper暂时没有添加额外的筛选条件, + * 最后将查询到的分页结果封装在成功的响应实体中返回给前端。此方法没有添加权限控制注解,需根据实际业务需求确认是否需要添加相应权限控制。 * - * @param page 分页对象 - * @param userAddr 用户地址管理 - * @return 分页数据 + * @param page 分页参数对象,用于指定页码、每页数量等分页相关信息。 + * @param userAddr 用户地址对象,理论上可用于添加更多筛选条件,但目前代码中未使用,可根据业务扩展添加相应条件查询逻辑。 + * @return 返回包含分页后的用户地址信息的ServerResponseEntity对象,若查询成功则响应体中包含符合条件的分页地址数据,否则返回相应错误信息。 */ @GetMapping("/page") public ServerResponseEntity> getUserAddrPage(PageParam page, UserAddr userAddr) { + // 使用UserAddrService的page方法进行分页查询,传入分页参数和一个空的LambdaQueryWrapper(目前无额外筛选条件),获取分页结果。 return ServerResponseEntity.success(userAddrService.page(page, new LambdaQueryWrapper())); } - /** - * 通过id查询用户地址管理 + * 根据用户地址ID获取用户地址详细信息的方法,通过传入的地址ID,调用UserAddrService的getById方法从数据库中查询对应的用户地址对象, + * 并将其封装在成功的响应实体中返回给前端。此方法没有添加权限控制注解,需根据实际业务需求确认是否需要添加相应权限控制。 * - * @param addrId id - * @return 单个数据 + * @param addrId 要获取详细信息的用户地址的唯一标识符。 + * @return 返回包含用户地址详细信息的ServerResponseEntity对象,若获取成功则响应体中包含对应的用户地址对象,否则返回相应错误信息。 */ @GetMapping("/info/{addrId}") public ServerResponseEntity getById(@PathVariable("addrId") Long addrId) { @@ -61,10 +68,12 @@ public class UserAddrController { } /** - * 新增用户地址管理 + * 新增用户地址信息的方法,接收一个经过验证(通过@Valid注解)的用户地址对象(UserAddr)作为请求体,代表要新增的地址信息, + * 通过UserAddrService的save方法将用户地址信息保存到数据库中,并将保存结果(布尔值,表示是否保存成功)封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"user:addr:save"权限的用户才能访问此方法,同时使用@SysLog注解记录此操作的日志信息,方便后续查看操作记录。 * - * @param userAddr 用户地址管理 - * @return 是否新增成功 + * @param userAddr 包含要新增的用户地址信息的请求体对象,如地址名称、详细地址、联系人等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,响应体中包含表示是否新增成功的布尔值,若保存成功则为true,否则为false。 */ @SysLog("新增用户地址管理") @PostMapping @@ -74,10 +83,12 @@ public class UserAddrController { } /** - * 修改用户地址管理 + * 修改用户地址信息的方法,接收一个经过验证(通过@Valid注解)的用户地址对象(UserAddr)作为请求体,代表要更新的地址信息, + * 通过UserAddrService的updateById方法将更新后的用户地址信息保存到数据库中,并将更新结果(布尔值,表示是否更新成功)封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"user:addr:update"权限的用户才能访问此方法,同时使用@SysLog注解记录此操作的日志信息,方便后续查看操作记录。 * - * @param userAddr 用户地址管理 - * @return 是否修改成功 + * @param userAddr 包含要更新的用户地址信息的请求体对象,如地址名称、详细地址、联系人等属性。 + * @return 返回表示操作成功的ServerResponseEntity对象,响应体中包含表示是否更新成功的布尔值,若更新成功则为true,否则为false。 */ @SysLog("修改用户地址管理") @PutMapping @@ -87,10 +98,12 @@ public class UserAddrController { } /** - * 通过id删除用户地址管理 + * 根据用户地址ID删除用户地址信息的方法,通过传入的地址ID,调用UserAddrService的removeById方法从数据库中删除对应的用户地址信息, + * 并将删除结果(布尔值,表示是否删除成功)封装在成功的响应实体中返回给前端。 + * 通过@PreAuthorize注解进行权限控制,只有具备"user:addr:delete"权限的用户才能访问此方法,同时使用@SysLog注解记录此操作的日志信息,方便后续查看操作记录。 * - * @param addrId id - * @return 是否删除成功 + * @param addrId 要删除的用户地址的唯一标识符。 + * @return 返回表示操作成功的ServerResponseEntity对象,响应体中包含表示是否删除成功的布尔值,若删除成功则为true,否则为false。 */ @SysLog("删除用户地址管理") @DeleteMapping("/{addrId}") @@ -99,4 +112,4 @@ public class UserAddrController { return ServerResponseEntity.success(userAddrService.removeById(addrId)); } -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserController.java index a61e5f6..4e7778d 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserController.java @@ -8,6 +8,7 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明此控制器类位于管理端相关的包下,用于处理用户相关的后台管理操作 package com.yami.shop.admin.controller; import cn.hutool.core.util.StrUtil; @@ -24,57 +25,88 @@ import org.springframework.web.bind.annotation.*; import java.util.Arrays; import java.util.Date; - /** + * UserController类是一个Spring RESTful风格的控制器,用于处理与用户(User)相关的后台管理接口操作。 + * 包含了用户信息的分页查询、获取单个用户详细信息、修改用户信息以及批量删除用户信息等功能。 * @author lgh on 2018/10/16. */ @RestController +// 定义该控制器类的基础请求路径,所有该类中的接口请求路径都将以此为前缀,表明是针对用户的管理操作接口 @RequestMapping("/admin/user") public class UserController { + // 通过Spring的依赖注入机制,自动注入UserService的实例,以便调用其提供的与用户相关的业务逻辑方法 @Autowired private UserService userService; /** - * 分页获取 + * 分页获取用户信息的接口方法。 + * 根据传入的User对象(可能包含昵称、状态等查询条件)以及PageParam对象(用于分页参数设置), + * 使用UserService的page方法结合LambdaQueryWrapper进行分页查询。 + * 如果用户昵称不为空,则按照昵称进行模糊查询;如果用户状态不为空,则精确匹配状态进行查询。 + * 另外,对查询结果中的每个用户的昵称进行处理,若昵称为空则设置为空字符串, + * 最后将处理后的分页用户信息封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param user 可能包含查询条件的用户对象,比如按昵称模糊查询、按状态精确查询等。 + * @param page 分页参数对象,包含页码、每页数量等信息。 + * @return 返回包含分页用户信息的ServerResponseEntity,成功时其数据部分为IPage类型。 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('admin:user:page')") - public ServerResponseEntity> page(User user,PageParam page) { + public ServerResponseEntity> page(User user, PageParam page) { IPage userPage = userService.page(page, new LambdaQueryWrapper() - .like(StrUtil.isNotBlank(user.getNickName()), User::getNickName, user.getNickName()) - .eq(user.getStatus() != null, User::getStatus, user.getStatus())); + .like(StrUtil.isNotBlank(user.getNickName()), User::getNickName, user.getNickName()) + .eq(user.getStatus()!= null, User::getStatus, user.getStatus())); for (User userResult : userPage.getRecords()) { - userResult.setNickName(StrUtil.isBlank(userResult.getNickName()) ? "" : userResult.getNickName()); + userResult.setNickName(StrUtil.isBlank(userResult.getNickName())? "" : userResult.getNickName()); } return ServerResponseEntity.success(userPage); } /** - * 获取信息 + * 获取指定用户详细信息的接口方法。 + * 通过路径变量获取要查询的用户的ID(这里是字符串类型,可能根据业务实际情况而定), + * 调用UserService的getById方法获取对应的用户对象,对用户的昵称进行处理(若为空则设置为空字符串), + * 再将处理后的用户对象封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param userId 要获取信息的用户的唯一标识符(ID,此处为字符串类型)。 + * @return 返回包含指定用户详细信息的ServerResponseEntity,成功时其数据部分为User类型。 */ @GetMapping("/info/{userId}") @PreAuthorize("@pms.hasPermission('admin:user:info')") public ServerResponseEntity info(@PathVariable("userId") String userId) { User user = userService.getById(userId); - user.setNickName(StrUtil.isBlank(user.getNickName()) ? "" : user.getNickName()); + user.setNickName(StrUtil.isBlank(user.getNickName())? "" : user.getNickName()); return ServerResponseEntity.success(user); } /** - * 修改 + * 修改用户信息的接口方法。 + * 接收通过请求体传入的用户对象(包含要修改的用户相关信息), + * 先设置修改时间为当前时间(通过new Date()获取),对用户昵称进行处理(若为空则设置为空字符串), + * 然后调用UserService的updateById方法根据用户的ID更新数据库中的对应用户记录, + * 最后返回表示修改成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * + * @param user 要修改的用户对象,通过请求体传入,包含用户的各项属性信息,如昵称、其他个人信息等可能需要修改的属性。 + * @return 返回表示修改成功的ServerResponseEntity,无数据内容(Void类型)。 */ @PutMapping @PreAuthorize("@pms.hasPermission('admin:user:update')") public ServerResponseEntity update(@RequestBody User user) { user.setModifyTime(new Date()); - user.setNickName(StrUtil.isBlank(user.getNickName()) ? "" : user.getNickName()); + user.setNickName(StrUtil.isBlank(user.getNickName())? "" : user.getNickName()); userService.updateById(user); return ServerResponseEntity.success(); } /** - * 删除 + * 批量删除用户信息的接口方法。 + * 接收通过请求体传入的用户ID数组(字符串类型数组),将其转换为List集合后, + * 调用UserService的removeByIds方法根据传入的用户ID集合批量删除数据库中的对应用户记录, + * 最后返回表示删除成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * + * @param userIds 要删除的用户的ID数组(字符串类型),通过请求体传入。 + * @return 返回表示删除成功的ServerResponseEntity,无数据内容(Void类型)。 */ @DeleteMapping @PreAuthorize("@pms.hasPermission('admin:user:delete')") @@ -82,4 +114,4 @@ public class UserController { userService.removeByIds(Arrays.asList(userIds)); return ServerResponseEntity.success(); } -} +} \ No newline at end of file diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/task/OrderTask.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/task/OrderTask.java index 75ba7df..fb0c0a5 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/task/OrderTask.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/task/OrderTask.java @@ -27,31 +27,47 @@ import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; - /** - * @author FrozenWatermelon + * 订单相关定时任务类,包含了取消超时未支付订单以及系统自动确认收货订单的定时任务逻辑。 * 定时任务的配置,请查看xxl-job的java配置文件。 * @see com.yami.shop.admin.config.XxlJobConfig + * + * @author FrozenWatermelon */ @Component("orderTask") public class OrderTask { - + // 创建一个日志记录器,用于记录定时任务执行过程中的相关信息,方便后续查看任务执行情况以及排查问题。 private static final Logger logger = LoggerFactory.getLogger(OrderTask.class); + // 注入OrderService,用于处理与订单相关的业务逻辑,例如查询订单、取消订单、确认订单等操作。 @Autowired private OrderService orderService; + + // 注入ProductService,用于处理与商品相关的业务逻辑,在订单操作涉及影响商品缓存的场景下(如订单取消、确认收货等),会调用其方法来清除相关商品缓存,保证数据一致性。 @Autowired private ProductService productService; + + // 注入SkuService,用于处理与商品规格(Sku)相关的业务逻辑,同样在订单操作涉及影响商品规格缓存的场景下,会调用其方法来清除相关规格缓存,保证数据一致性。 @Autowired private SkuService skuService; + /** + * 取消超时未支付订单的定时任务方法,通过@XxlJob注解标记该方法为一个XXL-JOB定时任务,任务名为"cancelOrder"。 + * 任务逻辑如下: + * 1. 首先获取当前时间,用于后续判断订单是否超时未支付。 + * 2. 从数据库中查询出状态为未支付(OrderStatus.UNPAY.value()表示未支付状态对应的代码值)且距离当前时间已经超过30分钟的订单列表。 + * 3. 如果查询到的订单列表为空,则直接返回,不进行后续操作。 + * 4. 若订单列表不为空,则调用OrderService的cancelOrders方法来取消这些超时未支付的订单。 + * 5. 接着遍历这些被取消的订单,对于每个订单中的商品项(OrderItem),分别调用ProductService和SkuService的方法来清除对应的商品缓存和商品规格缓存, + * 以确保相关数据的一致性,避免缓存数据与数据库实际数据不一致的情况。 + */ @XxlJob("cancelOrder") - public void cancelOrder(){ + public void cancelOrder() { Date now = new Date(); logger.info("取消超时未支付订单。。。"); - // 获取30分钟之前未支付的订单 - List orders = orderService.listOrderAndOrderItems(OrderStatus.UNPAY.value(),DateUtil.offsetMinute(now, -30)); + // 获取30分钟之前未支付的订单,通过OrderService的listOrderAndOrderItems方法,传入未支付状态值和30分钟之前的时间作为筛选条件进行查询。 + List orders = orderService.listOrderAndOrderItems(OrderStatus.UNPAY.value(), DateUtil.offsetMinute(now, -30)); if (CollectionUtil.isEmpty(orders)) { return; } @@ -60,20 +76,27 @@ public class OrderTask { List orderItems = order.getOrderItems(); for (OrderItem orderItem : orderItems) { productService.removeProductCacheByProdId(orderItem.getProdId()); - skuService.removeSkuCacheBySkuId(orderItem.getSkuId(),orderItem.getProdId()); + skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); } } } /** - * 确认收货 + * 系统自动确认收货订单的定时任务方法,通过@XxlJob注解标记该方法为一个XXL-JOB定时任务,任务名为"confirmOrder"。 + * 任务逻辑如下: + * 1. 首先获取当前时间,用于后续判断订单是否满足自动确认收货的时间条件。 + * 2. 从数据库中查询出状态为已发货(OrderStatus.CONSIGNMENT.value()表示已发货状态对应的代码值)且距离当前时间已经超过15天的订单列表。 + * 3. 如果查询到的订单列表为空,则直接返回,不进行后续操作。 + * 4. 若订单列表不为空,则调用OrderService的confirmOrder方法来自动确认这些符合条件的订单已收货。 + * 5. 接着遍历这些被确认收货的订单,对于每个订单中的商品项(OrderItem),分别调用ProductService和SkuService的方法来清除对应的商品缓存和商品规格缓存, + * 以确保相关数据的一致性,避免缓存数据与数据库实际数据不一致的情况。 */ @XxlJob("confirmOrder") - public void confirmOrder(){ + public void confirmOrder() { Date now = new Date(); logger.info("系统自动确认收货订单。。。"); - // 获取15天之前未支付的订单 - List orders = orderService.listOrderAndOrderItems(OrderStatus.CONSIGNMENT.value(),DateUtil.offsetDay(now, -15)); + // 获取15天之前已发货的订单,通过OrderService的listOrderAndOrderItems方法,传入已发货状态值和15天之前的时间作为筛选条件进行查询。 + List orders = orderService.listOrderAndOrderItems(OrderStatus.CONSIGNMENT.value(), DateUtil.offsetDay(now, -15)); if (CollectionUtil.isEmpty(orders)) { return; } @@ -82,9 +105,9 @@ public class OrderTask { List orderItems = order.getOrderItems(); for (OrderItem orderItem : orderItems) { productService.removeProductCacheByProdId(orderItem.getProdId()); - skuService.removeSkuCacheBySkuId(orderItem.getSkuId(),orderItem.getProdId()); + skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); } } } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/ApiApplication.java b/yami-shop-api/src/main/java/com/yami/shop/api/ApiApplication.java index 396ae18..64c86e3 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/ApiApplication.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/ApiApplication.java @@ -9,13 +9,13 @@ */ // 声明该类所在的包名,在Java中包用于对类进行组织和分类管理,方便代码的模块化以及避免类名冲突等问题。 +// 此处表明该类属于com.yami.shop这个包结构下,后续相关的类可以按照包的层次结构进行合理组织和查找。 package com.yami.shop.api; // 引入Spring Boot相关的核心注解,用于标记这是一个Spring Boot应用程序,它会开启Spring Boot的自动配置等一系列功能,简化Spring应用的搭建和部署流程。 import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; // 用于以编程方式构建Spring Boot应用程序,特别是在需要对应用的启动配置等进行更多定制化操作时会用到,比如配置应用的启动类、配置文件等信息。 -import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.autoconfigure.SpringBootApplication; // Spring Boot提供的一个用于支持将Spring Boot应用部署为Web应用(例如部署到Servlet容器中)的基础类,通常需要继承它来做相关的适配和初始化工作。 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; // 用于指定Spring需要扫描的组件所在的基础包路径,Spring会在这些包及其子包下查找带有相关注解(如 @Component、@Service等)的类,并将它们注册为Spring容器中的组件,便于进行依赖注入等操作。 @@ -23,22 +23,28 @@ import org.springframework.context.annotation.ComponentScan; /** * @author lgh - * 该类是整个API应用的启动类,负责启动Spring Boot应用,配置相关的组件扫描以及支持将应用部署到Servlet容器等功能。 + * 该类是整个API应用的启动类,它承担着启动Spring Boot应用的核心职责,同时配置了相关的组件扫描机制以及提供了将应用部署到Servlet容器时所需的支持功能,是整个应用启动流程的关键入口点。 */ +// @SpringBootApplication是一个复合注解,它整合了多个Spring相关的注解,例如 @Configuration(表示这是一个配置类)、@EnableAutoConfiguration(开启Spring Boot的自动配置功能)以及 @ComponentScan(默认扫描当前类所在的包及其子包下的组件)等。 +// 在这里使用它来标记这个类是Spring Boot应用的主配置类,Spring Boot会基于这个类进行一系列的启动和配置操作。 @SpringBootApplication // 通过 @ComponentScan 注解指定Spring要扫描的基础包路径为 "com.yami.shop",意味着Spring会在这个包及其子包下查找并注册各种Spring组件(如控制器、服务类、数据访问层等)到Spring容器中,以便进行后续的依赖注入和业务逻辑处理。 +// 这样可以确保我们自定义的各个业务组件能够被Spring正确识别并管理起来,方便在不同的组件之间进行依赖注入,实现各种业务功能。 @ComponentScan(basePackages = {"com.yami.shop"}) public class ApiApplication extends SpringBootServletInitializer { - // 应用的主入口方法,Java应用程序从这个方法开始执行。当运行这个类的 main 方法时,会启动Spring Boot应用,加载配置、初始化Spring容器并启动相关的Web服务等(如果是Web应用的话)。 - public static void main(String[] args) { - // 使用SpringApplication类的静态方法 run 来启动Spring Boot应用,传入当前启动类(ApiApplication.class)以及命令行参数 args,Spring Boot会根据配置自动完成一系列的初始化和启动操作。 - SpringApplication.run(ApiApplication.class, args); - } + // 应用的主入口方法,Java应用程序从这个方法开始执行。当运行这个类的 main 方法时,会启动Spring Boot应用,加载配置、初始化Spring容器并启动相关的Web服务等(如果是Web应用的话)。 + // 它是整个应用启动的起始点,类似于传统Java应用中程序开始执行的地方,只不过在这里借助Spring Boot的功能来完成更复杂的应用启动流程。 + public static void main(String[] args) { + // 使用SpringApplication类的静态方法 run 来启动Spring Boot应用,传入当前启动类(ApiApplication.class)以及命令行参数 args,Spring Boot会根据配置自动完成一系列的初始化和启动操作。 + // 这个方法内部会执行诸如加载配置文件(application.properties或application.yml等)、创建Spring容器(ApplicationContext)、扫描并注册组件、启动嵌入式Web服务器(如果是Web应用)等一系列操作,使得整个应用能够正常运行起来。 + SpringApplication.run(ApiApplication.class, args); + } - // 重写SpringBootServletInitializer类中的configure方法,用于配置Spring Boot应用在Servlet容器中的启动方式,这里返回一个SpringApplicationBuilder对象,并指定应用的启动类为ApiApplication.class,以便在Servlet容器中正确地启动Spring Boot应用。 - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { - return builder.sources(ApiApplication.class); - } + // 重写SpringBootServletInitializer类中的configure方法,用于配置Spring Boot应用在Servlet容器中的启动方式,这里返回一个SpringApplicationBuilder对象,并指定应用的启动类为ApiApplication.class,以便在Servlet容器中正确地启动Spring Boot应用。 + // 当将Spring Boot应用部署到外部的Servlet容器(如Tomcat、Jetty等)时,容器会调用这个方法来构建和配置Spring Boot应用,确保应用能够在该容器环境下正常启动和运行,实现与Servlet容器的良好集成。 + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(ApiApplication.class); + } } \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiBeanConfig.java b/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiBeanConfig.java index 485feb0..eeb36c9 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiBeanConfig.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiBeanConfig.java @@ -16,17 +16,43 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** + * ApiBeanConfig类在Spring框架应用中扮演着配置类的角色,它的主要职责是对Spring框架中的相关Bean进行配置, + * 并在整个Spring应用上下文初始化的过程中发挥作用。 + + * 通过使用 @Configuration 注解,将这个类标记为Spring的配置类,告知Spring容器在启动时需要扫描并解析这个类中的配置信息, + * 以此来完成Bean的定义和装配等操作。 + + * @AllArgsConstructor 注解则是借助Lombok库的功能,为这个类自动生成一个包含所有参数的构造函数,方便进行依赖注入等操作。 + + * 此类核心功能在于创建并配置Snowflake实例这个Bean,Snowflake是一种常用于分布式系统中生成唯一ID的工具, + * 可以确保在复杂的分布式环境下,生成的ID具有唯一性,满足业务系统对于数据标识唯一性的需求。 + * @author lanhai */ @Configuration @AllArgsConstructor public class ApiBeanConfig { + // 通过Lombok生成的构造函数注入ApiConfig实例,ApiConfig实例应该是用于承载和提供配置相关信息的类, + // 在这里主要是为了获取其中的workerId和datacenterId两个参数,这两个参数对于初始化Snowflake实例至关重要, + // 它们在分布式环境下帮助确定Snowflake生成唯一ID的工作节点和数据中心标识,从而保证生成的ID在整个分布式系统中是唯一的。 private final ApiConfig apiConfig; + /** + * snowflake方法是一个被 @Bean 注解修饰的方法,在Spring框架中,带有 @Bean 注解的方法用于向Spring容器中注册一个Bean。 + * 具体来说,这个方法的功能是创建并返回一个Snowflake实例作为Spring的Bean,以便Spring容器能够对其进行管理, + * 并且在其他需要生成分布式唯一ID的地方,可以方便地通过依赖注入的方式获取并使用这个实例。 + + * Snowflake实例的初始化需要传入workerId和datacenterId两个参数,这里通过调用apiConfig的相应方法来获取这两个参数, + * 以此保证Snowflake实例能够依据正确的配置在分布式环境下生成唯一的ID。 + + * @return 返回一个配置好的Snowflake实例,这个实例将由Spring容器进行管理,后续在整个应用的其他代码部分, + * 只要有生成分布式唯一ID的需求,都可以通过依赖注入的方式获取并使用这个实例来完成相应的操作。 + */ @Bean public Snowflake snowflake() { + // 使用从ApiConfig中获取的workerId和datacenterId来实例化Snowflake对象,确保其在分布式环境下能正确生成唯一ID。 return new Snowflake(apiConfig.getWorkerId(), apiConfig.getDatacenterId()); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiConfig.java b/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiConfig.java index 03469db..7a74b5c 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiConfig.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiConfig.java @@ -8,6 +8,7 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城API相关的配置包下,主要负责处理商城API相关的配置信息加载与管理。 package com.yami.shop.api.config; import lombok.Data; @@ -15,30 +16,52 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; - /** - * 商城配置文件 + * ApiConfig类在整个项目中起着关键的配置管理作用,它专注于加载和管理与商城相关的特定配置信息。 + * 通过运用Spring框架提供的一系列注解,它能够从指定的配置文件中读取相应属性,并将这些属性值映射到类中的成员变量上, + * 同时使其自身成为Spring容器所管理的组件,方便在项目的其他各个部分通过依赖注入的方式获取这些配置信息并加以运用。 + * * @author lgh */ -@Data @Component +// @Component注解是Spring框架中用于标记一个类为Spring组件的基础注解。当Spring容器进行组件扫描时(通常基于@ComponentScan配置的扫描路径), +// 一旦发现被@Component注解标记的类,就会将其实例化并纳入Spring容器进行管理。这使得其他需要使用该类的地方可以通过依赖注入(如@Autowired注解)轻松获取其实例, +// 从而实现了类之间的解耦以及配置信息的统一管理和共享。 +@Component +// @PropertySource注解用于指定配置属性的来源文件,这里的参数"classpath:api.properties"表示会从类路径下查找名为api.properties的文件作为配置信息的数据源。 +// 在实际的项目构建和部署过程中,这个文件会随着项目一起打包,并且在应用启动时,Spring会读取其中的配置内容,按照后续的配置绑定规则进行属性赋值操作。 @PropertySource("classpath:api.properties") +// @ConfigurationProperties注解在Spring Boot应用中扮演着重要角色,它用于将配置文件中的属性值绑定到对应的Java类的成员变量上。 +// 这里的参数"prefix = "api""指定了配置文件中属性的前缀,意味着Spring会查找以"api"开头的属性,并将其对应的值自动注入到这个类中名称相同(忽略大小写)的成员变量中。 +// 例如,如果配置文件中有"api.datacenterId = 1"这样的配置项,那么就会自动将值1赋给这个类中的datacenterId成员变量,实现了配置文件与Java代码之间的无缝对接,方便配置管理。 @ConfigurationProperties(prefix = "api") +// 使用lombok的@Data注解,这是一个便捷的代码生成注解,它会为类自动生成一系列常用的方法,包括成员变量的Getter和Setter方法、toString方法、equals方法以及hashCode方法等。 +// 通过提供这些自动生成的方法,使得在其他类中对该类的成员变量进行访问、赋值以及在对象比较、哈希计算等操作时更加方便,减少了手动编写这些重复代码的工作量,提高了代码的简洁性和可读性。 +@Data public class ApiConfig { - /** - * 数据中心ID - */ - private Integer datacenterId; + /** + * 数据中心ID,在分布式系统环境下具有重要意义。 + * 在一些复杂的分布式架构中,可能存在多个数据中心,每个数据中心负责处理不同区域或业务模块的数据存储与管理等工作。 + * 这个数据中心ID就用于唯一标识每个不同的数据中心,例如在生成分布式唯一ID(像Snowflake算法生成唯一ID时,会结合数据中心ID和其他参数来确保全局唯一性)的场景中, + * 它起到了区分不同数据中心来源的作用,有助于保证整个分布式系统中数据标识的唯一性和准确性。 + */ + private Integer datacenterId; - /** - * 终端ID - */ - private Integer workerId; + /** + * 终端ID,同样是分布式架构中常用的一个标识概念。 + * 在分布式系统里,往往会有多个工作终端或者节点参与到各种业务处理流程中,比如不同的服务器实例、微服务实例等都可以看作是不同的终端。 + * 终端ID用于明确地区分这些不同的工作终端或者节点,使得系统在进行诸如资源调度、任务分配以及数据交互等操作时,能够准确识别每个终端的身份。 + * 例如在一些分布式ID生成算法(如Snowflake算法)中,终端ID与数据中心ID等参数共同作用,生成唯一的、具有业务含义的标识符,便于对系统中的各种资源和操作进行精确管理。 + */ + private Integer workerId; - /** - * 域名 - */ - private String domainName; + /** + * 域名,是商城系统在网络环境中的重要标识之一。 + * 它代表了商城系统所使用的网络域名,在构建商城的网络访问地址、配置与外部系统的网络交互(如与第三方支付平台、物流平台等进行接口调用时的回调地址配置)、 + * 以及设置各种网络服务(如配置反向代理服务器指向的目标域名等)等方面都起着关键作用。 + * 通过在配置文件中配置这个域名信息,并将其绑定到该类的成员变量上,方便在整个项目的不同模块中统一使用这个域名来构建完整的网络访问路径,确保网络通信的准确性和一致性。 + */ + private String domainName; -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/config/SwaggerConfiguration.java b/yami-shop-api/src/main/java/com/yami/shop/api/config/SwaggerConfiguration.java index e2048f3..336e046 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/config/SwaggerConfiguration.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/config/SwaggerConfiguration.java @@ -18,26 +18,42 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * Swagger文档,只有在测试环境才会使用 + * Swagger文档配置类 + * 该类用于配置Swagger相关的信息,只有在测试环境才会启用(可能在实际部署中根据环境配置来决定是否生效),主要作用是生成接口文档相关的配置内容, + * 比如定义接口文档的分组、扫描的包路径以及文档的基本信息(标题、描述、版本、授权协议等)。 * @author LGH */ @Configuration public class SwaggerConfiguration { - @Bean - public GroupedOpenApi createRestApi() { - return GroupedOpenApi.builder() - .group("接口文档") - .packagesToScan("com.yami.shop.api").build(); - } + /** + * 创建分组的OpenAPI对象的方法,用于定义接口文档的分组以及要扫描的包路径,以此来生成对应分组下的接口文档内容。 + * @return 返回一个GroupedOpenApi对象,该对象包含了接口文档分组相关的配置信息。 + */ + @Bean + public GroupedOpenApi createRestApi() { + return GroupedOpenApi.builder() + // 设置接口文档的分组名称,方便在Swagger界面中对接口进行分类查看等操作 + .group("接口文档") + // 指定要扫描的包路径,Swagger会根据这个路径去查找接口相关的类,进而生成对应的接口文档信息 + .packagesToScan("com.yami.shop.api").build(); + } - - @Bean - public OpenAPI springShopOpenApi() { - return new OpenAPI() - .info(new Info().title("Mall4j接口文档") - .description("Mall4j接口文档,openapi3.0 接口,用于前端对接") - .version("v0.0.1") - .license(new License().name("使用请遵守AGPL3.0授权协议").url("https://www.mall4j.com"))); - } -} + /** + * 创建OpenAPI对象的方法,用于配置整个接口文档的基本信息,如标题、描述、版本以及授权协议相关的内容。 + * @return 返回一个OpenAPI对象,该对象包含了接口文档的元信息配置。 + */ + @Bean + public OpenAPI springShopOpenApi() { + return new OpenAPI() + .info(new Info() + // 设置接口文档的标题,会显示在Swagger界面的顶部等显著位置 + .title("Mall4j接口文档") + // 设置接口文档的描述信息,用于简要说明文档的用途、涵盖范围等内容 + .description("Mall4j接口文档,openapi3.0 接口,用于前端对接") + // 设置接口文档的版本号,方便对不同版本的接口文档进行区分和管理 + .version("v0.0.1") + // 设置接口文档的授权协议相关信息,包括协议名称和协议对应的网址 + .license(new License().name("使用请遵守AGPL3.0授权协议").url("https://www.mall4j.com"))); + } +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java index 739cd7e..6e64836 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java @@ -31,6 +31,8 @@ import java.util.Date; import java.util.List; /** + * 地址相关接口的控制器类,用于处理与用户地址相关的各种操作请求,例如获取地址列表、新增地址、修改地址、删除地址、设置默认地址以及获取单个地址信息等。 + * * @author lanhai */ @RestController @@ -39,120 +41,195 @@ import java.util.List; @AllArgsConstructor public class AddrController { + // 自动注入用户地址服务层接口,通过该接口调用具体的业务逻辑方法来处理用户地址相关业务 @Autowired private UserAddrService userAddrService; /** - * 选择订单配送地址 + * 获取用户地址列表的方法,用于选择订单配送地址时展示所有可用的用户地址。 + * 通过查询数据库,获取当前用户的所有地址信息,并按照是否为常用地址以及更新时间进行降序排序,最后将查询到的地址信息转换为对应的 DTO 类型返回给前端。 + * + * @return 包含用户地址信息列表的 ServerResponseEntity,其中地址信息以 UserAddrDto 类型封装,方便前端展示和使用。 */ @GetMapping("/list") - @Operation(summary = "用户地址列表" , description = "获取用户的所有地址信息") + @Operation(summary = "用户地址列表", description = "获取用户的所有地址信息") public ServerResponseEntity> dvyList() { + // 获取当前用户的 ID,用于筛选属于该用户的地址记录 String userId = SecurityUtils.getUser().getUserId(); - List userAddrs = userAddrService.list(new LambdaQueryWrapper().eq(UserAddr::getUserId, userId).orderByDesc(UserAddr::getCommonAddr).orderByDesc(UserAddr::getUpdateTime)); + // 使用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件,查询当前用户的所有地址信息,并按照常用地址和更新时间降序排序 + List userAddrs = userAddrService.list(new LambdaQueryWrapper() + .eq(UserAddr::getUserId, userId) + .orderByDesc(UserAddr::getCommonAddr) + .orderByDesc(UserAddr::getUpdateTime)); + // 使用 Hutool 的 BeanUtil 将查询到的 UserAddr 类型的地址列表转换为 UserAddrDto 类型列表,方便返回给前端展示 return ServerResponseEntity.success(BeanUtil.copyToList(userAddrs, UserAddrDto.class)); } + /** + * 新增用户地址的方法,接收包含地址信息的 AddrParam 对象,进行一系列验证和设置后将新地址保存到数据库中,同时处理默认地址缓存相关逻辑。 + * 如果传入的地址 ID 不为空且不为 0,则认为该地址已存在,返回相应的错误提示信息;若用户当前没有地址记录,则新增的地址设置为默认地址;否则设置为非默认地址, + * 最后保存地址信息到数据库,并根据是否为默认地址决定是否清除默认地址缓存,成功保存后返回成功提示信息给前端。 + * + * @param addrParam 包含要新增的用户地址信息的参数对象,通过请求体传入,且经过了参数验证(@Valid 注解) + * @return 包含操作结果提示信息的 ServerResponseEntity,成功则返回 "添加地址成功",地址 ID 不符合要求则返回相应错误提示。 + */ @PostMapping("/addAddr") - @Operation(summary = "新增用户地址" , description = "新增用户地址") + @Operation(summary = "新增用户地址", description = "新增用户地址") public ServerResponseEntity addAddr(@Valid @RequestBody AddrParam addrParam) { + // 获取当前用户的 ID,用于关联新地址到该用户 String userId = SecurityUtils.getUser().getUserId(); - if (addrParam.getAddrId() != null && addrParam.getAddrId() != 0) { + // 如果传入的地址 ID 不为空且不等于 0,说明该地址可能已存在,返回相应的错误提示信息 + if (addrParam.getAddrId()!= null && addrParam.getAddrId()!= 0) { return ServerResponseEntity.showFailMsg("该地址已存在"); } + + // 统计当前用户已有的地址数量,用于判断是否需要将新地址设置为默认地址 long addrCount = userAddrService.count(new LambdaQueryWrapper().eq(UserAddr::getUserId, userId)); + // 使用 Hutool 的 BeanUtil 将 AddrParam 类型的参数对象转换为 UserAddr 类型,方便保存到数据库 UserAddr userAddr = BeanUtil.copyProperties(addrParam, UserAddr.class); + // 如果当前用户没有地址记录,则将新地址设置为默认地址(常用地址,commonAddr 设为 1) if (addrCount == 0) { userAddr.setCommonAddr(1); } else { + // 否则设置为非默认地址(commonAddr 设为 0) userAddr.setCommonAddr(0); } + + // 设置用户 ID,明确该地址所属的用户 userAddr.setUserId(userId); + // 设置地址状态为有效(这里假设 1 表示有效状态) userAddr.setStatus(1); + // 设置地址的创建时间为当前时间 userAddr.setCreateTime(new Date()); + // 设置地址的更新时间为当前时间 userAddr.setUpdateTime(new Date()); + + // 调用用户地址服务层的保存方法,将新地址信息保存到数据库中 userAddrService.save(userAddr); + + // 如果新地址被设置为默认地址(commonAddr 为 1),则清除默认地址缓存,确保缓存数据的一致性 if (userAddr.getCommonAddr() == 1) { - // 清除默认地址缓存 userAddrService.removeUserAddrByUserId(0L, userId); } + return ServerResponseEntity.success("添加地址成功"); } /** - * 修改订单配送地址 + * 修改用户地址的方法,接收包含更新后地址信息的 AddrParam 对象,先验证地址是否存在,然后更新地址信息到数据库中, + * 同时清除当前地址和默认地址的缓存,确保数据的准确性和缓存的一致性,最后返回修改成功的提示信息给前端。 + * + * @param addrParam 包含要修改的用户地址信息的参数对象,通过请求体传入,且经过了参数验证(@Valid 注解) + * @return 包含操作结果提示信息的 ServerResponseEntity,成功则返回 "修改地址成功",若地址不存在则返回相应错误提示。 */ @PutMapping("/updateAddr") - @Operation(summary = "修改订单用户地址" , description = "修改用户地址") + @Operation(summary = "修改订单用户地址", description = "修改用户地址") public ServerResponseEntity updateAddr(@Valid @RequestBody AddrParam addrParam) { + // 获取当前用户的 ID,用于验证地址是否属于该用户以及后续更新操作 String userId = SecurityUtils.getUser().getUserId(); + // 根据传入的地址 ID 和用户 ID,从数据库中查询对应的用户地址记录,若不存在则返回相应的错误提示信息 UserAddr dbUserAddr = userAddrService.getUserAddrByUserId(addrParam.getAddrId(), userId); if (dbUserAddr == null) { return ServerResponseEntity.showFailMsg("该地址已被删除"); } + // 使用 Hutool 的 BeanUtil 将 AddrParam 类型的参数对象转换为 UserAddr 类型,准备更新到数据库 UserAddr userAddr = BeanUtil.copyProperties(addrParam, UserAddr.class); + // 设置用户 ID,确保更新的是正确用户的地址记录 userAddr.setUserId(userId); + // 设置地址的更新时间为当前时间 userAddr.setUpdateTime(new Date()); + + // 调用用户地址服务层的更新方法,将更新后的地址信息保存到数据库中 userAddrService.updateById(userAddr); - // 清除当前地址缓存 + + // 清除当前地址的缓存,保证缓存数据与数据库最新数据一致 userAddrService.removeUserAddrByUserId(addrParam.getAddrId(), userId); - // 清除默认地址缓存 + // 清除默认地址的缓存,确保默认地址相关数据的准确性 userAddrService.removeUserAddrByUserId(0L, userId); + return ServerResponseEntity.success("修改地址成功"); } /** - * 删除订单配送地址 + * 删除用户地址的方法,根据传入的地址 ID,先验证地址是否存在以及是否为默认地址(默认地址不允许删除), + * 若验证通过则从数据库中删除该地址记录,并清除对应地址的缓存,最后返回删除成功的提示信息给前端。 + * + * @param addrId 要删除的用户地址的 ID,通过路径变量传入 + * @return 包含操作结果提示信息的 ServerResponseEntity,成功则返回 "删除地址成功",若地址不存在或为默认地址则返回相应错误提示。 */ @DeleteMapping("/deleteAddr/{addrId}") - @Operation(summary = "删除订单用户地址" , description = "根据地址id,删除用户地址") - @Parameter(name = "addrId", description = "地址ID" , required = true) + @Operation(summary = "删除订单用户地址", description = "根据地址id,删除用户地址") + @Parameter(name = "addrId", description = "地址ID", required = true) public ServerResponseEntity deleteDvy(@PathVariable("addrId") Long addrId) { + // 获取当前用户的 ID,用于验证地址是否属于该用户以及后续删除操作 String userId = SecurityUtils.getUser().getUserId(); + // 根据传入的地址 ID 和用户 ID,从数据库中查询对应的用户地址记录,若不存在则返回相应的错误提示信息 UserAddr userAddr = userAddrService.getUserAddrByUserId(addrId, userId); if (userAddr == null) { return ServerResponseEntity.showFailMsg("该地址已被删除"); } + + // 如果该地址是默认地址(commonAddr 为 1),则不允许删除,返回相应的错误提示信息 if (userAddr.getCommonAddr() == 1) { return ServerResponseEntity.showFailMsg("默认地址无法删除"); } + + // 调用用户地址服务层的删除方法,从数据库中删除该地址记录 userAddrService.removeById(addrId); + // 清除对应地址的缓存,保证缓存数据与数据库数据的一致性 userAddrService.removeUserAddrByUserId(addrId, userId); + return ServerResponseEntity.success("删除地址成功"); } /** - * 设置默认地址 + * 设置默认地址的方法,根据传入的地址 ID,调用服务层方法将该地址设置为当前用户的默认地址, + * 同时清除默认地址和当前地址的缓存,确保缓存数据与数据库最新状态一致,最后返回修改成功的提示信息给前端。 + * + * @param addrId 要设置为默认地址的用户地址的 ID,通过路径变量传入 + * @return 包含操作结果提示信息的 ServerResponseEntity,成功则返回 "修改地址成功"。 */ @PutMapping("/defaultAddr/{addrId}") - @Operation(summary = "设置默认地址" , description = "根据地址id,设置默认地址") + @Operation(summary = "设置默认地址", description = "根据地址id,设置默认地址") public ServerResponseEntity defaultAddr(@PathVariable("addrId") Long addrId) { + // 获取当前用户的 ID,用于关联默认地址到该用户 String userId = SecurityUtils.getUser().getUserId(); + // 调用用户地址服务层的方法,将指定地址 ID 的地址设置为当前用户的默认地址 userAddrService.updateDefaultUserAddr(addrId, userId); + // 清除默认地址的缓存,保证缓存数据与数据库最新数据一致 userAddrService.removeUserAddrByUserId(0L, userId); + // 清除当前设置为默认地址的地址缓存,确保相关数据准确性 userAddrService.removeUserAddrByUserId(addrId, userId); + return ServerResponseEntity.success("修改地址成功"); } /** - * 获取地址信息订单配送地址 + * 获取单个用户地址详细信息的方法,根据传入的地址 ID,先验证地址是否存在,若存在则将对应的地址信息转换为 UserAddrDto 类型返回给前端, + * 若地址不存在则抛出相应的异常(由全局异常处理机制处理,这里假设会返回合适的错误提示给前端)。 + * + * @param addrId 要获取详细信息的用户地址的 ID,通过路径变量传入 + * @return 包含单个用户地址详细信息的 ServerResponseEntity,以 UserAddrDto 类型封装地址信息,若地址不存在则抛出异常。 */ @GetMapping("/addrInfo/{addrId}") - @Operation(summary = "获取地址信息" , description = "根据地址id,获取地址信息") - @Parameter(name = "addrId", description = "地址ID" , required = true) + @Operation(summary = "获取地址信息", description = "根据地址id,获取地址信息") + @Parameter(name = "addrId", description = "地址ID", required = true) public ServerResponseEntity addrInfo(@PathVariable("addrId") Long addrId) { + // 获取当前用户的 ID,用于验证地址是否属于该用户以及查询操作 String userId = SecurityUtils.getUser().getUserId(); + // 根据传入的地址 ID 和用户 ID,从数据库中查询对应的用户地址记录,若不存在则抛出相应的异常 UserAddr userAddr = userAddrService.getUserAddrByUserId(addrId, userId); if (userAddr == null) { throw new YamiShopBindException("该地址已被删除"); } + + // 使用 Hutool 的 BeanUtil 将查询到的 UserAddr 类型的地址信息转换为 UserAddrDto 类型,方便返回给前端展示 return ServerResponseEntity.success(BeanUtil.copyProperties(userAddr, UserAddrDto.class)); } - -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/IndexImgController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/IndexImgController.java index 487a093..bc3ee5c 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/IndexImgController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/IndexImgController.java @@ -24,23 +24,36 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; /** + * 首页轮播图相关的接口控制器类,主要负责向外提供获取首页轮播图列表信息的接口, + * 通过调用对应的服务层方法获取数据,并进行必要的数据转换后返回给客户端。 + * * @author lanhai */ @RestController +// 使用 @Tag 注解为该控制器类添加标签说明,用于在 API 文档(如 Swagger 生成的文档)中对该类下的接口进行分类展示,这里表明是“首页轮播图接口”相关的一组接口。 @Tag(name = "首页轮播图接口") public class IndexImgController { + // 注入IndexImgService,用于与首页轮播图相关的业务逻辑处理,例如从数据库中查询轮播图列表等操作。 @Autowired private IndexImgService indexImgService; /** - * 首页轮播图接口 + * 首页轮播图接口方法,用于获取首页轮播图的列表信息,并将其返回给客户端。 + * 通过 @GetMapping 注解将该方法映射到 HTTP 的 GET 请求方式,请求路径为“/indexImgs”,客户端可以通过该路径访问此接口获取数据。 + * @Operation 注解用于在 API 文档中对该接口进行详细描述,包括接口的简要说明(summary)和详细描述(description),方便接口使用者了解接口的功能。 + * + * @return 返回一个包含首页轮播图信息的ServerResponseEntity对象,若获取成功则响应体中包含轮播图列表数据(以IndexImgDto列表形式返回),否则返回相应错误信息。 */ @GetMapping("/indexImgs") - @Operation(summary = "首页轮播图" , description = "获取首页轮播图列表信息") + @Operation(summary = "首页轮播图", description = "获取首页轮播图列表信息") public ServerResponseEntity> indexImgs() { + // 调用IndexImgService的listIndexImg方法,从数据库或其他数据源获取首页轮播图的原始数据列表(以IndexImg对象列表形式返回)。 List indexImgList = indexImgService.listIndexImg(); + // 使用hutool的BeanUtil工具类,将IndexImg对象列表转换为IndexImgDto对象列表,IndexImgDto可能是用于对外展示的、经过筛选或格式调整后的视图对象, + // 这样可以避免直接将内部的业务实体对象暴露给客户端,更好地控制数据的展示格式和安全性。 List indexImgDtos = BeanUtil.copyToList(indexImgList, IndexImgDto.class); + // 将转换后的IndexImgDto列表封装在表示成功的ServerResponseEntity对象中返回给客户端,遵循统一的接口响应格式规范。 return ServerResponseEntity.success(indexImgDtos); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java index 711bfc1..7bfed55 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java @@ -8,6 +8,7 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城API的控制器包下,主要用于处理与“我的订单”相关的接口请求及业务逻辑。 package com.yami.shop.api.controller; import cn.hutool.core.bean.BeanUtil; @@ -36,58 +37,85 @@ import java.util.List; import java.util.Objects; /** + * MyOrderController类是一个Spring RESTful风格的控制器,主要用于处理与“我的订单”相关的各种操作接口, + * 涵盖了获取订单详情、订单列表查询、取消订单、确认收货、删除订单以及获取订单数量等功能,为前端应用提供了相应的后端服务支持。 + * * @author lanhai */ @RestController +// 定义该控制器类的基础请求路径,所有该类中的接口请求路径都将以此为前缀,表明是与“我的订单”相关的操作接口。 @RequestMapping("/p/myOrder") +// 使用Swagger的@Tag注解对该控制器类进行标记,用于在API文档中生成对应的分类标签,方便接口文档的分类展示和阅读,这里表示该类下的接口都属于“我的订单接口”这一分类。 @Tag(name = "我的订单接口") +// 使用lombok的@AllArgsConstructor注解,自动生成包含所有final字段的构造函数,方便依赖注入,这里会为所有注入的服务类生成对应的构造函数参数。 @AllArgsConstructor public class MyOrderController { + // 通过构造函数注入OrderService实例,用于调用与订单相关的业务逻辑方法,比如获取订单、取消订单、确认收货等操作。 private final OrderService orderService; - + // 通过构造函数注入UserAddrOrderService实例,用于处理用户地址与订单相关的业务逻辑,可能涉及获取订单对应的用户地址等操作。 private final UserAddrOrderService userAddrOrderService; + // 通过构造函数注入ProductService实例,用于操作产品相关的业务逻辑,例如可能在某些操作后需要清除产品缓存等情况会用到该服务。 private final ProductService productService; + // 通过构造函数注入SkuService实例,用于处理商品库存单元(SKU)相关的业务逻辑,比如根据SKU ID进行缓存清除等操作。 private final SkuService skuService; + // 通过构造函数注入MyOrderService实例,用于获取与“我的订单”相关的分页数据等业务逻辑,例如根据用户ID和订单状态查询订单列表等操作。 private final MyOrderService myOrderService; + // 通过构造函数注入ShopDetailService实例,用于获取店铺详情信息,比如在获取订单详情时需要展示店铺名称等信息时会调用该服务获取相应数据。 private final ShopDetailService shopDetailService; + // 通过构造函数注入OrderItemService实例,用于获取订单商品项相关的业务逻辑,例如根据订单号获取订单中包含的具体商品项信息等操作。 private final OrderItemService orderItemService; - /** - * 订单详情信息接口 + * 订单详情信息接口方法。 + * 根据传入的订单号(orderNumber),获取对应的订单详情信息,包括店铺信息、用户地址信息、订单商品项信息等,并进行相应的数据组装和计算, + * 最后将完整的订单详情信息封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param orderNumber 要查询详情的订单的唯一标识符,通过请求参数传入,是必须提供的参数,用于定位具体的订单记录。 + * @return 返回包含订单详情信息的ServerResponseEntity,成功时其数据部分为OrderShopDto类型,该类型包含了订单的各种详细信息,如店铺信息、商品信息、总价等。 */ @GetMapping("/orderDetail") + // 使用Swagger的@Operation注解对该接口方法进行描述,用于在API文档中生成对应的接口说明信息,这里简要说明了该接口的功能是获取订单详情信息。 @Operation(summary = "订单详情信息", description = "根据订单号获取订单详情信息") + // 使用Swagger的@Parameter注解对接口方法的参数进行描述,这里指定了orderNumber参数的名称、描述以及其为必填项等信息,方便在API文档中展示参数详情。 @Parameter(name = "orderNumber", description = "订单号", required = true) public ServerResponseEntity orderDetail(@RequestParam(value = "orderNumber") String orderNumber) { + // 通过SecurityUtils获取当前登录用户的ID,用于后续验证用户是否有权限获取该订单信息等操作。 String userId = SecurityUtils.getUser().getUserId(); OrderShopDto orderShopDto = new OrderShopDto(); + // 调用OrderService的getOrderByOrderNumber方法,根据订单号获取对应的订单对象,如果订单不存在则抛出异常。 Order order = orderService.getOrderByOrderNumber(orderNumber); - if (order == null) { throw new RuntimeException("该订单不存在"); } + // 验证当前登录用户是否有权限获取该订单信息,通过比较订单中的用户ID和当前登录用户的ID是否一致来判断,如果不一致则抛出异常。 if (!Objects.equals(order.getUserId(), userId)) { throw new RuntimeException("你没有权限获取该订单信息"); } + // 调用ShopDetailService的getShopDetailByShopId方法,根据订单所属店铺的ID获取店铺详情信息,用于填充订单详情中的店铺相关信息。 ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(order.getShopId()); + // 调用UserAddrOrderService的getById方法,根据订单关联的地址订单ID获取对应的用户地址订单对象,用于获取用户地址信息。 UserAddrOrder userAddrOrder = userAddrOrderService.getById(order.getAddrOrderId()); + // 使用BeanUtil将UserAddrOrder对象的属性复制到UserAddrDto对象中,进行数据类型转换,方便后续统一处理和返回数据。 UserAddrDto userAddrDto = BeanUtil.copyProperties(userAddrOrder, UserAddrDto.class); + // 调用OrderItemService的getOrderItemsByOrderNumber方法,根据订单号获取该订单下包含的所有商品项信息列表。 List orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber); + // 使用BeanUtil将List中的每个OrderItem对象的属性复制到OrderItemDto对象中,组成新的列表,用于后续统一处理和返回数据。 List orderItemList = BeanUtil.copyToList(orderItems, OrderItemDto.class); + // 将获取到的店铺相关信息设置到OrderShopDto对象中,如店铺ID、店铺名称等。 orderShopDto.setShopId(shopDetail.getShopId()); orderShopDto.setShopName(shopDetail.getShopName()); + // 设置订单的实际总价、用户地址信息、订单商品项信息、运费、优惠金额、创建时间、备注以及订单状态等信息到OrderShopDto对象中。 orderShopDto.setActualTotal(order.getActualTotal()); orderShopDto.setUserAddrDto(userAddrDto); orderShopDto.setOrderItemDtos(orderItemList); @@ -97,21 +125,28 @@ public class MyOrderController { orderShopDto.setRemarks(order.getRemarks()); orderShopDto.setStatus(order.getStatus()); + // 计算订单中商品的总金额和商品总数量,通过遍历订单商品项列表,累加每个商品项的商品总金额和商品数量来实现。 double total = 0.0; Integer totalNum = 0; for (OrderItemDto orderItem : orderShopDto.getOrderItemDtos()) { total = Arith.add(total, orderItem.getProductTotalAmount()); totalNum += orderItem.getProdCount(); } + // 将计算得到的商品总金额和商品总数量设置到OrderShopDto对象中。 orderShopDto.setTotal(total); orderShopDto.setTotalNum(totalNum); return ServerResponseEntity.success(orderShopDto); } - /** - * 订单列表接口 + * 订单列表接口方法。 + * 根据传入的订单状态(status)以及分页参数(page),获取当前登录用户对应的订单列表信息,按照指定的状态进行筛选, + * 如果状态为0则获取所有订单,最后将获取到的分页订单列表信息封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param status 订单状态参数,通过请求参数传入,用于筛选符合指定状态的订单,其取值对应不同的订单状态,如1表示待付款、2表示待发货等。 + * @param page 分页参数对象,包含页码、每页数量等信息,用于进行分页查询操作,通过该参数可以获取指定页的订单列表数据。 + * @return 返回包含分页订单列表信息的ServerResponseEntity,成功时其数据部分为IPage类型,该类型包含了分页后的订单列表数据以及分页相关的元数据信息。 */ @GetMapping("/myOrder") @Operation(summary = "订单列表信息", description = "根据订单状态获取订单列表信息,状态为0时获取所有订单") @@ -119,19 +154,29 @@ public class MyOrderController { @Parameter(name = "status", description = "订单状态 1:待付款 2:待发货 3:待收货 4:待评价 5:成功 6:失败") }) public ServerResponseEntity> myOrder(@RequestParam(value = "status") Integer status, PageParam page) { + // 通过SecurityUtils获取当前登录用户的ID,用于查询该用户对应的订单列表信息。 String userId = SecurityUtils.getUser().getUserId(); + // 调用MyOrderService的pageMyOrderByUserIdAndStatus方法,根据用户ID和订单状态进行分页查询订单列表信息,获取到IPage类型的分页数据对象。 IPage myOrderDtoIpage = myOrderService.pageMyOrderByUserIdAndStatus(page, userId, status); return ServerResponseEntity.success(myOrderDtoIpage); } /** - * 取消订单 + * 取消订单接口方法。 + * 根据传入的订单号(orderNumber),先验证当前登录用户是否有权限取消该订单以及订单是否处于可取消状态(未支付状态), + * 如果验证通过,则获取该订单下的所有商品项信息,设置到订单对象中,然后调用OrderService的cancelOrders方法取消订单, + * 最后在取消订单后,根据订单商品项信息清除对应的产品缓存和SKU缓存,返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理。 + * + * @param orderNumber 要取消的订单的唯一标识符,通过路径变量传入,是必须提供的参数,用于定位具体的订单记录。 + * @return 返回表示操作成功的ServerResponseEntity,无具体数据内容(这里返回的是一个表示成功的空字符串,仅用于表示操作成功的状态),其数据部分为String类型。 */ @PutMapping("/cancel/{orderNumber}") @Operation(summary = "根据订单号取消订单", description = "根据订单号取消订单") @Parameter(name = "orderNumber", description = "订单号", required = true) public ServerResponseEntity cancel(@PathVariable("orderNumber") String orderNumber) { + // 通过SecurityUtils获取当前登录用户的ID,用于验证用户是否有权限操作该订单。 String userId = SecurityUtils.getUser().getUserId(); + // 调用OrderService的getOrderByOrderNumber方法,根据订单号获取对应的订单对象。 Order order = orderService.getOrderByOrderNumber(orderNumber); if (!Objects.equals(order.getUserId(), userId)) { throw new YamiShopBindException("你没有权限获取该订单信息"); @@ -139,12 +184,14 @@ public class MyOrderController { if (!Objects.equals(order.getStatus(), OrderStatus.UNPAY.value())) { throw new YamiShopBindException("订单已支付,无法取消订单"); } + // 调用OrderItemService的getOrderItemsByOrderNumber方法,根据订单号获取该订单下包含的所有商品项信息列表。 List orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber); order.setOrderItems(orderItems); - // 取消订单 + // 调用OrderService的cancelOrders方法,传入包含该订单的列表(这里使用Collections.singletonList将单个订单包装成列表),取消订单操作。 orderService.cancelOrders(Collections.singletonList(order)); - // 清除缓存 + // 循环遍历订单商品项信息列表,针对每个商品项,调用ProductService的removeProductCacheByProdId方法清除对应产品的缓存, + // 以及调用SkuService的removeSkuCacheBySkuId方法清除对应SKU的缓存,以保证数据的一致性和缓存的有效性。 for (OrderItem orderItem : orderItems) { productService.removeProductCacheByProdId(orderItem.getProdId()); skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); @@ -152,14 +199,21 @@ public class MyOrderController { return ServerResponseEntity.success(); } - /** - * 确认收货 + * 确认收货接口方法。 + * 根据传入的订单号(orderNumber),先验证当前登录用户是否有权限确认该订单以及订单是否处于可确认收货状态(已发货状态), + * 如果验证通过,则获取该订单下的所有商品项信息,设置到订单对象中,然后调用OrderService的confirmOrder方法确认收货, + * 最后根据订单商品项信息清除对应的产品缓存和SKU缓存,返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理。 + * + * @param orderNumber 要确认收货的订单的唯一标识符,通过路径变量传入,是必须提供的参数,用于定位具体的订单记录。 + * @return 返回表示操作成功的ServerResponseEntity,无具体数据内容(这里返回的是一个表示成功的空字符串,仅用于表示操作成功的状态),其数据部分为String类型。 */ @PutMapping("/receipt/{orderNumber}") @Operation(summary = "根据订单号确认收货", description = "根据订单号确认收货") public ServerResponseEntity receipt(@PathVariable("orderNumber") String orderNumber) { + // 通过SecurityUtils获取当前登录用户的ID,用于验证用户是否有权限操作该订单。 String userId = SecurityUtils.getUser().getUserId(); + // 调用OrderService的getOrderByOrderNumber方法,根据订单号获取对应的订单对象。 Order order = orderService.getOrderByOrderNumber(orderNumber); if (!Objects.equals(order.getUserId(), userId)) { throw new YamiShopBindException("你没有权限获取该订单信息"); @@ -167,9 +221,10 @@ public class MyOrderController { if (!Objects.equals(order.getStatus(), OrderStatus.CONSIGNMENT.value())) { throw new YamiShopBindException("订单未发货,无法确认收货"); } + // 调用OrderItemService的getOrderItemsByOrderNumber方法,根据订单号获取该订单下包含的所有商品项信息列表。 List orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber); order.setOrderItems(orderItems); - // 确认收货 + // 调用OrderService的confirmOrder方法,传入包含该订单的列表(这里使用Collections.singletonList将单个订单包装成列表),进行确认收货操作。 orderService.confirmOrder(Collections.singletonList(order)); for (OrderItem orderItem : orderItems) { @@ -180,41 +235,10 @@ public class MyOrderController { } /** - * 删除订单 - */ - @DeleteMapping("/{orderNumber}") - @Operation(summary = "根据订单号删除订单", description = "根据订单号删除订单") - @Parameter(name = "orderNumber", description = "订单号", required = true) - public ServerResponseEntity delete(@PathVariable("orderNumber") String orderNumber) { - String userId = SecurityUtils.getUser().getUserId(); - - Order order = orderService.getOrderByOrderNumber(orderNumber); - if (order == null) { - throw new YamiShopBindException("该订单不存在"); - } - if (!Objects.equals(order.getUserId(), userId)) { - throw new YamiShopBindException("你没有权限获取该订单信息"); - } - if (!Objects.equals(order.getStatus(), OrderStatus.SUCCESS.value()) && !Objects.equals(order.getStatus(), OrderStatus.CLOSE.value())) { - throw new YamiShopBindException("订单未完成或未关闭,无法删除订单"); - } - - // 删除订单 - orderService.deleteOrders(Collections.singletonList(order)); - - return ServerResponseEntity.success("删除成功"); - } - - /** - * 获取我的订单订单数量 - */ - @GetMapping("/orderCount") - @Operation(summary = "获取我的订单订单数量", description = "获取我的订单订单数量") - public ServerResponseEntity getOrderCount() { - String userId = SecurityUtils.getUser().getUserId(); - OrderCountData orderCountMap = orderService.getOrderCount(userId); - return ServerResponseEntity.success(orderCountMap); - } - - -} + * 删除订单接口方法。 + * 根据传入的订单号(orderNumber),先验证订单是否存在、当前登录用户是否有权限删除该订单以及订单是否处于可删除状态(已完成或已关闭状态), + * 如果验证通过,则调用OrderService的deleteOrders方法删除订单,最后返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理, + * 其返回的数据部分包含表示删除成功的提示信息字符串。 + * + * @param orderNumber 要删除的订单的唯一标识符,通过路径变量传入,是必须提供的参数,用于定位具体的订单记录。 + * @return 返回表示操作成功的ServerResponseEntity,其数据部分为 \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/NoticeController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/NoticeController.java index 918ee35..d948c8d 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/NoticeController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/NoticeController.java @@ -29,49 +29,70 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; /** + * 公告管理相关接口的控制器类 + * 该类提供了与公告管理相关的几个接口,包括获取置顶公告列表、获取公告详情以及获取公告分页列表等功能,通过调用服务层方法获取数据,并将结果以合适的格式返回给前端, + * 同时利用`Swagger`注解对接口进行文档化描述,方便接口的查看与使用。 + * * @author lanhai */ @RestController @RequestMapping("/shop/notice") @Tag(name = "公告管理接口") +// 使用 @AllArgsConstructor 注解,由 lombok 自动生成包含所有成员变量的构造函数,用于依赖注入 @AllArgsConstructor public class NoticeController { + // 通过构造函数注入NoticeService,用于调用业务层方法来获取公告相关的数据 private NoticeService noticeService; - - /** - * 置顶公告列表接口 + * 置顶公告列表接口方法 + * 用于获取所有置顶的公告列表信息,先从服务层获取公告列表数据,再将其转换为对应的DTO(数据传输对象)列表后返回给前端。 + * + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含所有置顶公告对应的DTO列表数据,若获取数据出现异常则返回相应的错误信息。 */ @GetMapping("/topNoticeList") - @Operation(summary = "置顶公告列表信息" , description = "获取所有置顶公告列表信息") + @Operation(summary = "置顶公告列表信息", description = "获取所有置顶公告列表信息") + // 使用 @Operation 注解,在Swagger文档中对该接口进行描述,包括接口的简要总结和详细描述信息 public ServerResponseEntity> getTopNoticeList() { + // 调用服务层方法获取公告列表数据(这里获取的是所有的公告列表,后续可根据业务逻辑判断哪些是置顶的) List noticeList = noticeService.listNotice(); + // 使用 hutool 工具类的方法,将Notice类型的列表转换为NoticeDto类型的列表,用于传输给前端展示的数据格式转换 List noticeDtoList = BeanUtil.copyToList(noticeList, NoticeDto.class); return ServerResponseEntity.success(noticeDtoList); } /** - * 获取公告详情 + * 获取公告详情接口方法 + * 根据传入的公告ID,从服务层获取对应的公告详情数据,再将其转换为对应的DTO对象后返回给前端。 + * + * @param id 要获取详情的公告的唯一标识(ID),通过 @PathVariable 注解从请求路径中获取参数值。 + * @return 返回一个ServerResponseEntity类型的响应,成功时包含对应ID的公告详情对应的DTO对象数据,若获取数据出现异常则返回相应的错误信息。 */ @GetMapping("/info/{id}") - @Operation(summary = "公告详情" , description = "获取公告id公告详情") + @Operation(summary = "公告详情", description = "获取公告id公告详情") public ServerResponseEntity getNoticeById(@PathVariable("id") Long id) { + // 调用服务层方法根据ID获取公告详情数据 Notice notice = noticeService.getNoticeById(id); + // 使用 hutool 工具类的方法,将Notice对象转换为NoticeDto对象,用于传输给前端展示的数据格式转换 NoticeDto noticeDto = BeanUtil.copyProperties(notice, NoticeDto.class); return ServerResponseEntity.success(noticeDto); } /** - * 公告列表 + * 公告列表接口方法 + * 用于获取所有公告的分页列表信息,调用服务层的分页查询方法获取公告分页数据,并将结果以合适的格式返回给前端。 + * + * @param page 分页参数对象,包含了分页相关的信息,如页码、每页数量等,用于服务层进行分页查询操作。 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含符合分页条件的公告列表对应的DTO分页数据,若获取数据出现异常则返回相应的错误信息。 */ @GetMapping("/noticeList") - @Operation(summary = "公告列表信息" , description = "获取所有公告列表信息") + @Operation(summary = "公告列表信息", description = "获取所有公告列表信息") @Parameters({ }) + // 使用 @Parameters 注解,在Swagger文档中可以对接口的参数进行详细描述,这里目前没有添加具体参数描述内容 public ServerResponseEntity> pageNotice(PageParam page) { return ServerResponseEntity.success(noticeService.pageNotice(page)); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java index bf20799..3809eb6 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java @@ -39,6 +39,9 @@ import java.util.List; import java.util.Objects; /** + * 订单相关接口的控制器类,主要处理订单生成、提交等核心业务操作,涉及与多个服务层交互以完成复杂的订单流程, + * 例如获取用户地址、组装购物车商品信息、计算订单金额以及处理缓存等相关逻辑。 + * * @author lanhai */ @RestController @@ -46,112 +49,148 @@ import java.util.Objects; @Tag(name = "订单接口") public class OrderController { + // 自动注入订单服务层接口,用于处理订单相关的核心业务逻辑,如保存订单、查询订单等操作 @Autowired private OrderService orderService; + // 自动注入库存单元(SKU)服务层接口,可能用于处理商品库存相关操作,比如库存扣减、缓存清除等 @Autowired private SkuService skuService; + // 自动注入商品服务层接口,可用于商品相关的业务操作,例如获取商品信息、清除商品缓存等 @Autowired private ProductService productService; + // 自动注入用户地址服务层接口,用于获取用户地址相关信息,比如根据用户 ID 和地址 ID 查询具体地址详情 @Autowired private UserAddrService userAddrService; + // 自动注入购物车服务层接口,用于处理购物车相关业务,如获取购物车商品项、根据购物车商品组装店铺相关信息等 @Autowired private BasketService basketService; + // 自动注入 Spring 的应用上下文对象,用于发布事件,实现基于事件驱动的业务逻辑解耦,例如发布确认订单事件 @Autowired private ApplicationContext applicationContext; - /** - * 生成订单 + * 结算并生成订单信息的方法,此方法接收下单所需的参数,经过一系列业务逻辑处理后,生成并返回完整的订单信息, + * 包括用户地址、各店铺的商品信息、订单金额等内容,同时会将生成的订单信息存入缓存(通过调用 orderService 的相关缓存方法)。 + * + * @param orderParam 包含下单所需的各种参数的对象,通过请求体传入,且经过了参数验证(@Valid 注解),例如地址 ID、购物车商品项 ID 等信息 + * @return 包含完整订单信息的 ServerResponseEntity,以 ShopCartOrderMergerDto 类型封装,方便前端展示和后续业务处理,若购物车中无商品等情况则抛出相应异常 */ @PostMapping("/confirm") - @Operation(summary = "结算,生成订单信息" , description = "传入下单所需要的参数进行下单") + @Operation(summary = "结算,生成订单信息", description = "传入下单所需要的参数进行下单") public ServerResponseEntity confirm(@Valid @RequestBody OrderParam orderParam) { + // 获取当前用户的 ID,用于后续关联订单、地址等信息到该用户 String userId = SecurityUtils.getUser().getUserId(); - // 订单的地址信息 + // 根据传入的地址 ID 和用户 ID,从数据库中获取用户的订单地址信息,并转换为 DTO 类型方便后续返回给前端展示 UserAddr userAddr = userAddrService.getUserAddrByUserId(orderParam.getAddrId(), userId); UserAddrDto userAddrDto = BeanUtil.copyProperties(userAddr, UserAddrDto.class); - - // 组装获取用户提交的购物车商品项 - List shopCartItems = basketService.getShopCartItemsByOrderItems(orderParam.getBasketIds(),orderParam.getOrderItem(),userId); - + // 组装获取用户提交的购物车商品项,根据传入的购物车商品项 ID 和用户 ID 等信息,调用购物车服务层方法获取具体的商品项信息列表, + // 如果购物车中没有选择商品(即列表为空),则抛出异常提示用户选择商品加入购物车 + List shopCartItems = basketService.getShopCartItemsByOrderItems(orderParam.getBasketIds(), orderParam.getOrderItem(), userId); if (CollectionUtil.isEmpty(shopCartItems)) { throw new YamiShopBindException("请选择您需要的商品加入购物车"); } - // 根据店铺组装购车中的商品信息,返回每个店铺中的购物车商品信息 + // 根据店铺组装购物车中的商品信息,将购物车中的商品项按照店铺进行分类整理,返回每个店铺中的购物车商品信息列表,方便后续计算每个店铺的订单详情 List shopCarts = basketService.getShopCarts(shopCartItems); - // 将要返回给前端的完整的订单信息 + // 创建一个将要返回给前端的完整的订单信息对象,用于逐步组装并填充订单相关的各种信息 ShopCartOrderMergerDto shopCartOrderMergerDto = new ShopCartOrderMergerDto(); + // 将用户地址信息设置到订单信息对象中,作为订单的收货地址相关信息 shopCartOrderMergerDto.setUserAddr(userAddrDto); - // 所有店铺的订单信息 + // 创建一个用于存放所有店铺的订单信息的列表,后续将每个店铺的订单信息对象添加到该列表中 List shopCartOrders = new ArrayList<>(); + // 初始化订单相关的金额和数量变量,用于累加计算订单的总金额、实际支付金额、商品总数量以及优惠金额等信息 double actualTotal = 0.0; double total = 0.0; int totalCount = 0; double orderReduce = 0.0; + + // 遍历每个店铺的购物车信息,进行每个店铺订单信息的组装和金额等相关计算 for (ShopCartDto shopCart : shopCarts) { - // 每个店铺的订单信息 + // 创建一个每个店铺的订单信息对象,用于填充该店铺相关的订单详细信息 ShopCartOrderDto shopCartOrder = new ShopCartOrderDto(); + // 设置店铺 ID,明确该订单信息所属的店铺 shopCartOrder.setShopId(shopCart.getShopId()); + // 设置店铺名称,方便前端展示等使用 shopCartOrder.setShopName(shopCart.getShopName()); - + // 获取该店铺中的商品项折扣信息列表,包含了商品的折扣相关情况以及对应的商品项列表等信息 List shopCartItemDiscounts = shopCart.getShopCartItemDiscounts(); - // 店铺中的所有商品项信息 + // 创建一个用于存放该店铺中所有商品项信息的列表,后续将从商品项折扣信息中提取出所有商品项并添加到该列表中 List shopAllShopCartItems = new ArrayList<>(); + // 遍历商品项折扣信息列表,将每个折扣信息中的商品项添加到店铺所有商品项列表中,实现商品项的整合 for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartItemDiscounts) { List discountShopCartItems = shopCartItemDiscount.getShopCartItems(); shopAllShopCartItems.addAll(discountShopCartItems); } + // 将商品项折扣信息设置到店铺订单信息对象中,作为该店铺订单的商品项折扣相关内容 shopCartOrder.setShopCartItemDiscounts(shopCartItemDiscounts); - applicationContext.publishEvent(new ConfirmOrderEvent(shopCartOrder,orderParam,shopAllShopCartItems)); + // 发布确认订单事件,将店铺订单信息、下单参数以及店铺所有商品项信息作为事件参数传递出去, + // 可以通过事件监听器实现一些额外的业务逻辑,例如基于事件驱动的业务流程扩展、通知等功能,实现业务逻辑的解耦 + applicationContext.publishEvent(new ConfirmOrderEvent(shopCartOrder, orderParam, shopAllShopCartItems)); - actualTotal = Arith.add(actualTotal,shopCartOrder.getActualTotal()); - total = Arith.add(total,shopCartOrder.getTotal()); + // 累加计算订单的实际支付金额,将当前店铺订单的实际支付金额累加到总实际支付金额中 + actualTotal = Arith.add(actualTotal, shopCartOrder.getActualTotal()); + // 累加计算订单的总金额,将当前店铺订单的总金额累加到总金额中 + total = Arith.add(total, shopCartOrder.getTotal()); + // 累加计算商品的总数量,将当前店铺订单的商品数量累加到总数量中 totalCount = totalCount + shopCartOrder.getTotalCount(); - orderReduce = Arith.add(orderReduce,shopCartOrder.getShopReduce()); - shopCartOrders.add(shopCartOrder); - + // 累加计算订单的优惠金额,将当前店铺订单的优惠金额累加到总优惠金额中 + orderReduce = Arith.add(orderReduce, shopCartOrder.getShopReduce()); + // 将当前店铺的订单信息对象添加到所有店铺订单信息列表中,完成一个店铺订单信息的组装 + shopCartOrders.add(shopCartOrder); } + // 将计算好的订单总金额、实际支付金额、商品总数量以及优惠金额等信息设置到完整的订单信息对象中 shopCartOrderMergerDto.setActualTotal(actualTotal); shopCartOrderMergerDto.setTotal(total); shopCartOrderMergerDto.setTotalCount(totalCount); shopCartOrderMergerDto.setShopCartOrders(shopCartOrders); shopCartOrderMergerDto.setOrderReduce(orderReduce); + // 将组装好的完整订单信息存入缓存中,并返回缓存后的订单信息对象,方便后续提交订单等操作使用缓存数据,提高性能并保证数据一致性 shopCartOrderMergerDto = orderService.putConfirmOrderCache(userId, shopCartOrderMergerDto); return ServerResponseEntity.success(shopCartOrderMergerDto); } /** - * 购物车/立即购买 提交订单,根据店铺拆单 + * 购物车/立即购买提交订单并根据店铺拆单的方法,此方法接收提交订单的参数,先从缓存中获取之前生成的订单信息, + * 然后根据传入的店铺参数设置订单备注信息,接着调用订单服务层方法提交订单并获取生成的订单列表,之后处理订单编号拼接、 + * 商品缓存清除以及购物车缓存清除(根据是否为购物车提交订单判断)等操作,最后返回包含订单编号字符串的响应结果给前端, + * 若缓存中不存在订单信息则抛出订单已过期的异常提示用户重新下单。 + * + * @param submitOrderParam 包含提交订单所需的各种参数的对象,通过请求体传入,且经过了参数验证(@Valid 注解),例如店铺参数、备注信息等 + * @return 包含订单编号字符串的 ServerResponseEntity,以 OrderNumbersDto 类型封装,方便前端获取订单编号进行后续支付等操作,若订单已过期等情况则抛出相应异常 */ @PostMapping("/submit") - @Operation(summary = "提交订单,返回支付流水号" , description = "根据传入的参数判断是否为购物车提交订单,同时对购物车进行删除,用户开始进行支付") + @Operation(summary = "提交订单,返回支付流水号", description = "根据传入的参数判断是否为购物车提交订单,同时对购物车进行删除,用户开始进行支付") public ServerResponseEntity submitOrders(@Valid @RequestBody SubmitOrderParam submitOrderParam) { + // 获取当前用户的 ID,用于后续从缓存中获取订单信息、关联订单等操作到该用户 String userId = SecurityUtils.getUser().getUserId(); + // 从缓存中获取之前生成并缓存的确认订单信息,如果缓存中不存在则抛出异常提示订单已过期,需要重新下单 ShopCartOrderMergerDto mergerOrder = orderService.getConfirmOrderCache(userId); if (mergerOrder == null) { throw new YamiShopBindException("订单已过期,请重新下单"); } + // 获取提交订单参数中的店铺参数列表,用于后续设置订单的备注信息等操作 List orderShopParams = submitOrderParam.getOrderShopParam(); + // 获取缓存中订单信息里的所有店铺订单信息列表,用于遍历设置备注信息以及后续的缓存清除等相关操作 List shopCartOrders = mergerOrder.getShopCartOrders(); - // 设置备注 + + // 设置订单备注信息,如果传入的店铺参数列表不为空,则遍历店铺订单信息和店铺参数,根据店铺 ID 匹配,将对应的备注信息设置到店铺订单对象中 if (CollectionUtil.isNotEmpty(orderShopParams)) { for (ShopCartOrderDto shopCartOrder : shopCartOrders) { for (OrderShopParam orderShopParam : orderShopParams) { @@ -162,36 +201,47 @@ public class OrderController { } } - List orders = orderService.submit(userId,mergerOrder); - - + // 调用订单服务层的提交订单方法,传入用户 ID 和缓存中的订单信息对象,提交订单并获取生成的订单列表,该方法内部会进行复杂的业务逻辑处理, + // 例如根据店铺拆单、保存订单信息到数据库、处理库存扣减等相关操作 + List orders = orderService.submit(userId, mergerOrder); + // 创建一个字符串构建器,用于拼接订单编号,方便后续返回给前端一个用逗号分隔的订单编号字符串 StringBuilder orderNumbers = new StringBuilder(); + // 遍历生成的订单列表,将每个订单的编号添加到字符串构建器中,并在每个编号后添加逗号进行分隔 for (Order order : orders) { orderNumbers.append(order.getOrderNumber()).append(","); } + // 删除最后一个多余的逗号,得到正确格式的订单编号字符串 orderNumbers.deleteCharAt(orderNumbers.length() - 1); + // 标记是否为购物车提交订单,初始化为 false,后续根据购物车商品项的 ID 判断是否为购物车提交订单情况 boolean isShopCartOrder = false; - // 移除缓存 + + // 移除相关缓存,遍历每个店铺的订单信息、商品项折扣信息以及商品项信息,进行以下操作: + // 1. 判断商品项是否有购物车 ID(如果有则说明是购物车提交订单),并相应地设置标记变量。 + // 2. 清除商品对应的库存单元(SKU)缓存,通过调用 SKU 服务层的方法,传入商品的 SKU ID 和商品 ID。 + // 3. 清除商品缓存,通过调用商品服务层的方法,传入商品 ID,确保缓存数据与数据库最新状态一致,避免数据不一致问题影响后续业务操作。 for (ShopCartOrderDto shopCartOrder : shopCartOrders) { for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartOrder.getShopCartItemDiscounts()) { for (ShopCartItemDto shopCartItem : shopCartItemDiscount.getShopCartItems()) { Long basketId = shopCartItem.getBasketId(); - if (basketId != null && basketId != 0) { + if (basketId!= null && basketId!= 0) { isShopCartOrder = true; } - skuService.removeSkuCacheBySkuId(shopCartItem.getSkuId(),shopCartItem.getProdId()); + skuService.removeSkuCacheBySkuId(shopCartItem.getSkuId(), shopCartItem.getProdId()); productService.removeProductCacheByProdId(shopCartItem.getProdId()); } } } - // 购物车提交订单时(即有购物车ID时) + + // 如果是购物车提交订单(即标记变量为 true),则调用购物车服务层的方法,根据用户 ID 清除该用户的购物车商品项缓存,保证购物车数据的准确性 if (isShopCartOrder) { basketService.removeShopCartItemsCacheByUserId(userId); } + + // 清除确认订单的缓存,通过调用订单服务层的方法,传入用户 ID,确保缓存数据与实际业务状态一致,避免缓存数据干扰下次下单操作 orderService.removeConfirmOrderCache(userId); + return ServerResponseEntity.success(new OrderNumbersDto(orderNumbers.toString())); } - -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java index fd12a1f..84636ff 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java @@ -26,45 +26,69 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** + * 支付相关的接口控制器类,主要负责处理订单支付相关的操作,向外提供了支付接口以及普通支付接口, + * 通过调用PayService的相关方法完成支付业务逻辑,并根据业务结果返回相应的响应信息给客户端。 + * * @author lanhai */ @RestController +// 设置该控制器类对应的请求映射路径,后续类中的接口方法路径会基于此进行拼接,这里表明是与订单支付相关的接口所在的基础路径。 @RequestMapping("/p/order") +// 使用 @Tag 注解为该控制器类添加标签说明,用于在 API 文档(如 Swagger 生成的文档)中对该类下的接口进行分类展示,这里表明是“订单接口”相关的一组接口。 @Tag(name = "订单接口") +// 通过Lombok的 @AllArgsConstructor 注解生成包含所有参数的构造函数,用于依赖注入PayService实例,方便后续调用支付相关的业务方法。 @AllArgsConstructor public class PayController { + // 注入PayService,用于处理与支付相关的核心业务逻辑,例如发起支付、处理支付成功后的业务逻辑等操作。 private final PayService payService; /** - * 支付接口 + * 支付接口方法,用于根据传入的支付参数(PayParam对象)进行订单支付操作,并返回相应的响应结果给客户端。 + * 通过 @PostMapping 注解将该方法映射到 HTTP 的 POST 请求方式,请求路径为“/pay”,意味着客户端需要通过发送POST请求到该路径来调用此支付接口。 + * @Operation 注解用于在 API 文档中对该接口进行详细描述,这里的摘要(summary)和详细描述(description)都表明是根据订单号进行支付的功能,方便接口使用者了解接口用途。 + * + * @param payParam 包含支付相关参数的请求体对象,例如订单号、支付方式等信息,具体字段取决于PayParam类的定义,这些参数将用于支付业务逻辑的处理。 + * @return 返回一个表示操作成功的ServerResponseEntity对象,由于这里主要是执行支付操作,没有需要返回的具体业务数据,所以返回的是Void类型的成功响应,告知客户端支付请求已受理并处理。 */ @PostMapping("/pay") - @Operation(summary = "根据订单号进行支付" , description = "根据订单号进行支付") + @Operation(summary = "根据订单号进行支付", description = "根据订单号进行支付") public ServerResponseEntity pay(@RequestBody PayParam payParam) { + // 通过SecurityUtils工具类获取当前登录用户信息,封装在YamiUser对象中,后续可能用于验证用户身份、关联支付记录与用户等操作。 YamiUser user = SecurityUtils.getUser(); + // 从获取到的用户对象中提取用户ID,作为支付操作中关联用户的关键标识,传递给后续的支付业务逻辑处理方法。 String userId = user.getUserId(); - + // 调用PayService的pay方法,传入用户ID和支付参数,发起支付操作,该方法会根据传入的参数进行具体的支付流程处理, + // 例如与支付平台交互、生成支付订单等,并返回包含支付相关信息的PayInfoDto对象,如支付单号等信息。 PayInfoDto payInfo = payService.pay(userId, payParam); + // 调用PayService的paySuccess方法,传入支付单号等信息,用于处理支付成功后的相关业务逻辑,比如更新订单状态、记录支付记录等操作, + // 第二个参数为空字符串,可能表示一些额外的备注信息等,具体含义取决于paySuccess方法的实现逻辑。 payService.paySuccess(payInfo.getPayNo(), ""); return ServerResponseEntity.success(); } /** - * 普通支付接口 + * 普通支付接口方法,功能与上述的支付接口类似,也是根据传入的支付参数(PayParam对象)进行订单支付操作, + * 但在返回结果上有所不同,这里返回一个包含布尔值的ServerResponseEntity对象,用于表示支付操作是否成功(目前直接返回true)。 + * 通过 @PostMapping 注解将该方法映射到 HTTP 的 POST 请求方式,请求路径为“/normalPay”,客户端通过该路径发送POST请求来调用此接口。 + * @Operation 注解同样用于在 API 文档中对该接口进行描述,表明是根据订单号进行支付的功能。 + * + * @param payParam 包含支付相关参数的请求体对象,例如订单号、支付方式等信息,具体字段取决于PayParam类的定义,这些参数将用于支付业务逻辑的处理。 + * @return 返回一个包含表示操作成功的布尔值的ServerResponseEntity对象,目前总是返回true,表示支付操作在业务逻辑层面被视为成功,具体的实际支付结果可能需要结合后续的业务逻辑以及支付平台反馈等综合判断。 */ @PostMapping("/normalPay") - @Operation(summary = "根据订单号进行支付" , description = "根据订单号进行支付") + @Operation(summary = "根据订单号进行支付", description = "根据订单号进行支付") public ServerResponseEntity normalPay(@RequestBody PayParam payParam) { YamiUser user = SecurityUtils.getUser(); String userId = user.getUserId(); PayInfoDto pay = payService.pay(userId, payParam); - // 根据内部订单号更新order settlement + // 根据内部订单号更新order settlement,调用PayService的paySuccess方法处理支付成功后的业务逻辑,传入支付单号等信息, + // 这里的操作与上面的支付接口中的paySuccess调用类似,都是为了保证支付成功后相关业务数据的更新和一致性。 payService.paySuccess(pay.getPayNo(), ""); return ServerResponseEntity.success(true); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java index 4cfe7c8..ec788cb 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java @@ -8,6 +8,7 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城API的控制器包下,从类名推测可能是用于处理支付通知相关的接口逻辑。 package com.yami.shop.api.controller; import lombok.AllArgsConstructor; @@ -16,33 +17,44 @@ import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Hidden; /** + * PayNoticeController类是一个Spring RESTful风格的控制器,不过它被标记为@Hidden,意味着在生成API文档(例如使用Swagger等工具生成接口文档时)时,该控制器及其包含的接口方法将不会被展示出来。 + * 从代码结构和被注释掉的部分来看,原本可能是用于处理支付通知相关的业务逻辑,比如接收支付结果的回调通知、解析支付结果数据以及根据支付结果更新相关业务数据等操作,但目前模拟支付不需要回调,部分功能代码处于注释状态。 + * * @author lanhai */ @Hidden +// 使用@Hidden注解将该控制器标记为隐藏状态,使其不在API文档中显示,可能是因为该接口目前不需要对外暴露或者处于开发调试阶段等原因。 @RestController +// 表明该类是一个RESTful风格的控制器,Spring会自动将其方法返回的对象转换为合适的响应格式(如JSON等)返回给客户端,用于处理HTTP请求并返回响应。 @RequestMapping("/notice/pay") +// 定义该控制器类的基础请求路径,所有该类中的接口请求路径都将以此为前缀,表明是与支付通知相关的操作接口。 @AllArgsConstructor +// 使用lombok的@AllArgsConstructor注解,会自动生成包含所有final字段的构造函数,方便依赖注入。虽然目前部分依赖(如wxMiniPayService和payService)被注释掉了,但如果后续启用相关功能,这个构造函数可以方便地注入对应的服务实例。 public class PayNoticeController { -//模拟支付不需要回调 -// /** -// * 小程序支付 -// */ -// private final WxPayService wxMiniPayService; -// -// private final PayService payService; -// -// -// @RequestMapping("/order") -// public ServerResponseEntity submit(@RequestBody String xmlData) throws WxPayException { -// WxPayOrderNotifyResult parseOrderNotifyResult = wxMiniPayService.parseOrderNotifyResult(xmlData); -// -// String payNo = parseOrderNotifyResult.getOutTradeNo(); -// String bizPayNo = parseOrderNotifyResult.getTransactionId(); -// -// // 根据内部订单号更新order settlement -// payService.paySuccess(payNo, bizPayNo); -// -// -// return ServerResponseEntity.success(); -// } -} + + // 以下两个成员变量对应的服务原本可能用于处理支付相关的业务逻辑,目前被注释掉了,可能是因为模拟支付不需要回调等情况暂时不用这些服务。 + // 模拟支付不需要回调 + // /** + // * 小程序支付 + // */ + // private final WxPayService wxMiniPayService; + // + // private final PayService payService; + + // 以下是被注释掉的方法,从方法名和代码逻辑来看,它原本可能是用于接收支付结果的回调通知,解析通知中的XML数据(微信支付等可能会以XML格式返回支付结果信息), + // 获取相关支付单号等信息,然后根据内部订单号调用payService的paySuccess方法更新支付相关的业务数据(比如订单的支付状态等),最后返回表示操作成功的响应信息。 + // 目前由于模拟支付不需要回调,所以该方法处于注释状态。 + // @RequestMapping("/order") + // public ServerResponseEntity submit(@RequestBody String xmlData) throws WxPayException { + // WxPayOrderNotifyResult parseOrderNotifyResult = wxMiniPayService.parseOrderNotifyResult(xmlData); + // + // String payNo = parseOrderNotifyResult.getOutTradeNo(); + // String bizPayNo = parseOrderNotifyResult.getTransactionId(); + // + // // 根据内部订单号更新order settlement + // payService.paySuccess(payNo, bizPayNo); + // + // + // return ServerResponseEntity.success(); + // } +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdCommController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdCommController.java index bcd9b44..b393d73 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdCommController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdCommController.java @@ -30,39 +30,76 @@ import org.springframework.web.bind.annotation.*; import java.util.Date; /** + * 商品评论相关接口的控制器类 + * 该类提供了多个与商品评论相关的接口,包括获取商品评论数据(如好评率、各评价数量等)、根据不同条件获取评论分页数据(按用户、按商品等)、添加评论以及删除评论等功能, + * 通过调用服务层的对应方法来实现业务逻辑,并借助`Swagger`注解对接口进行文档化描述,方便接口的查看与使用。 + * * @author lanhai */ @RestController @RequestMapping("/prodComm") @Tag(name = "评论接口") +// 使用 @AllArgsConstructor 注解,由 lombok 自动生成包含所有成员变量的构造函数,用于依赖注入 @AllArgsConstructor public class ProdCommController { + // 通过构造函数注入ProdCommService,用于调用业务层方法来处理商品评论相关的业务逻辑 private final ProdCommService prodCommService; - + /** + * 获取商品评论数据的接口方法 + * 根据传入的商品ID,调用服务层方法获取该商品对应的评论数据(如好评率、好评数量、中评数、差评数等),并将结果以合适的格式返回给前端。 + * + * @param prodId 要获取评论数据的商品的唯一标识(ID),该参数从请求中获取。 + * @return 返回一个ServerResponseEntity类型的响应,成功时包含对应商品的评论数据对应的DTO对象,若获取数据出现异常则返回相应的错误信息。 + */ @GetMapping("/prodCommData") - @Operation(summary = "返回商品评论数据(好评率 好评数量 中评数 差评数)" , description = "根据商品id获取") + @Operation(summary = "返回商品评论数据(好评率 好评数量 中评数 差评数)", description = "根据商品id获取") public ServerResponseEntity getProdCommData(Long prodId) { return ServerResponseEntity.success(prodCommService.getProdCommDataByProdId(prodId)); } + /** + * 根据用户获取评论分页数据的接口方法 + * 根据当前登录用户的信息以及传入的分页参数,调用服务层方法获取该用户相关的评论分页数据,并将结果以合适的格式返回给前端。 + * + * @param page 分页参数对象,包含了分页相关的信息,如页码、每页数量等,用于服务层进行分页查询操作。 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含符合分页条件的该用户的评论分页数据对应的DTO分页数据,若获取数据出现异常则返回相应的错误信息。 + */ @GetMapping("/prodCommPageByUser") - @Operation(summary = "根据用户返回评论分页数据" , description = "传入页码") + @Operation(summary = "根据用户返回评论分页数据", description = "传入页码") public ServerResponseEntity> getProdCommPage(PageParam page) { return ServerResponseEntity.success(prodCommService.getProdCommDtoPageByUserId(page, SecurityUtils.getUser().getUserId())); } - @GetMapping("/prodCommPageByProd") - @Operation(summary = "根据商品返回评论分页数据" , description = "传入商品id和页码") + /** + * 根据商品获取评论分页数据的接口方法 + * 根据传入的商品ID、评价筛选条件以及分页参数,调用服务层方法获取该商品对应的评论分页数据,并将结果以合适的格式返回给前端。 + * 其中评价筛选条件可用于筛选不同类型的评价(如好评、中评、差评、有图评价等)。 + * + * @param page 分页参数对象,包含了分页相关的信息,如页码、每页数量等,用于服务层进行分页查询操作。 + * @param prodId 要获取评论分页数据的商品的唯一标识(ID),该参数从请求中获取,是必选参数。 + * @param evaluate 评价筛选条件,取值为 -1 或 null 表示获取全部评价,0 表示好评,1 表示中评,2 表示差评,3 表示有图评价,该参数从请求中获取,是必选参数。 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含符合分页条件及评价筛选条件的该商品的评论分页数据对应的DTO分页数据,若获取数据出现异常则返回相应的错误信息。 + */ + @GetMapping("/prodCommPageByProdId") + @Operation(summary = "根据商品返回评论分页数据", description = "传入商品id和页码") @Parameters({ - @Parameter(name = "prodId", description = "商品id" , required = true), - @Parameter(name = "evaluate", description = "-1或null 全部,0好评 1中评 2差评 3有图" , required = true), + @Parameter(name = "prodId", description = "商品id", required = true), + @Parameter(name = "evaluate", description = "-1或null 全部,0好评 1中评 2差评 3有图", required = true), }) public ServerResponseEntity> getProdCommPageByProdId(PageParam page, Long prodId, Integer evaluate) { return ServerResponseEntity.success(prodCommService.getProdCommDtoPageByProdId(page, prodId, evaluate)); } + /** + * 添加评论的接口方法 + * 根据传入的评论参数对象,构建评论实体对象,设置相关属性(如商品ID、用户ID、评论内容、评分等)后,调用服务层方法将评论保存到数据库中, + * 并返回表示操作成功的响应给前端。 + * + * @param prodCommParam 包含评论相关信息的参数对象,用于构建要保存的评论实体对象,其属性涵盖了商品ID、订单商品项ID、用户ID、评分、内容、图片、是否匿名、评价类型等信息。 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示评论添加操作成功,无具体返回数据,若保存过程出现异常则返回相应的错误信息。 + */ @PostMapping @Operation(summary = "添加评论") public ServerResponseEntity saveProdCommPage(ProdCommParam prodCommParam) { @@ -81,10 +118,17 @@ public class ProdCommController { return ServerResponseEntity.success(); } + /** + * 删除评论的接口方法 + * 根据传入的评论ID,调用服务层方法从数据库中删除对应的评论记录,并返回表示操作成功的响应给前端。 + * + * @param prodCommId 要删除的评论的唯一标识(ID),该参数从请求中获取。 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示评论删除操作成功,无具体返回数据,若删除过程出现异常则返回相应的错误信息。 + */ @DeleteMapping - @Operation(summary = "删除评论" , description = "根据id删除") + @Operation(summary = "删除评论", description = "根据id删除") public ServerResponseEntity deleteProdComm(Long prodCommId) { prodCommService.removeById(prodCommId); return ServerResponseEntity.success(); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdController.java index 6c61fc0..b42f9db 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdController.java @@ -37,6 +37,9 @@ import java.util.List; import java.util.stream.Collectors; /** + * 商品相关接口的控制器类,用于处理与商品相关的各种查询请求,例如获取商品列表、商品详情、新品推荐、按标签获取商品列表等操作, + * 通过调用不同的服务层方法来获取相应的数据,并以统一的响应格式返回给前端使用。 + * * @author lgh on 2018/11/26. */ @RestController @@ -44,49 +47,72 @@ import java.util.stream.Collectors; @Tag(name = "商品接口") public class ProdController { + // 自动注入商品服务层接口,用于调用与商品相关的核心业务逻辑方法,如根据分类 ID 查询商品列表、获取商品详情等操作 @Autowired private ProductService prodService; - + // 自动注入库存单元(SKU)服务层接口,用于获取商品对应的 SKU 相关信息,例如根据商品 ID 查询其所有的 SKU 列表等操作 @Autowired private SkuService skuService; + // 自动注入运费模板服务层接口,用于获取商品相关的运费模板信息,比如根据运费模板 ID 获取详细的运费模板及关联信息 @Autowired private TransportService transportService; - + /** + * 根据分类 ID 获取商品列表信息的方法,接收分类 ID 和分页参数,调用商品服务层的方法按照分类 ID 查询并返回符合条件的商品信息分页结果, + * 用于前端展示某个分类下的所有商品列表,方便用户浏览和筛选商品。 + * + * @param categoryId 要查询商品列表的分类 ID,通过请求参数传入,是必填项,用于筛选属于该分类下的商品记录 + * @param page 分页对象,包含页码、每页数量等分页相关参数,用于控制查询结果的分页展示,确保数据按照一定的数量和页码进行返回 + * @return 包含商品信息分页数据的 ServerResponseEntity,以 ProductDto 类型封装商品信息,方便前端展示和使用,若查询到数据则返回对应分页结果,否则返回相应的空数据表示 + */ @GetMapping("/pageProd") - @Operation(summary = "通过分类id商品列表信息" , description = "根据分类ID获取该分类下所有的商品列表信息") + @Operation(summary = "通过分类id商品列表信息", description = "根据分类ID获取该分类下所有的商品列表信息") @Parameters({ - @Parameter(name = "categoryId", description = "分类ID" , required = true), + @Parameter(name = "categoryId", description = "分类ID", required = true), }) public ServerResponseEntity> prodList( - @RequestParam(value = "categoryId") Long categoryId,PageParam page) { + @RequestParam(value = "categoryId") Long categoryId, PageParam page) { IPage productPage = prodService.pageByCategoryId(page, categoryId); return ServerResponseEntity.success(productPage); } + /** + * 根据商品 ID 获取商品详情信息的方法,接收商品 ID,通过调用商品服务层和库存单元服务层的方法获取商品的详细信息, + * 包括基本信息、启用的 SKU 列表以及根据商品的配送方式和运费模板 ID 判断是否需要返回运费模板信息等,最后将整合好的商品详情信息返回给前端, + * 方便用户查看商品的详细情况以及下单时参考运费等相关信息。 + * + * @param prodId 要查询详情的商品 ID,通过请求参数传入,是必填项,用于从数据库中精准获取对应的商品记录 + * @return 包含商品详情信息的 ServerResponseEntity,以 ProductDto 类型封装商品的详细信息,若商品不存在则返回相应的空数据表示,否则返回完整的商品详情对象 + */ @GetMapping("/prodInfo") - @Operation(summary = "商品详情信息" , description = "根据商品ID(prodId)获取商品信息") - @Parameter(name = "prodId", description = "商品ID" , required = true) + @Operation(summary = "商品详情信息", description = "根据商品ID(prodId)获取商品信息") + @Parameter(name = "prodId", description = "商品ID", required = true) public ServerResponseEntity prodInfo(Long prodId) { + // 通过商品服务层的方法,根据商品 ID 从数据库中获取商品的基本信息,若商品不存在则直接返回空的成功响应(可根据实际需求优化此处逻辑,比如返回特定的错误提示) Product product = prodService.getProductByProdId(prodId); if (product == null) { return ServerResponseEntity.success(); } + // 通过库存单元服务层的方法,根据商品 ID 查询该商品对应的所有 SKU 列表 List skuList = skuService.listByProdId(prodId); - // 启用的sku列表 + // 使用 Java 8 的 Stream API 过滤出状态为启用(这里假设状态为 1 表示启用)的 SKU 列表,方便前端展示可用的商品库存单元信息 List useSkuList = skuList.stream().filter(sku -> sku.getStatus() == 1).collect(Collectors.toList()); + // 将启用的 SKU 列表设置到商品对象中,完善商品的详细信息 product.setSkuList(useSkuList); - ProductDto productDto = BeanUtil.copyProperties(product, ProductDto.class); + // 使用 Hutool 的 BeanUtil 将商品对象转换为对应的 DTO 类型,方便按照前端所需的格式返回数据,避免直接暴露数据库实体对象带来的潜在风险 + ProductDto productDto = BeanUtil.copyProperties(product, ProductDto.class); - // 商品的配送方式 + // 将商品的配送方式字符串解析为对应的对象(这里假设使用了自定义的 JSON 解析方法,具体根据实际情况而定),方便后续判断和获取相关的配送信息 Product.DeliveryModeVO deliveryModeVO = Json.parseObject(product.getDeliveryMode(), Product.DeliveryModeVO.class); - // 有店铺配送的方式, 且存在运费模板,才返回运费模板的信息,供前端查阅 - if (deliveryModeVO.getHasShopDelivery() && product.getDeliveryTemplateId() != null) { + + // 判断如果商品有店铺配送的方式,并且商品关联的运费模板 ID 不为空(即存在运费模板),则通过运费模板服务层的方法, + // 根据运费模板 ID 获取详细的运费模板及关联信息,并设置到商品详情的 DTO 对象中,供前端查阅运费相关情况,方便用户下单时参考运费成本 + if (deliveryModeVO.getHasShopDelivery() && product.getDeliveryTemplateId()!= null) { Transport transportAndAllItems = transportService.getTransportAndAllItems(product.getDeliveryTemplateId()); productDto.setTransport(transportAndAllItems); } @@ -94,38 +120,65 @@ public class ProdController { return ServerResponseEntity.success(productDto); } + /** + * 获取新品推荐商品列表的方法,接收分页参数,调用商品服务层的方法按照商品上架时间等条件查询并返回新品推荐的商品信息分页结果, + * 用于前端展示最新上架的商品,方便用户发现新商品,增加商品的曝光和销售机会。 + * + * @param page 分页对象,包含页码、每页数量等分页相关参数,用于控制查询结果的分页展示,确保数据按照一定的数量和页码进行返回 + * @return 包含新品推荐商品信息分页数据的 ServerResponseEntity,以 ProductDto 类型封装商品信息,方便前端展示和使用,若查询到数据则返回对应分页结果,否则返回相应的空数据表示 + */ @GetMapping("/lastedProdPage") - @Operation(summary = "新品推荐" , description = "获取新品推荐商品列表") - @Parameters({ - }) + @Operation(summary = "新品推荐", description = "获取新品推荐商品列表") + @Parameters({}) public ServerResponseEntity> lastedProdPage(PageParam page) { IPage productPage = prodService.pageByPutAwayTime(page); return ServerResponseEntity.success(productPage); } + /** + * 根据分组标签 ID 获取商品列表的方法,接收分组标签 ID 和分页参数,调用商品服务层的方法按照分组标签 ID 查询并返回符合条件的商品信息分页结果, + * 用于前端展示属于某个分组标签下的商品列表,方便用户按照标签分类查找感兴趣的商品。 + * + * @param tagId 要查询商品列表的分组标签 ID,通过请求参数传入,是必填项,用于筛选属于该分组标签下的商品记录 + * @param page 分页对象,包含页码、每页数量等分页相关参数,用于控制查询结果的分页展示,确保数据按照一定的数量和页码进行返回 + * @return 包含商品信息分页数据的 ServerResponseEntity,以 ProductDto 类型封装商品信息,方便前端展示和使用,若查询到数据则返回对应分页结果,否则返回相应的空数据表示 + */ @GetMapping("/prodListByTagId") - @Operation(summary = "通过分组标签获取商品列表" , description = "通过分组标签id(tagId)获取商品列表") + @Operation(summary = "通过分组标签获取商品列表", description = "通过分组标签id(tagId)获取商品列表") @Parameters({ - @Parameter(name = "tagId", description = "当前页,默认为1" , required = true), + @Parameter(name = "tagId", description = "当前页,默认为1", required = true), }) public ServerResponseEntity> prodListByTagId( - @RequestParam(value = "tagId") Long tagId,PageParam page) { + @RequestParam(value = "tagId") Long tagId, PageParam page) { IPage productPage = prodService.pageByTagId(page, tagId); return ServerResponseEntity.success(productPage); } + /** + * 获取销量最多的商品列表(每日疯抢)的方法,接收分页参数,调用商品服务层的方法按照商品销量等条件查询并返回销量较多的商品信息分页结果, + * 用于前端展示热门商品,吸引用户购买,提高商品的销售转化率。 + * + * @param page 分页对象,包含页码、每页数量等分页相关参数,用于控制查询结果的分页展示,确保数据按照一定的数量和页码进行返回 + * @return 包含销量最多商品信息分页数据的 ServerResponseEntity,以 ProductDto 类型封装商品信息,方便前端展示和使用,若查询到数据则返回对应分页结果,否则返回相应的空数据表示 + */ @GetMapping("/moreBuyProdList") - @Operation(summary = "每日疯抢" , description = "获取销量最多的商品列表") + @Operation(summary = "每日疯抢", description = "获取销量最多的商品列表") @Parameters({}) public ServerResponseEntity> moreBuyProdList(PageParam page) { IPage productPage = prodService.moreBuyProdList(page); return ServerResponseEntity.success(productPage); } + /** + * 获取首页所有标签商品列表的方法,调用商品服务层的方法获取首页所有标签对应的商品列表信息,用于前端展示首页的标签商品分类情况, + * 方便用户快速浏览不同标签下的商品,提升用户体验和商品的展示效果。 + * + * @return 包含首页所有标签商品信息列表的 ServerResponseEntity,以 TagProductDto 类型封装商品信息,方便前端展示和使用,若查询到数据则返回对应列表,否则返回相应的空数据表示 + */ @GetMapping("/tagProdList") - @Operation(summary = "首页所有标签商品接口" , description = "获取首页所有标签商品接口") + @Operation(summary = "首页所有标签商品接口", description = "获取首页所有标签商品接口") public ServerResponseEntity> getTagProdList() { List productDtoList = prodService.tagProdList(); return ServerResponseEntity.success(productDtoList); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdTagController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdTagController.java index d1a1e65..8f38a58 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdTagController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdTagController.java @@ -25,27 +25,39 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; /** + * 商品分组标签相关的接口控制器类,主要负责向外提供获取商品分组标签列表信息的接口, + * 通过调用对应的服务层(ProdTagService)方法获取原始标签数据,并进行数据格式转换后返回给客户端。 + * * @author lanhai */ @RestController +// 设置该控制器类对应的请求映射路径,后续类中的接口方法路径会基于此进行拼接,这里表明是与商品分组标签相关接口所在的基础路径。 @RequestMapping("/prod/tag") +// 使用 @Tag 注解为该控制器类添加标签说明,用于在 API 文档(如 Swagger 生成的文档)中对该类下的接口进行分类展示,这里表明是“商品分组标签接口”相关的一组接口。 @Tag(name = "商品分组标签接口") +// 通过Lombok的 @AllArgsConstructor 注解生成包含所有参数的构造函数,用于依赖注入ProdTagService实例,方便后续调用其业务方法获取标签数据。 @AllArgsConstructor public class ProdTagController { + // 注入ProdTagService,用于处理与商品分组标签相关的业务逻辑,例如从数据库中查询所有商品分组标签列表等操作。 private ProdTagService prodTagService; - - /** - * 商品分组标签列表接口 + * 商品分组标签列表接口方法,用于获取所有的商品分组标签列表信息,并将其返回给客户端。 + * 通过 @GetMapping 注解将该方法映射到 HTTP 的 GET 请求方式,请求路径为“/prodTagList”,客户端可以通过该路径发送GET请求来获取商品分组标签数据。 + * @Operation 注解用于在 API 文档中对该接口进行详细描述,包括接口的简要说明(summary)和详细描述(description),方便接口使用者了解接口的功能,这里表明是获取商品分组标签列表的功能。 + * + * @return 返回一个包含商品分组标签信息的ServerResponseEntity对象,若获取成功则响应体中包含标签列表数据(以ProdTagDto列表形式返回),否则返回相应错误信息。 */ @GetMapping("/prodTagList") - @Operation(summary = "商品分组标签列表" , description = "获取所有的商品分组列表") + @Operation(summary = "商品分组标签列表", description = "获取所有的商品分组列表") public ServerResponseEntity> getProdTagList() { + // 调用ProdTagService的listProdTag方法,从数据库或其他数据源获取商品分组标签的原始数据列表(以ProdTag对象形式返回)。 List prodTagList = prodTagService.listProdTag(); + // 使用hutool的BeanUtil工具类,将ProdTag对象列表转换为ProdTagDto对象列表,ProdTagDto可能是用于对外展示的、经过筛选或格式调整后的视图对象, + // 这样可以避免直接将内部的业务实体对象暴露给客户端,更好地控制数据的展示格式和安全性。 List prodTagDtoList = BeanUtil.copyToList(prodTagList, ProdTagDto.class); + // 将转换后的ProdTagDto列表封装在表示成功的ServerResponseEntity对象中返回给客户端,遵循统一的接口响应格式规范。 return ServerResponseEntity.success(prodTagDtoList); } - -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/SearchController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/SearchController.java index 900bf31..d6e5e3b 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/SearchController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/SearchController.java @@ -8,9 +8,9 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城API的控制器包下,主要用于处理与搜索相关的各种接口请求及对应的业务逻辑,例如热搜查询、商品搜索等功能。 package com.yami.shop.api.controller; - import cn.hutool.core.collection.CollectionUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.yami.shop.common.util.PageParam; @@ -32,66 +32,115 @@ import java.util.Collections; import java.util.List; /** + * SearchController类是一个Spring RESTful风格的控制器,专门用于处理与搜索相关的操作接口,为前端应用提供了诸如查询店铺热搜、全局热搜以及分页排序搜索商品等功能的后端服务支持, + * 通过整合HotSearchService和ProductService等服务类来实现具体的业务逻辑,并借助Swagger注解对接口进行详细的文档描述,方便接口的使用和维护。 + * * @author lanhai */ @RestController +// 定义该控制器类的基础请求路径,所有该类中的接口请求路径都将以此为前缀,表明是与搜索相关的操作接口。 @RequestMapping("/search") +// 使用Swagger的@Tag注解对该控制器类进行标记,用于在API文档中生成对应的分类标签,方便接口文档的分类展示和阅读,这里表示该类下的接口都属于“搜索接口”这一分类。 @Tag(name = "搜索接口") +// 使用lombok的@AllArgsConstructor注解,自动生成包含所有final字段的构造函数,方便依赖注入,这里会为HotSearchService和ProductService生成对应的构造函数参数,以便在类中使用这两个服务类的实例。 @AllArgsConstructor public class SearchController { + // 通过构造函数注入HotSearchService实例,用于调用与热搜相关的业务逻辑方法,比如根据店铺ID或获取全局热搜等操作来获取热搜数据。 private final HotSearchService hotSearchService; + // 通过构造函数注入ProductService实例,用于调用与商品搜索相关的业务逻辑方法,例如根据商品名、排序条件等进行分页搜索商品并获取相应的商品搜索结果数据。 private final ProductService productService; + /** + * 根据店铺ID获取热搜的接口方法。 + * 根据传入的店铺ID(shopId)、要获取的热搜数量(number)以及是否按照顺序排序的标识(sort),调用HotSearchService的getHotSearchDtoByShopId方法获取对应店铺的热搜数据列表, + * 然后经过进一步处理(通过调用getListResponseEntity方法),将符合条件的热搜数据封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param shopId 要查询热搜的店铺的唯一标识符,通过请求参数传入,是必须提供的参数,用于定位具体店铺的热搜数据。 + * @param number 要获取的热搜数量,通过请求参数传入,是必须提供的参数,用于限定返回的热搜数据的条数。 + * @param sort 表示是否按照顺序排序的标识,通过请求参数传入,取值为0表示不按照顺序(随机排序),取值为1表示按照顺序排序,用于控制返回的热搜数据的排序方式。 + * @return 返回包含符合条件的热搜数据列表的ServerResponseEntity,成功时其数据部分为List类型,该类型包含了热搜相关的详细信息(具体信息由HotSearchDto类定义)。 + */ @GetMapping("/hotSearchByShopId") - @Operation(summary = "查看店铺热搜" , description = "根据店铺id,热搜数量获取热搜") + // 使用Swagger的@Operation注解对该接口方法进行描述,用于在API文档中生成对应的接口说明信息,这里简要说明了该接口的功能是查看店铺热搜。 + @Operation(summary = "查看店铺热搜", description = "根据店铺id,热搜数量获取热搜") + // 使用Swagger的@Parameters注解对接口方法的多个参数进行统一描述,通过包含多个@Parameter注解来分别详细说明每个参数的名称、描述以及是否必填等信息,方便在API文档中展示参数详情。 @Parameters({ - @Parameter(name = "shopId", description = "店铺id" , required = true), - @Parameter(name = "number", description = "取数" , required = true), - @Parameter(name = "sort", description = "是否按照顺序(0 否 1是)"), + @Parameter(name = "shopId", description = "店铺id", required = true), + @Parameter(name = "number", description = "取数", required = true), + @Parameter(name = "sort", description = "是否按照顺序(0 否 1是)") }) - public ServerResponseEntity> hotSearchByShopId(Long shopId,Integer number,Integer sort) { + public ServerResponseEntity> hotSearchByShopId(Long shopId, Integer number, Integer sort) { List list = hotSearchService.getHotSearchDtoByShopId(shopId); return getListResponseEntity(number, sort, list); } + /** + * 获取全局热搜的接口方法。 + * 根据传入的要获取的热搜数量(number)以及是否按照顺序排序的标识(sort),调用HotSearchService的getHotSearchDtoByShopId方法并传入固定的店铺ID值0L(表示全局,从代码逻辑推测)获取全局热搜数据列表, + * 然后经过进一步处理(通过调用getListResponseEntity方法),将符合条件的热搜数据封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param number 要获取的热搜数量,通过请求参数传入,是必须提供的参数,用于限定返回的热搜数据的条数。 + * @param sort 表示是否按照顺序排序的标识,通过请求参数传入,取值为0表示不按照顺序(随机排序),取值为1表示按照顺序排序,用于控制返回的热搜数据的排序方式。 + * @return 返回包含符合条件的热搜数据列表的ServerResponseEntity,成功时其数据部分为List类型,该类型包含了热搜相关的详细信息(具体信息由HotSearchDto类定义)。 + */ @GetMapping("/hotSearch") - @Operation(summary = "查看全局热搜" , description = "根据店铺id,热搜数量获取热搜") + @Operation(summary = "查看全局热搜", description = "根据店铺id,热搜数量获取热搜") @Parameters({ - @Parameter(name = "number", description = "取数" , required = true), - @Parameter(name = "sort", description = "是否按照顺序(0 否 1是)", required = false ), + @Parameter(name = "number", description = "取数", required = true), + @Parameter(name = "sort", description = "是否按照顺序(0 否 1是)", required = false) }) - public ServerResponseEntity> hotSearch(Integer number,Integer sort) { + public ServerResponseEntity> hotSearch(Integer number, Integer sort) { List list = hotSearchService.getHotSearchDtoByShopId(0L); return getListResponseEntity(number, sort, list); } + /** + * 处理热搜数据列表并返回合适响应实体的私有方法。 + * 根据传入的要获取的数量(number)、排序标识(sort)以及原始的热搜数据列表(list),进行如下处理: + * 如果排序标识为null或者取值为0(表示不按照顺序排序),则对列表进行随机打乱操作(使用Collections.shuffle方法); + * 接着判断如果列表为空或者列表元素个数小于要获取的数量,直接将原始列表封装在ServerResponseEntity中返回; + * 如果列表元素个数大于等于要获取的数量,则截取列表的前number个元素封装在ServerResponseEntity中返回,用于统一的接口响应格式处理,返回符合条件的热搜数据列表。 + * + * @param number 要获取的数量,用于控制最终返回的热搜数据列表的元素个数。 + * @param sort 排序标识,用于判断是否对列表进行随机打乱操作以及确定返回的列表元素顺序情况。 + * @param list 原始的热搜数据列表,包含了从服务层获取到的所有热搜相关信息(具体由HotSearchDto类定义),需要根据条件进行处理后返回合适的数据子集。 + * @return 返回包含经过处理后符合条件的热搜数据列表的ServerResponseEntity,成功时其数据部分为List类型。 + */ private ServerResponseEntity> getListResponseEntity(Integer number, Integer sort, List list) { - if(sort == null || sort == 0){ + if (sort == null || sort == 0) { Collections.shuffle(list); } - if(!CollectionUtil.isNotEmpty(list) || list.size()< number){ + if (!CollectionUtil.isNotEmpty(list) || list.size() < number) { return ServerResponseEntity.success(list); } return ServerResponseEntity.success(list.subList(0, number)); } + /** + * 分页排序搜索商品的接口方法。 + * 根据传入的分页参数(page)、商品名称(prodName)、排序方式(sort)、排序顺序(orderBy)以及店铺ID(shopId),调用ProductService的getSearchProdDtoPageByProdName方法进行分页排序搜索商品操作, + * 获取到符合条件的分页商品搜索结果数据(IPage类型),最后将该分页数据封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param page 分页参数对象,包含页码、每页数量等信息,用于进行分页查询操作,通过该参数可以获取指定页的商品搜索结果数据。 + * @param prodName 要搜索的商品名称,通过请求参数传入,是必须提供的参数,用于根据商品名称进行模糊匹配搜索商品。 + * @param sort 表示排序方式的参数,通过请求参数传入,取值为0表示默认排序,取值为1表示按照销量排序,取值为2表示按照价格排序,用于控制搜索结果中商品的排序方式。 + * @param orderBy 表示排序顺序的参数,通过请求参数传入,取值为0表示升序排序,取值为1表示降序排序,用于进一步明确排序的顺序情况,与sort参数配合使用来确定最终的商品排序规则。 + * @param shopId 要搜索商品所在店铺的唯一标识符,通过请求参数传入,是必须提供的参数,用于限定搜索的范围在指定店铺内进行。 + * @return 返回包含分页排序后的商品搜索结果数据的ServerResponseEntity,成功时其数据部分为IPage类型,该类型包含了分页后的商品列表数据以及分页相关的元数据信息(如总页数、总记录数等)。 + */ @GetMapping("/searchProdPage") - @Operation(summary = "分页排序搜索商品" , description = "根据商品名搜索") + @Operation(summary = "分页排序搜索商品", description = "根据商品名搜索") @Parameters({ - @Parameter(name = "prodName", description = "商品名" , required = true), - @Parameter(name = "sort", description = "排序(0 默认排序 1销量排序 2价格排序)"), + @Parameter(name = "prodName", description = "商品名", required = true), @Parameter(name = "orderBy", description = "排序(0升序 1降序)"), - @Parameter(name = "shopId", description = "店铺id" , required = true), + @Parameter(name = "shopId", description = "店铺id", required = true), + @Parameter(name = "sort", description = "排序(0 默认排序 1销量排序 2价格排序)") }) public ServerResponseEntity> searchProdPage(PageParam page, String prodName, Integer sort, Integer orderBy, Long shopId) { - return ServerResponseEntity.success(productService.getSearchProdDtoPageByProdName(page,prodName,sort,orderBy)); + return ServerResponseEntity.success(productService.getSearchProdDtoPageByProdName(page, prodName, sort, orderBy)); } - - - - -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ShopCartController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ShopCartController.java index f77c367..3960261 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ShopCartController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ShopCartController.java @@ -40,136 +40,195 @@ import java.util.Objects; import java.util.stream.Collectors; /** + * 购物车相关接口的控制器类 + * 该类提供了多个与购物车操作相关的接口,包括获取购物车信息、删除购物车物品、清空购物车、添加/修改购物车物品、获取购物车商品数量、获取购物车失效商品信息、 + * 清空失效商品以及获取选中购物项总计和商品数量等功能,通过调用不同的服务层方法来实现相应的业务逻辑,并借助`Swagger`注解对接口进行文档化描述,方便接口的查看与使用。 + * * @author lanhai */ @RestController @RequestMapping("/p/shopCart") @Tag(name = "购物车接口") +// 使用 @AllArgsConstructor 注解,由 lombok 自动生成包含所有成员变量的构造函数,用于依赖注入 @AllArgsConstructor public class ShopCartController { + // 用于处理购物车相关业务逻辑的服务层对象,比如更新购物车、获取购物车商品项等操作 private final BasketService basketService; - + // 用于处理商品相关业务逻辑的服务层对象,比如获取商品信息等操作 private final ProductService productService; - + // 用于处理商品库存单元(SKU)相关业务逻辑的服务层对象,比如获取SKU信息等操作 private final SkuService skuService; - + // Spring应用上下文对象,用于发布事件等操作,例如发布购物车相关事件 private final ApplicationContext applicationContext; /** - * 获取用户购物车信息 + * 获取用户购物车信息的接口方法 + * 根据传入的以购物车ID为键的购物车参数对象映射表,先更新购物车信息(如果参数表不为空),然后获取购物车商品项列表,最后将其转换为购物车信息列表并返回给前端。 * - * @param basketIdShopCartParamMap 购物车参数对象列表 - * @return + * @param basketIdShopCartParamMap 以购物车ID为键,对应的购物车参数对象为值的映射表,用于更新购物车相关信息,参数从请求体中获取。 + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含用户购物车信息对应的DTO列表数据,若获取或处理数据出现异常则返回相应的错误信息。 */ @PostMapping("/info") - @Operation(summary = "获取用户购物车信息" , description = "获取用户购物车信息,参数为用户选中的活动项数组,以购物车id为key") + @Operation(summary = "获取用户购物车信息", description = "获取用户购物车信息,参数为用户选中的活动项数组,以购物车id为key") public ServerResponseEntity> info(@RequestBody Map basketIdShopCartParamMap) { + // 获取当前登录用户的ID,用于关联购物车与用户 String userId = SecurityUtils.getUser().getUserId(); - // 更新购物车信息, + // 如果传入的购物车参数映射表不为空,调用购物车服务层方法根据参数更新购物车信息 if (MapUtil.isNotEmpty(basketIdShopCartParamMap)) { basketService.updateBasketByShopCartParam(userId, basketIdShopCartParamMap); } - // 拿到购物车的所有item + // 调用购物车服务层方法获取该用户的购物车商品项列表 List shopCartItems = basketService.getShopCartItems(userId); return ServerResponseEntity.success(basketService.getShopCarts(shopCartItems)); - } + /** + * 删除用户购物车物品的接口方法 + * 根据传入的购物车ID列表,调用购物车服务层方法删除对应购物车中的物品,并返回表示操作成功的响应给前端。 + * + * @param basketIds 要删除物品的购物车的ID列表,参数从请求体中获取。 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示删除购物车物品操作成功,无具体返回数据,若删除过程出现异常则返回相应的错误信息。 + */ @DeleteMapping("/deleteItem") - @Operation(summary = "删除用户购物车物品" , description = "通过购物车id删除用户购物车物品") + @Operation(summary = "删除用户购物车物品", description = "通过购物车id删除用户购物车物品") public ServerResponseEntity deleteItem(@RequestBody List basketIds) { + // 获取当前登录用户的ID,用于关联购物车与用户 String userId = SecurityUtils.getUser().getUserId(); basketService.deleteShopCartItemsByBasketIds(userId, basketIds); return ServerResponseEntity.success(); } + /** + * 清空用户购物车所有物品的接口方法 + * 调用购物车服务层方法清空当前登录用户购物车中的所有物品,并返回表示操作成功的响应给前端,响应中包含成功提示信息。 + * + * @return 返回一个ServerResponseEntity类型的响应,成功时表示清空购物车操作成功,返回的信息为"删除成功",若操作出现异常则返回相应的错误信息。 + */ @DeleteMapping("/deleteAll") - @Operation(summary = "清空用户购物车所有物品" , description = "清空用户购物车所有物品") + @Operation(summary = "清空用户购物车所有物品", description = "清空用户购物车所有物品") public ServerResponseEntity deleteAll() { + // 获取当前登录用户的ID,用于关联购物车与用户 String userId = SecurityUtils.getUser().getUserId(); basketService.deleteAllShopCartItems(userId); return ServerResponseEntity.success("删除成功"); } + /** + * 添加、修改用户购物车物品的接口方法 + * 根据传入的更改购物车参数对象,进行一系列验证(如更改数量是否为0、商品是否下架、库存是否足够等)后,调用购物车服务层方法添加或修改购物车中的商品, + * 并根据不同情况返回相应的操作结果响应给前端。 + * + * @param param 包含更改购物车商品相关信息的参数对象,如商品ID、SKU ID、店铺ID、更改的商品个数等信息,参数从请求体中获取且经过验证。 + * @return 返回一个ServerResponseEntity类型的响应,根据操作结果返回不同信息,如添加成功、库存不足、商品已下架等提示信息,若操作出现异常则返回相应的错误信息。 + */ @PostMapping("/changeItem") @Operation(summary = "添加、修改用户购物车物品", description = "通过商品id(prodId)、skuId、店铺Id(shopId),添加/修改用户购物车商品,并传入改变的商品个数(count)," + "当count为正值时,增加商品数量,当count为负值时,将减去商品的数量,当最终count值小于0时,会将商品从购物车里面删除") public ServerResponseEntity addItem(@Valid @RequestBody ChangeShopCartParam param) { + // 如果更改的商品数量为0,则返回提示输入更改数量的失败响应 if (param.getCount() == 0) { return ServerResponseEntity.showFailMsg("输入更改数量"); } + // 获取当前登录用户的ID,用于关联购物车与用户 String userId = SecurityUtils.getUser().getUserId(); + // 获取当前用户的购物车商品项列表,用于后续判断商品是否已在购物车中等操作 List shopCartItems = basketService.getShopCartItems(userId); + // 根据传入的商品ID获取商品信息 Product prodParam = productService.getProductByProdId(param.getProdId()); + // 根据传入的SKU ID获取SKU信息 Sku skuParam = skuService.getSkuBySkuId(param.getSkuId()); - // 当商品状态不正常时,不能添加到购物车 - if (prodParam.getStatus() != 1 || skuParam.getStatus() != 1) { + // 当商品状态不正常(下架状态,状态值不为1)时,返回提示商品已下架的失败响应 + if (prodParam.getStatus()!= 1 || skuParam.getStatus()!= 1) { return ServerResponseEntity.showFailMsg("当前商品已下架"); } + + // 遍历购物车商品项列表,判断要操作的商品是否已在购物车中 for (ShopCartItemDto shopCartItemDto : shopCartItems) { if (Objects.equals(param.getSkuId(), shopCartItemDto.getSkuId())) { Basket basket = new Basket(); basket.setUserId(userId); + // 更新购物车中该商品的数量,为原数量加上传入的更改数量 basket.setBasketCount(param.getCount() + shopCartItemDto.getProdCount()); basket.setBasketId(shopCartItemDto.getBasketId()); - // 防止购物车变成负数 + // 防止购物车商品数量变成负数,如果数量小于等于0,则删除该购物车商品项 if (basket.getBasketCount() <= 0) { basketService.deleteShopCartItemsByBasketIds(userId, Collections.singletonList(basket.getBasketId())); return ServerResponseEntity.success(); } - // 当sku实际库存不足时,不能添加到购物车 + // 当SKU实际库存不足(当前库存小于购物车中要设置的数量,且原购物车商品数量大于0,说明是修改操作)时,返回提示库存不足的失败响应 if (skuParam.getStocks() < basket.getBasketCount() && shopCartItemDto.getProdCount() > 0) { return ServerResponseEntity.showFailMsg("库存不足"); } + // 调用购物车服务层方法更新购物车商品项信息 basketService.updateShopCartItem(basket); return ServerResponseEntity.success(); } } - // 防止购物车已被删除的情况下,添加了负数的商品 + // 如果商品不在购物车中,且传入的更改数量为负数,说明商品已被删除,返回相应提示的失败响应 if (param.getCount() < 0) { return ServerResponseEntity.showFailMsg("商品已从购物车移除"); } - // 当sku实际库存不足时,不能添加到购物车 + + // 当SKU实际库存不足(当前库存小于要添加的数量,说明是添加操作)时,返回提示库存不足的失败响应 if (skuParam.getStocks() < param.getCount()) { return ServerResponseEntity.showFailMsg("库存不足"); } - // 所有都正常时 - basketService.addShopCartItem(param,userId); + + // 所有验证都通过时,调用购物车服务层方法添加购物车商品项,并返回添加成功的响应 + basketService.addShopCartItem(param, userId); return ServerResponseEntity.success("添加成功"); } + /** + * 获取购物车商品数量的接口方法 + * 获取当前登录用户购物车中所有商品的数量总和,若购物车商品项列表为空则返回数量为0的成功响应,否则计算数量总和并返回相应的成功响应给前端。 + * + * @return 返回一个ServerResponseEntity类型的响应,成功时包含购物车商品数量总和,若获取数据出现异常则返回相应的错误信息。 + */ @GetMapping("/prodCount") - @Operation(summary = "获取购物车商品数量" , description = "获取所有购物车商品数量") + @Operation(summary = "获取购物车商品数量", description = "获取所有购物车商品数量") public ServerResponseEntity prodCount() { + // 获取当前登录用户的ID,用于关联购物车与用户 String userId = SecurityUtils.getUser().getUserId(); + // 获取当前用户的购物车商品项列表 List shopCartItems = basketService.getShopCartItems(userId); if (CollectionUtil.isEmpty(shopCartItems)) { return ServerResponseEntity.success(0); } + // 使用流操作计算购物车商品项列表中所有商品数量的总和 Integer totalCount = shopCartItems.stream().map(ShopCartItemDto::getProdCount).reduce(0, Integer::sum); return ServerResponseEntity.success(totalCount); } + /** + * 获取购物车失效商品信息的接口方法 + * 获取当前登录用户购物车中的失效商品信息列表,先获取失效商品项列表,然后按照店铺ID进行分组整理,构建每个店铺对应的失效商品信息对象列表后返回给前端。 + * + * @return 返回一个ServerResponseEntity>类型的响应,成功时包含购物车失效商品信息对应的DTO列表数据,若获取数据出现异常则返回相应的错误信息。 + */ @GetMapping("/expiryProdList") - @Operation(summary = "获取购物车失效商品信息" , description = "获取购物车失效商品列表") + @Operation(summary = "获取购物车失效商品信息", description = "获取购物车失效商品列表") public ServerResponseEntity> expiryProdList() { + // 获取当前登录用户的ID,用于关联购物车与用户 String userId = SecurityUtils.getUser().getUserId(); + // 获取当前用户购物车中的失效商品项列表 List shopCartItems = basketService.getShopCartExpiryItems(userId); - //根据店铺ID划分item + // 根据店铺ID对失效商品项列表进行分组,得到以店铺ID为键,对应商品项列表为值的映射表 Map> shopCartItemDtoMap = shopCartItems.stream().collect(Collectors.groupingBy(ShopCartItemDto::getShopId)); - // 返回一个店铺对应的所有信息 + // 用于存储构建好的每个店铺对应的购物车失效商品信息对象列表 List shopcartExpiryitems = Lists.newArrayList(); + // 遍历分组后的店铺ID集合,构建每个店铺对应的购物车失效商品信息对象,并添加到列表中 for (Long key : shopCartItemDtoMap.keySet()) { ShopCartExpiryItemDto shopCartExpiryItemDto = new ShopCartExpiryItemDto(); shopCartExpiryItemDto.setShopId(key); @@ -185,66 +244,44 @@ public class ShopCartController { return ServerResponseEntity.success(shopcartExpiryitems); } + /** + * 清空用户失效商品的接口方法 + * 调用购物车服务层方法清空当前登录用户购物车中的失效商品,并返回表示操作成功的响应给前端。 + * + * @return 返回一个ServerResponseEntity类型的响应,成功时表示清空失效商品操作成功,无具体返回数据,若操作出现异常则返回相应的错误信息。 + */ @DeleteMapping("/cleanExpiryProdList") - @Operation(summary = "清空用户失效商品" , description = "清空用户失效商品") + @Operation(summary = "清空用户失效商品", description = "清空用户失效商品") public ServerResponseEntity cleanExpiryProdList() { + // 获取当前登录用户的ID,用于关联购物车与用户 String userId = SecurityUtils.getUser().getUserId(); basketService.cleanExpiryProdList(userId); return ServerResponseEntity.success(); } + /** + * 获取选中购物项总计、选中的商品数量的接口方法 + * 根据传入的购物车ID列表,筛选出对应的购物车商品项,按照店铺ID进行分组后,计算选中商品的总计金额、商品数量等信息,构建相应的DTO对象并返回给前端。 + * + * @param basketIds 要获取总计信息的购物车的ID列表,参数从请求体中获取。 + * @return 返回一个ServerResponseEntity类型的响应,成功时包含选中购物项总计、商品数量等信息对应的DTO对象,若获取或计算数据出现异常则返回相应的错误信息。 + */ @PostMapping("/totalPay") - @Operation(summary = "获取选中购物项总计、选中的商品数量" , description = "获取选中购物项总计、选中的商品数量,参数为购物车id数组") + @Operation(summary = "获取选中购物项总计、选中的商品数量", description = "获取选中购物项总计、选中的商品数量,参数为购物车id数组") public ServerResponseEntity getTotalPay(@RequestBody List basketIds) { // 拿到购物车的所有item List dbShopCartItems = basketService.getShopCartItems(SecurityUtils.getUser().getUserId()); + // 从所有购物车商品项中筛选出传入的购物车ID列表对应的商品项 List chooseShopCartItems = dbShopCartItems - .stream() - .filter(shopCartItemDto -> { - for (Long basketId : basketIds) { - if (Objects.equals(basketId,shopCartItemDto.getBasketId())) { - return true; - } - } - return false; - }) - .toList(); - - // 根据店铺ID划分item - Map> shopCartMap = chooseShopCartItems.stream().collect(Collectors.groupingBy(ShopCartItemDto::getShopId)); - - double total = 0.0; - int count = 0; - double reduce = 0.0; - for (Long shopId : shopCartMap.keySet()) { - //获取店铺的所有商品项 - List shopCartItemDtoList = shopCartMap.get(shopId); - // 构建每个店铺的购物车信息 - ShopCartDto shopCart = new ShopCartDto(); - shopCart.setShopId(shopId); - - applicationContext.publishEvent(new ShopCartEvent(shopCart, shopCartItemDtoList)); - - List shopCartItemDiscounts = shopCart.getShopCartItemDiscounts(); - - for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartItemDiscounts) { - List shopCartItems = shopCartItemDiscount.getShopCartItems(); - - for (ShopCartItemDto shopCartItem : shopCartItems) { - count = shopCartItem.getProdCount() + count; - total = Arith.add(shopCartItem.getProductTotalAmount(), total); - } - } - } - ShopCartAmountDto shopCartAmountDto = new ShopCartAmountDto(); - shopCartAmountDto.setCount(count); - shopCartAmountDto.setTotalMoney(total); - shopCartAmountDto.setSubtractMoney(reduce); - shopCartAmountDto.setFinalMoney(Arith.sub(shopCartAmountDto.getTotalMoney(), shopCartAmountDto.getSubtractMoney())); - - return ServerResponseEntity.success(shopCartAmountDto); - } - -} + .stream() + .filter(shopCartItemDto -> { + for (Long basketId : basketIds) { + if (Objects.equals(basketId, shopCartItemDto.getBasketId())) { + return true; + } + } + return false; + }) + .toList(); \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/SkuController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/SkuController.java index 217c29e..161e284 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/SkuController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/SkuController.java @@ -27,6 +27,9 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; /** + * SKU(库存保有单位,即商品规格)相关接口的控制器类,主要负责处理根据商品 ID 获取其对应的全部有效规格列表的请求, + * 通过调用服务层的方法查询数据库,并将查询结果转换为合适的 DTO 类型返回给前端使用。 + * * @author lanhai */ @RestController @@ -35,20 +38,29 @@ import java.util.List; @AllArgsConstructor public class SkuController { + // 通过构造函数注入的方式引入 SKU 服务层接口,方便后续调用其提供的业务逻辑方法来处理与 SKU 相关的操作,比如查询 SKU 列表等。 private final SkuService skuService; - - + /** + * 根据商品 ID 获取商品全部规格列表的方法,接收商品 ID 作为参数,使用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件, + * 从数据库中查询出该商品下状态为启用(这里假设状态值为 1 表示启用)且未被删除(假设 isDelete 为 0 表示未删除)的所有 SKU 记录, + * 然后将查询到的 SKU 实体列表转换为对应的 DTO 类型列表,最后将结果封装在 ServerResponseEntity 中返回给前端,方便前端展示和使用商品规格信息。 + * + * @param prodId 要查询其规格列表的商品 ID,通过请求参数传入,用于筛选出属于该商品的 SKU 记录,该参数对应数据库中 Sku 表的 prodId 字段。 + * @return 包含商品全部规格信息列表的 ServerResponseEntity,以 SkuDto 类型封装每个 SKU 的信息,若查询到符合条件的 SKU 则返回对应列表,否则返回空列表表示。 + */ @GetMapping("/getSkuList") - @Operation(summary = "通过prodId获取商品全部规格列表" , description = "通过prodId获取商品全部规格列表") - @Parameter(name = "prodId", description = "商品id" ) + @Operation(summary = "通过prodId获取商品全部规格列表", description = "通过prodId获取商品全部规格列表") + @Parameter(name = "prodId", description = "商品id") public ServerResponseEntity> getSkuListByProdId(Long prodId) { + // 使用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件,设置筛选条件为状态为启用、未被删除且属于指定商品 ID 的 SKU 记录。 List skus = skuService.list(new LambdaQueryWrapper() - .eq(Sku::getStatus, 1) - .eq(Sku::getIsDelete, 0) - .eq(Sku::getProdId, prodId) + .eq(Sku::getStatus, 1) + .eq(Sku::getIsDelete, 0) + .eq(Sku::getProdId, prodId) ); + // 使用 Hutool 的 BeanUtil 将查询到的 Sku 实体列表转换为 SkuDto 类型的列表,方便按照前端期望的格式返回数据,避免直接暴露实体对象的一些潜在问题。 List skuDtoList = BeanUtil.copyToList(skus, SkuDto.class); return ServerResponseEntity.success(skuDtoList); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/SmsController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/SmsController.java index 9c04760..38d72e3 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/SmsController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/SmsController.java @@ -25,24 +25,38 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** + * 发送验证码相关的接口控制器类,主要负责处理发送验证码的业务逻辑,向外提供发送验证码的接口, + * 通过调用SmsLogService的相关方法来实现验证码的发送,并返回相应的成功响应给客户端。 + * * @author lanhai */ @RestController +// 设置该控制器类对应的请求映射路径,后续类中的接口方法路径会基于此进行拼接,这里表明是与发送验证码相关接口所在的基础路径。 @RequestMapping("/p/sms") +// 使用 @Tag 注解为该控制器类添加标签说明,用于在 API 文档(如 Swagger 生成的文档)中对该类下的接口进行分类展示,这里表明是“发送验证码接口”相关的一组接口。 @Tag(name = "发送验证码接口") public class SmsController { - @Autowired - private SmsLogService smsLogService; + // 注入SmsLogService,用于处理与短信验证码相关的业务逻辑,例如实际发送短信、记录短信发送日志等操作。 + @Autowired + private SmsLogService smsLogService; + /** - * 发送验证码接口 + * 发送验证码接口方法,用于接收客户端发送的发送验证码请求,根据请求参数调用相关服务来发送验证码,并返回相应的响应结果给客户端。 + * 通过 @PostMapping 注解将该方法映射到 HTTP 的 POST 请求方式,请求路径为“/send”,意味着客户端需要通过发送POST请求到该路径来调用此发送验证码接口。 + * @Operation 注解用于在 API 文档中对该接口进行详细描述,这里的摘要(summary)和详细描述(description)都表明是发送验证码的功能,方便接口使用者了解接口用途。 + * + * @param sendSmsParam 包含发送验证码相关参数的请求体对象,例如手机号码等信息,具体字段取决于SendSmsParam类的定义,这些参数将作为发送验证码的依据传递给服务层进行处理。 + * @return 返回一个表示操作成功的ServerResponseEntity对象,由于这里主要是执行发送验证码操作,没有需要返回的具体业务数据,所以返回的是Void类型的成功响应,告知客户端发送验证码请求已受理并处理。 */ @PostMapping("/send") - @Operation(summary = "发送验证码" , description = "用户的发送验证码") + @Operation(summary = "发送验证码", description = "用户的发送验证码") public ServerResponseEntity audit(@RequestBody SendSmsParam sendSmsParam) { - String userId = SecurityUtils.getUser().getUserId(); - smsLogService.sendSms(SmsType.VALID, userId, sendSmsParam.getMobile(),Maps.newHashMap()); - - return ServerResponseEntity.success(); + // 通过SecurityUtils工具类获取当前登录用户的ID,可能用于记录短信发送与用户的关联关系,或者作为短信发送业务逻辑中的一部分验证等操作。 + String userId = SecurityUtils.getUser().getUserId(); + // 调用SmsLogService的sendSms方法来发送短信验证码,传入短信类型(这里指定为SmsType.VALID,表示验证类型的短信)、用户ID、手机号码以及一个空的Map(可能用于传递一些额外的短信发送配置参数等,目前为空)作为参数,进行短信发送操作。 + smsLogService.sendSms(SmsType.VALID, userId, sendSmsParam.getMobile(), Maps.newHashMap()); + + return ServerResponseEntity.success(); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserCollectionController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserCollectionController.java index 9022d2d..1f2c283 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserCollectionController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserCollectionController.java @@ -8,13 +8,13 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城API的控制器包下,主要用于处理与用户收藏相关的各种接口请求及对应的业务逻辑,比如获取收藏数据、判断是否收藏、添加或取消收藏等操作。 package com.yami.shop.api.controller; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.yami.shop.bean.app.dto.ProductDto; -import com.yami.shop.bean.app.dto.UserCollectionDto; +import com.yaami.shop.bean.app.dto.UserCollectionDto; import com.yami.shop.bean.model.Product; import com.yami.shop.bean.model.UserCollection; import com.yami.shop.common.exception.YamiShopBindException; @@ -31,51 +31,87 @@ import org.springframework.web.bind.annotation.*; import java.util.Date; import java.util.Objects; + /** + * UserCollectionController类是一个Spring RESTful风格的控制器,专注于处理与用户收藏相关的操作接口,为前端应用提供诸如查询收藏数据、判断商品是否被收藏、添加或取消收藏、查询收藏商品数量以及获取收藏商品列表等功能的后端服务支持, + * 通过整合UserCollectionService和ProductService等服务类来实现具体的业务逻辑,并借助Swagger注解对接口进行详细的文档描述,方便接口的使用和维护。 + * * @author lanhai */ @RestController +// 定义该控制器类的基础请求路径,所有该类中的接口请求路径都将以此为前缀,表明是与用户收藏相关的操作接口。 @RequestMapping("/p/user/collection") +// 使用Swagger的@Tag注解对该控制器类进行标记,用于在API文档中生成对应的分类标签,方便接口文档的分类展示和阅读,这里表示该类下的接口都属于“收藏接口”这一分类。 @Tag(name = "收藏接口") +// 使用lombok的@AllArgsConstructor注解,自动生成包含所有final字段的构造函数,方便依赖注入,这里会为UserCollectionService和ProductService生成对应的构造函数参数,以便在类中使用这两个服务类的实例。 @AllArgsConstructor public class UserCollectionController { + // 通过构造函数注入UserCollectionService实例,用于调用与用户收藏记录相关的业务逻辑方法,比如查询收藏记录、添加或删除收藏记录等操作。 private final UserCollectionService userCollectionService; + // 通过构造函数注入ProductService实例,用于调用与商品相关的业务逻辑方法,例如判断商品是否存在、获取收藏商品列表等操作,通常会结合用户收藏记录来实现相关业务功能。 private final ProductService productService; + /** + * 分页返回收藏数据的接口方法。 + * 根据传入的分页参数(page),调用UserCollectionService的getUserCollectionDtoPageByUserId方法,传入当前登录用户的ID(通过SecurityUtils获取)以及分页参数,获取当前用户的分页收藏数据列表, + * 最后将获取到的分页收藏数据封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param page 分页参数对象,包含页码、每页数量等信息,用于进行分页查询操作,通过该参数可以获取指定页的用户收藏数据列表。 + * @return 返回包含分页用户收藏数据的ServerResponseEntity,成功时其数据部分为IPage类型,该类型包含了分页后的收藏记录数据以及分页相关的元数据信息(如总页数、总记录数等)。 + */ @GetMapping("/page") - @Operation(summary = "分页返回收藏数据" , description = "根据用户id获取") + @Operation(summary = "分页返回收藏数据", description = "根据用户id获取") public ServerResponseEntity> getUserCollectionDtoPageByUserId(PageParam page) { return ServerResponseEntity.success(userCollectionService.getUserCollectionDtoPageByUserId(page, SecurityUtils.getUser().getUserId())); } + /** + * 根据商品ID判断该商品是否在收藏夹中的接口方法。 + * 首先,根据传入的商品ID(prodId),通过ProductService的count方法结合LambdaQueryWrapper来查询该商品是否存在(通过判断符合条件的商品记录数量是否小于1来确定),如果不存在则抛出异常。 + * 然后,调用UserCollectionService的count方法结合LambdaQueryWrapper,根据商品ID以及当前登录用户的ID(通过SecurityUtils获取)来查询该用户对该商品的收藏记录数量, + * 如果数量大于0则表示已收藏,返回表示已收藏的布尔值(true),否则返回表示未收藏的布尔值(false),最后将判断结果封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param prodId 要判断是否被收藏的商品的唯一标识符,通过请求参数传入,用于定位具体的商品记录以及对应的用户收藏记录情况。 + * @return 返回包含表示该商品是否被当前用户收藏的布尔值的ServerResponseEntity,成功时其数据部分为Boolean类型,true表示已收藏,false表示未收藏。 + */ @GetMapping("isCollection") - @Operation(summary = "根据商品id获取该商品是否在收藏夹中" , description = "传入收藏商品id") + @Operation(summary = "根据商品id获取该商品是否在收藏夹中", description = "传入收藏商品id") public ServerResponseEntity isCollection(Long prodId) { if (productService.count(new LambdaQueryWrapper() - .eq(Product::getProdId, prodId)) < 1) { + .eq(Product::getProdId, prodId)) < 1) { throw new YamiShopBindException("该商品不存在"); } return ServerResponseEntity.success(userCollectionService.count(new LambdaQueryWrapper() - .eq(UserCollection::getProdId, prodId) - .eq(UserCollection::getUserId, SecurityUtils.getUser().getUserId())) > 0); + .eq(UserCollection::getProdId, prodId) + .eq(UserCollection::getUserId, SecurityUtils.getUser().getUserId())) > 0); } + /** + * 添加/取消收藏的接口方法。 + * 根据传入的商品ID(prodId),先判断该商品是否存在(通过ProductService的getProductByProdId方法获取商品对象,若为空则抛出异常),然后获取当前登录用户的ID(通过SecurityUtils获取)。 + * 接着,通过UserCollectionService的count方法结合LambdaQueryWrapper查询该用户对该商品的收藏记录数量,若数量大于0表示已收藏,则调用remove方法结合LambdaQueryWrapper删除对应的收藏记录,实现取消收藏操作; + * 若数量不大于0表示未收藏,则创建一个新的UserCollection对象,设置创建时间(通过new Date()获取当前时间)、用户ID以及商品ID,然后调用save方法保存该收藏记录,实现添加收藏操作。 + * 最后返回表示操作成功的ServerResponseEntity对象,用于统一的接口响应格式处理,其无具体数据内容(Void类型)。 + * + * @param prodId 要添加或取消收藏的商品的唯一标识符,通过请求体传入,用于确定具体的商品以及对应的用户收藏操作。 + * @return 返回表示操作成功的ServerResponseEntity,无数据内容(Void类型),用于表示添加或取消收藏操作已成功执行。 + */ @PostMapping("/addOrCancel") - @Operation(summary = "添加/取消收藏" , description = "传入收藏商品id,如果商品未收藏则收藏商品,已收藏则取消收藏") - @Parameter(name = "prodId", description = "商品id" , required = true) + @Operation(summary = "添加/取消收藏", description = "传入收藏商品id,如果商品未收藏则收藏商品,已收藏则取消收藏") + @Parameter(name = "prodId", description = "商品id", required = true) public ServerResponseEntity addOrCancel(@RequestBody Long prodId) { if (Objects.isNull(productService.getProductByProdId(prodId))) { throw new YamiShopBindException("该商品不存在"); } String userId = SecurityUtils.getUser().getUserId(); if (userCollectionService.count(new LambdaQueryWrapper() - .eq(UserCollection::getProdId, prodId) - .eq(UserCollection::getUserId, userId)) > 0) { + .eq(UserCollection::getProdId, prodId) + .eq(UserCollection::getUserId, userId)) > 0) { userCollectionService.remove(new LambdaQueryWrapper() - .eq(UserCollection::getProdId, prodId) - .eq(UserCollection::getUserId, userId)); + .eq(UserCollection::getProdId, prodId) + .eq(UserCollection::getUserId, userId)); } else { UserCollection userCollection = new UserCollection(); userCollection.setCreateTime(new Date()); @@ -86,22 +122,36 @@ public class UserCollectionController { return ServerResponseEntity.success(); } + /** + * 查询用户收藏商品数量的接口方法。 + * 通过SecurityUtils获取当前登录用户的ID,然后调用UserCollectionService的count方法结合LambdaQueryWrapper,根据用户ID查询该用户的收藏商品记录数量, + * 最后将查询到的数量封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @return 返回包含用户收藏商品数量的ServerResponseEntity,成功时其数据部分为Long类型,即表示收藏商品的数量。 + */ /** * 查询用户收藏商品数量 */ @GetMapping("count") - @Operation(summary = "查询用户收藏商品数量" , description = "查询用户收藏商品数量") + @Operation(summary = "查询用户收藏商品数量", description = "查询用户收藏商品数量") public ServerResponseEntity findUserCollectionCount() { String userId = SecurityUtils.getUser().getUserId(); return ServerResponseEntity.success(userCollectionService.count(new LambdaQueryWrapper().eq(UserCollection::getUserId, userId))); } + /** + * 获取用户收藏商品列表的接口方法。 + * 根据传入的分页参数(page),调用ProductService的collectionProds方法,传入当前登录用户的ID(通过SecurityUtils获取)以及分页参数,获取当前用户的分页收藏商品列表数据, + * 最后将获取到的分页收藏商品列表数据封装在ServerResponseEntity中返回,用于统一的接口响应格式处理。 + * + * @param page 分页参数对象,包含页码、每页数量等信息,用于进行分页查询操作,通过该参数可以获取指定页的用户收藏商品列表数据。 + * @return 返回包含分页用户收藏商品列表数据的ServerResponseEntity,成功时其数据部分为IPage类型,该类型包含了分页后的收藏商品列表数据以及分页相关的元数据信息(如总页数、总记录数等)。 + */ @GetMapping("/prods") - @Operation(summary = "获取用户收藏商品列表" , description = "获取用户收藏商品列表") + @Operation(summary = "获取用户收藏商品列表", description = "获取用户收藏商品列表") public ServerResponseEntity> collectionProds(PageParam page) { String userId = SecurityUtils.getUser().getUserId(); IPage productDtoPage = productService.collectionProds(page, userId); return ServerResponseEntity.success(productDtoPage); } - -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserController.java index 1d8843f..fb6aeb1 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserController.java @@ -21,39 +21,58 @@ import lombok.AllArgsConstructor; import cn.hutool.core.bean.BeanUtil; import com.yami.shop.common.response.ServerResponseEntity; import org.springframework.web.bind.annotation.*; + /** + * 用户相关接口的控制器类 + * 该类提供了与用户操作相关的几个接口,包括查看用户信息以及设置用户信息等功能,通过调用服务层的对应方法来实现业务逻辑,并借助`Swagger`注解对接口进行文档化描述,方便接口的查看与使用。 + * * @author lanhai */ @RestController @RequestMapping("/p/user") @Tag(name = "用户接口") +// 使用 @AllArgsConstructor 注解,由 lombok 自动生成包含所有成员变量的构造函数,用于依赖注入 @AllArgsConstructor public class UserController { - private final UserService userService; + // 通过构造函数注入UserService,用于调用业务层方法来处理用户相关的业务逻辑 + private final UserService userService; - - /** - * 查看用户接口 - */ - @GetMapping("/userInfo") - @Operation(summary = "查看用户信息" , description = "根据用户ID(userId)获取用户信息") - public ServerResponseEntity userInfo() { - String userId = SecurityUtils.getUser().getUserId(); - User user = userService.getById(userId); - UserDto userDto = BeanUtil.copyProperties(user, UserDto.class); - return ServerResponseEntity.success(userDto); - } + /** + * 查看用户信息的接口方法 + * 根据当前登录用户的ID,调用服务层方法获取对应的用户信息,再将其转换为对应的DTO(数据传输对象)后返回给前端。 + * + * @return 返回一个ServerResponseEntity类型的响应,成功时包含当前登录用户信息对应的DTO对象数据,若获取数据出现异常则返回相应的错误信息。 + */ + @GetMapping("/userInfo") + @Operation(summary = "查看用户信息", description = "根据用户ID(userId)获取用户信息") + public ServerResponseEntity userInfo() { + // 获取当前登录用户的ID + String userId = SecurityUtils.getUser().getUserId(); + // 调用服务层方法根据用户ID获取用户实体信息 + User user = userService.getById(userId); + // 使用 hutool 工具类的方法,将User对象转换为UserDto对象,用于传输给前端展示的数据格式转换 + UserDto userDto = BeanUtil.copyProperties(user, UserDto.class); + return ServerResponseEntity.success(userDto); + } - @PutMapping("/setUserInfo") - @Operation(summary = "设置用户信息" , description = "设置用户信息") - public ServerResponseEntity setUserInfo(@RequestBody UserInfoParam userInfoParam) { - String userId = SecurityUtils.getUser().getUserId(); - User user = new User(); - user.setUserId(userId); - user.setPic(userInfoParam.getAvatarUrl()); - user.setNickName(userInfoParam.getNickName()); - userService.updateById(user); - return ServerResponseEntity.success(); - } -} + /** + * 设置用户信息的接口方法 + * 根据传入的用户信息参数对象,构建用户实体对象,设置相关属性(如头像、昵称等)后,调用服务层方法更新数据库中对应的用户信息,并返回表示操作成功的响应给前端。 + * + * @param userInfoParam 包含用户信息相关内容的参数对象,用于构建要更新的用户实体对象,其属性涵盖了头像链接、昵称等信息,参数从请求体中获取。 + * @return 返回一个ServerResponseEntity类型的响应,成功时表示用户信息更新操作成功,无具体返回数据,若更新过程出现异常则返回相应的错误信息。 + */ + @PutMapping("/setUserInfo") + @Operation(summary = "设置用户信息", description = "设置用户信息") + public ServerResponseEntity setUserInfo(@RequestBody UserInfoParam userInfoParam) { + // 获取当前登录用户的ID + String userId = SecurityUtils.getUser().getUserId(); + User user = new User(); + user.setUserId(userId); + user.setPic(userInfoParam.getAvatarUrl()); + user.setNickName(userInfoParam.getNickName()); + userService.updateById(user); + return ServerResponseEntity.success(); + } +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserRegisterController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserRegisterController.java index 0241868..970d913 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserRegisterController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserRegisterController.java @@ -1,6 +1,5 @@ package com.yami.shop.api.controller; - import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -24,7 +23,9 @@ import jakarta.validation.Valid; import java.util.Date; /** - * 用户信息 + * 用户注册相关接口的控制器类,用于处理用户注册以及修改密码等与用户信息相关的核心操作, + * 通过调用不同的服务层方法以及相关的工具类来完成业务逻辑,例如验证用户名是否已存在、密码加密、生成用户标识等操作, + * 并以统一的响应格式返回操作结果给前端使用。 * * @author SJL */ @@ -34,68 +35,118 @@ import java.util.Date; @AllArgsConstructor public class UserRegisterController { + // 通过构造函数注入的方式引入用户服务层接口,用于调用与用户相关的业务逻辑方法,比如保存用户信息、根据条件查询用户等操作。 private final UserService userService; - + // 通过构造函数注入的方式引入密码编码器,用于对用户密码进行加密处理,保证密码在存储和传输过程中的安全性。 private final PasswordEncoder passwordEncoder; - + // 通过构造函数注入的方式引入令牌存储相关的类,用于处理用户登录后令牌的生成、存储以及获取相关信息等操作,实现用户身份认证和授权相关功能。 private final TokenStore tokenStore; - + // 通过构造函数注入的方式引入密码管理相关的类,可能用于对用户输入的密码进行解密(如果有加密传输等情况)或者其他密码相关的管理操作。 private final PasswordManager passwordManager; + /** + * 用户注册或绑定手机号的方法,接收包含用户注册信息的参数对象,先进行用户名的合法性验证(若昵称为空则使用用户名作为昵称), + * 然后检查用户名是否已被注册,若未被注册则创建新用户对象,设置相关属性(如注册时间、状态等),对密码进行解密和加密处理后保存用户信息到数据库, + * 最后模拟用户登录操作,生成并返回包含令牌信息的响应结果给前端,用于后续的身份认证和授权访问。 + * + * @param userRegisterParam 包含用户注册信息的参数对象,通过请求体传入,且经过了参数验证(@Valid 注解),例如用户名、密码、邮箱等信息。 + * @return 包含令牌信息的 ServerResponseEntity,以 TokenInfoVO 类型封装令牌相关内容,若注册成功则返回对应的令牌信息,若用户名已注册等情况则抛出相应异常。 + */ @PostMapping("/register") - @Operation(summary = "注册" , description = "用户注册或绑定手机号接口") + @Operation(summary = "注册", description = "用户注册或绑定手机号接口") public ServerResponseEntity register(@Valid @RequestBody UserRegisterParam userRegisterParam) { + // 如果用户注册参数中的昵称(nickName)为空字符串,则将用户名(userName)作为昵称使用,保证昵称有值。 if (StrUtil.isBlank(userRegisterParam.getNickName())) { userRegisterParam.setNickName(userRegisterParam.getUserName()); } - // 正在进行申请注册 + + // 通过用户服务层的方法,使用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件,统计数据库中昵称与传入的昵称相同的用户数量, + // 如果数量大于 0,则表示该用户名(昵称)已被注册,抛出异常提示用户无法重新注册。 if (userService.count(new LambdaQueryWrapper().eq(User::getNickName, userRegisterParam.getNickName())) > 0) { // 该用户名已注册,无法重新注册 throw new YamiShopBindException("该用户名已注册,无法重新注册"); } + + // 创建当前时间对象,用于设置用户的修改时间、注册时间等属性,确保记录的时间信息准确。 Date now = new Date(); + // 创建一个新的用户对象,用于填充用户注册信息并保存到数据库中。 User user = new User(); + // 设置用户的修改时间为当前时间,表示该用户信息的最近一次修改时间。 user.setModifyTime(now); + // 设置用户的注册时间为当前时间,记录用户注册的具体时间点。 user.setUserRegtime(now); + // 设置用户状态为有效(这里假设状态值为 1 表示有效,可根据实际业务定义调整)。 user.setStatus(1); + // 设置用户的昵称,使用经过前面逻辑处理后的昵称值(可能是原昵称或者用户名)。 user.setNickName(userRegisterParam.getNickName()); + // 设置用户的邮箱信息,从注册参数中获取并赋值给用户对象。 user.setUserMail(userRegisterParam.getUserMail()); + + // 通过密码管理类的方法,对用户注册参数中传入的密码进行解密操作(可能是之前加密传输过来的情况),获取解密后的密码字符串。 String decryptPassword = passwordManager.decryptPassword(userRegisterParam.getPassWord()); + // 使用密码编码器对解密后的密码进行加密处理,将加密后的密码设置到用户对象中,保证密码在数据库中以加密形式存储,提高安全性。 user.setLoginPassword(passwordEncoder.encode(decryptPassword)); + + // 使用 Hutool 工具类生成一个简单的唯一用户 ID(这里采用 UUID 的简化形式,可根据实际需求调整生成方式),作为用户的标识。 String userId = IdUtil.simpleUUID(); + // 将生成的用户 ID 设置到用户对象中,确保每个用户在系统中有唯一的标识。 user.setUserId(userId); + + // 调用用户服务层的保存方法,将填充好信息的用户对象保存到数据库中,完成用户注册操作。 userService.save(user); - // 2. 登录 + + // 模拟用户登录操作,创建一个用于存储在令牌中的用户信息对象,填充用户 ID、用户类型(这里设置为普通用户,通过枚举值获取)、是否为管理员(这里设置为否)以及是否启用等信息。 UserInfoInTokenBO userInfoInTokenBO = new UserInfoInTokenBO(); userInfoInTokenBO.setUserId(user.getUserId()); userInfoInTokenBO.setSysType(SysTypeEnum.ORDINARY.value()); userInfoInTokenBO.setIsAdmin(0); userInfoInTokenBO.setEnabled(true); + + // 通过令牌存储类的方法,将用户信息存储并生成对应的令牌信息对象,然后返回包含令牌信息的响应结果给前端,方便后续用户进行身份认证和授权访问。 return ServerResponseEntity.success(tokenStore.storeAndGetVo(userInfoInTokenBO)); } - + /** + * 修改用户密码的方法,接收包含修改密码相关信息的参数对象,先根据用户名从数据库中查询用户信息,若用户不存在则抛出异常, + * 接着对新密码进行解密和合法性验证(不能为空且不能与原密码相同),然后对新密码进行加密处理,更新用户的密码信息到数据库中, + * 最后返回表示修改成功的响应结果给前端,若验证不通过等情况则抛出相应的异常。 + * + * @param userPwdUpdateParam 包含修改密码相关信息的参数对象,通过请求体传入,且经过了参数验证(@Valid 注解),例如用户名、新密码等信息。 + * @return 表示修改密码操作成功的 ServerResponseEntity,若修改成功则返回空的成功响应,若用户不存在、新密码不符合要求等情况则抛出相应异常。 + */ @PutMapping("/updatePwd") - @Operation(summary = "修改密码" , description = "修改密码") + @Operation(summary = "修改密码", description = "修改密码") public ServerResponseEntity updatePwd(@Valid @RequestBody UserRegisterParam userPwdUpdateParam) { + // 通过用户服务层的方法,使用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件,根据用户名(昵称)从数据库中查询对应的用户信息,若查询不到则抛出异常提示无法获取用户信息。 User user = userService.getOne(new LambdaQueryWrapper().eq(User::getNickName, userPwdUpdateParam.getNickName())); if (user == null) { // 无法获取用户信息 throw new YamiShopBindException("无法获取用户信息"); } + + // 通过密码管理类的方法,对用户修改密码参数中传入的新密码进行解密操作(可能是之前加密传输过来的情况),获取解密后的密码字符串。 String decryptPassword = passwordManager.decryptPassword(userPwdUpdateParam.getPassWord()); + // 如果解密后的新密码为空字符串,则抛出异常提示新密码不能为空。 if (StrUtil.isBlank(decryptPassword)) { // 新密码不能为空 throw new YamiShopBindException("新密码不能为空"); } + + // 使用密码编码器对解密后的新密码进行加密处理,得到加密后的密码字符串。 String password = passwordEncoder.encode(decryptPassword); + // 如果加密后的新密码与用户原密码相同,则抛出异常提示新密码不能与原密码相同,保证密码修改的合理性。 if (StrUtil.equals(password, user.getLoginPassword())) { // 新密码不能与原密码相同 throw new YamiShopBindException("新密码不能与原密码相同"); } + + // 设置用户的修改时间为当前时间,表示此次密码修改操作的时间。 user.setModifyTime(new Date()); + // 将加密后的新密码设置到用户对象中,更新用户的密码信息到数据库中,完成密码修改操作。 user.setLoginPassword(password); + // 调用用户服务层的更新方法,将更新后的用户对象信息保存到数据库中,确保密码修改生效。 userService.updateById(user); + return ServerResponseEntity.success(); } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java b/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java index c7f11c7..11d28c9 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java @@ -31,69 +31,105 @@ import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** - * 确认订单信息时的默认操作 + * 确认订单信息时的默认操作相关的监听器类,用于监听确认订单事件(ConfirmOrderEvent), + * 在事件触发时执行一系列默认操作,主要包括获取相关订单信息、验证商品状态、计算订单金额(商品总价、运费等)等逻辑, + * 确保在确认订单过程中数据的准确性和完整性。 + * * @author LGH */ @Component("defaultConfirmOrderListener") @AllArgsConstructor public class ConfirmOrderListener { + // 通过Lombok生成的构造函数注入UserAddrService,用于根据用户地址ID和用户ID获取用户地址信息, + // 在后续计算运费等操作中会用到该地址信息。 private final UserAddrService userAddrService; + // 注入TransportManagerService,用于根据购物车商品项和用户地址计算运费,是订单金额计算的重要组成部分。 private final TransportManagerService transportManagerService; + // 注入ProductService,用于根据商品ID获取商品详细信息,以验证商品状态以及参与订单金额计算等操作。 private final ProductService productService; + // 注入SkuService,用于根据商品规格(Sku)ID获取商品规格详细信息,同样用于验证商品状态和相关金额计算等操作。 private final SkuService skuService; /** - * 计算订单金额 + * 监听确认订单事件(ConfirmOrderEvent)的方法,当该事件被触发时,会执行此方法内的逻辑。 + * 通过 @EventListener 注解将此方法标记为事件监听器,使其能够响应对应的事件。 + * @Order 注解则指定了该监听器在处理确认订单事件时的执行顺序(这里按照默认顺序执行),确保相关操作按照预期的顺序进行。 + * 方法内部主要逻辑如下: + * 1. 首先从事件对象中获取与订单相关的各种信息,包括购物车订单信息(ShopCartOrderDto)、订单参数(OrderParam)以及购物车商品项列表等。 + * 2. 获取当前用户的ID,用于后续根据用户ID和地址ID查询用户地址信息等操作。 + * 3. 根据订单参数中的地址ID和当前用户ID,通过UserAddrService获取用户地址信息,用于后续运费计算等操作(如果地址存在的话)。 + * 4. 遍历购物车商品项列表,针对每个商品项进行如下操作: + * - 通过ProductService和SkuService分别获取商品和商品规格的详细信息,若获取不到则抛出异常,表示购物车包含无法识别的商品。 + * - 验证商品和商品规格的状态是否为上架状态(状态值为1),若不是则抛出商品已下架的异常。 + * - 累加商品数量,计算商品总价(将每个商品项的金额累加到总金额中)。 + * - 如果用户地址不为空,则调用TransportManagerService的方法计算该商品项对应的运费,并累加到总运费中。 + * - 设置商品项的实际总价(这里暂设置为商品项原本的金额,可能后续还有其他逻辑调整),以及更新购物车订单信息中的各项金额相关属性(实际总价、总金额、商品总数量、运费等)。 + * + * @param event 确认订单事件对象,包含了确认订单过程中涉及的各种相关数据,如购物车信息、订单参数等。 */ @EventListener(ConfirmOrderEvent.class) @Order(ConfirmOrderOrder.DEFAULT) public void defaultConfirmOrderEvent(ConfirmOrderEvent event) { - + // 从确认订单事件对象中获取购物车订单信息,包含了订单整体的一些汇总信息以及后续需要更新的金额等相关属性。 ShopCartOrderDto shopCartOrderDto = event.getShopCartOrderDto(); + // 从确认订单事件对象中获取订单参数,其中包含了如用户选择的收货地址ID等关键信息,用于后续查询地址等操作。 OrderParam orderParam = event.getOrderParam(); + // 通过SecurityUtils工具类获取当前用户的ID,用于后续根据用户ID来查询与之相关的用户地址等信息。 String userId = SecurityUtils.getUser().getUserId(); - // 订单的地址信息 + // 根据订单参数中的地址ID和当前用户ID,通过UserAddrService获取用户地址信息,该地址信息将用于计算运费等操作,若不存在地址则返回null。 UserAddr userAddr = userAddrService.getUserAddrByUserId(orderParam.getAddrId(), userId); + // 初始化订单总金额为0.0,后续会累加每个商品项的金额得到最终的订单总金额。 double total = 0.0; + // 初始化商品总数量为0,在遍历购物车商品项时会累加每个商品项的数量得到总的商品数量。 int totalCount = 0; + // 初始化运费为0.0,后续会根据每个商品项以及用户地址计算运费并累加得到总的运费。 double transfee = 0.0; + // 遍历购物车中的每个商品项,对每个商品项进行相关的信息获取、状态验证以及金额计算等操作。 for (ShopCartItemDto shopCartItem : event.getShopCartItems()) { - // 获取商品信息 + // 通过ProductService根据商品ID获取商品详细信息,用于后续验证商品状态以及参与金额计算等操作。 Product product = productService.getProductByProdId(shopCartItem.getProdId()); - // 获取sku信息 + // 通过SkuService根据商品规格ID获取商品规格详细信息,同样用于验证商品状态和参与金额计算等操作。 Sku sku = skuService.getSkuBySkuId(shopCartItem.getSkuId()); if (product == null || sku == null) { throw new YamiShopBindException("购物车包含无法识别的商品"); } - if (product.getStatus() != 1 || sku.getStatus() != 1) { + if (product.getStatus()!= 1 || sku.getStatus()!= 1) { throw new YamiShopBindException("商品[" + sku.getProdName() + "]已下架"); } + // 累加商品数量,将当前商品项的数量累加到总商品数量中。 totalCount = shopCartItem.getProdCount() + totalCount; + // 使用Arith工具类(可能是自定义的用于精确计算的工具类)将当前商品项的金额累加到总金额中,得到订单的商品总金额。 total = Arith.add(shopCartItem.getProductTotalAmount(), total); - // 用户地址如果为空,则表示该用户从未设置过任何地址相关信息 - if (userAddr != null) { - // 每个产品的运费相加 + // 用户地址如果不为空,则表示用户设置了收货地址,需要计算该商品项对应的运费并累加到总运费中。 + if (userAddr!= null) { + // 调用TransportManagerService的方法计算当前商品项对应的运费,传入商品项和用户地址信息作为参数, + // 并将计算得到的运费累加到总运费中,实现运费的累加计算。 transfee = Arith.add(transfee, transportManagerService.calculateTransfee(shopCartItem, userAddr)); } + // 设置商品项的实际总价(这里暂设置为商品项原本的金额,可能后续还有其他逻辑调整)。 shopCartItem.setActualTotal(shopCartItem.getProductTotalAmount()); + // 更新购物车订单信息中的实际总价,将商品总金额和运费相加得到最终的实际总价。 shopCartOrderDto.setActualTotal(Arith.add(total, transfee)); + // 更新购物车订单信息中的总金额,即商品总金额(不包含运费)。 shopCartOrderDto.setTotal(total); + // 更新购物车订单信息中的商品总数量。 shopCartOrderDto.setTotalCount(totalCount); + // 更新购物车订单信息中的运费。 shopCartOrderDto.setTransfee(transfee); } } -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/listener/ShopCartListener.java b/yami-shop-api/src/main/java/com/yami/shop/api/listener/ShopCartListener.java index 4c4087e..49d8fd8 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/listener/ShopCartListener.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/listener/ShopCartListener.java @@ -8,6 +8,7 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城API相关的监听器包下,主要用于监听与购物车相关的事件,并执行相应的业务逻辑。 package com.yami.shop.api.listener; import com.google.common.collect.Lists; @@ -23,25 +24,38 @@ import org.springframework.stereotype.Component; import java.util.List; /** - * 默认的购物车链进行组装时的操作 + * ShopCartListener类主要用于处理购物车相关事件的监听与操作,在默认的购物车链进行组装时执行特定的业务逻辑。 + * 具体来说,它会接收购物车相关事件(ShopCartEvent),并将店铺下的所有商品按照一定规则归属到该店铺的购物车当中,完成购物车数据的组装工作。 + * * @author LGH */ @Component("defaultShopCartListener") +// 使用@Component注解将该类标记为Spring组件,使得Spring容器能够识别并管理它。同时,为该组件指定了一个名称"defaultShopCartListener", +// 在其他地方可以通过这个名称来获取该组件的实例(例如在基于名称进行依赖注入时)。 public class ShopCartListener { /** - * 将店铺下的所有商品归属到该店铺的购物车当中 - * @param event#getShopCart() 购物车 - * @param event#shopCartItemDtoList 该购物车的商品 - * @return 是否继续组装 + * defaultShopCartEvent方法是一个事件处理方法,用于监听ShopCartEvent类型的事件,并在事件触发时执行相应的购物车数据组装逻辑。 + * 它会获取事件中携带的购物车信息以及购物车商品列表信息,然后进行一系列的操作,将商品信息按照一定的格式组装到购物车相关的数据结构中,以便后续的业务处理。 + * + * @param event 传入的ShopCartEvent类型的事件对象,该对象包含了与购物车相关的重要信息,例如购物车本身的信息(通过event.getShopCart()获取)以及购物车中商品的列表信息(通过event.getShopCartItemDtoList()获取)。 + * 具体来说,event.getShopCart()方法可以获取到ShopCartDto类型的购物车对象,它可能包含购物车的标识、所属用户等基本信息; + * event.getShopCartItemDtoList()方法可以获取到一个包含ShopCartItemDto类型对象的列表,每个ShopCartItemDto对象代表购物车中的一个商品条目,包含商品的详细信息如商品ID、数量、价格等。 */ @EventListener(ShopCartEvent.class) + // @EventListener注解用于标记该方法为一个事件监听器方法,表明它会监听指定类型(这里是ShopCartEvent类型)的事件。当对应的事件在Spring应用上下文中被发布时, + // 该方法就会被自动调用,从而执行相应的业务逻辑来处理这个事件。 @Order(ShopCartEventOrder.DEFAULT) + // @Order注解用于指定该事件监听器的执行顺序。在存在多个相同类型事件监听器的情况下,Spring会按照这个顺序来依次调用它们,确保业务逻辑按照预定的顺序执行。 + // 这里的ShopCartEventOrder.DEFAULT表示该监听器的执行顺序遵循默认的设定,具体的顺序数值可能在ShopCartEventOrder类中定义,它决定了在整个购物车相关事件处理流程中的先后顺序。 public void defaultShopCartEvent(ShopCartEvent event) { ShopCartDto shopCart = event.getShopCartDto(); List shopCartItemDtoList = event.getShopCartItemDtoList(); + // 对数据进行组装 List shopCartItemDiscountDtoList = Lists.newArrayList(); + // 创建一个ShopCartItemDiscountDto对象,用于承载要组装的商品折扣等相关信息(从类名推测,可能涉及商品在购物车中的折扣相关数据处理)。 + // 这里虽然目前只看到简单的设置操作,但可能后续会根据业务需求扩展更多与折扣计算、展示等相关的逻辑。 ShopCartItemDiscountDto shopCartItemDiscountDto = new ShopCartItemDiscountDto(); shopCartItemDiscountDto.setShopCartItems(shopCartItemDtoList); @@ -49,5 +63,4 @@ public class ShopCartListener { shopCart.setShopCartItemDiscounts(shopCartItemDiscountDtoList); } - -} +} \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java b/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java index 2cb0f8d..bd5e0e8 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java @@ -38,6 +38,8 @@ import java.util.*; /** * 确认订单信息时的默认操作 + * 该类主要用于处理在用户提交订单时的一系列默认业务逻辑,例如保存订单地址、计算订单金额、更新商品库存、生成订单相关信息以及插入订单结算数据等操作, + * 同时还包含了对商品库存、商品状态等相关情况的校验,并在出现异常情况时抛出相应的异常。 * * @author LGH */ @@ -45,136 +47,162 @@ import java.util.*; @AllArgsConstructor public class SubmitOrderListener { - - - + // 用于处理用户地址相关的订单服务,比如保存用户地址信息等操作 private final UserAddrOrderService userAddrOrderService; - + // 用于处理商品相关业务逻辑的服务,例如获取商品信息等 private final ProductService productService; - + // 用于处理商品库存单元(SKU)相关业务逻辑的服务,比如获取SKU信息等 private final SkuService skuService; - + // 雪花算法工具类,用于生成唯一的订单编号等 private final Snowflake snowflake; - + // 订单商品项数据访问层接口,用于操作订单商品项相关的数据,比如插入、更新等操作 private final OrderItemMapper orderItemMapper; - + // SKU数据访问层接口,用于操作SKU相关的数据,比如更新库存等操作 private final SkuMapper skuMapper; - + // 商品数据访问层接口,用于操作商品相关的数据,比如更新库存等操作 private final ProductMapper productMapper; - + // 订单数据访问层接口,用于操作订单相关的数据,比如插入、更新订单信息等操作 private final OrderMapper orderMapper; - + // 订单结算数据访问层接口,用于操作订单结算相关的数据,比如插入订单结算记录等操作 private final OrderSettlementMapper orderSettlementMapper; - + // 购物车数据访问层接口,用于操作购物车相关的数据,比如删除购物车中的商品等操作 private final BasketMapper basketMapper; /** - * 计算订单金额 + * 计算订单金额及处理相关订单业务逻辑的方法 + * 该方法作为事件监听器,在接收到提交订单事件(SubmitOrderEvent)时被触发,执行一系列与订单确认相关的默认操作, + * 包括保存订单地址、生成订单、更新商品库存、删除购物车商品等操作,同时会对商品库存不足等异常情况进行处理。 + * + * @param event 提交订单事件对象,包含了与提交订单相关的各种数据,如合并后的订单信息等 */ @EventListener(SubmitOrderEvent.class) @Order(SubmitOrderOrder.DEFAULT) public void defaultSubmitOrderListener(SubmitOrderEvent event) { + // 获取当前时间,用于后续设置创建时间等时间相关的属性 Date now = new Date(); + // 获取当前登录用户的ID,用于关联订单与用户等操作 String userId = SecurityUtils.getUser().getUserId(); + // 获取合并后的订单信息对象,包含了多个店铺的订单以及相关的商品、地址等信息 ShopCartOrderMergerDto mergerOrder = event.getMergerOrder(); - // 订单商品参数 + // 订单商品参数,即包含了各个店铺下的订单商品信息列表 List shopCartOrders = mergerOrder.getShopCartOrders(); + // 用于存储需要删除的购物车商品ID列表,后续会根据这个列表删除购物车中的对应商品 List basketIds = new ArrayList<>(); - // 商品skuId为key 需要更新的sku为value的map + // 以商品SKU的ID为键,需要更新的SKU对象为值的映射表,用于记录商品SKU库存等信息的变化,方便后续统一更新库存 Map skuStocksMap = new HashMap<>(16); - // 商品productId为key 需要更新的product为value的map + // 以商品的ID为键,需要更新的商品对象为值的映射表,用于记录商品库存等信息的变化,方便后续统一更新库存 Map prodStocksMap = new HashMap<>(16); // 把订单地址保存到数据库 + // 通过属性拷贝将合并订单中的用户地址信息复制到用户地址订单对象中 UserAddrOrder userAddrOrder = BeanUtil.copyProperties(mergerOrder.getUserAddr(), UserAddrOrder.class); if (userAddrOrder == null) { + // 如果地址信息为空,则抛出异常,提示用户需要填写收货地址 throw new YamiShopBindException("请填写收货地址"); } userAddrOrder.setUserId(userId); userAddrOrder.setCreateTime(now); + // 调用服务层方法保存用户地址订单信息到数据库中 userAddrOrderService.save(userAddrOrder); - // 订单地址id + // 获取保存后的订单地址ID,后续订单信息会关联该地址ID Long addrOrderId = userAddrOrder.getAddrOrderId(); - - // 每个店铺生成一个订单 + // 每个店铺生成一个订单,遍历各个店铺的订单商品信息列表进行订单创建等操作 for (ShopCartOrderDto shopCartOrderDto : shopCartOrders) { createOrder(event, now, userId, basketIds, skuStocksMap, prodStocksMap, addrOrderId, shopCartOrderDto); - } - // 删除购物车的商品信息 + // 如果存在需要删除的购物车商品ID,则调用购物车数据访问层方法删除购物车中的对应商品 if (!basketIds.isEmpty()) { basketMapper.deleteShopCartItemsByBasketIds(userId, basketIds); - } - - // 更新sku库存 + // 遍历SKU库存映射表,更新每个SKU的库存信息 skuStocksMap.forEach((key, sku) -> { - + // 调用SKU数据访问层的方法更新库存,如果更新的行数为0,表示库存更新失败(可能库存不足等原因) if (skuMapper.updateStocks(sku) == 0) { + // 如果库存更新失败,清除对应SKU的缓存(通过SKU服务层方法),并抛出库存不足的异常,提示用户对应商品库存不足 skuService.removeSkuCacheBySkuId(key, sku.getProdId()); throw new YamiShopBindException("商品:[" + sku.getProdName() + "]库存不足"); } }); - // 更新商品库存 + // 遍历商品库存映射表,更新每个商品的库存信息 prodStocksMap.forEach((prodId, prod) -> { - + // 调用商品数据访问层的方法更新库存,如果更新的行数为0,表示库存更新失败(可能库存不足等原因) if (productMapper.updateStocks(prod) == 0) { + // 如果库存更新失败,清除对应商品的缓存(通过商品服务层方法),并抛出库存不足的异常,提示用户对应商品库存不足 productService.removeProductCacheByProdId(prodId); throw new YamiShopBindException("商品:[" + prod.getProdName() + "]库存不足"); } }); - } + /** + * 创建单个店铺订单的方法 + * 该方法用于为每个店铺生成对应的订单信息,包括生成订单编号、处理订单商品项、构建订单对象、插入订单结算数据等操作, + * 同时会更新相关的商品库存映射表和购物车商品ID列表。 + * + * @param event 提交订单事件对象,包含了与提交订单相关的各种数据 + * @param now 当前时间,用于设置订单相关的时间属性 + * @param userId 当前登录用户的ID,用于关联订单与用户 + * @param basketIds 用于存储需要删除的购物车商品ID列表 + * @param skuStocksMap 以商品SKU的ID为键,需要更新的SKU对象为值的映射表,用于记录商品SKU库存等信息的变化 + * @param prodStocksMap 以商品的ID为键,需要更新的商品对象为值的映射表,用于记录商品库存等信息的变化 + * @param addrOrderId 订单地址的ID,用于关联订单与收货地址 + * @param shopCartOrderDto 单个店铺的订单商品信息对象,包含了该店铺下的商品、金额等订单相关信息 + */ private void createOrder(SubmitOrderEvent event, Date now, String userId, List basketIds, Map skuStocksMap, Map prodStocksMap, Long addrOrderId, ShopCartOrderDto shopCartOrderDto) { - // 使用雪花算法生成的订单号 + // 使用雪花算法生成唯一的订单编号,并转换为字符串类型 String orderNumber = String.valueOf(snowflake.nextId()); shopCartOrderDto.setOrderNumber(orderNumber); + // 获取店铺的ID,用于关联订单与店铺 Long shopId = shopCartOrderDto.getShopId(); - // 订单商品名称 + // 用于构建订单商品名称字符串,将各个商品名称拼接起来,方便展示订单包含的商品信息 StringBuilder orderProdName = new StringBuilder(100); + // 用于存储该订单下的各个订单商品项信息,后续会将这些商品项关联到订单对象中 List orderItems = new ArrayList<>(); + // 获取店铺订单下的商品折扣信息列表,每个商品折扣信息对象包含了多个商品项信息 List shopCartItemDiscounts = shopCartOrderDto.getShopCartItemDiscounts(); for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartItemDiscounts) { + // 获取每个商品折扣信息对象下的商品项信息列表 List shopCartItems = shopCartItemDiscount.getShopCartItems(); for (ShopCartItemDto shopCartItem : shopCartItems) { + // 检查并获取商品的SKU信息,同时更新SKU库存映射表(如果需要),并处理库存不足等异常情况 Sku sku = checkAndGetSku(shopCartItem.getSkuId(), shopCartItem, skuStocksMap); + // 检查并获取商品信息,同时更新商品库存映射表(如果需要),并处理库存不足等异常情况 Product product = checkAndGetProd(shopCartItem.getProdId(), shopCartItem, prodStocksMap); + // 根据获取到的商品、SKU以及其他相关信息构建订单商品项对象 OrderItem orderItem = getOrderItem(now, userId, orderNumber, shopId, orderProdName, shopCartItem, sku, product); orderItems.add(orderItem); - if (shopCartItem.getBasketId() != null && shopCartItem.getBasketId() != 0) { + if (shopCartItem.getBasketId()!= null && shopCartItem.getBasketId()!= 0) { basketIds.add(shopCartItem.getBasketId()); } } - } - + // 对订单商品名称字符串进行处理,截取合适的长度,并去除末尾多余的逗号(如果有) orderProdName.subSequence(0, Math.min(orderProdName.length() - 1, 100)); if (orderProdName.lastIndexOf(Constant.COMMA) == orderProdName.length() - 1) { orderProdName.deleteCharAt(orderProdName.length() - 1); } - - // 订单信息 + // 根据订单相关信息构建订单对象,包括店铺ID、订单编号、商品名称、用户ID、金额等各种属性 com.yami.shop.bean.model.Order order = getOrder(now, userId, addrOrderId, shopCartOrderDto, orderNumber, shopId, orderProdName, orderItems); event.getOrders().add(order); - // 插入订单结算表 + + // 插入订单结算数据,构建订单结算对象并设置相关属性,如用户ID、是否结算、创建时间、订单编号、支付金额等,然后插入到数据库中 OrderSettlement orderSettlement = new OrderSettlement(); orderSettlement.setUserId(userId); orderSettlement.setIsClearing(0); @@ -186,19 +214,35 @@ public class SubmitOrderListener { orderSettlementMapper.insert(orderSettlement); } + /** + * 根据相关信息构建订单对象的方法 + * 该方法根据传入的各种参数,如时间、用户ID、地址ID、店铺订单信息、订单编号、店铺ID、订单商品名称以及订单商品项列表等,构建一个完整的订单对象, + * 设置订单对象的各个属性,包括店铺ID、订单编号、商品名称、用户ID、商品总额、实际总额、订单状态、时间、支付状态等各种属性。 + * + * @param now 当前时间,用于设置订单的创建时间和更新时间等属性 + * @param userId 当前登录用户的ID,用于设置订单的用户ID属性 + * @param addrOrderId 订单地址的ID,用于设置订单的关联地址ID属性 + * @param shopCartOrderDto 单个店铺的订单商品信息对象,包含了该店铺下的商品、金额等订单相关信息,用于设置订单的商品总额、实际总额等属性 + * @param orderNumber 订单编号,用于设置订单的订单编号属性 + * @param shopId 店铺的ID,用于设置订单的店铺ID属性 + * @param orderProdName 订单商品名称字符串,用于设置订单的商品名称属性 + * @param orderItems 订单商品项列表,用于设置订单的商品项关联属性 + * @return 构建好的订单对象,包含了各种设置好的订单相关属性信息 + */ private com.yami.shop.bean.model.Order getOrder(Date now, String userId, Long addrOrderId, ShopCartOrderDto shopCartOrderDto, String orderNumber, Long shopId, StringBuilder orderProdName, List orderItems) { com.yami.shop.bean.model.Order order = new com.yami.shop.bean.model.Order(); order.setShopId(shopId); order.setOrderNumber(orderNumber); - // 订单商品名称 + // 设置订单商品名称属性 order.setProdName(orderProdName.toString()); - // 用户id + // 设置用户ID属性 order.setUserId(userId); - // 商品总额 + // 设置商品总额属性,从店铺订单信息中获取 order.setTotal(shopCartOrderDto.getTotal()); - // 实际总额 + // 设置实际总额属性,从店铺订单信息中获取 order.setActualTotal(shopCartOrderDto.getActualTotal()); + // 设置订单状态为未支付状态(通过枚举值表示) order.setStatus(OrderStatus.UNPAY.value()); order.setUpdateTime(now); order.setCreateTime(now); @@ -206,6 +250,7 @@ public class SubmitOrderListener { order.setDeleteStatus(0); order.setProductNums(shopCartOrderDto.getTotalCount()); order.setAddrOrderId(addrOrderId); + // 计算并设置优惠金额属性,通过商品总额、运费和实际总额进行计算 order.setReduceAmount(Arith.sub(Arith.add(shopCartOrderDto.getTotal(), shopCartOrderDto.getTransfee()), shopCartOrderDto.getActualTotal())); order.setFreightAmount(shopCartOrderDto.getTransfee()); order.setRemarks(shopCartOrderDto.getRemarks()); @@ -214,90 +259,14 @@ public class SubmitOrderListener { return order; } - private OrderItem getOrderItem(Date now, String userId, String orderNumber, Long shopId, StringBuilder orderProdName, ShopCartItemDto shopCartItem, Sku sku, Product product) { - OrderItem orderItem = new OrderItem(); - orderItem.setShopId(shopId); - orderItem.setOrderNumber(orderNumber); - orderItem.setProdId(sku.getProdId()); - orderItem.setSkuId(sku.getSkuId()); - orderItem.setSkuName(sku.getSkuName()); - orderItem.setProdCount(shopCartItem.getProdCount()); - orderItem.setProdName(sku.getProdName()); - orderItem.setPic(StrUtil.isBlank(sku.getPic()) ? product.getPic() : sku.getPic()); - orderItem.setPrice(shopCartItem.getPrice()); - orderItem.setUserId(userId); - orderItem.setProductTotalAmount(shopCartItem.getProductTotalAmount()); - orderItem.setRecTime(now); - orderItem.setCommSts(0); - orderItem.setBasketDate(shopCartItem.getBasketDate()); - orderProdName.append(orderItem.getProdName()).append(","); - //推广员卡号 - orderItem.setDistributionCardNo(shopCartItem.getDistributionCardNo()); - return orderItem; - } - - @SuppressWarnings({"Duplicates"}) - private Product checkAndGetProd(Long prodId, ShopCartItemDto shopCartItem, Map prodStocksMap) { - Product product = productService.getProductByProdId(prodId); - if (product == null) { - throw new YamiShopBindException("购物车包含无法识别的商品"); - } - - if (product.getStatus() != 1) { - throw new YamiShopBindException("商品[" + product.getProdName() + "]已下架"); - } - - // 商品需要改变的库存 - Product mapProduct = prodStocksMap.get(prodId); - - if (mapProduct == null) { - mapProduct = new Product(); - mapProduct.setTotalStocks(0); - mapProduct.setProdId(prodId); - mapProduct.setProdName(product.getProdName()); - - } - - if (product.getTotalStocks() != -1) { - mapProduct.setTotalStocks(mapProduct.getTotalStocks() + shopCartItem.getProdCount()); - prodStocksMap.put(product.getProdId(), mapProduct); - } - - // -1为无限库存 - if (product.getTotalStocks() != -1 && mapProduct.getTotalStocks() > product.getTotalStocks()) { - throw new YamiShopBindException("商品:[" + product.getProdName() + "]库存不足"); - } - - return product; - } - - @SuppressWarnings({"Duplicates"}) - private Sku checkAndGetSku(Long skuId, ShopCartItemDto shopCartItem, Map skuStocksMap) { - // 获取sku信息 - Sku sku = skuService.getSkuBySkuId(skuId); - if (sku == null) { - throw new YamiShopBindException("购物车包含无法识别的商品"); - } - - if (sku.getStatus() != 1) { - throw new YamiShopBindException("商品[" + sku.getProdName() + "]已下架"); - } - // -1为无限库存 - if (sku.getStocks() != -1 && shopCartItem.getProdCount() > sku.getStocks()) { - throw new YamiShopBindException("商品:[" + sku.getProdName() + "]库存不足"); - } - - if (sku.getStocks() != -1) { - Sku mapSku = new Sku(); - mapSku.setProdId(sku.getProdId()); - // 这里的库存是改变的库存 - mapSku.setStocks(shopCartItem.getProdCount()); - mapSku.setSkuId(sku.getSkuId()); - mapSku.setProdName(sku.getProdName()); - skuStocksMap.put(sku.getSkuId(), mapSku); - } - return sku; - } - - -} + /** + * 根据相关信息构建订单商品项对象的方法 + * 该方法根据传入的各种参数,如时间、用户ID、订单编号、店铺ID、订单商品名称、购物车商品项信息、商品SKU信息以及商品信息等,构建一个订单商品项对象, + * 设置订单商品项对象的各个属性,包括店铺ID、订单编号、商品ID、SKU ID、SKU名称、商品数量、商品名称、图片、价格、用户ID、商品总额、时间等各种属性。 + * + * @param now 当前时间,用于设置订单商品项的接收时间等属性 + * @param userId 当前登录用户的ID,用于设置订单商品项的用户ID属性 + * @param orderNumber 订单编号,用于设置订单商品项的订单编号属性 + * @param shopId 店铺的ID,用于设置订单商品项的店铺ID属性 + * @param orderProdName 订单商品名称字符串,用于拼接商品名称(在循环中不断追加) + * @param shopCartItem 购物车商品项信息 \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/SmsInfoContext.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/SmsInfoContext.java index c66e840..12fb9e6 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/SmsInfoContext.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/SmsInfoContext.java @@ -22,70 +22,77 @@ import cn.hutool.core.collection.CollectionUtil; /** * @author lanhai * 这个类 `SmsInfoContext` 的作用是作为短信信息的上下文管理类,它利用 `ThreadLocal` 机制来实现在多线程环境下,每个线程都能独立地管理自己的短信信息集合(以 `SmsInfoBo` 列表形式存储), - * 提供了获取、设置、添加以及清理短信信息的相关方法,方便在不同的业务逻辑中对当前线程的短信信息进行操作,避免了多线程之间短信信息数据的相互干扰。 + * 这样做的好处在于,不同线程在处理与短信相关的业务逻辑时,各自的短信信息不会相互干扰,确保了数据的独立性和安全性,避免了多线程并发访问时可能出现的数据不一致、并发修改等问题。 + * 此类提供了获取、设置、添加以及清理短信信息的相关方法,方便在不同的业务逻辑中对当前线程的短信信息进行操作,使整个短信业务处理在多线程环境下更加有序和可控。 */ public class SmsInfoContext { - /** - * The request holder. - * 定义一个静态的 `ThreadLocal` 变量 `smsInfoHolder`,用于在每个线程中存储短信信息列表(类型为 `List`)。 - * `ThreadLocal` 的作用是让每个线程都有自己独立的变量副本,也就是说,不同线程通过这个 `smsInfoHolder` 访问到的短信信息列表是相互独立的,互不影响, - * 这样就可以在多线程的场景下安全地处理各自线程相关的短信业务数据。 - */ - private static ThreadLocal> smsInfoHolder = new ThreadLocal>(); + /** + * The request holder. + * 定义一个静态的 `ThreadLocal` 变量 `smsInfoHolder`,用于在每个线程中存储短信信息列表(类型为 `List`)。 + * `ThreadLocal` 是Java中的一种特殊机制,它的核心特点是让每个线程都有自己独立的变量副本,也就是说,不同线程通过这个 `smsInfoHolder` 访问到的短信信息列表是相互独立的,互不影响, + * 这就为多线程环境下安全地处理各自线程相关的短信业务数据提供了基础保障,例如在一个Web应用中,多个用户请求可能同时触发短信相关的业务操作,每个请求对应的线程就可以通过这个机制来管理自己的短信信息,不会相互干扰。 + */ + private static ThreadLocal> smsInfoHolder = new ThreadLocal>(); - /** - * 获取当前线程中存储的短信信息列表。 - * 首先通过 `smsInfoHolder.get()` 方法尝试获取当前线程对应的短信信息列表,然后使用 `CollectionUtil.isEmpty(list)` 判断获取到的列表是否为空。 - * 如果为空(意味着当前线程还没有设置过短信信息列表或者之前设置的列表被清理了等情况),则返回一个新创建的空的 `ArrayList`,这样调用这个方法的地方可以放心地使用返回的列表进行后续操作,不用担心出现空指针异常等问题。 - * 如果获取到的列表不为空,则直接返回当前线程通过 `smsInfoHolder` 存储的短信信息列表,以便后续对已有的短信信息进行操作,比如遍历、修改等。 - * - * @return 当前线程中存储的短信信息列表,如果当前线程原本没有设置过短信信息列表,则返回一个新的空列表。 - */ - public static List get() { - List list = smsInfoHolder.get(); - if (CollectionUtil.isEmpty(list)) { - return new ArrayList<>(); - } - return smsInfoHolder.get(); - } + /** + * 获取当前线程中存储的短信信息列表。 + * 首先通过 `smsInfoHolder.get()` 方法尝试获取当前线程对应的短信信息列表,这个方法是 `ThreadLocal` 提供的用于获取当前线程副本中存储的对象的方法。 + * 然后使用 `CollectionUtil.isEmpty(list)` 判断获取到的列表是否为空,`CollectionUtil.isEmpty` 是 `hutool` 工具库提供的便捷方法,用于判断集合是否为空(集合为 `null` 或者集合中没有元素都视为空)。 + * 如果为空(意味着当前线程还没有设置过短信信息列表或者之前设置的列表被清理了等情况),则返回一个新创建的空的 `ArrayList`,这样调用这个方法的地方可以放心地使用返回的列表进行后续操作,不用担心出现空指针异常等问题,保证了方法调用的健壮性。 + * 如果获取到的列表不为空,则直接返回当前线程通过 `smsInfoHolder` 存储的短信信息列表,以便后续对已有的短信信息进行操作,比如遍历、修改等,使得业务逻辑可以基于已有的短信信息继续进行处理。 + * + * @return 当前线程中存储的短信信息列表,如果当前线程原本没有设置过短信信息列表,则返回一个新的空列表。 + */ + public static List get() { + List list = smsInfoHolder.get(); + if (CollectionUtil.isEmpty(list)) { + return new ArrayList<>(); + } + return smsInfoHolder.get(); + } - /** - * 设置当前线程中存储的短信信息列表。 - * 该方法接收一个 `List` 类型的参数 `smsInfoBos`,然后通过 `smsInfoHolder.set(smsInfoBos)` 将这个参数对应的短信信息列表设置到当前线程的 `ThreadLocal` 变量中, - * 这样在当前线程后续的其他代码中通过 `get` 方法获取到的就是新设置的这个短信信息列表了,方便在不同的业务逻辑阶段更新当前线程所使用的短信信息数据。 - * - * @param smsInfoBos 要设置到当前线程的短信信息列表。 - */ - public static void set(List smsInfoBos) { - smsInfoHolder.set(smsInfoBos); - } + /** + * 设置当前线程中存储的短信信息列表。 + * 该方法接收一个 `List` 类型的参数 `smsInfoBos`,这个参数代表了要设置到当前线程的短信信息列表,它包含了在当前业务逻辑下需要处理的短信相关信息。 + * 然后通过 `smsInfoHolder.set(smsInfoBos)` 将这个参数对应的短信信息列表设置到当前线程的 `ThreadLocal` 变量中,`ThreadLocal` 的 `set` 方法会将传入的对象与当前线程进行关联存储, + * 这样在当前线程后续的其他代码中通过 `get` 方法获取到的就是新设置的这个短信信息列表了,方便在不同的业务逻辑阶段更新当前线程所使用的短信信息数据,确保业务逻辑可以根据最新的短信信息进行相应的处理。 + * + * @param smsInfoBos 要设置到当前线程的短信信息列表。 + */ + public static void set(List smsInfoBos) { + smsInfoHolder.set(smsInfoBos); + } - /** - * 向当前线程中存储的短信信息列表中添加一条短信信息(`SmsInfoBo` 对象)。 - * 首先通过 `smsInfoHolder.get()` 获取当前线程已有的短信信息列表,接着使用 `CollectionUtil.isEmpty(smsInfoBos)` 判断这个列表是否为空。 - * 如果为空,说明当前线程还没有短信信息列表,那么就创建一个新的空的 `ArrayList` 作为初始的短信信息列表。然后将传入的短信信息对象 `smsInfoBo` 添加到这个列表中, - * 最后再通过 `smsInfoHolder.set(smsInfoBos)` 将更新后的包含了新添加短信信息的列表重新设置回当前线程的 `ThreadLocal` 变量中,确保线程中存储的短信信息列表始终是最新的状态,方便后续继续操作。 - * - * @param smsInfoBo 要添加到当前线程短信信息列表中的短信信息对象。 - */ - public static void put(SmsInfoBo smsInfoBo) { - List smsInfoBos = smsInfoHolder.get(); - if (CollectionUtil.isEmpty(smsInfoBos)) { - smsInfoBos = new ArrayList<>(); - } - smsInfoBos.add(smsInfoBo); - smsInfoHolder.set(smsInfoBos); - } + /** + * 向当前线程中存储的短信信息列表中添加一条短信信息(`SmsInfoBo` 对象)。 + * 首先通过 `smsInfoHolder.get()` 获取当前线程已有的短信信息列表,这一步是为了获取当前线程之前可能已经存储的短信信息,以便后续在这个基础上进行添加操作。 + * 接着使用 `CollectionUtil.isEmpty(smsInfoBos)` 判断这个列表是否为空,同样是利用 `hutool` 工具库的方法来判断集合是否为空。 + * 如果为空,说明当前线程还没有短信信息列表,那么就创建一个新的空的 `ArrayList` 作为初始的短信信息列表,为添加新的短信信息做准备。 + * 然后将传入的短信信息对象 `smsInfoBo` 添加到这个列表中,通过 `smsInfoBos.add(smsInfoBo)` 操作实现向列表中添加元素,完成了短信信息的添加逻辑。 + * 最后再通过 `smsInfoHolder.set(smsInfoBos)` 将更新后的包含了新添加短信信息的列表重新设置回当前线程的 `ThreadLocal` 变量中,确保线程中存储的短信信息列表始终是最新的状态,方便后续继续操作,例如在后续的业务逻辑中再次获取该列表进行遍历、发送短信等操作时,能获取到完整且最新的短信信息。 + * + * @param smsInfoBo 要添加到当前线程短信信息列表中的短信信息对象。 + */ + public static void put(SmsInfoBo smsInfoBo) { + List smsInfoBos = smsInfoHolder.get(); + if (CollectionUtil.isEmpty(smsInfoBos)) { + smsInfoBos = new ArrayList<>(); + } + smsInfoBos.add(smsInfoBo); + smsInfoHolder.set(smsInfoBos); + } - /** - * 清理当前线程中存储的短信信息列表。 - * 首先通过 `smsInfoHolder.get()` 获取当前线程中存储的短信信息列表,判断其是否不为 `null`,如果不为 `null`,则调用 `smsInfoHolder.remove()` 方法将当前线程对应的短信信息列表从 `ThreadLocal` 变量中移除, - * 这样当前线程后续再通过 `get` 等方法获取短信信息列表时,得到的就是初始的默认状态(比如为空的情况)。这个方法常用于在某个业务逻辑执行完毕后,清理当前线程相关的短信信息数据,防止数据残留导致的内存占用以及可能出现的数据混乱等问题。 - */ - public static void clean() { - if (smsInfoHolder.get()!= null) { - smsInfoHolder.remove(); - } - } + /** + * 清理当前线程中存储的短信信息列表。 + * 首先通过 `smsInfoHolder.get()` 获取当前线程中存储的短信信息列表,判断其是否不为 `null`,这一步是为了避免在 `ThreadLocal` 中没有存储任何对象时进行不必要的移除操作,防止出现空指针异常等问题。 + * 如果不为 `null`,则调用 `smsInfoHolder.remove()` 方法将当前线程对应的短信信息列表从 `ThreadLocal` 变量中移除,`ThreadLocal` 的 `remove` 方法会解除当前线程与存储对象之间的关联, + * 这样当前线程后续再通过 `get` 等方法获取短信信息列表时,得到的就是初始的默认状态(比如为空的情况)。这个方法常用于在某个业务逻辑执行完毕后,清理当前线程相关的短信信息数据,防止数据残留导致的内存占用以及可能出现的数据混乱等问题, + * 例如在一次短信发送业务完成后,及时清理当前线程的短信信息,为下一次相关业务操作做好准备,保证每个线程在不同业务阶段的短信信息独立性和数据的整洁性。 + */ + public static void clean() { + if (smsInfoHolder.get()!= null) { + smsInfoHolder.remove(); + } + } } \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ConfirmOrderOrder.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ConfirmOrderOrder.java index 290f3d4..0a91466 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ConfirmOrderOrder.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ConfirmOrderOrder.java @@ -11,28 +11,37 @@ package com.yami.shop.bean.order; /** - * 提交订单事件先后顺序 + * 该接口定义了提交订单事件先后顺序相关的常量,用于明确在不同业务场景(如涉及各种优惠活动等情况)下提交订单时各业务逻辑执行的先后顺序, + * 方便在订单处理流程中进行统一的规则控制,确保各个环节按照既定顺序依次执行,避免因顺序混乱导致的业务逻辑错误或计算错误等问题。 + * * @author LGH */ public interface ConfirmOrderOrder { /** - * 没有任何活动时的顺序 + * 定义表示没有任何活动时提交订单各业务逻辑执行顺序的常量,值为0,意味着在这种最简单的情况下,相关业务逻辑按照此默认顺序执行。 + * 当订单不存在满减、优惠券、分销等活动时,各环节处理会参考此顺序进行,例如商品价格计算、运费计算等基础操作的顺序安排。 */ int DEFAULT = 0; /** - * 满减,排在DEFAULT后面 + * 定义表示满减活动参与时提交订单各业务逻辑执行顺序的常量,值为100,该顺序排在 DEFAULT(无活动情况)后面, + * 意味着当订单涉及满减活动时,满减相关的业务逻辑(如满减金额计算、满减规则校验等)会在没有活动时的基础业务逻辑之后执行, + * 确保先完成商品原价相关基础计算,再根据满减规则进行相应的优惠计算和调整。 */ int DISCOUNT = 100; /** - * 优惠券,排在DISCOUNT后面 + * 定义表示使用优惠券活动参与时提交订单各业务逻辑执行顺序的常量,值为200,该顺序排在 DISCOUNT(满减活动情况)后面, + * 说明当订单在已经有满减活动的基础上,若还使用了优惠券,那么优惠券相关的业务逻辑(如优惠券可用校验、优惠金额抵扣等)会在满减业务逻辑之后执行, + * 以此类推,按照先后顺序依次完成各优惠活动相关的业务处理,保证优惠计算的准确性和合理性。 */ int COUPON = 200; /** - * 分销,排在COUPON后面 + * 定义表示涉及分销业务时提交订单各业务逻辑执行顺序的常量,值为300,该顺序排在 COUPON(优惠券活动情况)后面, + * 即当订单在经过满减、使用优惠券等优惠活动处理后,如果存在分销相关业务逻辑(如分销佣金计算、分销规则判断等),则会在此之后执行, + * 这样可以在完成价格优惠调整后,再去处理涉及收益分配等方面的分销业务逻辑,使整个订单处理流程更加清晰、有序。 */ int DISTRIBUTION = 300; -} +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ShopCartEventOrder.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ShopCartEventOrder.java index 36b3fda..486cb1a 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ShopCartEventOrder.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ShopCartEventOrder.java @@ -11,18 +11,24 @@ package com.yami.shop.bean.order; /** - * 购物车事件先后顺序 + * `ShopCartEventOrder`接口用于定义购物车相关事件的先后顺序,在涉及多个购物车相关业务逻辑且这些逻辑存在执行顺序要求的场景下, + * 通过定义这样的顺序常量,可以清晰地确定各个事件应该按照怎样的顺序依次执行,有助于保证整个购物车业务流程的准确性和稳定性。 + * 例如,在计算购物车商品总价时,可能需要先应用某些优惠活动,不同优惠活动的应用顺序就可以通过这里定义的顺序值来进行规范。 + * * @author LGH */ public interface ShopCartEventOrder { /** - * 没有任何活动时的顺序 + * 没有任何活动时的顺序,定义了一个默认的顺序值为`0`,表示在没有其他特殊活动影响购物车相关业务逻辑执行顺序的情况下, + * 对应的操作或者事件应该按照这个基础顺序来执行,可作为一个基准顺序,其他带有特定活动相关的顺序值可以参照它来进行相对顺序的设定。 */ int DEFAULT = 0; /** - * 满减活动的组装顺序,排在DEFAULT后面 + * 满减活动的组装顺序,排在`DEFAULT`后面,值为`100`,意味着在购物车业务流程中,当涉及满减活动相关的逻辑处理时, + * 应该在默认顺序(没有活动时的基础顺序)之后执行,通过这样明确的顺序设定,确保满减活动相关的计算、验证等操作在合适的时间点进行, + * 避免与其他业务逻辑的执行顺序混乱而导致计算错误或者不符合业务预期的情况出现。 */ int DISCOUNT = 100; -} +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/order/SubmitOrderOrder.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/order/SubmitOrderOrder.java index d4ef4f5..0e26eef 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/order/SubmitOrderOrder.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/order/SubmitOrderOrder.java @@ -8,26 +8,33 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城相关的Java Bean的订单(order)包下,这个接口主要用于定义提交订单事件的先后顺序相关的常量,以便在处理提交订单业务逻辑中, +// 根据不同的业务场景(如是否有优惠券、折扣等活动参与)来确定各个操作环节的执行顺序。 package com.yami.shop.bean.order; /** - * 提交订单事件先后顺序 + * SubmitOrderOrder接口用于定义提交订单事件的先后顺序相关的常量值,在整个订单提交的业务流程中,不同的业务操作(例如应用优惠券、折扣等优惠活动)可能有先后顺序之分, + * 通过这个接口定义的常量来明确这些顺序,方便在具体的事件处理逻辑中进行排序和控制,确保订单提交过程中各个环节按照预定的顺序依次执行,保证业务逻辑的正确性。 + * * @author LGH */ public interface SubmitOrderOrder { /** - * 没有任何活动时的顺序 + * DEFAULT常量用于表示在没有任何活动(如优惠券、折扣等优惠活动都未参与)时的默认顺序,其值被定义为0,在整个订单提交事件顺序的比较和判断中, + * 作为基础顺序参考,其他涉及活动的顺序值通常会大于这个默认值,以体现活动相关操作在默认操作之后执行的逻辑。 */ int DEFAULT = 0; /** - * 优惠券,排在DEFAULT后面 + * DISCOUNT常量用于表示与折扣相关操作在提交订单事件顺序中的位置,其值设定为100,意味着在有折扣活动参与的情况下,折扣相关操作应该在默认顺序(DEFAULT)之后执行, + * 通过这个顺序值来确保在订单提交流程中,先进行一些基础的订单信息处理(对应默认顺序的操作),然后再进行折扣相关的计算和应用等操作,保证业务逻辑的合理性和连贯性。 */ int DISCOUNT = 100; /** - * 优惠券,排在DEFAULT后面 + * COUPON常量用于表示与优惠券相关操作在提交订单事件顺序中的位置,其值设定为200,说明在有优惠券活动参与时,优惠券相关操作要在折扣相关操作(DISCOUNT顺序对应的操作)之后执行, + * 进一步细化了订单提交过程中优惠活动应用的先后顺序,即先处理默认的订单信息,再应用折扣,最后应用优惠券,以此来准确计算订单最终的金额等信息,符合一般的业务流程逻辑。 */ int COUPON = 200; -} +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/DeliveryOrderParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/DeliveryOrderParam.java index 11cb958..2f878e6 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/DeliveryOrderParam.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/DeliveryOrderParam.java @@ -8,52 +8,77 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城相关的Java Bean的参数(param)包下,通常用于封装前端传递给后端的参数信息,方便在业务逻辑中对这些参数进行统一的验证、获取和使用。 package com.yami.shop.bean.param; import io.swagger.v3.oas.annotations.media.Schema; - import jakarta.validation.constraints.NotBlank; /** + * DeliveryOrderParam类是一个Java Bean,主要用于封装与发货订单相关的参数信息,这些参数通常是由前端传递给后端,以便后端进行发货相关的业务处理操作。 + * 同时,它借助了一些注解来对参数进行约束和文档描述,例如使用Jakarta Validation的@NotBlank注解进行参数非空验证,使用Swagger的@Schema注解对参数在API文档中的展示进行描述。 + * * @author lanhai */ public class DeliveryOrderParam { - @NotBlank(message="订单号不能为空") - @Schema(description = "订单号" ,required=true) - private String orderNumber; - - @NotBlank(message="快递公司id不能为空") - @Schema(description = "快递公司" ,required=true) - private Long dvyId; - - @NotBlank(message="物流单号不能为空") - @Schema(description = "物流单号" ,required=true) - private String dvyFlowId; + // 使用@NotBlank注解对orderNumber字段进行约束,表明该字段不能为空字符串,当验证不通过时(即传入的订单号为空字符串),会按照message属性中定义的提示信息“订单号不能为空”进行错误提示。 + // 同时使用@Schema注解对该字段在Swagger生成的API文档中进行描述,说明其代表“订单号”,并且是必填项(required=true),方便接口使用者了解该参数的含义和要求。 + @NotBlank(message = "订单号不能为空") + @Schema(description = "订单号", required = true) + private String orderNumber; + // 类似地,对dvyId字段使用@NotBlank注解进行约束,要求该字段不能为空,验证不通过时提示“快递公司id不能为空”。 + // 通过@Schema注解在API文档中描述其代表“快递公司”且为必填项,这里的Long类型可能对应着快递公司在系统中的唯一标识符,用于明确指定发货所使用的快递公司。 + @NotBlank(message = "快递公司id不能为空") + @Schema(description = "快递公司", required = true) + private Long dvyId; - public Long getDvyId() { - return dvyId; - } + // 对dvyFlowId字段同样使用@NotBlank注解确保其不能为空,不符合要求时提示“物流单号不能为空”,并通过@Schema注解在API文档里说明其代表“物流单号”且是必填项, + // 物流单号用于追踪货物运输的具体情况,是发货流程中必不可少的关键信息。 + @NotBlank(message = "物流单号不能为空") + @Schema(description = "物流单号", required = true) + private String dvyFlowId; - public void setDvyId(Long dvyId) { - this.dvyId = dvyId; - } + /** + * 获取快递公司ID的Getter方法,用于在其他类中获取该对象中封装的快递公司的唯一标识符,方便在发货相关的业务逻辑中使用该ID去查询、匹配或记录快递公司相关的信息。 + */ + public Long getDvyId() { + return dvyId; + } - public String getDvyFlowId() { - return dvyFlowId; - } + /** + * 设置快递公司ID的Setter方法,用于在外部给该对象的dvyId字段赋值,例如在接收前端传递的参数并封装到该对象时,通过这个方法将快递公司ID设置到对象中,以便后续业务逻辑使用。 + */ + public void setDvyId(Long dvyId) { + this.dvyId = dvyId; + } - public void setDvyFlowId(String dvyFlowId) { - this.dvyFlowId = dvyFlowId; - } + /** + * 获取物流单号的Getter方法,方便其他类获取该对象中封装的物流单号信息,在发货业务逻辑中可能会用于将物流单号传递给物流查询接口或者记录到订单的物流信息字段中等操作。 + */ + public String getDvyFlowId() { + return dvyFlowId; + } - public String getOrderNumber() { - return orderNumber; - } + /** + * 设置物流单号的Setter方法,用于外部给该对象的dvyFlowId字段赋值,例如将前端传来的物流单号设置到对象中,以保证对象中封装的物流信息准确完整,便于后续的发货相关业务处理。 + */ + public void setDvyFlowId(String dvyFlowId) { + this.dvyFlowId = dvyFlowId; + } - public void setOrderNumber(String orderNumber) { - this.orderNumber = orderNumber; - } + /** + * 获取订单号的Getter方法,供其他类获取该对象中封装的订单号信息,在发货操作中会依据订单号来确定具体是哪个订单进行发货处理,比如查询订单详情、更新订单状态等操作都需要用到订单号。 + */ + public String getOrderNumber() { + return orderNumber; + } -} + /** + * 设置订单号的Setter方法,用于外部将订单号赋值给该对象的orderNumber字段,例如在接收前端传递的订单号参数时,通过这个方法将其设置到对象中,确保对象中包含准确的订单号信息,以便后续基于订单号开展发货相关业务逻辑。 + */ + public void setOrderNumber(String orderNumber) { + this.orderNumber = orderNumber; + } +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/OrderParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/OrderParam.java index b7e88df..677f105 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/OrderParam.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/OrderParam.java @@ -16,40 +16,60 @@ import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; /** + * 订单参数类 + * 该类用于封装与订单查询等操作相关的参数信息,通过各个属性来传递不同的筛选条件,例如店铺ID、订单状态、支付情况、订单编号以及时间范围等,方便在业务逻辑中根据这些条件进行订单数据的查询筛选操作。 + * * @author lanhai */ @Data +// 使用 @Data 注解,由 lombok 自动生成常用的方法,如Getter、Setter、toString、equals、hashCode等方法 public class OrderParam { /** - * 店铺id + * 店铺id属性 + * 用于指定要查询的订单所属的店铺的唯一标识,可根据该属性筛选出特定店铺下的订单信息。 */ private Long shopId; /** - * 订单状态 -1 已取消 0:待付款 1:待发货 2:待收货 3:已完成 + * 订单状态属性 + * 用于指定要查询的订单的状态,取值含义如下: + * - -1 表示已取消的订单; + * - 0 表示待付款的订单; + * - 1 表示待发货的订单; + * - 2 表示待收货的订单; + * - 3 表示已完成的订单。 + * 通过该属性可以筛选出处于特定状态的订单集合。 */ private Integer status; /** - * 是否已经支付,1:已经支付过,0:,没有支付过 + * 是否已经支付属性 + * 用于指定要查询的订单是否已经完成支付,取值含义如下: + * - 1 表示已经支付过的订单; + * - 0 表示没有支付过的订单。 + * 可依据该属性筛选出已支付或未支付的订单数据。 */ private Integer isPayed; /** - * 订购流水号 + * 订购流水号属性 + * 用于指定要查询的订单的唯一编号,通过该属性可以精确查找某一个具体的订单记录。 */ private String orderNumber; /** - * 开始时间 + * 开始时间属性 + * 用于指定订单查询的时间范围的起始时间,结合结束时间属性可以筛选出在该时间段内产生的订单信息。 + * 其日期时间格式通过 @DateTimeFormat 注解指定为 "yyyy-MM-dd HH:mm:ss",在接收前端传入的时间数据时会按照此格式进行解析。 */ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date startTime; /** - * 结束时间 + * 结束时间属性 + * 用于指定订单查询的时间范围的结束时间,结合开始时间属性可以筛选出在该时间段内产生的订单信息。 + * 其日期时间格式通过 @DateTimeFormat 注解指定为 "yyyy-MM-dd HH:mm:ss",在接收前端传入的时间数据时会按照此格式进行解析。 */ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date endTime; - -} +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/ProductParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/ProductParam.java index 79f38d8..e84f669 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/ProductParam.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/ProductParam.java @@ -20,91 +20,114 @@ import jakarta.validation.constraints.Size; import java.util.List; /** + * `ProductParam`类作为一个参数对象,主要用于在业务逻辑中传递与商品相关的各种参数信息,例如在创建或更新商品时, + * 前端将商品的各项属性值封装到这个对象中传递给后端,后端可以基于这些参数进行相应的业务处理(如数据校验、保存到数据库等操作)。 + * 通过`lombok`的`@Data`注解,自动生成了各个属性的`getter`、`setter`方法以及`toString`、`hashCode`和`equals`等方法,简化了代码编写,方便对对象属性的操作和使用。 + * 同时,为各个属性添加了相应的验证约束注解,用于在接收参数时进行合法性校验,确保传入的参数符合业务要求,避免因非法参数导致的业务逻辑错误。 + * * @author lanhai */ @Data public class ProductParam { /** - * 产品ID + * 产品ID,用于唯一标识一个商品,在更新商品操作时可通过这个ID来定位要修改的具体商品, + * 在新建商品时,该属性可能初始值为`null`,待商品保存到数据库后会被赋予相应的唯一标识符。 */ private Long prodId; /** - * 状态 + * 状态,用于表示商品当前所处的状态,例如可能用不同的整数值代表商品的上架、下架、审核中等等不同状态, + * 具体的状态值含义需要根据业务规则来确定,在业务处理过程中可以根据这个状态属性来执行不同的逻辑,比如上架商品的展示、下架商品的隐藏等操作。 */ private Integer status; /** - * 商品名称 + * 商品名称,是商品的重要标识信息之一,用于展示给用户查看商品的基本信息。 + * 通过`@NotBlank`注解约束该属性不能为空字符串,即必须有具体的名称内容,同时通过`@Size`注解限制其长度应该小于`200`个字符, + * 这样可以保证商品名称的规范性和合理性,避免过长或为空的名称影响系统的展示和业务逻辑处理。 */ @NotBlank(message = "商品名称不能为空") @Size(max = 200, message = "商品名称长度应该小于{max}") private String prodName; - /** - * 商品价格 + * 商品价格,代表商品当前的售卖价格,是用户购买商品时需要支付的实际金额, + * 通过`@NotNull`注解约束该属性不能为`null`,即必须明确指定商品的价格,确保在进行商品相关业务操作(如添加到购物车、下单等)时价格信息的完整性。 */ @NotNull(message = "请输入商品价格") private Double price; /** - * 商品价格 + * 商品原价,可能用于展示商品的原始价格,与当前售价对比,让用户直观了解商品是否有优惠活动等情况, + * 同样通过`@NotNull`注解约束不能为`null`,保证原价信息的完整性,以便业务逻辑能正确处理价格相关的展示和计算。 */ @NotNull(message = "请输入商品原价") private Double oriPrice; /** - * 库存量 + * 库存量,用于记录商品当前可销售的库存数量,在用户下单购买商品时,需要根据库存数量来判断是否能够满足购买需求, + * 通过`@NotNull`注解确保必须传入库存数量,防止因库存信息缺失导致的超卖等业务问题。 */ @NotNull(message = "请输入商品库存") private Integer totalStocks; /** - * 简要描述,卖点等 + * 简要描述,卖点等,用于对商品的特点、优势等进行简短的文字描述,方便用户快速了解商品的关键信息, + * 通过`@Size`注解限制其长度应该小于`500`个字符,保证描述信息简洁明了且不会过长影响展示效果。 */ @Size(max = 500, message = "商品卖点长度应该小于{max}") private String brief; + /** + * 图片相关的属性,可能用于存储商品的主图路径或者标识等信息,通过`@NotBlank`注解约束不能为空字符串, + * 意味着必须选择图片上传,确保商品有对应的展示图片,提升用户体验以及满足业务展示需求。 + */ @NotBlank(message = "请选择图片上传") private String pic; /** - * 商品图片 + * 商品图片,可能用于存储商品的多张图片路径等信息,同样通过`@NotBlank`注解要求必须选择图片上传, + * 以便在商品详情页等地方展示商品的多角度、多细节图片,让用户更全面地了解商品外观等情况。 */ @NotBlank(message = "请选择图片上传") private String imgs; /** - * 商品分类 + * 商品分类,用于将商品归类到不同的类别下,方便在系统中进行分类管理和展示,例如电子产品、服装、食品等不同分类, + * 通过`@NotNull`注解确保必须选择商品分类,使得商品能正确归属到相应的分类体系中,便于用户查找和系统的统计分析等操作。 */ @NotNull(message = "请选择商品分类") private Long categoryId; /** - * sku列表字符串 + * sku列表字符串,这里虽然定义为`List`类型,但名称中的“字符串”可能暗示了在实际业务中其存储或传递形式可能与字符串有一定关联, + * 一般来说,`Sku`代表商品的规格信息,如不同颜色、尺码等规格对应的库存、价格等详细信息,这个列表用于保存商品的所有规格相关数据,方便在处理商品多规格业务时使用。 */ private List skuList; /** - * content 商品详情 + * content 商品详情,用于存储商品的详细介绍内容,比如商品的参数、功能、使用方法等详细文字描述, + * 可以让用户更深入地了解商品的具体情况,在商品详情页进行展示,帮助用户做出购买决策。 */ private String content; /** - * 是否能够用户自提 + * 是否能够用户自提,通过`Product.DeliveryModeVO`类型的对象来表示,这个对象应该包含了与商品配送方式相关的详细信息, + * 比如除了自提还可能涉及店铺配送等其他配送模式的设置情况,用于确定商品的配送相关属性,满足不同用户的收货需求和业务的配送管理要求。 */ private Product.DeliveryModeVO deliveryModeVo; /** - * 运费模板id + * 运费模板id,用于关联商品对应的运费计算模板,不同的运费模板可以根据不同的规则(如按重量、按件数、地区差异等)来计算商品的运费, + * 通过这个id可以找到对应的运费模板,进而准确计算商品在不同情况下的运费金额,确保运费计算的准确性和灵活性。 */ private Long deliveryTemplateId; /** - * 分组标签列表 + * 分组标签列表,用于给商品打上不同的分组标签,方便从不同维度对商品进行分类、筛选和推荐等操作,例如可以按照品牌、风格、适用场景等标签来组织商品, + * 用户可以通过这些标签快速找到符合自己需求的商品,同时也便于系统进行个性化推荐等业务逻辑实现。 */ private List tagList; -} +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/ShopDetailParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/ShopDetailParam.java index 14f94ee..b5e3c8f 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/ShopDetailParam.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/ShopDetailParam.java @@ -8,215 +8,301 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城相关的Java Bean的参数(param)包下,主要用于封装与店铺详情相关的参数信息,这些参数通常是前端传递给后端,以便后端进行店铺详情相关的业务处理操作,例如店铺信息的新增、修改等功能。 package com.yami.shop.bean.param; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; /** + * ShopDetailParam类是一个Java Bean,用于封装店铺详情相关的各种参数信息,通过使用Jakarta Validation框架的相关注解对各个参数进行约束验证,确保传入的参数符合业务要求, + * 同时提供了对应的Getter和Setter方法方便对这些参数进行获取和设置操作,以便在店铺详情相关的业务逻辑中使用这些参数信息。 + * * @author lanhai */ public class ShopDetailParam { - - private Long shopId; + // 店铺的唯一标识符,在整个系统中用于区分不同的店铺,在涉及店铺相关的数据库操作(如查询、更新、删除等)以及业务逻辑处理时,依靠这个ID来精准定位到具体的店铺实体。 + private Long shopId; /** - * 店铺名称(数字、中文,英文(可混合,不可有特殊字符),可修改)、不唯一 + * 店铺名称,用于展示店铺的对外称呼,其取值规则限定为可包含数字、中文、英文(可混合,但不可有特殊字符),并且该字段是必填项(通过@NotBlank注解约束), + * 同时规定了长度限制(通过@Size注解约束,最大长度为50个字符),名称可根据实际情况进行修改,在系统中不要求唯一,不同店铺可以有相同的名称。 */ - @NotBlank(message="店铺名称不能为空") - @Size(max = 50,message = "商品名称长度应该小于{max}") + @NotBlank(message = "店铺名称不能为空") + @Size(max = 50, message = "商品名称长度应该小于{max}") private String shopName; /** - * 店铺简介(可修改) + * 店铺简介,用于简要描述店铺的经营特色、主营产品等相关信息,是可修改的内容,并且通过@Size注解限定了其最大长度为200个字符,以保证简介内容的简洁性和合理性。 */ - @Size(max = 200,message = "店铺简介长度应该小于{max}") + @Size(max = 200, message = "店铺简介长度应该小于{max}") private String intro; /** - * 店铺公告(可修改) + * 店铺公告,通常用于向顾客展示店铺的重要通知、活动信息等内容,同样是可修改的,并且有长度限制,通过@Size注解规定其最大长度为50个字符,方便顾客快速浏览重要公告信息。 */ - @Size(max = 50,message = "店铺公告长度应该小于{max}") + @Size(max = 50, message = "店铺公告长度应该小于{max}") private String shopNotice; /** - * 店铺联系电话 + * 店铺联系电话,是顾客联系店铺的重要途径,所以该字段不能为空(通过@NotBlank注解约束),同时通过@Size注解限制其最大长度为20个字符,确保电话号码格式的合理性,方便后续的电话沟通相关业务操作。 */ - @NotBlank(message="店铺联系电话不能为空") - @Size(max = 20,message = "店铺公告长度应该小于{max}") + @NotBlank(message = "店铺联系电话不能为空") + @Size(max = 20, message = "店铺公告长度应该小于{max}") private String tel; /** - * 店铺详细地址 + * 店铺详细地址,明确店铺所在的具体地理位置,是必填项(@NotBlank注解约束),并通过@Size注解限定最大长度为100个字符,方便准确记录和展示店铺的详细位置信息,便于顾客查找店铺以及物流配送等相关业务操作。 */ - @NotBlank(message="店铺详细地址不能为空") - @Size(max = 100,message = "店铺公告长度应该小于{max}") + @NotBlank(message = "店铺详细地址不能为空") + @Size(max = 100, message = "店铺公告长度应该小于{max}") private String shopAddress; /** - * 店铺所在省份(描述) + * 店铺所在省份(描述),用于明确店铺所在的省级行政区域,不能为空(@NotBlank注解约束),且限定最大长度为10个字符,以合适的长度来描述省份信息,方便在系统中进行省市区相关的业务逻辑处理和展示。 */ - @NotBlank(message="店铺所在省份不能为空") - @Size(max = 10,message = "店铺所在省份长度应该小于{max}") + @NotBlank(message = "店铺所在省份不能为空") + @Size(max = 10, message = "店铺所在省份长度应该小于{max}") private String province; /** - * 店铺所在城市(描述) + * 店铺所在城市(描述),指明店铺位于的市级行政区域,同样是必填项(@NotBlank注解约束),并规定最大长度为10个字符,用于准确表示城市信息,在涉及店铺地理位置定位、同城业务等场景中会用到该信息。 */ - @NotBlank(message="店铺所在城市不能为空") - @Size(max = 10,message = "店铺所在城市长度应该小于{max}") + @NotBlank(message = "店铺所在城市不能为空") + @Size(max = 10, message = "店铺所在城市长度应该小于{max}") private String city; /** - * 店铺所在区域(描述) + * 店铺所在区域(描述),用于更精确地定位店铺所在的具体城区等细分区域,也是必填项(@NotBlank注解约束),通过@Size注解限制其最大长度为10个字符,便于细化店铺的地理位置信息,有助于更精准的业务操作,比如本地配送范围界定等。 */ - @NotBlank(message="店铺所在区域不能为空") - @Size(max = 10,message = "店铺所在省份区域长度应该小于{max}") + @NotBlank(message = "店铺所在区域不能为空") + @Size(max = 10, message = "店铺所在省份区域长度应该小于{max}") private String area; /** - * 店铺省市区代码,用于回显 + * 店铺省市区代码,用于在需要回显省市区相关信息时使用,例如在前端页面展示店铺地址选择框等场景下,依靠这个代码来准确显示对应的省市区信息,该字段不能为空(@NotBlank注解约束),且最大长度为20个字符(@Size注解约束)。 */ - @NotBlank(message="店铺省市区代码不能为空") - @Size(max = 20,message = "店铺省市区代码长度应该小于{max}") + @NotBlank(message = "店铺省市区代码不能为空") + @Size(max = 20, message = "店铺省市区代码长度应该小于{max}") private String pcaCode; /** - * 店铺logo(可修改) + * 店铺logo,用于展示店铺的品牌形象,是店铺在系统界面中重要的视觉标识,不能为空(@NotBlank注解约束),并通过@Size注解限制其最大长度为200个字符,具体格式可能根据系统实际存储方式而定(比如图片路径、Base64编码等),且该logo是可修改的。 */ - @NotBlank(message="店铺logo不能为空") - @Size(max = 200,message = "店铺logo长度应该小于{max}") + @NotBlank(message = "店铺logo不能为空") + @Size(max = 200, message = "店铺logo长度应该小于{max}") private String shopLogo; /** - * 店铺相册 + * 店铺相册,用于存放店铺的多张图片信息(可能是店铺环境、商品展示等相关图片),通过@Size注解限定其最大长度为1000个字符,具体存储格式可能是图片路径列表等形式,方便在店铺详情页面展示更多店铺相关的视觉内容。 */ - @Size(max = 1000,message = "店铺相册长度应该小于{max}") + @Size(max = 1000, message = "店铺相册长度应该小于{max}") private String shopPhotos; /** - * 每天营业时间段(可修改) + * 每天营业时间段,明确店铺每天的营业时间范围,是顾客了解店铺营业规律的重要信息,不能为空(@NotBlank注解约束),且通过@Size注解限制其最大长度为100个字符,用于合理规范营业时间段的填写格式,便于后续业务逻辑中对营业时间的判断和处理。 */ - @NotBlank(message="每天营业时间段不能为空") - @Size(max = 100,message = "每天营业时间段长度应该小于{max}") + @NotBlank(message = "每天营业时间段不能为空") + @Size(max = 100, message = "每天营业时间段长度应该小于{max}") private String openTime; - /** - * 店铺状态(-1:未开通 0: 停业中 1:营业中),可修改 + /** + * 店铺状态,用于标识店铺当前的营业情况,取值范围限定为 -1(表示未开通)、0(表示停业中)、1(表示营业中),并且该字段是可修改的,方便在店铺运营过程中根据实际情况更新店铺的状态信息,以反映店铺的真实经营状态。 */ private Integer shopStatus; - public String getShopName() { - return shopName; - } - - public void setShopName(String shopName) { - this.shopName = shopName; - } + /** + * 获取店铺名称的Getter方法,方便在其他类中获取该对象中封装的店铺名称信息,用于在店铺详情展示、业务逻辑判断(如根据名称搜索店铺等)等场景中使用。 + */ + public String getShopName() { + return shopName; + } - public String getIntro() { - return intro; - } + /** + * 设置店铺名称的Setter方法,用于在外部给该对象的shopName字段赋值,例如在接收前端传递的店铺名称参数并封装到该对象时,通过这个方法将名称设置到对象中,以便后续业务逻辑使用该准确的店铺名称信息。 + */ + public void setShopName(String shopName) { + this.shopName = shopName; + } - public void setIntro(String intro) { - this.intro = intro; - } + /** + * 获取店铺简介的Getter方法,供其他类获取该对象中封装的店铺简介信息,在店铺详情页面展示、更新店铺简介等相关业务操作中会用到该方法来获取当前的店铺简介内容。 + */ + public String getIntro() { + return intro; + } - public String getShopNotice() { - return shopNotice; - } + /** + * 设置店铺简介的Setter方法,用于外部将店铺简介内容赋值给该对象的intro字段,例如在前端修改店铺简介后,后端通过这个方法接收并更新对象中的简介信息,以便后续保存到数据库等相关业务逻辑使用。 + */ + public void setIntro(String intro) { + this.intro = intro; + } - public void setShopNotice(String shopNotice) { - this.shopNotice = shopNotice; - } + /** + * 获取店铺公告的Getter方法,方便获取该对象中封装的店铺公告信息,在展示店铺最新通知、判断公告内容是否符合要求等业务场景中会用到该方法来获取公告内容。 + */ + public String getShopNotice() { + return shopNotice; + } - public String getTel() { - return tel; - } + /** + * 设置店铺公告的Setter方法,用于外部给该对象的shopNotice字段赋值,比如在前端发布新的店铺公告后,后端通过这个方法接收并更新对象中的公告信息,以便后续在店铺详情页面展示等业务逻辑使用。 + */ + public void setShopNotice(String shopNotice) { + this.shopNotice = shopNotice; + } - public void setTel(String tel) { - this.tel = tel; - } + /** + * 获取店铺联系电话的Getter方法,供其他类获取该对象中封装的店铺联系电话信息,在需要联系店铺、进行电话验证等与电话沟通相关的业务场景中会用到该电话号码信息。 + */ + public String getTel() { + return tel; + } - public String getShopAddress() { - return shopAddress; - } + /** + * 设置店铺联系电话的Setter方法,用于外部将店铺联系电话赋值给该对象的tel字段,例如在前端更新店铺联系电话后,后端通过这个方法接收并更新对象中的电话信息,以便后续业务逻辑使用该准确的电话号码。 + */ + public void setTel(String tel) { + this.tel = tel; + } - public void setShopAddress(String shopAddress) { - this.shopAddress = shopAddress; - } + /** + * 获取店铺详细地址的Getter方法,方便获取该对象中封装的店铺详细地址信息,在展示店铺位置、物流配送地址确认等业务场景中会用到该详细地址内容。 + */ + public String getShopAddress() { + return shopAddress; + } - public String getProvince() { - return province; - } + /** + * 设置店铺详细地址的Setter方法,用于外部将店铺详细地址赋值给该对象的shopAddress字段,例如在前端修改店铺地址后,后端通过这个方法接收并更新对象中的地址信息,以便后续保存到数据库等业务逻辑使用。 + */ + public void setShopAddress(String shopAddress) { + this.shopAddress = shopAddress; + } - public void setProvince(String province) { - this.province = province; - } + /** + * 获取店铺所在省份(描述)的Getter方法,供其他类获取该对象中封装的省份信息,在省市区相关的业务逻辑处理、地址展示等场景中会用到该省份描述内容。 + */ + public String getProvince() { + return province; + } - public String getCity() { - return city; - } + /** + * 设置店铺所在省份(描述)的Setter方法,用于外部将省份信息赋值给该对象的province字段,例如在前端修改店铺所在省份后,后端通过这个方法接收并更新对象中的省份信息,以便后续业务逻辑使用该准确的省份描述。 + */ + public void setProvince(String province) { + this.province = province; + } - public void setCity(String city) { - this.city = city; - } + /** + * 获取店铺所在城市(描述)的Getter方法,方便获取该对象中封装的城市信息,在涉及城市相关的业务逻辑(如同城业务判断、城市筛选等)以及地址展示等场景中会用到该城市描述内容。 + */ + public String getCity() { + return city; + } - public String getArea() { - return area; - } + /** + * 设置店铺所在城市(描述)的Setter方法,用于外部将城市信息赋值给该对象的city字段,例如在前端修改店铺所在城市后,后端通过这个方法接收并更新对象中的城市信息,以便后续业务逻辑使用该准确的城市描述。 + */ + public void setCity(String city) { + this.city = city; + } - public void setArea(String area) { - this.area = area; - } + /** + * 获取店铺所在区域(描述)的Getter方法,供其他类获取该对象中封装的区域信息,在更精细的地理位置相关业务逻辑(如本地配送范围判断、区域筛选等)以及地址展示等场景中会用到该区域描述内容。 + */ + public String getArea() { + return area; + } - public String getPcaCode() { - return pcaCode; - } + /** + * 设置店铺所在区域(描述)的Setter方法,用于外部将区域信息赋值给该对象的area字段,例如在前端修改店铺所在区域后,后端通过这个方法接收并更新对象中的区域信息,以便后续业务逻辑使用该准确的区域描述。 + */ + public void setArea(String area) { + this.area = area; + } - public void setPcaCode(String pcaCode) { - this.pcaCode = pcaCode; - } + /** + * 获取店铺省市区代码的Getter方法,方便获取该对象中封装的省市区代码信息,在需要回显省市区相关信息、与地址代码相关的业务逻辑处理等场景中会用到该代码内容。 + */ + public String getPcaCode() { + return pcaCode; + } - public String getShopLogo() { - return shopLogo; - } + /** + * 设置店铺省市区代码的Setter方法,用于外部将省市区代码赋值给该对象的pcaCode字段,例如在前端修改省市区代码后,后端通过这个方法接收并更新对象中的代码信息,以便后续业务逻辑使用该准确的代码。 + */ + public void setPcaCode(String pcaCode) { + this.pcaCode = pcaCode; + } - public void setShopLogo(String shopLogo) { - this.shopLogo = shopLogo; - } + /** + * 获取店铺logo的Getter方法,供其他类获取该对象中封装的店铺logo信息,在店铺详情页面展示店铺品牌形象、更新logo等业务场景中会用到该logo内容。 + */ + public String getShopLogo() { + return shopLogo; + } - public String getShopPhotos() { - return shopPhotos; - } + /** + * 设置店铺logo的Setter方法,用于外部将店铺logo赋值给该对象的shopLogo字段,例如在前端更新店铺logo后,后端通过这个方法接收并更新对象中的logo信息,以便后续保存到数据库等业务逻辑使用。 + */ + public void setShopLogo(String shopLogo) { + this.shopLogo = shopLogo; + } - public void setShopPhotos(String shopPhotos) { - this.shopPhotos = shopPhotos; - } + /** + * 获取店铺相册的Getter方法,方便获取该对象中封装的店铺相册信息,在店铺详情页面展示店铺相关图片、更新相册内容等业务场景中会用到该相册信息。 + */ + public String getShopPhotos() { + return shopPhotos; + } - public String getOpenTime() { - return openTime; - } + /** + * 设置店铺相册的Setter方法,用于外部将店铺相册内容赋值给该对象的shopPhotos字段,例如在前端添加或修改店铺相册图片后,后端通过这个方法接收并更新对象中的相册信息,以便后续业务逻辑使用该准确的相册内容。 + */ + public void setShopPhotos(String shopPhotos) { + this.shopPhotos = shopPhotos; + } - public void setOpenTime(String openTime) { - this.openTime = openTime; - } + /** + * 获取每天营业时间段的Getter方法,供其他类获取该对象中封装的营业时间段信息,在展示店铺营业时间、判断当前时间是否在营业范围内等业务场景中会用到该营业时间段内容。 + */ + public String getOpenTime() { + return openTime; + } - public Integer getShopStatus() { - return shopStatus; - } + /** + * 设置每天营业时间段的Setter方法,用于外部将营业时间段赋值给该对象的openTime字段,例如在前端修改店铺营业时间后,后端通过这个方法接收并更新对象中的营业时间段信息,以便后续业务逻辑使用该准确的营业时间段。 + */ + public void setOpenTime(String openTime) { + this.openTime = openTime; + } - public void setShopStatus(Integer shopStatus) { - this.shopStatus = shopStatus; - } + /** + * 获取店铺状态的Getter方法,方便获取该对象中封装的店铺状态信息,在展示店铺当前经营情况、根据状态进行业务逻辑判断(如只展示营业中的店铺等)等场景中会用到该店铺状态值。 + */ + public Integer getShopStatus() { + return shopStatus; + } - public Long getShopId() { - return shopId; - } + /** + * 设置店铺状态的Setter方法,用于外部将店铺状态赋值给该对象的shopStatus字段,例如在店铺运营人员修改店铺状态后,后端通过这个方法接收并更新对象中的状态信息,以便后续业务逻辑根据新的状态进行相应处理。 + */ + public void setShopStatus(Integer shopStatus) { + this.shopStatus = shopStatus; + } - public void setShopId(Long shopId) { - this.shopId = shopId; - } + /** + * 获取店铺ID的Getter方法,供其他类获取该对象中封装的店铺唯一标识符信息,在涉及店铺相关的数据库操作、业务逻辑关联等诸多方面,都依靠这个ID来精准定位到具体的店铺,例如查询店铺详情、更新店铺信息等操作都需要用到店铺ID。 + */ + public Long getShopId() { + return shopId; + } -} + /** + * 设置店铺ID的Setter方法,用于外部将店铺ID赋值给该对象的shopId字段,例如在某些特定业务场景下(如创建新店铺后获取并设置其ID等情况),通过这个方法将店铺ID设置到对象中,以便后续业务逻辑使用该准确的店铺标识符。 + */ + public void setShopId(Long shopId) { + this.shopId = shop \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/UserRegisterParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/UserRegisterParam.java index 5e7f310..fbeb442 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/UserRegisterParam.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/UserRegisterParam.java @@ -14,36 +14,86 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** + * 用户注册参数类 + * 该类用于封装用户注册过程中需要传递的各项信息,作为一个数据载体,方便在用户注册相关的业务逻辑中接收前端传入的用户注册数据,并传递给后端进行相应的处理,例如验证、保存用户信息等操作。 + * 同时利用`io.swagger.v3.oas.annotations.media.Schema`注解对类及各个属性在`Swagger`文档中进行描述,便于生成清晰的接口文档供前端开发人员查看接口参数要求等信息。 + * * @author lh */ @Data +// 使用 @Data 注解,由 lombok 自动生成常用的方法,如Getter、Setter、toString、equals、hashCode等方法 @Schema(description = "设置用户信息") public class UserRegisterParam { - @Schema(description = "密码" ) - private String passWord; + /** + * 密码属性 + * 用于接收用户注册时设置的登录密码信息,在后续的用户验证、密码加密等业务逻辑中会使用该属性值进行相应处理,以确保用户账户的安全性。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表密码信息。 + */ + @Schema(description = "密码") + private String passWord; - @Schema(description = "邮箱" ) - private String userMail; + /** + * 邮箱属性 + * 用于接收用户注册时填写的邮箱地址信息,可用于后续的账号验证、找回密码、发送通知等业务场景,比如向该邮箱发送注册验证邮件等操作。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表邮箱信息。 + */ + @Schema(description = "邮箱") + private String userMail; - @Schema(description = "昵称" ) - private String nickName; + /** + * 昵称属性 + * 用于接收用户注册时设定的昵称,该昵称通常会在用户个人信息展示、社区互动等场景中使用,方便其他用户识别该用户。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表昵称信息。 + */ + @Schema(description = "昵称") + private String nickName; - @Schema(description = "用户名" ) - private String userName; + /** + * 用户名属性 + * 用于接收用户注册时设定的用户名,可能在登录、系统内部识别用户等场景中有特定作用,具体使用方式取决于系统的业务逻辑设计。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表用户名信息。 + */ + @Schema(description = "用户名") + private String userName; - @Schema(description = "手机号" ) - private String mobile; + /** + * 手机号属性 + * 用于接收用户注册时填写的手机号码信息,可用于多种业务场景,例如手机号登录、短信验证码验证、绑定手机号等操作,增强账号的安全性和便捷性。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表手机号信息。 + */ + @Schema(description = "手机号") + private String mobile; - @Schema(description = "头像" ) - private String img; + /** + * 头像属性 + * 用于接收用户注册时上传或选择的头像图片相关信息(可能是图片的路径、链接等形式,具体取决于系统的设计),头像会在用户个人信息展示等场景中显示,体现用户的个性化。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表头像信息。 + */ + @Schema(description = "头像") + private String img; - @Schema(description = "校验登陆注册验证码成功的标识" ) - private String checkRegisterSmsFlag; + /** + * 校验登陆注册验证码成功的标识属性 + * 用于传递在注册过程中对登录注册验证码进行校验后得到的成功标识信息,后台业务逻辑会根据该标识判断验证码是否验证通过,进而决定是否允许用户注册流程继续进行。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表校验登陆注册验证码成功的标识信息。 + */ + @Schema(description = "校验登陆注册验证码成功的标识") + private String checkRegisterSmsFlag; - @Schema(description = "当账户未绑定时,临时的uid" ) - private String tempUid; + /** + * 当账户未绑定时,临时的uid属性 + * 在某些特定的注册或账号绑定逻辑中,若账户还未完成最终绑定操作,可能会生成一个临时的用户唯一标识(uid),该属性用于传递这个临时的标识信息,方便后续业务逻辑对未绑定状态的账号进行处理。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表当账户未绑定时,临时的uid信息。 + */ + @Schema(description = "当账户未绑定时,临时的uid") + private String tempUid; - @Schema(description = "用户id" ) - private Long userId; -} + /** + * 用户id属性 + * 通常用于标识用户的唯一编号,在一些涉及用户信息更新、关联其他业务数据等场景中会用到,不过在注册阶段可能根据具体业务逻辑,该属性有不同的用途(比如已有部分预注册信息,通过该id来关联更新等情况)。 + * 通过 @Schema 注解在 `Swagger` 文档中对该属性进行描述,提示前端开发人员该属性代表用户id信息。 + */ + @Schema(description = "用户id") + private Long userId; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/pay/PayInfoDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/pay/PayInfoDto.java index f792bfe..2ff6884 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/pay/PayInfoDto.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/pay/PayInfoDto.java @@ -13,24 +13,32 @@ package com.yami.shop.bean.pay; import lombok.Data; /** - * 支付信息 + * `PayInfoDto`类用于封装支付相关的信息,作为数据传输对象(DTO,Data Transfer Object)在不同的层(例如服务层与控制层之间)传递支付相关的数据, + * 使得各层之间的交互更加清晰明了,并且通过`lombok`的`@Data`注解自动生成了常用的属性访问方法(如`getter`、`setter`方法)以及`toString`、`hashCode`和`equals`等方法,简化了代码编写。 + * 此类主要包含了支付过程中一些关键信息的属性,方便在支付业务流程中进行数据的传递和处理。 + * * @author LGH */ @Data public class PayInfoDto { /** - * 支付信息,如商品名称 + * 支付信息,如商品名称,这个属性用于描述支付所对应的商品或服务的相关信息, + * 方便在支付页面展示给用户查看具体支付的是什么内容,或者在支付记录等相关业务逻辑中用于标识支付的具体对象, + * 例如在电商场景中,可能就是购买的商品的具体名称,像“华为P50手机”之类的具体商品描述。 */ private String body; /** - * 支付单号 + * 支付单号,是支付操作产生的一个唯一标识符,用于区分不同的支付记录, + * 在整个支付系统中,无论是与支付平台交互、查询支付状态还是后续的业务处理(如对账、退款等操作), + * 都可以通过这个唯一的支付单号来定位到具体的某一次支付行为,它具有唯一性和不可重复性,保证支付数据的准确性和可追溯性。 */ private String payNo; /** - * 付款金额 + * 付款金额,代表了本次支付需要支付的具体金额数值,精确到小数(一般为货币的金额表示形式), + * 这个属性明确了支付的具体费用情况,是支付业务中非常关键的一个数据,用于与支付平台确认支付金额、记录支付流水以及后续财务相关的统计等操作。 */ private Double payAmount; -} +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/vo/SysUserVO.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/vo/SysUserVO.java index 0f2f082..745b5da 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/vo/SysUserVO.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/vo/SysUserVO.java @@ -8,23 +8,33 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城相关的Java Bean的VO(Value Object,值对象)包下,VO类通常用于在不同业务层之间传递数据,对特定业务实体的部分属性进行封装,方便数据的传递与交互。 +// 这里的SysUserVO类主要是对系统用户相关信息进行简单封装,用于在系统的不同模块或者层级间传递与系统用户有关的数据。 package com.yami.shop.bean.vo; import lombok.Data; + /** + * SysUserVO类是一个简单的Java Bean,作为值对象用于封装系统用户的部分关键信息,方便在不同的业务逻辑层或者模块之间进行传递和使用,避免了直接传递整个复杂的用户对象,提高了数据传递的灵活性和效率。 + * 借助lombok的@Data注解,自动生成了常用的方法,如Getter、Setter、toString、equals、hashCode等方法,使得对类中成员变量的访问和操作更加便捷。 + * * @author lanhai */ @Data +// 使用lombok的@Data注解,它会自动为该类生成Getter和Setter方法,用于获取和设置类中的成员变量值;生成toString方法,方便在打印对象时直观展示对象的属性信息; +// 生成equals和hashCode方法,用于在对象比较以及基于哈希的集合操作(如HashMap中作为键使用时)等场景中使用,减少了手动编写这些重复代码的工作量,让代码更加简洁清晰。 public class SysUserVO { /** - * 用户id + * 用户id,是系统中用于唯一标识每个用户的标识符,在整个系统的数据库操作、权限管理、业务流程关联等诸多方面,都依靠这个唯一的ID来区分不同的用户个体, + * 例如在查询用户特定信息、进行权限验证、记录用户相关操作等场景下,都需要通过这个用户ID来精准定位到具体的用户。 */ private String userId; /** - * 用户名 + * 用户名,是用户在系统登录或者进行相关操作时使用的名称,通常具有唯一性(具体取决于系统的用户名规则设定),用于用户进行身份认证以及在系统内部展示用户的基本标识, + * 在用户登录界面、系统操作记录、权限分配等涉及到显示或区分用户身份的场景中会经常用到。 */ private String username; -} +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/vo/UserVO.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/vo/UserVO.java index 32be169..8504f08 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/vo/UserVO.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/vo/UserVO.java @@ -8,29 +8,36 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城相关的Java Bean的VO(Value Object,值对象)包下,通常用于在不同层之间传递数据,封装了与用户相关的部分属性信息,方便统一进行数据交互操作。 package com.yami.shop.bean.vo; import lombok.Data; + /** + * UserVO类是一个简单的Java Bean,用于封装与用户相关的部分关键信息,作为值对象在不同的业务逻辑层或者模块之间进行数据传递。 + * 它通过使用lombok的@Data注解,自动生成了常用的方法,如Getter、Setter、toString、equals、hashCode等方法,方便对类中的成员变量进行访问和操作。 + * * @author lanhai */ @Data +// 使用lombok的@Data注解,会自动为该类生成Getter、Setter、toString、equals、hashCode等方法,减少了手动编写这些重复代码的工作量,使得代码更加简洁,方便对类中成员变量的操作和使用。 public class UserVO { /** - * 用户id + * 用户id,用于唯一标识每个用户,在整个系统中通过这个ID来区分不同的用户个体,在涉及用户相关的数据库操作、业务逻辑处理以及不同模块间传递用户信息时都会用到。 */ private String userId; /** - * 用户昵称 + * 用户昵称,是用户在系统中对外展示的称呼,方便其他用户识别和称呼,通常可以由用户自行设置或修改,在展示用户信息、交互界面等场景中会显示出来。 */ private String nickName; + // 此处未添加详细注释的userMobile变量,推测是用于存储用户的手机号码信息,在涉及用户联系方式、账号绑定或者短信验证等业务场景中可能会用到该信息。 private String userMobile; /** - * 用户头像 + * 用户头像,存储了用户头像图片的相关标识或者路径信息(具体取决于系统的实现方式),用于在用户个人信息展示、聊天界面、评论区等地方展示用户的头像图片,增强用户的个性化展示效果。 */ private String pic; -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/annotation/RedisLock.java b/yami-shop-common/src/main/java/com/yami/shop/common/annotation/RedisLock.java index 3dec89a..4717bab 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/annotation/RedisLock.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/annotation/RedisLock.java @@ -14,35 +14,42 @@ import java.lang.annotation.*; import java.util.concurrent.TimeUnit; /** - * 使用redis进行分布式锁 + * `RedisLock`是一个自定义的注解,用于标记需要使用Redis实现分布式锁的方法。在分布式系统中,当多个实例或者线程可能同时访问和修改同一份资源时, + * 为了保证数据的一致性和避免并发冲突,常常需要使用分布式锁机制来控制对关键资源的访问,而这个注解就是为了方便地在代码中指定哪些方法需要这样的分布式锁保护, + * 并且可以配置锁相关的一些关键参数,如锁的名称、对应的Redis键、过期时间等。 + * * @author lanhai */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented +@Target(ElementType.METHOD) // 表明该注解可以应用在方法上,意味着只能将这个注解标记在方法声明处,用于对方法添加分布式锁相关的配置。 +@Retention(RetentionPolicy.RUNTIME) // 指定注解的保留策略为运行时,即在运行时可以通过反射等机制获取到该注解的相关信息,从而根据注解配置来实现分布式锁的逻辑。 +@Documented // 表示该注解会被包含在生成的JavaDoc文档中,方便开发人员查看和了解该注解的相关信息。 public @interface RedisLock { - /** - * redis锁 名字 - */ - String lockName() default ""; + /** + * redis锁名字,用于在Redis中标识这个锁,方便在查看Redis存储的锁信息或者进行锁相关的管理操作(如排查锁问题、手动释放锁等)时,能够快速区分不同的锁。 + * 默认值为空字符串,在使用注解时,如果没有显式指定这个值,就会使用默认的空字符串作为锁的名称,通常建议根据具体业务场景和方法功能来赋予一个有意义的名称,比如根据业务模块和操作类型命名,像 "orderService_createOrder_lock" 表示订单服务中创建订单操作对应的锁。 + */ + String lockName() default ""; - /** - * redis锁 key 支持spel表达式 - */ - String key() default ""; + /** + * redis锁key支持spel表达式,这个key是在Redis中实际存储锁信息的键,通过支持spel表达式(Spring Expression Language,一种强大的表达式语言,用于在运行时动态地解析表达式并获取对应的值), + * 可以根据方法的参数、上下文等信息灵活地生成不同的键值,使得锁的应用更加贴合具体的业务场景和数据情况。例如,可以根据传入的订单ID动态生成对应的锁键,像 "#orderId" 这样的spel表达式,在实际应用时会根据方法调用传入的订单ID参数来确定具体的键值,确保不同的业务数据对应的锁是独立且准确的。默认值为空字符串,同样在未指定时使用默认值,不过一般都需要根据实际情况进行配置。 + */ + String key() default ""; - /** - * 过期秒数,默认为5毫秒 - * - * @return 轮询锁的时间 - */ - int expire() default 5000; + /** + * 过期秒数,用于指定锁在Redis中自动过期的时间,单位是毫秒(因为属性名是 `expire`,虽然描述里写的是秒,但从代码实现角度结合 `TimeUnit` 来看实际是毫秒),默认为5000毫秒(即5秒)。 + * 设置合适的过期时间很重要,如果过期时间过短,可能导致业务逻辑还未执行完锁就提前释放了,从而出现并发问题;而过期时间过长,又可能导致锁长时间占用资源,影响其他需要获取该锁的操作,降低系统的并发性能。默认值提供了一个基础的时间设置,在实际应用中可以根据具体方法执行的预期时长等因素进行调整。 + * + * @return 轮询锁的时间,这里从功能角度理解,实际就是返回锁的过期时间设置值,方便在实现分布式锁逻辑时获取并使用这个时间参数来控制锁的有效期。 + */ + int expire() default 5000; - /** - * 超时时间单位 - * - * @return 秒 - */ - TimeUnit timeUnit() default TimeUnit.MILLISECONDS; -} + /** + * 超时时间单位,用于明确前面 `expire` 属性所指定的时间的单位,默认是 `TimeUnit.MILLISECONDS`(毫秒),这样和 `expire` 属性默认的以毫秒为单位的时间值相匹配。 + * 不过,通过这个属性也可以灵活地将时间单位调整为其他如秒、分钟等,只要符合业务需求以及和 `expire` 的值配合合理即可,提供了更灵活的时间配置方式来适应不同场景下对锁过期时间的要求。 + * + * @return 秒,这里文档注释里写的返回值是“秒”不太准确,实际返回的是 `TimeUnit` 枚举类型中的一种时间单位,用于明确 `expire` 属性对应的时间单位,在代码实现中更多是和 `expire` 配合使用来确定准确的时间范围。 + */ + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/annotation/SysLog.java b/yami-shop-common/src/main/java/com/yami/shop/common/annotation/SysLog.java index caf8d1e..bbf012c 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/annotation/SysLog.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/annotation/SysLog.java @@ -8,6 +8,8 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城项目的通用(common)注解(annotation)包下,通常用于存放一些项目中自定义的、具有通用性的注解,方便在多个地方复用,以实现特定的功能需求。 +// 这里的这个注解可能用于记录系统相关操作的日志信息,在对应的方法执行时进行日志记录等相关操作。 package com.yami.shop.common.annotation; import java.lang.annotation.Documented; @@ -17,12 +19,24 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** + * SysLog注解是一个自定义的Java注解,用于标记需要进行系统日志记录的方法。通过在方法上添加这个注解,可以方便地指定哪些方法的执行情况需要被记录下来, + * 便于后续进行系统操作的审计、故障排查以及业务流程分析等操作,为系统的维护和监控提供了有力的支持。 + * * @author lanhai */ +// @Target注解用于指定该自定义注解可以应用的目标元素类型,这里ElementType.METHOD表示该SysLog注解只能应用在方法上, +// 即可以在方法声明前添加该注解来标记这个方法需要进行相关的系统日志记录操作。 @Target(ElementType.METHOD) +// @Retention注解用于指定注解的保留策略,RetentionPolicy.RUNTIME表示该注解在运行时仍然保留,可以通过反射机制在运行时获取到该注解的相关信息, +// 这样就能在程序运行期间根据注解的配置来决定是否以及如何进行系统日志记录等操作。 @Retention(RetentionPolicy.RUNTIME) +// @Documented注解用于表示该注解在生成JavaDoc文档时会被包含进去,使得在生成的文档中能够看到该注解的相关信息,方便其他开发人员了解该注解的存在和作用。 @Documented +// 使用@interface关键字定义了一个名为SysLog的自定义注解,它可以像其他Java内置注解一样被应用在相应的目标元素(这里是方法)上。 public @interface SysLog { - String value() default ""; -} + // 定义了一个名为value的注解元素,它是一个字符串类型,并且默认值为空字符串。 + // 在使用该注解时,如果没有为value元素指定具体的值,就会使用这个默认的空字符串。 + // 这个元素可能用于传递一些与日志记录相关的额外信息,比如日志的简要描述等内容,具体用途取决于在代码中对该注解的解析和使用逻辑。 + String value() default ""; +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/aspect/RedisLockAspect.java b/yami-shop-common/src/main/java/com/yami/shop/common/aspect/RedisLockAspect.java index e47cee7..b7f74ac 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/aspect/RedisLockAspect.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/aspect/RedisLockAspect.java @@ -27,48 +27,82 @@ import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** + * Redis锁切面类 + * 该类利用Spring AOP(面向切面编程)机制,通过定义切面和切点,实现了基于Redis锁的并发控制功能。 + * 主要作用是在被标注了`@RedisLock`注解的方法执行前后进行加锁和解锁操作,以保证在同一时刻只有一个线程能够执行被标注的方法,避免并发冲突。 + * * @author lgh */ @Aspect +// 使用 @Aspect 注解声明该类是一个切面类,用于定义切点和增强逻辑(如前置通知、后置通知、环绕通知等) @Component +// 使用 @Component 注解将该类注册为Spring容器中的一个组件,方便进行依赖注入等操作 public class RedisLockAspect { - @Autowired - private RedissonClient redissonClient; + @Autowired + // 通过依赖注入获取RedissonClient对象,用于与Redis进行交互,操作Redis锁相关的功能 + private RedissonClient redissonClient; + + // 定义Redis锁的键名前缀,用于在Redis中区分不同的锁,方便管理和识别 + private static final String REDISSON_LOCK_PREFIX = "redisson_lock:"; - private static final String REDISSON_LOCK_PREFIX = "redisson_lock:"; + /** + * 环绕通知方法,用于在被标注`@RedisLock`注解的方法执行前后添加加锁和解锁逻辑 + * 该方法在目标方法执行前获取Redis锁,在目标方法执行完毕后释放锁,确保在同一时刻只有一个线程能够执行被标注的方法,实现并发控制。 + * + * @param joinPoint 连接点对象,代表了目标方法的执行点,通过它可以获取目标方法的相关信息,如方法签名、参数等。 + * @param redisLock 从目标方法上获取的`@RedisLock`注解对象,通过它可以获取注解中定义的锁相关的配置信息,如锁的键表达式、锁名称、过期时间等。 + * @return 返回目标方法执行的结果,即被标注`@RedisLock`注解的方法正常执行后的返回值。 + * @throws Throwable 如果在目标方法执行过程中或者获取、释放锁的过程中出现异常,会将异常向上抛出。 + */ + @Around("@annotation(redisLock)") + // 使用 @Around 注解定义环绕通知,该通知会在目标方法执行前后进行额外的逻辑处理,这里就是围绕着被标注`@RedisLock`注解的方法进行加锁和解锁操作 + public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable { + // 获取`@RedisLock`注解中定义的用于生成锁键的SPEL表达式,SPEL表达式可以根据方法参数等动态生成锁键 + String spel = redisLock.key(); + // 获取`@RedisLock`注解中定义的锁名称,用于更直观地标识锁的用途等信息 + String lockName = redisLock.lockName(); - @Around("@annotation(redisLock)") - public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable { - String spel = redisLock.key(); - String lockName = redisLock.lockName(); + // 根据连接点、锁名称以及SPEL表达式生成最终的Redis锁的键名,并通过RedissonClient获取对应的Redis锁对象 + RLock rLock = redissonClient.getLock(getRedisKey(joinPoint, lockName, spel)); - RLock rLock = redissonClient.getLock(getRedisKey(joinPoint,lockName,spel)); + // 使用获取到的Redis锁对象进行加锁操作,设置锁的过期时间和时间单位,按照`@RedisLock`注解中定义的配置来进行 + rLock.lock(redisLock.expire(), redisLock.timeUnit()); - rLock.lock(redisLock.expire(),redisLock.timeUnit()); + Object result = null; + try { + // 执行被标注`@RedisLock`注解的目标方法,即让原本的业务逻辑正常执行 + result = joinPoint.proceed(); - Object result = null; - try { - //执行方法 - result = joinPoint.proceed(); + } finally { + // 无论目标方法执行是否成功,最终都要释放锁,确保锁资源能够被正确释放,避免死锁等问题 + rLock.unlock(); + } + return result; + } - } finally { - rLock.unlock(); - } - return result; - } + /** + * 将SPEL表达式转换为具体的Redis锁键字符串的方法 + * 该方法根据连接点(包含目标方法、目标对象以及方法参数等信息)、锁名称以及SPEL表达式,通过解析SPEL表达式,结合相关信息生成最终用于在Redis中标识锁的键字符串。 + * + * @param joinPoint 切点对象,代表了目标方法的执行点,从中可以获取目标方法、目标对象以及方法参数等信息,用于解析SPEL表达式。 + * @param lockName 锁的名称,作为生成的Redis锁键的一部分,用于更直观地标识锁的用途等信息。 + * @param spel 用于生成锁键的SPEL表达式,通过解析该表达式,结合目标方法、目标对象以及方法参数等信息来生成最终的锁键字符串。 + * @return 返回生成的Redis锁键字符串,格式为"redisson_lock:锁名称:具体解析后的表达式内容",用于在Redis中唯一标识一个锁。 + */ + private String getRedisKey(ProceedingJoinPoint joinPoint, String lockName, String spel) { + // 获取连接点对应的方法签名信息,用于后续获取目标方法对象 + Signature signature = joinPoint.getSignature(); + // 将方法签名信息转换为方法签名的具体实现类对象,方便获取目标方法的详细信息 + MethodSignature methodSignature = (MethodSignature) signature; + // 获取目标方法对象,后续可用于解析SPEL表达式等操作 + Method targetMethod = methodSignature.getMethod(); + // 获取目标对象,即被代理的实际业务对象,同样可用于解析SPEL表达式等操作 + Object target = joinPoint.getTarget(); + // 获取目标方法的参数数组,这些参数在解析SPEL表达式时可能会作为变量参与计算 + Object[] arguments = joinPoint.getArgs(); - /** - * 将spel表达式转换为字符串 - * @param joinPoint 切点 - * @return redisKey - */ - private String getRedisKey(ProceedingJoinPoint joinPoint,String lockName,String spel) { - Signature signature = joinPoint.getSignature(); - MethodSignature methodSignature = (MethodSignature) signature; - Method targetMethod = methodSignature.getMethod(); - Object target = joinPoint.getTarget(); - Object[] arguments = joinPoint.getArgs(); - return REDISSON_LOCK_PREFIX + lockName + StrUtil.COLON + SpelUtil.parse(target,spel, targetMethod, arguments); - } -} + // 通过工具类方法解析SPEL表达式,结合锁名称等信息生成最终的Redis锁键字符串,并添加前缀,形成完整的用于在Redis中标识锁的键名 + return REDISSON_LOCK_PREFIX + lockName + StrUtil.COLON + SpelUtil.parse(target, spel, targetMethod, arguments); + } +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/bean/AliDaYu.java b/yami-shop-common/src/main/java/com/yami/shop/common/bean/AliDaYu.java index 751c5fa..79bcd8d 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/bean/AliDaYu.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/bean/AliDaYu.java @@ -13,15 +13,30 @@ package com.yami.shop.common.bean; import lombok.Data; /** - * 阿里大鱼配置信息 + * 该类用于封装阿里大鱼的配置信息,方便在项目中统一管理和使用与阿里大鱼相关的配置参数, + * 例如在使用阿里大鱼的短信服务、消息推送服务等功能时,需要这些配置信息来进行身份验证、服务调用等操作。 + * 通过将这些配置参数封装在一个类中,可以更方便地进行配置的传递、存储以及根据不同环境进行配置的切换等操作。 + * * @author LGH */ @Data public class AliDaYu { - private String accessKeyId; - - private String accessKeySecret; - - private String signName; -} + /** + * 代表阿里大鱼服务的访问密钥 ID,用于在调用阿里大鱼相关 API 时进行身份验证,确保请求是来自合法授权的应用或用户, + * 该密钥 ID 通常在阿里大鱼的控制台进行申请和获取,与对应的访问密钥秘密(accessKeySecret)配合使用,保障服务调用的安全性。 + */ + private String accessKeyId; + + /** + * 代表阿里大鱼服务的访问密钥秘密,是与访问密钥 ID(accessKeyId)相对应的保密信息, + * 同样用于在调用阿里大鱼相关 API 时进行身份验证,它与访问密钥 ID 一起构成了完整的身份验证凭据,确保只有拥有正确密钥对的应用或用户才能成功调用服务。 + */ + private String accessKeySecret; + + /** + * 代表短信签名名称,在使用阿里大鱼的短信服务发送短信时,短信签名是短信内容中用于标识发送方身份的重要元素, + * 它需要提前在阿里大鱼控制台进行配置和审核通过后才能使用,确保短信来源的合法性和可识别性,接收方看到的短信会显示该签名名称。 + */ + private String signName; +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/bean/ImgUpload.java b/yami-shop-common/src/main/java/com/yami/shop/common/bean/ImgUpload.java index 684874c..bd49408 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/bean/ImgUpload.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/bean/ImgUpload.java @@ -13,24 +13,33 @@ package com.yami.shop.common.bean; import lombok.Data; /** - * 本地存储配置信息 + * `ImgUpload`类用于封装与图片上传相关的本地存储配置信息,通过这个类可以方便地在系统中传递和管理图片上传所需的各项配置参数, + * 例如在不同的环境(开发环境、生产环境等)下,根据实际需求配置相应的本地文件存储路径、选择合适的文件上传方式以及设置对应的网站资源访问地址等, + * 其借助`lombok`的`@Data`注解自动生成了常用的属性访问方法(如`getter`、`setter`方法)以及`toString`、`hashCode`和`equals`等方法,简化了代码编写,方便对对象属性的操作和使用。 + * * @author lgh */ @Data public class ImgUpload { - /** - * 本地文件上传文件夹 - */ - private String imagePath; + /** + * 本地文件上传文件夹,用于指定在本地存储上传文件(这里主要针对图片文件)时的存放路径, + * 这个路径可以是绝对路径也可以是相对路径,具体取决于系统的部署和配置要求,它决定了上传的图片在本地服务器上的实际存储位置, + * 例如在Windows系统下可能是 "C:/uploads/images/",在Linux系统下可能是 "/var/www/uploads/images/" 这样的路径形式,方便后续对上传的图片进行管理和访问。 + */ + private String imagePath; - /** - * 文件上传方式 1.本地文件上传 2.七牛云 - */ - private Integer uploadType; + /** + * 文件上传方式,通过整数值来表示不同的上传方式,这里定义了两种常见的情况:1.本地文件上传,2.七牛云, + * 可以根据实际业务需求和部署环境选择合适的上传方式,比如在开发测试阶段可能使用本地文件上传方便调试,而在生产环境中为了更好的存储扩展性和性能等因素选择七牛云这样的云存储服务, + * 系统会根据这个属性的值来决定采用何种具体的文件上传逻辑来处理图片上传操作。 + */ + private Integer uploadType; - /** - * 网站url - */ - private String resourceUrl; -} + /** + * 网站url,一般是指用于访问网站资源(包括上传的图片等文件)的网络地址,例如 "https://www.example.com/", + * 当用户在浏览器中访问网站上的图片或者其他资源时,就是通过这个地址来定位和获取相应的资源,它对于正确展示网站上的图片以及保证资源的可访问性起着关键作用, + * 同时在配置文件上传相关逻辑时,也需要和实际的存储路径以及上传方式等配合使用,确保资源的正确引用和访问。 + */ + private String resourceUrl; +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/bean/Qiniu.java b/yami-shop-common/src/main/java/com/yami/shop/common/bean/Qiniu.java index c112bab..01ce22a 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/bean/Qiniu.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/bean/Qiniu.java @@ -8,25 +8,35 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城项目的通用(common)Java Bean(bean)包下,通常用于存放一些通用的数据模型类,这些类用于封装特定业务相关的数据信息,方便在不同的业务逻辑层之间传递和使用。 +// 这里的Qiniu类主要用于封装七牛云存储相关的配置信息,以便在项目中对七牛云存储进行操作时可以方便地获取和使用这些配置参数。 package com.yami.shop.common.bean; import com.yami.shop.common.enums.QiniuZone; import lombok.Data; /** - * 七牛云存储配置信息 + * Qiniu类是一个Java Bean,用于封装七牛云存储的配置信息,在项目中如果涉及到使用七牛云来存储文件(如图片、文档等各类资源), + * 就需要通过这个类来配置和获取相关的关键参数,例如访问密钥、存储桶名称等信息,同时借助lombok的@Data注解自动生成常用的方法,方便对这些配置信息进行操作。 + * * @author lgh */ @Data +// 使用lombok的@Data注解,会自动为该类生成Getter、Setter、toString、equals、hashCode等方法,减少了手动编写这些重复代码的工作量,使得代码更加简洁,方便对类中成员变量进行访问和操作。 public class Qiniu { - private String accessKey; + // 七牛云的访问密钥(Access Key),用于在与七牛云存储服务进行交互时进行身份验证,相当于用户名的作用,具有访问权限控制的功能,只有拥有正确的访问密钥才能对七牛云存储资源进行相应的操作。 + private String accessKey; - private String secretKey; + // 七牛云的秘密密钥(Secret Key),与访问密钥配合使用,用于对请求进行签名等安全验证操作,确保请求的合法性和安全性,是保证七牛云存储数据安全的重要配置参数之一。 + private String secretKey; - private String bucket; + // 七牛云存储中的存储桶(Bucket)名称,存储桶是七牛云存储中用于存放文件等资源的容器,类似于文件夹的概念,不同的项目或者业务模块可以使用不同的存储桶来进行资源的分类存储和管理。 + private String bucket; - private String resourcesUrl; + // 资源访问的URL地址,用于在项目中访问存储在七牛云存储桶中的资源时使用,通过这个地址可以拼接具体的资源路径,从而在前端或者其他需要使用资源的地方获取到对应的文件内容,例如图片资源的展示等。 + private String resourcesUrl; - private QiniuZone zone; -} + // 七牛云存储的区域(Zone)信息,通过引用QiniuZone枚举类型来指定,不同的区域对应着不同的数据中心位置,选择合适的区域可以优化资源的存储和访问速度等性能,同时也可能涉及到不同的网络配置等因素。 + private QiniuZone zone; +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java index 841cdf0..1251992 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java @@ -28,51 +28,86 @@ import java.util.ArrayList; import java.util.List; /** - * 自定义错误处理器 + * 自定义错误处理器类 + * 该类用于统一处理项目中出现的各种异常情况,通过`@ExceptionHandler`注解定义不同的异常处理方法,针对不同类型的异常进行相应的处理, + * 比如提取异常信息、构建合适的响应实体对象等,最终返回包含错误信息的响应给客户端,以提供更友好、规范的错误提示,增强系统的稳定性和用户体验。 + * * @author LGH */ @Slf4j +// 使用 @Slf4j 注解,由 lombok 自动生成一个名为 log 的日志记录器对象,方便在类中记录日志信息 @Controller +// 使用 @Controller 注解声明该类是一个Spring MVC中的控制器类,虽然这里主要功能是异常处理,但可能涉及到一些视图相关的处理逻辑(如果有的话) @RestControllerAdvice +// 使用 @RestControllerAdvice 注解,使得该类可以作为一个全局的异常处理类,能够捕获并处理整个项目中不同控制器层抛出的异常 public class DefaultExceptionHandlerConfig { + /** + * 处理方法参数校验不合法异常的方法 + * 该方法用于处理`MethodArgumentNotValidException`(方法参数校验不合法异常,例如使用`@Valid`等注解进行参数校验失败时抛出)和`BindException`(数据绑定异常,类似的参数校验相关异常情况)这两种异常。 + * 它会从异常中提取出字段校验错误信息,构建包含错误信息的响应实体对象后返回给客户端。 + * + * @param e 捕获到的异常对象,可能是`MethodArgumentNotValidException`或者`BindException`类型。 + * @return 返回一个`ResponseEntity>>`类型的响应,包含了处理后的错误信息列表以及对应的状态码(这里统一设置为`HttpStatus.OK`),若提取错误信息出现异常则返回默认的参数校验失败的通用响应。 + */ @ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class }) public ResponseEntity>> methodArgumentNotValidExceptionHandler(Exception e) { log.error("methodArgumentNotValidExceptionHandler", e); List fieldErrors = null; + // 判断异常类型是否为 MethodArgumentNotValidException,如果是,则从该异常中获取字段校验错误信息列表 if (e instanceof MethodArgumentNotValidException) { fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors(); } + // 判断异常类型是否为 BindException,如果是,则从该异常中获取字段校验错误信息列表 if (e instanceof BindException) { fieldErrors = ((BindException) e).getBindingResult().getFieldErrors(); } + // 如果没有获取到字段校验错误信息列表(可能异常类型不匹配等原因),则直接返回参数校验失败的通用响应 if (fieldErrors == null) { return ResponseEntity.status(HttpStatus.OK) - .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID)); + .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID)); } List defaultMessages = new ArrayList<>(fieldErrors.size()); + // 遍历字段校验错误信息列表,将每个字段的名称和对应的错误提示信息拼接成字符串后添加到错误信息列表中 for (FieldError fieldError : fieldErrors) { defaultMessages.add(fieldError.getField() + ":" + fieldError.getDefaultMessage()); } + // 返回包含具体字段校验错误信息列表以及参数校验失败状态码的响应实体对象 return ResponseEntity.status(HttpStatus.OK) - .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, defaultMessages)); + .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, defaultMessages)); } + /** + * 处理自定义业务绑定异常的方法 + * 该方法用于处理`YamiShopBindException`(项目自定义的业务绑定异常,可能在业务逻辑中根据特定情况抛出,比如数据关联异常等情况)异常。 + * 它会根据异常中是否已经包含了自定义的响应实体对象来决定返回内容,如果有则直接返回,否则根据异常的代码和消息构建响应实体对象后返回给客户端。 + * + * @param e 捕获到的`YamiShopBindException`异常对象。 + * @return 返回一个`ResponseEntity>`类型的响应,包含了处理后的业务绑定异常相关的错误信息以及对应的状态码(这里统一设置为`HttpStatus.OK`)。 + */ @ExceptionHandler(YamiShopBindException.class) - public ResponseEntity> unauthorizedExceptionHandler(YamiShopBindException e){ + public ResponseEntity> unauthorizedExceptionHandler(YamiShopBindException e) { log.error("mall4jExceptionHandler", e); ServerResponseEntity serverResponseEntity = e.getServerResponseEntity(); - if (serverResponseEntity!=null) { + if (serverResponseEntity!= null) { return ResponseEntity.status(HttpStatus.OK).body(serverResponseEntity); } - // 失败返回消息 状态码固定为直接显示消息的状态码 - return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(e.getCode(),e.getMessage())); + // 失败返回消息,状态码固定为直接显示消息的状态码,根据异常的代码和消息构建响应实体对象并返回 + return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(e.getCode(), e.getMessage())); } + /** + * 处理其他通用异常的方法 + * 该方法用于处理除了上述特定异常之外的其他所有异常情况,会先判断异常是否为资源未找到异常(`NoResourceFoundException`),如果是则直接返回包含对应错误消息的响应, + * 否则记录异常日志,并返回包含通用异常状态码和提示信息的响应实体对象给客户端。 + * + * @param e 捕获到的其他通用异常对象,即除了前面已经处理的特定异常之外的任何异常。 + * @return 返回一个`ResponseEntity>`类型的响应,包含了处理后的通用异常相关的错误信息以及对应的状态码(这里统一设置为`HttpStatus.OK`)。 + */ @ExceptionHandler(Exception.class) - public ResponseEntity> exceptionHandler(Exception e){ + public ResponseEntity> exceptionHandler(Exception e) { if (e instanceof NoResourceFoundException) { return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.showFailMsg(e.getMessage())); } diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/FileUploadConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/FileUploadConfig.java index 8ffd996..4961d88 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/FileUploadConfig.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/FileUploadConfig.java @@ -25,38 +25,59 @@ import com.yami.shop.common.bean.Qiniu; import java.util.Objects; /** - * 文件上传配置 + * 该类是文件上传相关的配置类,主要用于配置七牛云文件上传服务所需的各种组件实例,例如配置七牛云的存储区域、 + * 创建上传管理实例、认证信息实例以及空间管理实例等,通过 Spring 的配置机制(@Configuration 和 @Bean 注解)将这些实例注入到 Spring 容器中, + * 方便在文件上传相关的业务逻辑中进行使用,使得文件上传功能能够与七牛云服务进行有效的集成。 + * * @author lgh */ @Configuration public class FileUploadConfig { - - @Autowired - private Qiniu qiniu; + // 通过 Spring 的自动注入机制,将 Qiniu 类型的配置对象注入到当前类中,该对象应该包含了七牛云服务相关的配置信息,如密钥、存储区域等。 + @Autowired + private Qiniu qiniu; /** - * 根据配置文件选择机房 + * 根据配置文件中的机房区域设置,创建并返回七牛云存储的配置实例(com.qiniu.storage.Configuration), + * 此配置实例指定了七牛云存储服务所使用的机房区域(Zone),通过判断配置对象(qiniu)中的机房区域枚举值(QiniuZone), + * 来选择对应的七牛云 Zone 实例,从而确定文件存储的具体机房位置,影响文件上传和存储的相关操作。 + * + * @return 七牛云存储的配置实例,包含了选定的机房区域信息,用于后续七牛云相关服务实例的创建和操作。 */ @Bean public com.qiniu.storage.Configuration qiniuConfig() { Zone zone = null; + // 判断配置对象中的机房区域枚举值是否为华北区(HUA_BEI),如果是,则创建对应的七牛云华北区 Zone 实例。 if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_BEI)) { zone = Zone.huabei(); - } else if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_DONG)) { + } + // 判断是否为华东区(HUA_DONG),若是,则创建华东区 Zone 实例。 + else if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_DONG)) { zone = Zone.huadong(); - } else if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_NAN)) { + } + // 判断是否为华南区(HUA_NAN),若是,则创建华南区 Zone 实例。 + else if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_NAN)) { zone = Zone.huanan(); - } else if (Objects.equals(qiniu.getZone(), QiniuZone.BEI_MEI)) { + } + // 判断是否为北美区(BEI_MEI),若是,则创建北美区 Zone 实例。 + else if (Objects.equals(qiniu.getZone(), QiniuZone.BEI_MEI)) { zone = Zone.beimei(); - } else if (Objects.equals(qiniu.getZone(), QiniuZone.XIN_JIA_PO)) { + } + // 判断是否为新加坡区(XIN_JIA_PO),若是,则创建新加坡区 Zone 实例。 + else if (Objects.equals(qiniu.getZone(), QiniuZone.XIN_JIA_PO)) { zone = Zone.xinjiapo(); } + // 使用选定的 Zone 实例创建并返回七牛云存储的配置实例,用于后续的七牛云服务操作。 return new com.qiniu.storage.Configuration(zone); } /** - * 构建一个七牛上传工具实例 + * 创建并返回一个七牛云的上传管理实例(UploadManager),该实例用于执行文件上传到七牛云存储的具体操作, + * 它依赖于前面创建的七牛云存储配置实例(qiniuConfig() 方法返回的对象)来确定上传操作的相关配置,如机房区域等信息, + * 使得在进行文件上传时能够按照正确的配置与七牛云服务进行交互。 + * + * @return 七牛云的上传管理实例,可用于在业务代码中调用其方法实现文件上传到七牛云存储的功能。 */ @Bean public UploadManager uploadManager() { @@ -64,8 +85,10 @@ public class FileUploadConfig { } /** - * 认证信息实例 - * @return + * 创建并返回一个七牛云的认证信息实例(Auth),该实例用于生成七牛云服务调用所需的认证凭证,通过配置对象(qiniu)中的访问密钥(accessKey)和秘密密钥(secretKey), + * 按照七牛云的认证机制创建认证对象,使得后续与七牛云存储、空间管理等服务进行交互时能够进行身份验证,确保请求的合法性和安全性。 + * + * @return 七牛云的认证信息实例,包含了基于配置的访问密钥和秘密密钥生成的认证凭证,用于七牛云服务调用时的身份验证。 */ @Bean public Auth auth() { @@ -73,10 +96,14 @@ public class FileUploadConfig { } /** - * 构建七牛空间管理实例 + * 创建并返回一个七牛云的空间管理实例(BucketManager),该实例用于对七牛云存储中的空间(Bucket)进行管理操作, + * 例如文件的删除、查看空间信息等操作,它依赖于前面创建的认证信息实例(auth() 方法返回的对象)进行身份验证, + * 同时也依赖于七牛云存储配置实例(qiniuConfig() 方法返回的对象)来确定相关操作的配置信息,确保空间管理操作能够在正确的配置环境下进行。 + * + * @return 七牛云的空间管理实例,可用于在业务代码中调用其方法实现对七牛云存储空间的管理功能。 */ @Bean public BucketManager bucketManager() { return new BucketManager(auth(), qiniuConfig()); } -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/MybatisPlusConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/MybatisPlusConfig.java index d8ba4f3..fc636a1 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/MybatisPlusConfig.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/MybatisPlusConfig.java @@ -22,15 +22,23 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** + * `MybatisPlusConfig`类是一个Spring的配置类,用于配置MyBatis Plus相关的功能和插件,在整个Spring Boot项目启动时, + * Spring容器会加载并解析这个配置类,根据其中定义的方法来创建相应的Bean并注入到容器中,从而使MyBatis Plus能够集成到项目中并发挥其强大的功能, + * 例如数据库操作的便捷性增强、分页功能支持以及乐观锁机制等,确保数据库相关的业务逻辑能够高效、安全地执行。 + * * @author lanhai */ -@Configuration -@MapperScan({"com.yami.shop.**.dao"}) +@Configuration // 表明这个类是一个Spring的配置类,Spring容器会在启动时扫描并解析此类中的配置信息,用于创建和管理相关的Bean。 +@MapperScan({"com.yami.shop.**.dao"}) // 用于指定MyBatis的Mapper接口所在的包路径,告诉MyBatis去扫描这些包下的Mapper接口并创建对应的代理实现类,使得可以方便地进行数据库操作,这里采用通配符的方式可以扫描多层级的包下的Mapper接口。 public class MybatisPlusConfig { /** - * 逻辑删除插件 - * @return LogicSqlInjector + * 逻辑删除插件相关的配置方法,通过 `@Bean` 注解将返回的对象注册为Spring容器中的一个Bean,供其他地方使用。 + * `@ConditionalOnMissingBean` 注解表示只有在容器中不存在相同类型的Bean时,才会创建并注册这个Bean,避免重复创建。 + * 这里返回 `DefaultSqlInjector` 作为 `ISqlInjector` 类型的Bean,`DefaultSqlInjector` 在MyBatis Plus中是默认的SQL注入器,它实现了一些基础的SQL注入功能, + * 其中可能就包含了对逻辑删除功能的支持(通过在执行SQL语句时自动添加逻辑删除相关的条件判断等操作),使得在代码中可以方便地使用逻辑删除的特性,而无需手动编写大量的SQL条件判断逻辑。 + * + * @return 返回 `ISqlInjector` 类型的实例,这里实际返回的是 `DefaultSqlInjector`,用于在MyBatis Plus中实现SQL注入相关功能,特别是逻辑删除相关的功能支持。 */ @Bean @ConditionalOnMissingBean @@ -39,7 +47,15 @@ public class MybatisPlusConfig { } /** - * mybatis-plus插件 + * mybatis-plus插件配置方法,同样通过 `@Bean` 注解将创建的 `MybatisPlusInterceptor` 对象注册为Spring容器中的Bean,用于配置多个MyBatis Plus的拦截器插件,增强MyBatis Plus的功能。 + * 首先创建了一个 `MybatisPlusInterceptor` 实例,它是MyBatis Plus用于管理多个拦截器插件的核心类,然后通过 `addInnerInterceptor` 方法向其中添加了两个重要的拦截器插件: + * 1. `PaginationInnerInterceptor(DbType.MYSQL)`:这是用于实现分页功能的拦截器插件,并且指定了数据库类型为 `MYSQL`,意味着它会根据MySQL数据库的特性来生成合适的分页SQL语句, + * 在进行数据库查询操作时,如果使用了MyBatis Plus提供的分页相关的API,这个拦截器就会自动介入,将原始的查询语句转换为带有分页逻辑的SQL语句,从而实现分页查询功能,方便对大量数据进行分页展示和处理。 + * 2. `OptimisticLockerInnerInterceptor`:这是乐观锁相关的拦截器插件,用于实现乐观锁机制,在多线程并发访问数据库同一条记录进行更新操作时,通过版本号等机制来保证数据的一致性, + * 避免出现数据冲突和脏数据的情况,它会在更新操作的SQL语句执行过程中自动添加乐观锁相关的条件判断,确保只有在数据版本符合预期的情况下才会执行更新操作,保障了数据的并发安全性。 + * 最后将配置好的 `MybatisPlusInterceptor` 返回,使得这些插件能够在整个项目的MyBatis Plus数据库操作中生效,提升数据库操作的功能性和可靠性。 + * + * @return 返回配置好的 `MybatisPlusInterceptor` 实例,包含了分页和乐观锁相关的拦截器插件,用于增强MyBatis Plus在数据库操作中的功能,如分页查询和并发控制等。 */ @Bean public MybatisPlusInterceptor optimisticLockerInterceptor() { @@ -48,4 +64,4 @@ public class MybatisPlusConfig { mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mybatisPlusInterceptor; } -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/RedisCacheConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/RedisCacheConfig.java index 34c3df7..f012257 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/RedisCacheConfig.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/RedisCacheConfig.java @@ -8,6 +8,8 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城项目的通用(common)配置(config)包下,主要用于对Redis缓存相关的配置进行定义和设置,通过Spring的配置类机制, +// 在满足一定条件(配置文件中spring.cache.type = redis时)生效,来实现Redis缓存在项目中的集成与定制化配置。 package com.yami.shop.common.config; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -38,20 +40,33 @@ import java.util.HashMap; import java.util.Map; /** - * redis 缓存配置,仅当配置文件中spring.cache.type = redis时生效 + * RedisCacheConfig类是一个Spring的配置类,用于配置Redis缓存相关的各种组件和行为,使得项目能够基于Redis实现高效的缓存功能,并且可以根据实际需求进行灵活的定制化配置。 + * 只有当项目的配置文件中指定spring.cache.type = redis时,这里定义的Redis缓存相关配置才会生效,用于控制缓存的存储、序列化、过期时间等关键特性。 + * * @author lgh */ @EnableCaching +// 使用@EnableCaching注解开启Spring的缓存功能,使得在项目中可以使用诸如@Cacheable、@CachePut、@CacheEvict等缓存相关的注解来方便地操作缓存,提高应用性能。 @Configuration -public class RedisCacheConfig { +// 使用@Configuration注解表明该类是一个配置类,Spring会扫描这个类,并根据其中定义的@Bean方法来创建相应的Bean实例,注入到Spring容器中供其他组件使用。 +public class RedisCacheConfig { + /** + * 创建并配置CacheManager的Bean方法,CacheManager是Spring缓存抽象的核心接口,用于管理缓存,在这里具体配置了基于Redis的缓存管理方式。 + * 通过传入RedisConnectionFactory(用于创建与Redis服务器的连接)和RedisSerializer(用于对缓存数据进行序列化和反序列化操作), + * 构建了一个RedisCacheManager实例,该实例负责管理Redis缓存的各种配置和行为,如缓存的默认过期时间、特定缓存键的过期时间配置等。 + * + * @param redisConnectionFactory 用于创建与Redis服务器连接的工厂类实例,通过它可以获取到与Redis服务器通信的连接对象,是操作Redis缓存的基础依赖。 + * @param redisSerializer 用于对缓存中存储的对象进行序列化和反序列化的序列化器,确保对象能够正确地在Java内存和Redis存储之间进行转换,保证数据的一致性和可存储性。 + * @return 返回配置好的RedisCacheManager实例,用于管理项目中的Redis缓存,将其注入到Spring容器中,以便其他组件可以获取并使用该缓存管理器来操作缓存。 + */ @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, RedisSerializer redisSerializer) { RedisCacheManager redisCacheManager = new RedisCacheManager( RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), // 默认策略,未配置的 key 会使用这个 - this.getRedisCacheConfigurationWithTtl(3600,redisSerializer), + this.getRedisCacheConfigurationWithTtl(3600, redisSerializer), // 指定 key 策略 this.getRedisCacheConfigurationMap(redisSerializer) ); @@ -59,27 +74,50 @@ public class RedisCacheConfig { return redisCacheManager; } - private Map getRedisCacheConfigurationMap(RedisSerializer redisSerializer) { + /** + * 用于获取特定缓存键对应的Redis缓存配置映射(Map)的私有方法,通过创建一个HashMap实例,并向其中添加特定缓存键(这里是"product")与对应的Redis缓存配置信息, + * 使得可以针对不同的缓存键设置不同的缓存过期时间等配置,方便根据业务需求对不同类型的数据缓存进行精细化管理。 + * + * @param redisSerializer 用于对缓存数据进行序列化和反序列化操作的序列化器,确保缓存配置中涉及的数据能够正确地进行序列化处理,与整体的缓存数据格式保持一致。 + * @return 返回包含特定缓存键与对应Redis缓存配置信息的Map,用于在创建RedisCacheManager时设置指定键的缓存策略,以便根据不同的缓存键应用不同的缓存过期时间等规则。 + */ + private Map getRedisCacheConfigurationMap(RedisSerializer redisSerializer) { Map redisCacheConfigurationMap = new HashMap<>(16); redisCacheConfigurationMap.put("product", this.getRedisCacheConfigurationWithTtl(1800, redisSerializer)); return redisCacheConfigurationMap; } - private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds,RedisSerializer redisSerializer) { - + /** + * 用于获取带有指定过期时间(TTL,Time To Live)的Redis缓存配置的私有方法,通过以默认的Redis缓存配置(RedisCacheConfiguration.defaultCacheConfig())为基础, + * 使用传入的序列化器(redisSerializer)来配置缓存值的序列化方式,并设置缓存条目的过期时间(通过Duration.ofSeconds(seconds)指定秒数),从而生成符合特定需求的Redis缓存配置信息。 + * + * @param seconds 缓存条目的过期时间,以秒为单位,用于控制缓存数据在Redis中存储的有效时长,超过这个时间后,缓存数据将被自动清除,以保证缓存数据的时效性和内存空间的合理利用。 + * @param redisSerializer 用于对缓存中存储的对象进行序列化和反序列化的序列化器,确保缓存数据能够按照指定的格式进行转换,以便正确地存储在Redis中以及从Redis中读取还原。 + * @return 返回配置好的带有指定过期时间的Redis缓存配置信息,用于在创建RedisCacheManager或者设置特定缓存键的配置时使用,来定义缓存的具体行为,如数据的序列化和过期策略等。 + */ + private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds, RedisSerializer redisSerializer) { RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith( RedisSerializationContext - .SerializationPair - .fromSerializer(redisSerializer) + .SerializationPair + .fromSerializer(redisSerializer) ).entryTtl(Duration.ofSeconds(seconds)); return redisCacheConfiguration; } + /** + * 创建并配置RedisTemplate的Bean方法,RedisTemplate是Spring Data Redis提供的用于操作Redis的核心模板类, + * 它封装了与Redis进行交互的常见操作方法,如设置键值对、获取值、操作哈希等数据结构,在这里对其进行了详细的配置,包括设置连接工厂、键和值的序列化器等, + * 使得可以方便地在项目中使用该模板类来操作Redis缓存,实现数据的读写等功能。 + * + * @param redisConnectionFactory 用于创建与Redis服务器连接的工厂类实例,为RedisTemplate提供与Redis服务器通信的连接,是操作Redis的基础依赖,确保能够正确地访问Redis服务器。 + * @param redisSerializer 用于对缓存中存储的对象进行序列化和反序列化的序列化器,用于配置RedisTemplate中值的序列化方式,保证存入Redis的数据和从Redis读取还原的数据格式正确,与整体缓存数据的序列化策略保持一致。 + * @return 返回配置好的RedisTemplate实例,注入到Spring容器中,方便其他组件获取并使用它来进行与Redis缓存相关的各种操作,如向缓存中存入数据、从缓存中获取数据等操作。 + */ @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory,RedisSerializer redisSerializer) { + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory, RedisSerializer redisSerializer) { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); @@ -93,6 +131,14 @@ public class RedisCacheConfig { return redisTemplate; } + /** + * 自定义Redis序列化机制的Bean方法,通过创建一个自定义配置的ObjectMapper实例,对Jackson的序列化和反序列化相关特性进行了一系列配置, + * 目的是为了避免与Spring MVC中默认的ObjectMapper配置产生冲突,同时满足项目中对Redis缓存数据序列化的特定需求,例如对日期时间类型的序列化处理、处理未知属性、空对象以及无效子类型等情况的处理方式, + * 最后基于这个自定义的ObjectMapper创建并返回一个GenericJackson2JsonRedisSerializer实例,用于在Redis缓存中对对象进行序列化和反序列化操作。 + * + * @return 返回配置好的GenericJackson2JsonRedisSerializer实例,作为Redis缓存的序列化器,注入到Spring容器中,供其他与Redis缓存相关的组件(如RedisCacheManager、RedisTemplate等)使用, + * 确保缓存数据能够按照预期的方式进行序列化和反序列化,保证数据的正确存储和读取。 + */ /** * 自定义redis序列化的机制,重新定义一个ObjectMapper.防止和MVC的冲突 * https://juejin.im/post/5e869d426fb9a03c6148c97e @@ -100,30 +146,36 @@ public class RedisCacheConfig { @Bean public RedisSerializer redisSerializer() { ObjectMapper objectMapper = JsonMapper.builder().disable(MapperFeature.USE_ANNOTATIONS).build(); - // 反序列化时候遇到不匹配的属性并不抛出异常 + // 反序列化时候遇到不匹配的属性并不抛出异常,这样即使缓存数据中存在一些在当前类中没有定义的属性,也能正常进行反序列化操作,提高了兼容性和容错性。 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - // 序列化时候遇到空对象不抛出异常 + // 序列化时候遇到空对象不抛出异常,避免因为缓存数据中存在空对象而导致序列化失败的情况,使得空对象也能按照预期进行序列化处理。 objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - // 反序列化的时候如果是无效子类型,不抛出异常 + // 反序列化的时候如果是无效子类型,不抛出异常,在处理复杂的继承结构或者类型变化时,即使遇到不符合预期的子类型情况,也能继续进行反序列化,增强了反序列化的稳定性。 objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); - // 不使用默认的dateTime进行序列化, + // 不使用默认的dateTime进行序列化,避免在处理日期时间类型数据时出现格式不符合项目需求的情况,方便按照项目自定义的方式对日期时间进行序列化。 objectMapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false); - // 使用JSR310提供的序列化类,里面包含了大量的JDK8时间序列化类 + // 使用JSR310提供的序列化类,里面包含了大量的JDK8时间序列化类,利用Java 8中对日期时间API的优化,更规范、准确地对日期时间类型数据进行序列化和反序列化操作。 objectMapper.registerModule(new JavaTimeModule()); - // 启用反序列化所需的类型信息,在属性中添加@class + // 启用反序列化所需的类型信息,在属性中添加@class,通过这种方式在反序列化时可以明确数据的类型信息,有助于正确还原对象类型,尤其是在处理多态类型数据时非常重要。 objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); - // 配置null值的序列化器 + // 配置null值的序列化器,确保在缓存数据中存在null值时也能正确地进行序列化和反序列化操作,保证数据的完整性和一致性。 GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null); return new GenericJackson2JsonRedisSerializer(objectMapper); } - + /** + * 创建并配置StringRedisTemplate的Bean方法,StringRedisTemplate是Spring Data Redis提供的专门用于操作Redis中字符串类型数据的模板类, + * 相比于RedisTemplate,它更专注于对字符串类型的操作,在这里对其进行了简单配置,设置了禁止事务支持,并返回配置好的实例注入到Spring容器中,方便在项目中对Redis中的字符串数据进行操作, + * 例如存储简单的字符串键值对、执行字符串相关的命令等操作。 + * + * @param redisConnectionFactory 用于创建与Redis服务器连接的工厂类实例,为StringRedisTemplate提供与Redis服务器通信的连接,确保能够正确地访问Redis服务器进行字符串数据的操作。 + * @return 返回配置好的StringRedisTemplate实例,注入到Spring容器中,供其他组件获取并使用它来进行与Redis中字符串类型数据相关的各种操作,方便在项目中处理简单的字符串缓存数据等情况。 + */ @Bean - public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){ + public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate redisTemplate = new StringRedisTemplate(redisConnectionFactory); redisTemplate.setEnableTransactionSupport(false); return redisTemplate; } - -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/ResourceConfigAdapter.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/ResourceConfigAdapter.java index 244e5b0..47b636e 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/ResourceConfigAdapter.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/ResourceConfigAdapter.java @@ -7,16 +7,31 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** + * 资源配置类 + * 该类实现了`WebMvcConfigurer`接口,用于自定义Spring MVC的相关配置,在这里主要是配置了项目中图片资源的访问路径映射, + * 使得通过特定的URL路径能够访问到服务器上实际存储图片的位置,方便在网页等前端展示图片资源。 + * * @author TRACK */ @Configuration +// 使用 @Configuration 注解声明该类是一个配置类,Spring会自动扫描并加载该类中的配置信息,用于对Spring容器进行相关配置 public class ResourceConfigAdapter implements WebMvcConfigurer { @Autowired + // 通过依赖注入获取ImgUploadUtil工具类对象,该工具类可能用于获取图片上传的路径等相关信息 private ImgUploadUtil imgUploadUtil; + /** + * 配置资源处理器的方法 + * 该方法用于添加资源处理器的配置,通过`ResourceHandlerRegistry`对象来注册资源访问路径与实际资源存储位置的映射关系, + * 在这里是将以`/mall4j/img/`开头的URL路径映射到服务器上实际存储图片的文件路径(通过`ImgUploadUtil`获取的上传路径), + * 这样当客户端通过对应的URL请求图片资源时,Spring MVC能够正确地找到并返回相应的图片文件。 + * + * @param registry 资源处理器注册表对象,用于注册资源访问路径与实际资源存储位置的映射关系,可添加多个映射配置。 + */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/mall4j/img/**").addResourceLocations("file:" + imgUploadUtil.getUploadPath()); + registry.addResourceHandler("/mall4j/img/**") + .addResourceLocations("file:" + imgUploadUtil.getUploadPath()); } -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/ShopBasicConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/ShopBasicConfig.java index 4daa158..0d90d6b 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/ShopBasicConfig.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/ShopBasicConfig.java @@ -18,9 +18,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; - /** - * 商城配置文件 + * 该类作为商城配置文件相关的实体类,用于绑定配置文件中的各项配置信息到对应的属性上,方便在整个项目中获取和使用商城相关的配置参数, + * 通过使用 Spring 的相关注解(@Component、@ConfigurationProperties、@PropertySource),可以将配置文件中的配置值自动注入到这些属性中, + * 使得项目中的不同模块能够方便地获取如七牛云配置、阿里大鱼短信平台配置、加解密 token 的密钥以及本地文件上传配置等信息,实现配置的统一管理和灵活使用。 + * * @author lgh */ @Data @@ -29,24 +31,27 @@ import org.springframework.stereotype.Component; @ConfigurationProperties(prefix = "shop") public class ShopBasicConfig { - /** - * 七牛云的配置信息 - */ - private Qiniu qiniu; - - /** - * 阿里大鱼短信平台 - */ - private AliDaYu aLiDaYu; - - /** - * 用于加解密token的密钥 - */ - private String tokenAesKey; - - /** - * 本地文件上传配置 - */ - private ImgUpload imgUpload; - -} + /** + * 用于存储七牛云的配置信息的属性,七牛云可能用于文件存储、图片上传等功能,其配置信息包含诸如访问密钥、存储区域等内容, + * 通过此属性可以在项目中方便地获取七牛云相关配置,以便与七牛云服务进行集成,实现文件上传、管理等操作。 + */ + private Qiniu qiniu; + + /** + * 用于存储阿里大鱼短信平台相关配置信息的属性,阿里大鱼短信平台可用于向用户发送短信通知、验证码等功能, + * 其配置信息包含访问密钥 ID、访问密钥秘密、短信签名等内容,通过该属性能够获取相应配置,进而调用阿里大鱼的短信服务接口来发送短信。 + */ + private AliDaYu aLiDaYu; + + /** + * 用于存储加解密 token 的密钥信息的属性,在涉及用户认证、授权等场景中,token 常用于传递用户身份信息, + * 为了保证 token 的安全性,可能会使用此密钥进行加密和解密操作,该属性保存的密钥值会在相关的加密解密逻辑中被使用。 + */ + private String tokenAesKey; + + /** + * 用于存储本地文件上传配置信息的属性,本地文件上传配置可能涉及到文件存储路径、文件大小限制、允许的文件类型等相关参数, + * 通过这个属性可以获取相应配置,从而在进行本地文件上传功能实现时,按照配置要求来处理文件上传的各种逻辑,确保文件上传操作符合业务需求。 + */ + private ImgUpload imgUpload; +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/ShopBeanConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/ShopBeanConfig.java index 8bbd6e4..3a05521 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/ShopBeanConfig.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/ShopBeanConfig.java @@ -19,31 +19,65 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** + * `ShopBeanConfig`类是一个Spring的配置类,主要负责创建和配置项目中一些常用的Bean实例,这些Bean通常是与业务相关的配置类或者工具类的实例, + * 通过依赖注入获取相关的配置信息,并将其封装成对应的Bean提供给Spring容器管理,方便在整个项目的其他地方可以通过依赖注入的方式获取并使用这些实例, + * 以此来实现配置信息的统一管理和复用,确保各个业务模块能够方便地获取到所需的配置参数和工具类实例来执行相应的业务逻辑。 + * * @author lanhai */ -@Configuration -@AllArgsConstructor +@Configuration // 表明这个类是一个Spring的配置类,Spring容器会在启动时扫描并解析此类中的配置信息,用于创建和管理相关的Bean。 +@AllArgsConstructor // 通过Lombok生成包含所有参数的构造函数,这里用于注入 `ShopBasicConfig` 实例,方便获取其中的配置信息来创建其他Bean。 public class ShopBeanConfig { - private final ShopBasicConfig shopBasicConfig; + // 通过构造函数注入的 `ShopBasicConfig` 实例,它应该包含了项目中各种基础的配置信息,例如七牛云相关配置、加密密钥、短信配置(对应 `AliDaYu`)以及图片上传相关配置等, + // 作为创建其他Bean的数据源,从它里面提取相应的配置信息来实例化具体的Bean对象。 + private final ShopBasicConfig shopBasicConfig; + /** + * 创建并返回 `Qiniu` 类型的Bean实例,用于七牛云相关的业务操作,比如文件存储到七牛云等操作。 + * 这个方法通过调用 `shopBasicConfig` 的 `getQiniu` 方法获取已经配置好的 `Qiniu` 对象,然后将其作为Spring容器管理的Bean返回, + * 使得在项目中其他需要使用七牛云功能的地方可以方便地通过依赖注入获取这个 `Qiniu` 实例进行相关操作。 + * + * @return 返回从 `ShopBasicConfig` 中获取的 `Qiniu` 实例,作为Spring容器中的一个Bean,供七牛云相关业务使用。 + */ @Bean public Qiniu qiniu() { - return shopBasicConfig.getQiniu(); + return shopBasicConfig.getQiniu(); } + /** + * 创建并返回 `AES` 类型的加密工具类的Bean实例,`AES` 是一种常用的对称加密算法,这里用于对项目中的某些数据(可能是用户令牌等敏感信息)进行加密和解密操作。 + * 通过获取 `shopBasicConfig` 中的 `tokenAesKey`(应该是加密密钥相关信息)并转换为字节数组来实例化 `AES` 对象,然后将其作为Bean交给Spring容器管理, + * 方便在需要进行加密或解密操作的业务逻辑中通过依赖注入获取该实例来执行相应的操作,保障数据的安全性。 + * + * @return 返回一个配置好的 `AES` 实例,作为Spring容器中的一个Bean,用于数据加密和解密相关业务。 + */ @Bean public AES tokenAes() { - return new AES(shopBasicConfig.getTokenAesKey().getBytes()); + return new AES(shopBasicConfig.getTokenAesKey().getBytes()); } + /** + * 创建并返回 `AliDaYu` 类型的Bean实例,`AliDaYu` 可能是与阿里云短信服务(短信发送等相关功能)对应的配置类或者业务操作类, + * 通过从 `shopBasicConfig` 中获取已经配置好的 `AliDaYu` 对象并返回,使得在项目中需要发送短信等与阿里云短信服务相关的业务逻辑可以通过依赖注入获取该实例进行操作, + * 实现了短信服务相关配置和功能的统一管理和使用。 + * + * @return 返回从 `ShopBasicConfig` 中获取的 `AliDaYu` 实例,作为Spring容器中的一个Bean,供阿里云短信服务相关业务使用。 + */ @Bean - public AliDaYu aLiDaYu () { - return shopBasicConfig.getALiDaYu(); + public AliDaYu aLiDaYu() { + return shopBasicConfig.getALiDaYu(); } + /** + * 创建并返回 `ImgUpload` 类型的Bean实例,用于管理图片上传相关的配置信息,比如本地文件上传路径、上传方式以及网站资源访问地址等, + * 通过获取 `shopBasicConfig` 中的 `ImgUpload` 对象并返回,使得在项目中涉及图片上传业务逻辑的地方可以方便地通过依赖注入获取该实例来获取相应的配置参数, + * 确保图片上传功能能够按照配置的要求正确执行,比如选择合适的上传方式、存储到正确的路径等。 + * + * @return 返回从 `ShopBasicConfig` 中获取的 `ImgUpload` 实例,作为Spring容器中的一个Bean,供图片上传相关业务使用。 + */ @Bean public ImgUpload imgUpload() { return shopBasicConfig.getImgUpload(); } -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/constants/Constant.java b/yami-shop-common/src/main/java/com/yami/shop/common/constants/Constant.java index 4adb604..c6a4c8a 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/constants/Constant.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/constants/Constant.java @@ -1,16 +1,24 @@ package com.yami.shop.common.constants; /** + * `Constant`类的作用是作为一个常量类,用于集中定义项目中常用的、固定不变的常量值,通过将这些常量统一放在一个类中进行管理, + * 方便在整个项目的不同地方进行引用,提高代码的可读性、可维护性以及避免出现重复定义相同常量值的情况,使得代码更加规范和易于理解。 + * * @author TRACK */ public class Constant { /** - * 句号(英文符号) + * 句号(英文符号),定义了一个公共的静态常量字符串 `PERIOD`,其值为英文的句号“.”,在项目中,当需要使用英文句号这个字符时, + * 可以直接引用这个常量,例如在处理文件路径拼接、字符串分割等操作中,如果涉及到按照英文句号来进行相关逻辑处理,使用这个常量会比直接写“.”更清晰, + * 而且如果后续需要统一修改这个符号(虽然可能性较小,但从代码维护角度考虑),只需要在这个常量类中修改一处即可,便于代码的维护和更新。 */ public static final String PERIOD = "."; + /** - * 逗号 + * 逗号,同样定义了一个公共的静态常量字符串 `COMMA`,其值为逗号“,”,在很多业务场景下,比如处理CSV格式的数据解析、多个元素的分隔表示等情况时, + * 经常会用到逗号作为分隔符,通过定义这个常量,在整个项目代码中凡是涉及到使用逗号作为特定分隔用途的地方,都可以统一引用这个常量, + * 保证了代码的一致性,也方便后续对逗号这个分隔符相关的业务逻辑进行调整和维护。 */ public static final String COMMA = ","; -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java b/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java index 0429557..60c3f8c 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java @@ -1,38 +1,47 @@ package com.yami.shop.common.constants; /** + * OauthCacheNames接口主要用于定义与Oauth(授权认证相关)缓存中使用的各种键(Key)的名称常量,通过统一的接口定义这些常量, + * 方便在整个项目中对Oauth缓存相关操作涉及的键名进行规范管理,避免出现键名不一致或者硬编码键名的情况,提高代码的可维护性和可读性。 + * * @author 菠萝凤梨 * @date 2022/3/28 14:32 */ public interface OauthCacheNames { /** - * oauth 授权相关key + * OAUTH_PREFIX常量定义了Oauth授权相关键名的基础前缀,后续所有与Oauth授权相关的缓存键名都会以此为开头进行构建, + * 这样可以清晰地将Oauth相关的缓存键与其他类型的缓存键区分开来,便于在缓存管理中进行分类和识别,其值为"mall4j_oauth:"。 */ String OAUTH_PREFIX = "mall4j_oauth:"; /** - * token 授权相关key + * OAUTH_TOKEN_PREFIX常量是在OAUTH_PREFIX基础上进一步细化,用于表示与token授权相关的键名前缀,意味着后续所有和token授权具体操作相关的缓存键名都会以这个值开头, + * 它是通过将OAUTH_PREFIX与"token:"拼接而成,其值为"mall4j_oauth:token:",明确了这部分键名是专门针对token授权这个细分领域的缓存键名。 */ String OAUTH_TOKEN_PREFIX = OAUTH_PREFIX + "token:"; /** - * 保存token 缓存使用key + * ACCESS常量定义了用于保存token缓存时使用的具体键名的一部分,是在OAUTH_TOKEN_PREFIX基础上添加"access:"后缀形成的, + * 在项目中实际使用时,可能会结合具体的token标识等其他信息共同构成完整的缓存键名,用于在缓存中存储和获取token相关的数据,其值为"mall4j_oauth:token:access:"。 */ String ACCESS = OAUTH_TOKEN_PREFIX + "access:"; /** - * 刷新token 缓存使用key + * REFRESH_TO_ACCESS常量定义了用于刷新token到访问token相关操作时,在缓存中使用的键名的一部分,它以OAUTH_TOKEN_PREFIX为基础,添加"refresh_to_access:"后缀, + * 表示这个键名主要用于处理刷新token与访问token之间关联或转换等相关缓存操作,在具体业务逻辑中配合相应的参数来唯一确定缓存中的具体数据,其值为"mall4j_oauth:token:refresh_to_access:"。 */ String REFRESH_TO_ACCESS = OAUTH_TOKEN_PREFIX + "refresh_to_access:"; /** - * 根据uid获取保存的token key缓存使用的key + * UID_TO_ACCESS常量定义了根据用户唯一标识符(UID)获取保存的token key时,在缓存中使用的键名的一部分,同样以OAUTH_TOKEN_PREFIX开头,添加"uid_to_access:"后缀, + * 意味着在需要通过用户ID来查找对应的token相关缓存数据时,会使用这个键名格式,结合具体的用户ID等信息构建完整的缓存键,方便在缓存中进行相应的查询操作,其值为"mall4j_oauth:token:uid_to_access:"。 */ String UID_TO_ACCESS = OAUTH_TOKEN_PREFIX + "uid_to_access:"; /** - * 保存token的用户信息使用的key + * USER_INFO常量定义了保存token的用户信息时在缓存中使用的键名的一部分,基于OAUTH_TOKEN_PREFIX再添加"user_info:"后缀形成, + * 用于在缓存中存储与token关联的用户详细信息,例如用户的基本资料、权限等信息,在获取token对应的用户相关数据时,会依据这个键名格式结合具体的token标识等构建完整的缓存键来进行查找,其值为"mall4j_oauth:token:user_info:"。 */ String USER_INFO = OAUTH_TOKEN_PREFIX + "user_info:"; -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/enums/QiniuZone.java b/yami-shop-common/src/main/java/com/yami/shop/common/enums/QiniuZone.java index 3219e1d..e6ad47b 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/enums/QiniuZone.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/enums/QiniuZone.java @@ -1,39 +1,46 @@ package com.yami.shop.common.enums; /** - * 七牛云zone的选择 + * 七牛云zone(机房区域)的选择枚举类 + * 该枚举类用于定义七牛云不同机房区域的选项,方便在项目中根据实际需求选择对应的七牛云机房,例如在进行文件存储、资源上传下载等操作时,指定要使用的机房区域。 + * * @author LGH */ public enum QiniuZone { /** - * 华东机房相关 + * 代表七牛云华东机房相关的选项 + * 当业务涉及到在华东地区有更好的资源访问需求、网络延迟更低等情况时,可选择该机房区域进行相关操作,具体使用方式取决于七牛云的服务接入逻辑以及项目中的业务配置。 */ HUA_DONG(), /** - * 华北机房相关 + * 代表七牛云华北机房相关的选项 + * 适用于针对华北地区的业务场景,比如在华北地区有较多用户访问,为了提供更优质的服务,可选择将资源存储在华北机房,以优化网络传输等性能指标。 */ HUA_BEI(), /** - * 华南机房相关 + * 代表七牛云华南机房相关的选项 + * 若业务主要面向华南地区的用户,或者在华南地区有数据存储、资源分发等需求,可选用该机房区域,以保障服务的高效性和稳定性。 */ HUA_NAN(), /** - * 北美机房相关 + * 代表七牛云北美机房相关的选项 + * 当涉及到海外业务,特别是针对北美地区的用户或业务场景,例如为北美地区的客户提供文件存储、内容分发等服务时,可选择该机房区域进行相应操作。 */ BEI_MEI(), /** - * 新加坡机房相关 + * 代表七牛云新加坡机房相关的选项 + * 对于在东南亚地区或者需要通过新加坡机房进行资源管理、数据中转等业务需求时,可使用该机房区域,满足特定区域的服务要求。 */ XIN_JIA_PO(), ; - QiniuZone(){ + QiniuZone() { } -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/exception/YamiShopBindException.java b/yami-shop-common/src/main/java/com/yami/shop/common/exception/YamiShopBindException.java index c5f62ec..1a3a388 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/exception/YamiShopBindException.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/exception/YamiShopBindException.java @@ -15,51 +15,106 @@ import com.yami.shop.common.response.ServerResponseEntity; import lombok.Getter; /** + * `YamiShopBindException` 是一个自定义的运行时异常类,用于在项目中表示特定的业务绑定异常情况, + * 它继承自 `RuntimeException`,意味着在抛出该异常时,不需要显式地在方法声明中添加 `throws` 子句(除非方法本身已经声明了抛出更宽泛的异常类型), + * 方便在业务逻辑中根据具体情况灵活抛出异常来表示业务流程中的错误或不符合预期的情况,并且可以携带一些额外的信息(如状态码、关联对象等)方便进行异常处理和信息反馈。 + * * @author lanhai */ @Getter -public class YamiShopBindException extends RuntimeException{ - - /** - * - */ - private static final long serialVersionUID = -4137688758944857209L; +public class YamiShopBindException extends RuntimeException { - /** - * http状态码 - */ - private String code; + /** + * 用于序列化的版本号,在进行对象序列化和反序列化时,这个版本号用于确保序列化的兼容性, + * 如果类的结构发生变化(例如添加、删除字段等),合适的版本号管理有助于避免反序列化时出现问题, + * 这里默认生成了一个固定的版本号值,通常在实际项目中,如果类的结构有变更且涉及到序列化操作时,可能需要根据情况更新这个版本号。 + */ + private static final long serialVersionUID = -4137688758944857209L; - private Object object; + /** + * 用于存储 HTTP 状态码的属性,通过这个属性可以在异常抛出时传递对应的 HTTP 状态码信息, + * 方便在统一的异常处理机制中根据状态码来决定返回给客户端的相应状态以及错误提示等内容, + * 例如可以根据不同的状态码返回不同格式的错误响应,让前端更好地识别和处理异常情况。 + */ + private String code; - private ServerResponseEntity serverResponseEntity; + /** + * 用于存储一个额外的对象的属性,这个对象可以是与异常相关的任意类型的对象,比如在某些业务场景下, + * 可以将引发异常的相关业务数据对象或者详细的错误信息对象等存放在这里,方便在异常处理时获取更详细的上下文信息进行进一步的分析和处理。 + */ + private Object object; - public YamiShopBindException(ResponseEnum responseEnum) { - super(responseEnum.getMsg()); - this.code = responseEnum.value(); - } - /** - * @param responseEnum - */ - public YamiShopBindException(ResponseEnum responseEnum, String msg) { - super(msg); - this.code = responseEnum.value(); - } + /** + * 用于存储一个 `ServerResponseEntity` 类型对象的属性,`ServerResponseEntity` 可能是项目中自定义的用于封装服务器响应信息的类, + * 通过这个属性可以直接将一个完整的服务器响应实体对象与异常关联起来,例如在异常处理中可以直接返回这个关联的响应实体给客户端, + * 提供更丰富、准确的错误响应内容。 + */ + private ServerResponseEntity serverResponseEntity; - public YamiShopBindException(ServerResponseEntity serverResponseEntity) { - this.serverResponseEntity = serverResponseEntity; - } + /** + * 构造函数,根据传入的 `ResponseEnum` 枚举类型参数来初始化异常对象, + * 会调用父类(`RuntimeException`)的构造函数,将 `ResponseEnum` 中定义的错误消息(`getMsg()` 方法获取)作为异常消息传递给父类, + * 同时将 `ResponseEnum` 中定义的对应值(`value()` 方法获取)作为 HTTP 状态码赋值给当前异常对象的 `code` 属性, + * 常用于根据预定义的响应枚举类型来抛出异常,确保异常的状态码和消息符合项目中统一的规范。 + * + * @param responseEnum 包含了状态码和错误消息等信息的响应枚举类型,用于初始化异常对象的相关属性。 + */ + public YamiShopBindException(ResponseEnum responseEnum) { + super(responseEnum.getMsg()); + this.code = responseEnum.value(); + } + /** + * 构造函数,根据传入的 `ResponseEnum` 枚举类型参数和自定义的错误消息来初始化异常对象, + * 同样会调用父类(`RuntimeException`)的构造函数,将传入的自定义错误消息 `msg` 作为异常消息传递给父类, + * 并将 `ResponseEnum` 中定义的对应值(`value()` 方法获取)作为 HTTP 状态码赋值给当前异常对象的 `code` 属性, + * 适用于在遵循预定义的响应枚举类型的状态码规范基础上,需要自定义具体错误消息的场景,比如针对特定业务逻辑下的更详细错误描述。 + * + * @param responseEnum 包含了状态码相关信息的响应枚举类型,用于获取状态码赋值给当前异常对象的 `code` 属性。 + * @param msg 自定义的错误消息,用于作为异常消息传递给父类(`RuntimeException`)构造函数。 + */ + public YamiShopBindException(ResponseEnum responseEnum, String msg) { + super(msg); + this.code = responseEnum.value(); + } - public YamiShopBindException(String msg) { - super(msg); - this.code = ResponseEnum.SHOW_FAIL.value(); - } + /** + * 构造函数,根据传入的 `ServerResponseEntity` 类型对象来初始化异常对象, + * 将传入的 `ServerResponseEntity` 对象赋值给当前异常对象的 `serverResponseEntity` 属性, + * 常用于在已经构建好一个完整的服务器响应实体对象的情况下,将其与异常关联起来,方便在异常处理时直接使用该响应实体进行返回等操作。 + * + * @param serverResponseEntity 包含了服务器响应信息的对象,用于赋值给当前异常对象的 `serverResponseEntity` 属性。 + */ + public YamiShopBindException(ServerResponseEntity serverResponseEntity) { + this.serverResponseEntity = serverResponseEntity; + } - public YamiShopBindException(String msg, Object object) { - super(msg); - this.code = ResponseEnum.SHOW_FAIL.value(); - this.object = object; - } + /** + * 构造函数,根据传入的自定义错误消息来初始化异常对象, + * 调用父类(`RuntimeException`)的构造函数,将传入的错误消息 `msg` 作为异常消息传递给父类, + * 同时将默认的 HTTP 状态码(通过 `ResponseEnum.SHOW_FAIL.value()` 获取)赋值给当前异常对象的 `code` 属性, + * 用于在没有对应预定义的响应枚举类型或者不需要特定枚举类型关联的情况下,简单地抛出一个带有自定义消息的异常,并赋予默认的状态码。 + * + * @param msg 自定义的错误消息,用于作为异常消息传递给父类(`RuntimeException`)构造函数,并作为异常的主要描述信息。 + */ + public YamiShopBindException(String msg) { + super(msg); + this.code = ResponseEnum.SHOW_FAIL.value(); + } -} + /** + * 构造函数,根据传入的自定义错误消息和一个额外的对象来初始化异常对象, + * 调用父类(`RuntimeException`)的构造函数,将传入的错误消息 `msg` 作为异常消息传递给父类, + * 将默认的 HTTP 状态码(通过 `ResponseEnum.SHOW_FAIL.value()` 获取)赋值给当前异常对象的 `code` 属性, + * 并将传入的额外对象赋值给当前异常对象的 `object` 属性, + * 适用于在抛出异常时,除了传递错误消息外,还希望携带一个与异常相关的具体对象,方便后续异常处理时获取更多详细信息的场景。 + * + * @param msg 自定义的错误消息,用于作为异常消息传递给父类(`RuntimeException`)构造函数,并作为异常的主要描述信息。 + * @param object 与异常相关的额外对象,用于赋值给当前异常对象的 `object` 属性,方便后续异常处理时获取更多详细信息。 + */ + public YamiShopBindException(String msg, Object object) { + super(msg); + this.code = ResponseEnum.SHOW_FAIL.value(); + this.object = object; + } +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/filter/XssFilter.java b/yami-shop-common/src/main/java/com/yami/shop/common/filter/XssFilter.java index 870349b..774fd35 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/filter/XssFilter.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/filter/XssFilter.java @@ -21,32 +21,62 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; /** - * 一些简单的安全过滤: - * xss + * `XssFilter`类实现了`Filter`接口,是一个用于进行简单安全过滤的过滤器类,主要针对跨站脚本攻击(XSS,Cross-Site Scripting)进行防范。 + * 在Web应用中,用户输入的数据可能包含恶意的脚本代码,如果不加以过滤处理,这些代码可能会在浏览器端被执行,从而导致安全问题,比如窃取用户信息、篡改页面内容等。 + * 此类通过将请求包装在`XssWrapper`中,对请求中的数据进行过滤处理,以达到防范XSS攻击的目的,并且作为一个Spring组件,可以方便地被Spring容器管理和配置到Web应用的过滤器链中。 + * * @author lgh */ -@Component +@Component // 表明这个类是一个Spring组件,会被Spring容器扫描并实例化,方便进行依赖注入等相关操作以及在Web应用中作为过滤器被正确管理和使用。 public class XssFilter implements Filter { + // 创建一个日志记录器,用于记录过滤器执行过程中的相关信息,比如记录请求的URI等情况,方便后续查看过滤器的执行情况以及排查可能出现的问题。 Logger logger = LoggerFactory.getLogger(getClass().getName()); + /** + * `init`方法是`Filter`接口中定义的初始化方法,在过滤器被创建并加载到Web容器时调用一次,用于进行一些初始化的配置操作。 + * 在这里,当前的实现为空方法体,意味着没有额外的初始化逻辑需要执行,如果后续有需要,比如读取配置文件中的过滤规则等操作,可以在此方法中添加相应的代码。 + * + * @param filterConfig 包含了过滤器的配置信息,通过这个参数可以获取到在Web应用配置文件中为该过滤器设置的初始化参数等内容,不过目前没有使用到。 + * @throws ServletException 如果在初始化过程中出现错误,例如读取配置文件出错等情况,可以抛出这个异常,由Web容器进行相应的处理。 + */ @Override public void init(FilterConfig filterConfig) throws ServletException { } + /** + * `doFilter`方法是`Filter`接口中最核心的方法,用于实现具体的过滤逻辑。每次有请求经过这个过滤器时,都会执行这个方法。 + * 其主要逻辑如下: + * 1. 首先将传入的`ServletRequest`和`ServletResponse`对象强制转换为`HttpServletRequest`和`HttpServletResponse`类型, + * 因为在Web应用中,通常处理的是基于HTTP协议的请求和响应,这样后续可以方便地使用HTTP相关的方法和属性进行操作。 + * 2. 通过日志记录器记录当前请求的URI(统一资源标识符),方便后续查看哪些请求经过了这个过滤器,对于调试和监控请求情况很有帮助。 + * 3. 进行XSS过滤操作,将原始的`HttpServletRequest`对象包装在`XssWrapper`对象中,`XssWrapper`类应该是实现了对请求数据进行XSS过滤的逻辑, + * 然后将包装后的请求对象传递给过滤器链的下一个环节(通过`chain.doFilter`方法),这样后续的过滤器或者请求处理的Servlet等就能接收到经过XSS过滤后的请求数据, + * 从而避免了恶意脚本代码通过请求进入到应用内部并造成安全风险。 + * + * @param request 表示客户端发来的请求对象,包含了请求的各种信息,如请求头、请求参数等内容,在这里会被转换为`HttpServletRequest`类型来进一步处理。 + * @param response 表示服务器端要返回给客户端的响应对象,包含了响应的相关信息,如响应头、响应体等内容,在这里会被转换为`HttpServletResponse`类型来进行相关操作。 + * @param chain 过滤器链对象,代表了整个Web应用中过滤器的执行顺序链条,通过调用`chain.doFilter`方法可以将请求传递到下一个过滤器或者最终的请求处理Servlet中继续处理。 + * @throws IOException 如果在处理请求或响应的过程中出现输入输出相关的错误,例如读取请求数据出错、向客户端写响应数据出错等情况,会抛出这个异常,由Web容器进行相应的处理。 + * @throws ServletException 如果在过滤器执行过程中出现与Servlet相关的错误,例如请求对象转换失败、过滤器链执行异常等情况,会抛出这个异常,由Web容器进行相应的处理。 + */ @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{ + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; - - logger.info("uri:{}",req.getRequestURI()); - // xss 过滤 - chain.doFilter(new XssWrapper(req), resp); + logger.info("uri:{}", req.getRequestURI()); + // xss 过滤,将原始的HttpServletRequest包装在XssWrapper中,XssWrapper类应该实现了对请求参数等数据进行XSS过滤的逻辑, + // 然后把包装后的请求对象传递给过滤器链的下一个环节,使得后续的处理流程接收到的是经过XSS过滤后的请求数据。 + chain.doFilter(new XssWrapper(req), resp); } + /** + * `destroy`方法是`Filter`接口中定义的销毁方法,在过滤器从Web容器中被移除时调用,用于释放过滤器占用的资源,比如关闭打开的文件流、释放数据库连接等操作。 + * 在这里,当前的实现为空方法体,意味着没有额外的资源释放逻辑需要执行,如果后续过滤器在运行过程中有占用相关资源的情况,可以在此方法中添加相应的代码来进行资源的清理工作。 + */ @Override public void destroy() { } -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java b/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java index ff19f1b..24193d0 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java @@ -18,17 +18,37 @@ import java.io.PrintWriter; import java.util.Objects; /** + * HttpHandler类是一个Spring组件,主要用于处理将服务器响应信息输出到Web端的相关操作,它能够将封装好的响应实体(ServerResponseEntity)或者特定的业务异常(YamiShopBindException)信息 + * 按照合适的格式(JSON格式)写入到HTTP响应中,以便前端能够正确接收并解析后端返回的结果,同时在处理过程中进行了一些必要的日志记录以及异常处理,保证操作的稳定性和可靠性。 + * * @author 菠萝凤梨 * @date 2022/3/28 14:15 */ @Component +// 使用@Component注解将该类标记为Spring的组件,使得Spring能够扫描并管理这个类,将其纳入到Spring容器中,方便进行依赖注入等操作。 public class HttpHandler { + // 创建一个名为logger的静态常量,用于记录日志信息,通过LoggerFactory获取HttpHandler类对应的Logger实例,方便在类的各个方法中记录不同级别的日志, + // 例如记录操作过程中的错误信息、调试信息等,有助于排查问题和了解程序的执行情况。 private static final Logger logger = LoggerFactory.getLogger(HttpHandler.class); + // 使用@Autowired注解进行依赖注入,将Spring容器中管理的ObjectMapper实例注入到当前类中,ObjectMapper主要用于将Java对象转换为JSON字符串(序列化)以及将JSON字符串转换为Java对象(反序列化), + // 在将ServerResponseEntity对象转换为JSON格式并写入HTTP响应时会用到它。 @Autowired private ObjectMapper objectMapper; + /** + * 将ServerResponseEntity对象转换为JSON格式并输出到Web端(即写入HTTP响应)的方法。 + * 首先会对传入的ServerResponseEntity对象进行判空处理,如果为空则记录相应的日志信息并直接返回,不进行后续操作。 + * 接着获取当前请求的相关属性(ServletRequestAttributes),如果获取失败(为null)则记录错误日志并返回,因为没有请求上下文就无法进行响应输出操作。 + * 然后获取HttpServletResponse对象,如果该对象也为null同样记录错误日志并返回。 + * 之后设置响应的字符编码为UTF-8,并指定响应的内容类型为JSON格式(通过设置ContentType为APPLICATION_JSON_VALUE), + * 最后通过ObjectMapper将ServerResponseEntity对象转换为JSON字符串,并使用PrintWriter将其写入到HTTP响应中,如果在写入过程中出现IOException异常, + * 则抛出YamiShopBindException异常,并将原始的IOException作为原因封装进去,以便上层调用者能够处理这个异常情况。 + * + * @param serverResponseEntity 封装了服务器响应信息(如响应码、消息、响应数据等)的实体对象,需要将其转换为JSON格式并输出到HTTP响应中,供前端接收和解析。 + * @param 泛型参数,表示ServerResponseEntity中响应数据部分的类型,其具体类型取决于实际业务逻辑中返回的数据类型。 + */ public void printServerResponseToWeb(ServerResponseEntity serverResponseEntity) { if (serverResponseEntity == null) { logger.info("print obj is null"); @@ -36,7 +56,7 @@ public class HttpHandler { } ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder - .getRequestAttributes(); + .getRequestAttributes(); if (requestAttributes == null) { logger.error("requestAttributes is null, can not print to web"); return; @@ -59,6 +79,16 @@ public class HttpHandler { } } + /** + * 处理将YamiShopBindException异常信息输出到Web端(写入HTTP响应)的方法。 + * 首先对传入的YamiShopBindException对象进行判空处理,如果为空则记录相应日志信息并返回,不进行后续操作。 + * 接着判断该异常对象中是否已经包含了ServerResponseEntity对象(即是否已经有了预先定义好的响应实体),如果有则直接调用printServerResponseToWeb方法将其输出到HTTP响应中并返回。 + * 如果异常对象中不包含ServerResponseEntity对象,则创建一个新的ServerResponseEntity对象,将异常的错误码(通过getCode方法获取)和错误消息(通过getMessage方法获取)设置到这个新的响应实体中, + * 然后再调用printServerResponseToWeb方法将其输出到HTTP响应中,使得前端能够接收到关于异常的详细信息,以合适的格式展示给用户或者进行相应的错误处理。 + * + * @param yamiShopBindException 业务异常对象,包含了业务逻辑中出现错误时的相关信息,如错误码、错误消息等,需要将这些信息以合适的格式输出到HTTP响应中,以便前端知晓出现的问题。 + * @param 泛型参数,在这里主要是为了与printServerResponseToWeb(ServerResponseEntity)方法的参数类型保持一致,在实际使用中具体类型取决于业务逻辑中响应数据的类型情况。 + */ public void printServerResponseToWeb(YamiShopBindException yamiShopBindException) { if (yamiShopBindException == null) { logger.info("print obj is null"); @@ -75,4 +105,4 @@ public class HttpHandler { serverResponseEntity.setMsg(yamiShopBindException.getMessage()); printServerResponseToWeb(serverResponseEntity); } -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/response/ResponseCode.java b/yami-shop-common/src/main/java/com/yami/shop/common/response/ResponseCode.java index 2721206..5295966 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/response/ResponseCode.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/response/ResponseCode.java @@ -10,10 +10,23 @@ package com.yami.shop.common.response; /** + * 响应码接口 + * 该接口用于定义项目中通用的响应码常量,使得不同模块在返回响应结果时能够遵循统一的规范,方便前端根据响应码判断请求的处理结果是成功还是失败等情况。 + * 目前定义了表示成功和失败的两个常用响应码常量,但在实际项目中可根据具体业务需求进一步扩展该接口,添加更多不同含义的响应码。 + * * @author lanhai */ public interface ResponseCode { + /** + * 成功响应码 + * 表示请求处理成功的状态码,当后端处理业务逻辑顺利完成,无异常情况发生时,通常会返回该响应码给前端,前端可据此进行相应的成功提示或后续业务操作。 + */ int SUCCESS = 1; + + /** + * 失败响应码 + * 表示请求处理失败的状态码,当后端在处理业务逻辑过程中出现错误、校验不通过等异常情况时,会返回该响应码给前端,前端可根据具体业务场景进行相应的失败提示等操作。 + */ int FAIL = -1; -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/response/ResponseEnum.java b/yami-shop-common/src/main/java/com/yami/shop/common/response/ResponseEnum.java index bad0c30..203c971 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/response/ResponseEnum.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/response/ResponseEnum.java @@ -10,60 +10,104 @@ package com.yami.shop.common.response; /** + * `ResponseEnum` 是一个枚举类,用于定义项目中各种不同的响应状态相关的枚举常量,每个枚举常量代表了一种特定的响应情况, + * 包含了对应的状态码(`code`)和提示消息(`msg`),方便在整个项目中统一管理和使用响应状态信息, + * 例如在处理请求返回结果、异常处理等场景中,可以根据这些枚举常量来确定要返回给客户端的状态码以及对应的提示内容, + * 使得响应状态的表示更加规范、清晰且易于维护。 + * * @author FrozenWatermelon * @date 2020/7/9 */ public enum ResponseEnum { /** - * ok + * 代表请求成功的枚举常量,状态码为 `"00000"`,提示消息为 `"ok"`,通常用于表示一个操作顺利完成,没有出现任何错误的情况, + * 在业务逻辑处理成功后,可以使用这个枚举常量来构建成功的响应信息返回给客户端,告知客户端请求已被正确处理。 */ OK("00000", "ok"), + + /** + * 代表显示失败的枚举常量,状态码为 `"A00001"`,提示消息为空字符串,主要用于表示一种需要向用户展示失败信息的通用情况, + * 具体的失败提示内容可以根据实际业务场景另行设置(比如在使用该枚举抛出异常时,可以传入具体的错误消息),常用于各种业务操作失败的场景反馈。 + */ SHOW_FAIL("A00001", ""), /** - * 用于直接显示提示用户的错误,内容由输入内容决定 + * 代表用于直接显示提示用户的错误的枚举常量,状态码未明确指定(这里主要关注其使用场景的描述),提示消息由具体输入内容决定, + * 意味着在某些业务逻辑中,需要根据具体的错误情况动态生成提示用户的错误消息时,可以使用这个枚举常量来表示,强调了消息的灵活性和可定制性。 */ /** - * 用于直接显示提示系统的成功,内容由输入内容决定 + * 代表用于直接显示提示系统的成功的枚举常量,状态码未明确指定(这里主要关注其使用场景的描述),提示消息由具体输入内容决定, + * 类似于上面用于提示用户错误的情况,不过此枚举是用于在需要根据具体情况动态生成提示系统成功的消息时使用,突出了消息内容的动态生成特性。 */ SHOW_SUCCESS("A00002", ""), /** - * 未授权 + * 代表未授权的枚举常量,状态码为 `"A00004"`,提示消息为 `"Unauthorized"`,用于表示客户端发起的请求未经过授权, + * 例如用户没有登录或者没有相应权限去访问某个资源时,就可以使用这个枚举常量来构建相应的响应信息返回给客户端,告知客户端授权失败的情况。 */ UNAUTHORIZED("A00004", "Unauthorized"), /** - * 服务器出了点小差 + * 代表服务器出现内部错误的枚举常量,状态码为 `"A00005"`,提示消息为 `"服务器出了点小差"`, + * 当服务器在处理请求过程中发生了一些未知的、内部的异常情况时,可以使用这个枚举常量来构建响应信息返回给客户端, + * 向客户端传达服务器出现问题的大致情况,同时避免暴露过多内部的错误细节。 */ EXCEPTION("A00005", "服务器出了点小差"), + /** - * 方法参数没有校验,内容由输入内容决定 + * 代表方法参数没有经过校验的枚举常量,状态码为 `"A00014"`,提示消息为 `"方法参数没有校验"`, + * 用于在业务逻辑中发现传入的方法参数不符合预期或者没有经过必要的校验时,通过使用这个枚举常量来反馈参数校验方面的问题, + * 告知客户端请求参数存在错误情况。 */ METHOD_ARGUMENT_NOT_VALID("A00014", "方法参数没有校验"); + // 用于存储每个枚举常量对应的状态码的私有属性,通过构造函数进行初始化,在项目中可以通过 `value()` 方法获取该状态码。 private final String code; + // 用于存储每个枚举常量对应的提示消息的私有属性,通过构造函数进行初始化,在项目中可以通过 `getMsg()` 方法获取该提示消息。 private final String msg; + /** + * 获取枚举常量对应的状态码的方法,返回存储在 `code` 属性中的状态码值,方便在构建响应信息等场景中获取状态码来设置响应的状态码字段。 + * + * @return 枚举常量对应的状态码字符串。 + */ public String value() { return code; } + /** + * 获取枚举常量对应的提示消息的方法,返回存储在 `msg` 属性中的提示消息内容,用于在构建响应信息时获取相应的提示消息来设置响应的消息字段, + * 或者在其他需要展示对应提示内容的场景中使用。 + * + * @return 枚举常量对应的提示消息字符串。 + */ public String getMsg() { return msg; } + /** + * 枚举类的构造函数,用于初始化每个枚举常量对应的状态码和提示消息,在定义枚举常量时通过传入相应的参数来设置其 `code` 和 `msg` 属性值, + * 确保每个枚举常量都有明确的状态码和提示消息与之对应,用于后续在项目中的统一使用。 + * + * @param code 枚举常量对应的状态码字符串,用于初始化 `code` 属性。 + * @param msg 枚举常量对应的提示消息字符串,用于初始化 `msg` 属性。 + */ ResponseEnum(String code, String msg) { this.code = code; this.msg = msg; } + /** + * 重写 `toString()` 方法,用于返回枚举常量更详细的字符串表示形式,除了包含默认的枚举相关信息外, + * 还添加了状态码和提示消息的内容,方便在调试或者日志记录等场景中更清晰地查看枚举常量所代表的具体信息。 + * + * @return 包含状态码、提示消息以及默认枚举相关信息的字符串表示形式。 + */ @Override public String toString() { return "ResponseEnum{" + "code='" + code + '\'' + ", msg='" + msg + '\'' + "} " + super.toString(); } - -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponse.java b/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponse.java index 79f0d66..2ec3e73 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponse.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponse.java @@ -16,21 +16,42 @@ import java.io.Serializable; import java.util.Objects; /** + * `ServerResponse`类是一个通用的服务器响应类,用于在服务端与客户端之间传递响应信息,它采用了泛型的设计,使得可以承载各种不同类型的数据作为响应内容返回给客户端, + * 并且通过`lombok`的`@Data`注解自动生成了常用的属性访问方法(如`getter`、`setter`方法)以及`toString`、`hashCode`和`equals`等方法,简化了代码编写,方便对对象属性的操作和使用。 + * 该类实现了`Serializable`接口,意味着它的实例可以被序列化和反序列化,便于在网络传输或者持久化存储等场景下使用,确保响应信息能够准确无误地在不同环境间传递。 + * * @author lanhai */ @Data public class ServerResponse implements Serializable { - + /** + * 响应状态码,用于表示请求处理的结果状态,不同的整数值对应不同的业务含义,例如常见的约定中,`200` 可能表示成功,`404` 表示资源未找到,`500` 表示服务器内部错误等, + * 在具体的项目中会根据业务需求定义一套自己的状态码规范,通过这个属性可以清晰地告知客户端请求的处理情况到底是成功还是出现了某种类型的错误,方便客户端根据状态码进行相应的处理。 + */ private int code; + /** + * 响应消息,是对响应状态的一个文字描述信息,用于更详细地向客户端解释请求处理的结果情况, + * 比如当状态码表示错误时,这里可以具体说明是何种错误,像“用户名或密码错误”“数据库连接失败”等具体的错误提示内容,使得客户端能够直观地了解出现问题的原因, + * 也可以在成功时给出一些友好的提示信息,增强用户体验。 + */ private String msg; + /** + * 响应数据对象,通过泛型 `` 来表示可以是任意类型的数据,根据具体的业务请求,这个属性可以承载不同类型的返回结果, + * 例如请求商品列表时,这里可以是商品信息的列表对象;请求用户详情时,这里可以是包含用户详细信息的对象等,实现了灵活地传递业务数据给客户端的功能。 + */ private T obj; - public boolean isSuccess(){ + /** + * 判断当前响应是否表示成功的方法,通过比较响应的状态码和预定义的成功状态码(这里应该是 `ResponseCode.SUCCESS`,不过从当前代码来看 `ResponseCode` 类未展示出来,推测它是定义了各种状态码常量的类)是否相等来确定。 + * 如果相等,意味着请求处理成功,返回 `true`;否则返回 `false`,表示请求出现了某种问题没有成功处理,客户端可以根据这个方法的返回值来决定后续如何展示界面或者进行其他操作, + * 比如成功时展示返回的数据,失败时弹出相应的错误提示信息等。 + * + * @return 返回一个布尔值,`true` 表示响应表示成功,`false` 表示响应表示出现了问题,请求未成功处理。 + */ + public boolean isSuccess() { return Objects.equals(ResponseCode.SUCCESS, this.code); } - - -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponseEntity.java b/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponseEntity.java index e3d1b35..a8ee1b8 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponseEntity.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponseEntity.java @@ -8,107 +8,172 @@ * 版权所有,侵权必究! */ +// 该类所属的包名,表明其位于商城项目的通用(common)响应(response)包下,主要用于封装服务器端向客户端返回的响应信息,统一响应格式,方便客户端解析和处理, +// 同时提供了多种静态方法来便捷地创建不同状态(成功、失败等)的响应实体对象,以满足不同业务场景下的需求。 package com.yami.shop.common.response; import lombok.extern.slf4j.Slf4j; - import java.io.Serializable; import java.util.Objects; /** + * ServerResponseEntity类是一个泛型类,用于构建服务器端向客户端返回的统一响应实体,它封装了响应的关键信息,如状态码、提示消息、具体数据内容、版本号以及时间戳等, + * 并且通过一系列静态方法可以方便地创建各种不同情况(成功、失败以及带特定数据的不同状态等)下的响应实体实例,同时还提供了判断响应是否成功或失败的方法,有助于在业务逻辑中对响应结果进行统一处理。 + * * @author lanhai */ @Slf4j +// 使用@Slf4j注解,会自动为该类生成一个名为log的日志记录器,方便在类的各个方法中记录日志信息,例如在创建失败响应时记录错误相关的日志等操作,便于后续排查问题和了解程序执行情况。 public class ServerResponseEntity implements Serializable { /** - * 状态码 + * 状态码,用于标识响应的状态,不同的业务场景或者不同的结果情况会对应不同的状态码,客户端可以根据状态码来判断请求是否成功以及具体的业务处理结果类型,例如常见的200表示成功等, + * 这里的状态码具体格式和含义由项目内部自行定义(可能参照了一些通用的HTTP状态码概念并结合自身业务做了扩展)。 */ private String code; /** - * 信息 + * 信息,用于向客户端传递一些提示性的消息内容,比如在成功时可以是操作成功的简要说明,在失败时则是具体的错误原因描述,方便客户端展示给用户或者进行相应的业务逻辑处理。 */ private String msg; /** - * 数据 + * 数据,是泛型类型的成员变量,用于承载具体的业务数据内容,根据不同的接口请求和业务逻辑,这个数据部分可以是各种类型的对象,例如查询商品信息接口返回的商品对象列表、用户信息接口返回的用户对象等。 */ private T data; /** - * 版本 + * 版本,用于标识当前响应实体对应的版本信息,可能在项目进行版本迭代、接口升级等情况下,通过这个版本号来区分不同版本的响应格式或者数据结构等变化,这里固定初始化为"mall4j.v230424",具体含义和使用场景由项目决定。 */ private String version; /** - * 时间 + * 时间戳,通常用于记录响应生成的时间,在一些需要考虑时效性、数据同步或者日志记录等业务场景中可能会用到这个时间信息,例如客户端可以根据时间戳来判断数据是否是最新的等情况。 */ private Long timestamp; + // 签名,可能用于对响应数据进行签名验证等安全相关操作,确保响应数据在传输过程中未被篡改,具体的签名生成和验证逻辑需要结合项目中其他相关部分来确定,此处仅作为响应实体的一个属性进行存储。 private String sign; + /** + * 获取签名的Getter方法,方便外部获取该响应实体中封装的签名信息,用于在需要验证响应数据完整性和安全性的业务场景中使用该签名值进行相应的验证操作。 + */ public String getSign() { return sign; } + /** + * 设置签名的Setter方法,用于外部给该响应实体的sign字段赋值,例如在生成响应时,根据一定的签名算法计算出签名值后,通过这个方法将签名设置到响应实体中,以保证响应数据的安全性。 + */ public void setSign(String sign) { this.sign = sign; } + /** + * 获取状态码的Getter方法,供其他类获取该响应实体中封装的状态码信息,客户端或者其他业务逻辑处理层可以通过这个方法获取状态码来判断响应的具体情况,决定后续的业务操作。 + */ public String getCode() { return code; } + /** + * 设置状态码的Setter方法,用于外部给该响应实体的code字段赋值,例如在创建不同状态的响应实体时(成功、失败等不同情况),通过这个方法将对应的状态码设置到对象中,以便准确反映响应的实际状态。 + */ public void setCode(String code) { this.code = code; } + /** + * 获取信息的Getter方法,方便获取该响应实体中封装的提示消息内容,客户端可以获取这个消息并展示给用户,或者根据消息进行一些错误提示、业务引导等相关操作。 + */ public String getMsg() { return msg; } + /** + * 设置信息的Setter方法,用于外部将提示消息赋值给该响应实体的msg字段,比如在构建失败响应时,将具体的错误消息通过这个方法设置到对象中,使得客户端能够知晓具体的错误原因。 + */ public void setMsg(String msg) { this.msg = msg; } + /** + * 获取数据的Getter方法,供其他类获取该响应实体中封装的具体业务数据内容,在不同的业务接口中,客户端可以通过这个方法获取到相应的查询结果、操作返回的数据等内容,进行后续的展示、处理等操作。 + */ public T getData() { return data; } + /** + * 设置数据并返回当前响应实体对象自身的方法,方便在链式调用时进行数据设置操作,例如可以连续调用setData方法后再进行其他属性的设置,使代码更加简洁流畅, + * 常用于构建响应实体并设置其数据部分的业务场景,返回自身对象可以继续进行其他属性的赋值等操作。 + */ public ServerResponseEntity setData(T data) { this.data = data; return this; } + /** + * 获取版本的Getter方法,用于获取该响应实体中封装的版本信息,在涉及到版本兼容性判断、接口升级等业务场景中,可能会通过这个方法获取版本号来进行相应的处理操作。 + */ public String getVersion() { return version; } + /** + * 设置版本的Setter方法,用于外部给该响应实体的version字段赋值,不过在当前代码中版本号初始化为固定值"mall4j.v230424",但在项目后续扩展或者特殊需求情况下,可能通过这个方法来动态修改版本号等操作。 + */ public void setVersion(String version) { this.version = version; } + /** + * 获取时间戳的Getter方法,方便获取该响应实体中封装的时间戳信息,在需要根据响应时间来进行业务逻辑判断(如判断数据时效性、统计响应耗时等)的场景中会用到这个时间戳值。 + */ public Long getTimestamp() { return timestamp; } + /** + * 设置时间戳的Setter方法,用于外部将时间戳赋值给该响应实体的timestamp字段,例如在响应生成时,获取当前的系统时间戳并通过这个方法设置到对象中,以记录准确的响应生成时间。 + */ public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } + /** + * 判断响应是否成功的方法,通过比较当前响应实体的状态码与预定义的表示成功的状态码(ResponseEnum.OK.value(),这里推测ResponseEnum是一个枚举类,OK是其中表示成功的枚举值)是否相等来确定, + * 如果相等则表示响应成功,返回true;否则返回false,方便在业务逻辑中快速判断响应结果是否符合预期,进而进行不同的后续处理操作。 + */ public boolean isSuccess() { return Objects.equals(ResponseEnum.OK.value(), this.code); } + + /** + * 判断响应是否失败的方法,与isSuccess方法相反,通过取反isSuccess方法的判断结果来确定响应是否失败,即如果状态码与表示成功的状态码不相等,则认为响应失败,返回true;否则返回false, + * 同样用于在业务逻辑中根据响应情况进行相应的错误处理、提示等操作。 + */ public boolean isFail() { - return !Objects.equals(ResponseEnum.OK.value(), this.code); + return!Objects.equals(ResponseEnum.OK.value(), this.code); } + /** + * 默认构造函数,在创建ServerResponseEntity对象时,如果没有显式传入参数,会调用这个构造函数进行初始化操作,在这里主要是将版本号初始化为"mall4j.v230424", + * 其他成员变量则保持默认的初始值(如null、0等,根据其类型而定),为后续根据具体业务需求设置其他属性值做好准备。 + */ public ServerResponseEntity() { // 版本号 this.version = "mall4j.v230424"; } + /** + * 创建表示成功且带有具体数据的响应实体的静态方法,首先创建一个ServerResponseEntity对象实例,然后通过链式调用setData方法将传入的数据设置到响应实体中, + * 再将状态码设置为表示成功的状态码(ResponseEnum.OK.value()),最后返回这个构建好的响应实体对象,方便在业务逻辑中当接口请求成功并需要返回具体数据时快速创建对应的响应实体。 + * + * @param data 具体的业务数据内容,根据不同的接口请求和业务场景,其类型可以是各种Java对象,例如商品列表、用户信息等,将被封装到响应实体的data属性中返回给客户端。 + * @param 泛型参数,表示响应实体中数据部分的类型,其具体类型由传入的实际参数决定,在调用该方法时,Java编译器会根据传入的数据类型自动推断出T的具体类型。 + * @return 返回构建好的表示成功且包含具体数据的ServerResponseEntity对象,客户端可以接收并解析这个响应实体,获取其中的数据和其他相关信息。 + */ public static ServerResponseEntity success(T data) { ServerResponseEntity serverResponseEntity = new ServerResponseEntity<>(); serverResponseEntity.setData(data); @@ -116,6 +181,14 @@ public class ServerResponseEntity implements Serializable { return serverResponseEntity; } + /** + * 创建表示成功但不带有具体数据(仅返回成功状态码和默认成功消息)的响应实体的静态方法,创建一个ServerResponseEntity对象实例后,将状态码设置为表示成功的状态码(ResponseEnum.OK.value()), + * 同时将提示消息设置为ResponseEnum.OK对应的消息(推测ResponseEnum.OK中除了有表示成功的状态码值,还有对应的默认成功消息内容),最后返回这个构建好的响应实体对象, + * 适用于那些只需要告知客户端请求成功,不需要返回具体业务数据的业务场景,例如删除操作成功等情况。 + * + * @param 泛型参数,同样用于表示响应实体中数据部分的类型,在这里由于不返回具体数据,所以主要是为了保持方法签名的一致性,方便统一使用这个泛型类来构建不同类型的响应实体。 + * @return 返回构建好的表示成功的ServerResponseEntity对象,客户端接收到这个响应实体后可以根据状态码知晓请求成功,可能进行一些相应的界面提示等操作。 + */ public static ServerResponseEntity success() { ServerResponseEntity serverResponseEntity = new ServerResponseEntity<>(); serverResponseEntity.setCode(ResponseEnum.OK.value()); @@ -123,10 +196,30 @@ public class ServerResponseEntity implements Serializable { return serverResponseEntity; } + /** + * 创建表示成功且带有具体数据的响应实体的重载静态方法,接受一个Integer类型的状态码参数,先将其转换为字符串类型(通过调用String.valueOf方法), + * 然后按照与success(String code, T data)方法类似的逻辑,创建ServerResponseEntity对象实例,设置传入的状态码和数据,最后返回构建好的响应实体对象, + * 提供了一种可以灵活传入不同状态码(以整数形式传入,方便与一些常见的状态码数字表示方式兼容)来创建成功响应实体的方式,满足特定业务场景下对状态码自定义的需求。 + * + * @param code 表示成功的状态码,以整数形式传入,会被转换为字符串类型后设置到响应实体的状态码属性中,具体取值和含义由项目内部根据业务需求自行定义。 + * @param data 具体的业务数据内容,将被封装到响应实体的data属性中返回给客户端,其类型根据实际业务场景而定。 + * @param 泛型参数,表示响应实体中数据部分的类型,由传入的数据实际类型决定,用于统一处理不同类型数据的响应实体构建。 + * @return 返回构建好的表示成功且包含指定状态码和具体数据的ServerResponseEntity对象,方便在特定业务场景下按照自定义的状态码返回成功响应给客户端。 + */ public static ServerResponseEntity success(Integer code, T data) { return success(String.valueOf(code), data); } + /** + * 创建表示成功且带有具体数据的响应实体的重载静态方法,直接接受一个字符串类型的状态码和具体业务数据参数,创建ServerResponseEntity对象实例后, + * 将传入的状态码和数据分别设置到响应实体的相应属性中,最后返回构建好的响应实体对象,提供了一种更加灵活的方式来创建带有自定义状态码和具体数据的成功响应实体, + * 以满足不同业务场景下对响应状态码和返回数据的多样化需求。 + * + * @param code 表示成功的状态码,以字符串形式传入,将直接设置到响应实体的状态码属性中,其具体取值和含义由项目内部根据业务需求自行定义。 + * @param data 具体的业务数据内容,将被封装到响应实体的data属性中返回给客户端,其类型根据实际业务场景而定。 + * @param 泛型参数,表示响应实体中数据部分的类型,由传入的数据实际类型决定,用于统一处理不同类型数据的响应实体构建。 + * @return 返回构建好的表示成功且包含指定状态码和具体数据的ServerResponseEntity对象,方便在各种业务场景下按照自定义要求返回成功响应给客户端。 + */ public static ServerResponseEntity success(String code, T data) { ServerResponseEntity serverResponseEntity = new ServerResponseEntity<>(); serverResponseEntity.setCode(code); @@ -135,9 +228,13 @@ public class ServerResponseEntity implements Serializable { } /** - * 前端显示失败消息 - * @param msg 失败消息 - * @return + * 创建表示前端显示失败消息的响应实体的静态方法,首先会记录传入的失败消息到日志中(通过log.error方法,方便后续排查问题),然后创建一个ServerResponseEntity对象实例, + * 将传入的失败消息设置到响应实体的msg属性中,同时将状态码设置为表示前端显示失败的特定状态码(ResponseEnum.SHOW_FAIL.value(),推测是专门用于前端展示失败消息的一种状态码定义), + * 最后返回这个构建好的响应实体对象,用于在业务逻辑中当需要向客户端明确展示某个失败消息时创建对应的响应实体,例如业务规则校验不通过等情况需要告知客户端具体的错误提示内容。 + * + * @param msg 失败消息,是要展示给客户端的具体错误提示内容,会被设置到响应实体的msg属性中,以便客户端获取并展示给用户,让用户知晓操作失败的原因。 + * @param 泛型参数,表示响应实体中数据部分的类型,在这里通常可以为null,因为主要是为了传递失败消息,不过也可以根据具体业务场景传入一些相关的数据(如错误详情对象等),由业务需求决定。 + * @return 返回构建好的表示前端显示失败消息的ServerResponseEntity对象,客户端接收到这个响应实体后会展示其中的失败消息给用户,提示操作失败。 */ public static ServerResponseEntity showFailMsg(String msg) { log.error(msg); @@ -147,52 +244,9 @@ public class ServerResponseEntity implements Serializable { return serverResponseEntity; } - public static ServerResponseEntity fail(ResponseEnum responseEnum) { - log.error(responseEnum.toString()); - ServerResponseEntity serverResponseEntity = new ServerResponseEntity<>(); - serverResponseEntity.setMsg(responseEnum.getMsg()); - serverResponseEntity.setCode(responseEnum.value()); - return serverResponseEntity; - } - - public static ServerResponseEntity fail(ResponseEnum responseEnum, T data) { - log.error(responseEnum.toString()); - ServerResponseEntity serverResponseEntity = new ServerResponseEntity<>(); - serverResponseEntity.setMsg(responseEnum.getMsg()); - serverResponseEntity.setCode(responseEnum.value()); - serverResponseEntity.setData(data); - return serverResponseEntity; - } - - public static ServerResponseEntity fail(String code, String msg, T data) { - log.error(msg); - ServerResponseEntity serverResponseEntity = new ServerResponseEntity<>(); - serverResponseEntity.setMsg(msg); - serverResponseEntity.setCode(code); - serverResponseEntity.setData(data); - return serverResponseEntity; - } - - public static ServerResponseEntity fail(String code, String msg) { - return fail(code, msg, null); - } - - public static ServerResponseEntity fail(Integer code, T data) { - ServerResponseEntity serverResponseEntity = new ServerResponseEntity<>(); - serverResponseEntity.setCode(String.valueOf(code)); - serverResponseEntity.setData(data); - return serverResponseEntity; - } - - @Override - public String toString() { - return "ServerResponseEntity{" + - "code='" + code + '\'' + - ", msg='" + msg + '\'' + - ", data=" + data + - ", version='" + version + '\'' + - ", timestamp=" + timestamp + - ", sign='" + sign + '\'' + - '}'; - } -} + /** + * 创建表示失败的响应实体的静态方法,根据传入的ResponseEnum枚举类型参数(推测其中定义了不同的失败情况对应的状态码、消息等信息),首先记录这个枚举对应的信息到日志中(方便排查问题), + * 然后创建一个ServerResponseEntity对象实例,将枚举中的失败消息设置到响应实体的msg属性中,将枚举对应的状态码设置到响应实体的code属性中,最后返回这个构建好的响应实体对象, + * 用于在业务逻辑中根据不同的业务失败场景(通过不同的ResponseEnum枚举值来区分)创建对应的失败响应实体,统一处理失败情况的响应返回。 + * + * @param \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/json/ImgJsonSerializer.java b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/json/ImgJsonSerializer.java index 7e82453..61bdb8c 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/json/ImgJsonSerializer.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/json/ImgJsonSerializer.java @@ -25,42 +25,76 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** + * 图片JSON序列化器类 + * 该类实现了`JsonSerializer`接口,用于自定义对字符串类型的图片相关数据进行JSON序列化的逻辑。 + * 主要功能是根据图片的上传类型以及是否已经是完整的URL等情况,对传入的图片字符串(可能是多个图片路径用逗号分隔的形式)进行处理, + * 将其转换为合适的格式后再进行JSON序列化,确保在序列化后的JSON数据中图片路径是符合要求且可正确访问的形式。 + * * @author lanhai */ @Component +// 使用 @Component 注解将该类注册为Spring容器中的一个组件,方便进行依赖注入等操作 public class ImgJsonSerializer extends JsonSerializer { @Autowired + // 通过依赖注入获取Qiniu对象,该对象可能包含与七牛云相关的配置信息,比如七牛云资源的URL等,用于处理图片相关逻辑 private Qiniu qiniu; @Autowired + // 通过依赖注入获取ImgUploadUtil对象,该工具类可能用于获取图片上传的相关路径、类型等信息,辅助图片处理逻辑 private ImgUploadUtil imgUploadUtil; + /** + * 重写的序列化方法 + * 该方法实现了对传入的字符串类型的图片数据进行具体的序列化处理逻辑。 + * 首先判断传入的字符串是否为空,如果为空则直接写入空字符串到JSON生成器中;然后根据图片上传类型确定资源URL, + * 接着对字符串中可能包含的多个图片路径(以逗号分隔)进行遍历处理,判断是否已经是完整的URL,如果是则直接添加,否则添加对应的资源URL前缀后再添加, + * 最后将处理后的字符串写入JSON生成器中完成序列化。 + * + * @param value 要进行序列化的字符串数据,通常是表示图片路径的字符串,可能是单个路径也可能是多个路径用逗号分隔的形式。 + * @param gen JSON生成器对象,用于将处理后的字符串数据按照JSON格式规则写入到最终的JSON输出中,实现序列化操作。 + * @param serializers 序列化器提供者对象,用于获取序列化相关的配置等信息,在这里主要是配合整个序列化流程的上下文环境。 + * @throws IOException 如果在向JSON生成器写入数据过程中出现I/O异常(比如写入文件错误等情况),则会抛出该异常,向上传播到调用者进行处理。 + */ @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + // 判断传入的字符串是否为空(空白字符串,包含空字符串、全是空格等情况),如果为空则直接写入空字符串到JSON生成器中,并结束当前方法执行 if (StrUtil.isBlank(value)) { gen.writeString(StrUtil.EMPTY); return; } + + // 将传入的字符串按照逗号进行分割,得到一个包含各个图片路径的字符串数组,假设传入的字符串格式是多个图片路径用逗号分隔的形式 String[] imgs = value.split(StrUtil.COMMA); StringBuilder sb = new StringBuilder(); String resourceUrl = ""; - String rule="^((http[s]{0,1})://)"; - Pattern pattern= Pattern.compile(rule); + String rule = "^((http[s]{0,1})://)"; + Pattern pattern = Pattern.compile(rule); + + // 根据图片上传工具类中获取的上传类型来确定资源URL,如果上传类型是2,可能表示使用七牛云存储,从Qiniu对象中获取资源URL if (Objects.equals(imgUploadUtil.getUploadType(), 2)) { resourceUrl = qiniu.getResourcesUrl(); - } else if (Objects.equals(imgUploadUtil.getUploadType(), 1)) { + } + // 如果上传类型是1,可能表示使用其他本地或指定的存储方式,从ImgUploadUtil对象中获取对应的资源URL + else if (Objects.equals(imgUploadUtil.getUploadType(), 1)) { resourceUrl = imgUploadUtil.getResourceUrl(); } + + // 遍历分割后的图片路径字符串数组,对每个图片路径进行处理 for (String img : imgs) { Matcher matcher = pattern.matcher(img); - //若图片以http或https开头,直接返回 - if (matcher.find()){ + // 使用正则表达式匹配器判断图片路径是否已经是以http或https开头(即是否已经是完整的URL形式),如果是则直接添加到结果字符串构建器中,并添加逗号作为分隔(后续会删除最后一个多余的逗号) + if (matcher.find()) { sb.append(img).append(StrUtil.COMMA); - }else { + } else { + // 如果图片路径不是完整的URL形式,则添加之前确定好的资源URL前缀,再添加图片路径本身,然后添加逗号作为分隔,构建完整的可访问的图片路径形式 sb.append(resourceUrl).append(img).append(StrUtil.COMMA); } } - sb.deleteCharAt(sb.length()-1); + + // 删除最后一个多余的逗号(因为在循环中每次添加路径后都添加了逗号,最后一个逗号是多余的) + sb.deleteCharAt(sb.length() - 1); + + // 将处理好的完整图片路径字符串写入到JSON生成器中,完成对图片相关字符串的JSON序列化操作 gen.writeString(sb.toString()); } -} +} \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/xss/XssUtil.java b/yami-shop-common/src/main/java/com/yami/shop/common/xss/XssUtil.java index 4cb5f25..275c458 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/xss/XssUtil.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/xss/XssUtil.java @@ -13,23 +13,52 @@ package com.yami.shop.common.xss; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.safety.Safelist; + /** - * 描述: 过滤 HTML 标签中 XSS 代码 + * `XssUtil` 是一个工具类,主要用于过滤 HTML 标签中可能存在的 XSS(跨站脚本攻击)代码,通过使用 `Jsoup` 库提供的相关功能, + * 基于预定义的白名单(`Safelist`)对输入的内容进行清理,确保在处理包含 HTML 内容的文本时,能够去除潜在的恶意脚本代码, + * 保障系统的安全性,防止恶意用户通过注入脚本等方式进行攻击,常用于对用户输入的富文本内容或者其他可能包含 HTML 的文本进行安全过滤处理。 + * * @author lgh */ public class XssUtil { + /** - * 使用自带的 basicWithImages 白名单 + * 定义一个 `Safelist` 类型的静态常量 `WHITE_LIST`,它使用了 `Jsoup` 库自带的 `relaxed` 白名单(`basicWithImages` 白名单), + * 这个白名单定义了允许存在的 HTML 标签和属性集合,在进行 XSS 代码过滤时,只有白名单中的标签和属性会被保留, + * 其他未在白名单内的 HTML 元素和属性将会被移除,以此来防止恶意的脚本代码混入合法的 HTML 内容中。 */ private static final Safelist WHITE_LIST = Safelist.relaxed(); - /** 配置过滤化参数, 不对代码进行格式化 */ + + /** + * 定义一个 `Document.OutputSettings` 类型的静态常量 `OUTPUT_SETTINGS`,用于配置 HTML 内容的输出设置, + * 这里将 `prettyPrint` 属性设置为 `false`,表示不对过滤后的 HTML 代码进行格式化操作, + * 即输出的 HTML 内容会保持原始的紧凑格式,而不会进行额外的美化排版处理,主要关注代码的安全性过滤而不是展示格式。 + */ private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false); + + /** + * 静态代码块,用于在类加载时进行一些初始化配置操作。在这个类中,由于富文本编辑时可能会通过 `style` 属性来实现一些样式设置, + * 比如设置红色字体(`style="color:red;"`)等情况,所以需要将 `style` 属性添加到所有允许的 HTML 标签(`:all` 表示所有标签)中, + * 确保在过滤 XSS 代码的同时,不会误删这些用于样式设置的合法 `style` 属性,使得富文本内容在经过安全过滤后依然能保留必要的样式信息。 + */ static { // 富文本编辑时一些样式是使用 style 来进行实现的 // 比如红色字体 style="color:red;" // 所以需要给所有标签添加 style 属性 - WHITE_LIST.addAttributes(":all", "style"); + WHITE_LIST.addAttributes(":all", "style"); } + + /** + * 核心的静态方法,用于对输入的字符串内容进行 XSS 代码过滤清理操作,它调用了 `Jsoup` 库的 `clean` 方法, + * 传入要清理的内容字符串、空字符串(此处可能是用于指定文档的基本 URL,这里不需要所以传入空字符串)、 + * 预定义的白名单(`WHITE_LIST`)以及配置好的输出设置(`OUTPUT_SETTINGS`)作为参数, + * 经过 `Jsoup` 内部的处理逻辑,会按照白名单规则对输入内容中的 HTML 标签和属性进行筛选过滤,去除不符合白名单要求的元素, + * 最终返回过滤后的安全的字符串内容,确保该内容不存在 XSS 安全风险。 + * + * @param content 需要进行 XSS 代码过滤的输入字符串内容,通常是包含 HTML 标签的文本,比如用户输入的富文本信息等。 + * @return 经过 XSS 代码过滤后的安全字符串内容,已去除了潜在的恶意脚本代码以及不符合白名单规则的 HTML 元素和属性。 + */ public static String clean(String content) { return Jsoup.clean(content, "", WHITE_LIST, OUTPUT_SETTINGS); } diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/xss/XssWrapper.java b/yami-shop-common/src/main/java/com/yami/shop/common/xss/XssWrapper.java index bb4239e..67c9f6d 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/xss/XssWrapper.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/xss/XssWrapper.java @@ -16,22 +16,35 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; /** - * xss 攻击过滤 + * `XssWrapper`类继承自`HttpServletRequestWrapper`,它的主要作用是对`HttpServletRequest`中的各类可能包含用户输入数据的部分进行跨站脚本攻击(XSS,Cross-Site Scripting)过滤, + * 通过重写一些关键方法,在获取请求参数、请求属性以及请求头信息等数据时,对其中可能存在的恶意脚本代码(如 `