From 5a8ee4a9022916904b63e2efa37e559b98302ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E8=90=B1?= <2593634984@qq.com> Date: Tue, 17 Dec 2024 19:56:45 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SnailmallCategoryServiceApplication.java | 20 +- .../njupt/swg/common/constants/Constants.java | 26 +- .../exception/ExceptionHandlerAdvice.java | 41 +- .../common/exception/SnailmallException.java | 32 +- .../njupt/swg/common/resp/ResponseEnum.java | 41 +- .../njupt/swg/common/resp/ServerResponse.java | 93 +++- .../swg/controller/CategoryController.java | 47 +- .../com/njupt/swg/dao/CategoryMapper.java | 50 +- .../java/com/njupt/swg/entity/Category.java | 29 +- .../main/java/com/njupt/swg/entity/User.java | 37 +- .../swg/service/CategoryServiceImpl.java | 127 +++-- .../njupt/swg/service/ICategoryService.java | 55 +- .../swg/SnailmallConfigServerApplication.java | 28 +- .../SnailmallProductServiceApplication.java | 39 +- .../com/njupt/swg/cache/CommonCacheUtil.java | 61 ++- .../com/njupt/swg/cache/JedisPoolWrapper.java | 44 +- .../java/com/njupt/swg/cache/Parameters.java | 32 +- .../com/njupt/swg/clients/CategoryClient.java | 28 +- .../njupt/swg/common/constants/Constants.java | 61 ++- .../exception/ExceptionHandlerAdvice.java | 51 +- .../common/exception/SnailmallException.java | 38 +- .../njupt/swg/common/resp/ResponseEnum.java | 38 +- .../njupt/swg/common/resp/ServerResponse.java | 110 +++- .../njupt/swg/common/utils/CookieUtil.java | 122 +++-- .../njupt/swg/common/utils/DateTimeUtil.java | 128 ++++- .../com/njupt/swg/common/utils/FtpUtil.java | 145 +++++- .../com/njupt/swg/common/utils/JsonUtil.java | 161 ++++-- .../com/njupt/swg/common/utils/MD5Util.java | 86 ++- .../swg/common/utils/PropertiesUtil.java | 71 ++- .../swg/controller/ProductController.java | 114 +++- .../controller/ProductManageController.java | 186 ++++--- .../java/com/njupt/swg/dao/ProductMapper.java | 174 ++++++- .../java/com/njupt/swg/entity/Category.java | 25 +- .../java/com/njupt/swg/entity/Product.java | 40 +- .../main/java/com/njupt/swg/entity/User.java | 41 +- .../njupt/swg/service/FileServiceImpl.java | 77 ++- .../com/njupt/swg/service/IFileService.java | 21 +- .../njupt/swg/service/IProductService.java | 5 + .../njupt/swg/service/ProductServiceImpl.java | 489 +++++++++++++++--- 39 files changed, 2527 insertions(+), 486 deletions(-) diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/SnailmallCategoryServiceApplication.java b/snailmall-category-service/src/main/java/com/njupt/swg/SnailmallCategoryServiceApplication.java index 0fe5c53..1032afc 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/SnailmallCategoryServiceApplication.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/SnailmallCategoryServiceApplication.java @@ -4,13 +4,27 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +/** + * SnailmallCategoryServiceApplication类是Spring Boot应用的启动类,它作为整个应用程序的入口点, + * 负责启动Spring Boot应用,并进行一系列的配置和初始化工作,使得应用能够正常运行并对外提供相应的服务。 + */ @SpringBootApplication +// @SpringBootApplication是一个组合注解,它整合了多个Spring相关的注解,包括@Configuration(表示这是一个配置类)、 +// @EnableAutoConfiguration(开启自动配置,根据项目依赖自动配置各种Spring组件和功能)以及@ComponentScan(扫描指定包及其子包下的所有Spring组件,如@Component、@Service、@Controller等注解标记的类), +// 方便快捷地构建一个Spring Boot应用,减少了手动配置的工作量。 + @EnableDiscoveryClient +// @EnableDiscoveryClient注解用于启用服务发现客户端功能,在微服务架构中,当应用需要注册到服务注册中心(如Eureka、Consul等)并能够被其他服务发现和调用时, +// 使用该注解来开启相关的功能,使得本应用可以将自身的服务信息注册上去,并且可以发现其他已注册的服务,方便服务之间的相互调用和协作。 + public class SnailmallCategoryServiceApplication { + /** + * main方法是Java应用程序的入口方法,在这里它通过调用SpringApplication.run方法来启动Spring Boot应用。 + * 它接收当前启动类的Class对象(SnailmallCategoryServiceApplication.class)以及命令行参数(args)作为参数, + * Spring Boot会基于这些信息进行初始化、配置加载、组件扫描以及启动嵌入式的Web服务器(如果有相关依赖)等一系列操作,最终使整个应用运行起来,开始对外提供服务。 + */ public static void main(String[] args) { SpringApplication.run(SnailmallCategoryServiceApplication.class, args); } - -} - +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/constants/Constants.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/constants/Constants.java index 91ab476..01da663 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/constants/Constants.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/constants/Constants.java @@ -1,22 +1,42 @@ package com.njupt.swg.common.constants; /** + * 该类Constants用于定义项目中的一些常用常量,主要是自定义的状态码相关常量。 + * 这些常量在整个项目中可用于表示不同的响应状态情况,方便统一处理和判断响应的状态含义。 * @Author swg. * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com * @DESC */ public class Constants { - /**自定义状态码 start**/ + /** + * 自定义状态码 start + * 以下几个常量分别代表不同的HTTP状态码类似的自定义业务状态码,用于在系统中表示相应的响应情况。 + */ + + /** + * 表示请求成功的状态码,对应HTTP状态码中的200,表示操作执行成功,客户端请求已被服务器正常处理并返回预期结果。 + */ public static final int RESP_STATUS_OK = 200; + /** + * 表示未授权的状态码,类似HTTP状态码中的401,意味着客户端发起的请求需要用户进行身份认证,但用户未提供有效的认证凭据或者认证失败。 + */ public static final int RESP_STATUS_NOAUTH = 401; + /** + * 表示服务器内部错误的状态码,等同于HTTP状态码中的500,说明服务器在处理请求时发生了内部错误,无法正常完成请求的处理。 + */ public static final int RESP_STATUS_INTERNAL_ERROR = 500; + /** + * 表示请求参数错误的状态码,类似HTTP状态码中的400,表明客户端发送的请求存在格式错误、参数缺失或者不符合要求等问题,导致服务器无法正确解析和处理该请求。 + */ public static final int RESP_STATUS_BADREQUEST = 400; - /**自定义状态码 end**/ + /** + * 自定义状态码 end + */ -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java index cef87ac..7100af0 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java @@ -1,6 +1,5 @@ package com.njupt.swg.common.exception; - import com.njupt.swg.common.constants.Constants; import com.njupt.swg.common.resp.ServerResponse; import lombok.extern.slf4j.Slf4j; @@ -9,25 +8,49 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** + * 该类ExceptionHandlerAdvice是一个全局异常处理类,用于统一处理项目中出现的各种异常情况。 + * 它利用Spring框架提供的相关注解来实现异常的拦截和处理,并返回合适的响应给客户端。 + * * @Author swg. * @Date 2019/1/1 13:21 * @CONTACT 317758022@qq.com * @DESC 全局异常处理 */ @ControllerAdvice +// 表明这个类可以包含多个 @ExceptionHandler 注解定义的方法,用于处理不同类型的异常,是一种全局的异常处理机制。 @ResponseBody +// 表示该类中的方法返回的数据直接作为响应体写入HTTP响应中(通常返回JSON等格式的数据),而不是跳转到视图页面。 @Slf4j +// Lombok注解,用于自动生成一个名为log的SLF4J日志记录器,方便在类中记录日志信息。 public class ExceptionHandlerAdvice { + + /** + * 异常处理方法,用于处理所有未被特定异常处理器捕获的Exception类型的异常。 + * 当项目中出现任何没有被更具体的异常处理器处理的异常时,都会进入到这个方法中进行处理。 + * + * @param e 捕获到的Exception类型的异常对象,包含了异常相关的详细信息,比如异常消息、堆栈跟踪等。 + * @return ServerResponse 返回一个ServerResponse对象,该对象封装了错误状态码和错误提示信息, + * 这里使用了自定义的Constants.RESP_STATUS_INTERNAL_ERROR作为状态码,表示服务器内部出现错误, + * 并提示客户端“系统异常,请稍后再试”,告知用户出现了问题,需要稍后再次尝试操作。 + */ @ExceptionHandler(Exception.class) - public ServerResponse handleException(Exception e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); + public ServerResponse handleException(Exception e) { + log.error(e.getMessage(), e); + // 记录异常信息到日志中,方便后续排查问题,error方法会记录错误级别(较严重)的日志信息,第一个参数是日志消息模板,第二个参数是异常对象本身,用于输出详细的堆栈跟踪等信息。 + return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试"); } + /** + * 异常处理方法,专门用于处理SnailmallException类型的异常。 + * 当项目中抛出SnailmallException异常时,会进入到这个方法进行针对性处理。 + * + * @param e 捕获到的SnailmallException类型的异常对象,此类异常可能包含了项目自定义的一些异常相关信息,比如特定的异常状态码等。 + * @return ServerResponse 返回一个ServerResponse对象,该对象会根据SnailmallException中携带的异常状态码(通过e.getExceptionStatus()获取) + * 和异常消息(通过e.getMessage()获取)来封装响应信息,将对应的错误状态码和具体的错误提示信息返回给客户端。 + */ @ExceptionHandler(SnailmallException.class) - public ServerResponse handleException(SnailmallException e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); + public ServerResponse handleException(SnailmallException e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage()); } - -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java index 363f19d..bb8da08 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java @@ -4,22 +4,44 @@ import com.njupt.swg.common.resp.ResponseEnum; import lombok.Getter; /** + * SnailmallException类是一个自定义的运行时异常类,继承自Java标准库中的RuntimeException。 + * 它用于在项目特定的业务逻辑中抛出具有明确含义的异常情况,方便统一处理和向客户端传达错误信息。 + * * @Author swg. * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC */ @Getter -public class SnailmallException extends RuntimeException{ +// Lombok的注解,用于自动生成对应的getter方法,在这里就是为exceptionStatus字段生成get方法,方便获取该字段的值。 +public class SnailmallException extends RuntimeException { + /** + * 用于存储异常对应的状态码,默认初始化为ResponseEnum.ERROR.getCode()的值, + * 即如果在创建异常对象时没有指定具体的状态码,将会使用这个默认值来表示异常状态。 + */ private int exceptionStatus = ResponseEnum.ERROR.getCode(); - public SnailmallException(String msg){ + /** + * 构造方法,用于创建一个SnailmallException异常对象,接收一个字符串类型的错误消息参数。 + * 此构造方法调用父类(RuntimeException)的构造方法,将传入的错误消息传递给父类, + * 同时会使用默认的异常状态码(即前面定义的ResponseEnum.ERROR.getCode())来表示该异常的状态。 + * + * @param msg 异常的详细描述信息,将展示给开发人员或者记录在日志中,以便排查问题,告知出现异常的具体原因。 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 重载的构造方法,用于创建一个SnailmallException异常对象,接收一个整数类型的状态码和一个字符串类型的错误消息参数。 + * 该构造方法同样调用父类(RuntimeException)的构造方法传递错误消息,并且会将传入的状态码赋值给exceptionStatus字段, + * 这样就可以根据不同的业务场景自定义异常状态码,更精准地表示具体的异常情况。 + * + * @param code 异常对应的状态码,用于在异常处理流程中区分不同类型或者原因导致的异常,便于进行针对性的处理和响应。 + * @param msg 异常的详细描述信息,作用同上面的单参数构造方法中的msg参数,用于描述异常出现的具体原因等内容。 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java index e4e59c7..88c03a0 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java @@ -3,23 +3,52 @@ package com.njupt.swg.common.resp; import lombok.Getter; /** + * ResponseEnum 是一个枚举类,用于定义项目中基本的返回状态描述。 + * 它将不同的业务返回状态抽象成一个个枚举常量,每个常量都对应着一个特定的状态码和描述信息, + * 方便在整个项目中统一使用这些标准的返回状态来表示操作的结果情况,增强代码的可读性和可维护性。 + * * @Author swg. * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com * @DESC 基本的返回状态描述 */ @Getter +// Lombok的注解,用于自动为枚举类中的成员变量(这里的code和desc)生成对应的getter方法,方便获取这些变量的值。 public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + /** + * 表示操作成功的枚举常量。对应的状态码为0,描述信息为"SUCCESS", + * 在业务处理中,当某个操作顺利完成且符合预期时,可以使用这个枚举常量来表示成功的返回状态。 + */ + SUCCESS(0, "SUCCESS"), + /** + * 表示操作出现错误的枚举常量。状态码为1,描述信息为"ERROR", + * 当业务逻辑执行过程中出现一般性错误,且没有更具体的错误分类时,可以用该常量来表示出现错误的返回状态。 + */ + ERROR(1, "ERROR"), + /** + * 表示传入参数非法的枚举常量。状态码为2,描述信息为"ILLEGAL_ARGUMENTS", + * 当客户端传入的请求参数不符合业务要求,例如格式错误、取值范围不对等情况时,可使用此常量来表示因参数问题导致的错误返回状态。 + */ + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + /** + * 表示需要用户登录的枚举常量。状态码为10,描述信息为"NEED_LOGIN", + * 当某个操作需要用户先进行登录认证才能继续执行,而用户当前未登录时,可通过该常量来表示这种需要登录的返回状态,提示用户进行登录操作。 + */ + NEED_LOGIN(10, "NEED_LOGIN"); private int code; private String desc; - ResponseEnum(int code,String desc){ + /** + * 枚举类的构造方法,用于初始化每个枚举常量对应的状态码和描述信息。 + * 在定义每个枚举常量(如SUCCESS、ERROR等)时,会调用这个构造方法来传入相应的状态码和描述内容, + * 从而为每个枚举常量赋予特定的含义和属性值。 + * + * @param code 表示该枚举常量对应的状态码,用于在代码中区分不同的返回状态情况,通常与前端或者其他调用方约定好具体含义。 + * @param desc 表示该枚举常量对应的描述信息,是一个更直观的、便于人理解的文本内容,用于说明该返回状态代表的具体意思。 + */ + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java index 53f3a65..2649234 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java @@ -4,75 +4,118 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Getter; import lombok.NoArgsConstructor; - import java.io.Serializable; /** + * ServerResponse类作为本项目的通用的返回封装类,主要用于将服务端返回给客户端的各种响应结果进行统一的格式化处理。 + * 它封装了响应的状态码、对应的提示消息以及具体的数据内容(如果有),使得前后端交互时返回的数据格式更加规范、易于理解和处理。 + * * @Author swg. * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com * @DESC 作为本项目的通用的返回封装类 */ @Getter +// 通过Lombok的@Getter注解,自动为类中的私有成员变量(status、msg、data)生成对应的getter方法,方便外部代码获取这些变量的值,遵循了Java的封装原则。 @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// 此注解用于配置Jackson序列化框架的行为,这里指定只序列化那些非空的属性。这样在将ServerResponse对象转换为JSON等格式进行传输时, +// 可以避免出现包含大量null值的冗余属性,减少数据传输量并使返回的数据结构更加清晰简洁。 public class ServerResponse implements Serializable { + // 声明对象可序列化,使得该类的实例能够在网络传输(例如通过HTTP协议返回给客户端)或者持久化存储(如保存到文件等场景)中被正确处理, + // 保证数据的完整性和可恢复性。 + private int status; + // 用于存储响应的状态码,通过不同的状态码来表示请求处理的结果情况,例如成功、失败、需要登录等,通常会和项目中定义的状态码枚举等进行对应。 + private String msg; + // 存放与响应状态相关的提示消息,用于向客户端传达更详细的、易于理解的信息,比如操作成功的具体描述或者失败的原因等。 + private T data; + // 泛型成员变量,用于承载具体的业务数据,根据不同的业务请求,这个字段可以是各种类型的数据,例如查询用户信息时可以是用户对象, + // 查询商品列表时可以是商品列表对象等,体现了该返回封装类的通用性。 - public ServerResponse(){} + public ServerResponse() { + // 默认的无参构造方法,方便在一些情况下创建一个空的ServerResponse对象,后续可以再通过setter方法(如果有)或者其他方式来填充具体的值。 + } - public ServerResponse(int status){ + public ServerResponse(int status) { + // 构造方法,用于创建一个只指定状态码的ServerResponse对象,适用于某些只需传达响应状态,不需要额外提示消息和业务数据的场景。 this.status = status; } - public ServerResponse(int status,String msg){ + + public ServerResponse(int status, String msg) { + // 构造方法,用于创建带有状态码和提示消息的ServerResponse对象,常用于需要向客户端反馈处理结果及对应简单说明的情况。 this.status = status; this.msg = msg; } - public ServerResponse(int status,T data){ + + public ServerResponse(int status, T data) { + // 构造方法,创建包含状态码和业务数据的ServerResponse对象,当业务处理成功且有具体数据需要返回给客户端时可以使用,此时提示消息可能使用默认值。 this.status = status; this.data = data; } - public ServerResponse(int status,String msg,T data){ + + public ServerResponse(int status, String msg, T data) { + // 最完整的构造方法,创建同时包含状态码、提示消息以及业务数据的ServerResponse对象,适用于各种需要详细反馈响应情况的业务场景。 this.status = status; this.msg = msg; this.data = data; } @JsonIgnore - public boolean isSuccess(){ + // 使用@JsonIgnore注解标记该方法,告诉Jackson序列化框架在将对象转换为JSON等格式时忽略这个方法,即不会把该方法作为一个属性进行序列化。 + public boolean isSuccess() { + // 用于判断当前响应是否表示成功的逻辑方法,通过比较存储的状态码和ResponseEnum中定义的表示成功的状态码(通常是约定好的一个特定值)来确定。 return this.status == ResponseEnum.SUCCESS.getCode(); } /** * 成功的方法 + * 以下是一组静态方法,用于方便快捷地创建表示成功状态的ServerResponse对象,不同的重载方法适用于不同的业务场景需求。 */ - public static ServerResponse createBySuccess(){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); + public static ServerResponse createBySuccess() { + // 创建一个表示成功状态的ServerResponse对象,使用ResponseEnum中定义的默认成功状态码和对应的默认成功描述信息进行初始化, + // 适用于不需要额外自定义提示消息和返回数据的简单成功场景,比如简单的操作成功反馈。 + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc()); } - public static ServerResponse createBySuccessMessage(String message){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); + + public static ServerResponse createBySuccessMessage(String message) { + // 创建一个表示成功状态的ServerResponse对象,但可以自定义提示消息,使用ResponseEnum中定义的成功状态码,同时传入自定义的消息内容, + // 方便在成功情况下向客户端传达更符合具体业务情况的提示内容,比如操作成功后的一些特定说明等。 + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); + + public static ServerResponse createBySuccess(T data) { + // 创建表示成功状态且带有具体业务数据的ServerResponse对象,使用成功状态码和传入的业务数据进行初始化, + // 适用于业务处理成功并且有相关数据需要返回给客户端的场景,比如查询操作成功后返回查询到的数据。 + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data); } - public static ServerResponse createBySuccess(String message,T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); + + public static ServerResponse createBySuccess(String message, T data) { + // 创建同时带有自定义提示消息和具体业务数据的成功状态的ServerResponse对象,结合了上述两个方法的功能, + // 更全面地满足各种需要详细反馈成功信息和相关数据的业务场景需求。 + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } /** * 失败的方法 + * 以下同样是一组静态方法,用于创建表示各种失败状态的ServerResponse对象,方便在不同的错误场景下统一返回规范的错误响应。 */ - public static ServerResponse createByError(){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); - } - public static ServerResponse createByErrorMessage(String msg){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); + public static ServerResponse createByError() { + // 创建一个表示一般错误状态的ServerResponse对象,使用ResponseEnum中定义的默认错误状态码和对应的默认错误描述信息进行初始化, + // 适用于没有更具体错误分类,只需简单反馈操作出现错误的情况。 + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); - } - + public static ServerResponse createByErrorMessage(String msg) { + // 创建一个表示错误状态且可以自定义错误提示消息的ServerResponse对象,使用ResponseEnum中定义的错误状态码,同时传入自定义的错误消息内容, + // 便于根据具体的错误原因向客户端传达更准确的错误信息,比如参数错误、权限不足等具体的错误提示。 + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } -} + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + // 创建一个可以指定具体错误状态码和错误提示消息的ServerResponse对象,更加灵活地适应各种不同错误码对应不同错误情况的场景, + // 通过传入自定义的状态码和消息,能够准确地反馈具体的错误状态和原因,比如不同业务模块下的特定错误处理等。 + return new ServerResponse<>(code, msg); + } +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/controller/CategoryController.java b/snailmall-category-service/src/main/java/com/njupt/swg/controller/CategoryController.java index 1eb7330..836bd34 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/controller/CategoryController.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/controller/CategoryController.java @@ -7,66 +7,85 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - /** + * CategoryController类是一个Spring MVC中的RESTful风格的控制器类,主要用于处理与品类相关的后端接口请求。 + * 这些接口属于后端管理系统的一部分,操作通常需要管理员权限(当前文档中提到后续鉴权相关处理会有调整,如移植到网关统一做)。 + * * @Author swg. * @Date 2019/1/2 12:57 * @CONTACT 317758022@qq.com * @DESC 品类接口,这属于后端管理系统人员可以操作的,所以需要管理员权限 */ - //TODO 这里首先实现业务 关于这里重复的鉴权,后面将会移植到网关中统一去做 +// 此处的TODO注释表明当前控制器内存在重复鉴权的情况,后续计划将鉴权逻辑统一移植到网关层去处理,以优化和简化鉴权流程,避免代码重复,提高系统的可维护性。 //TODO 先开放GET请求 +// 此TODO注释说明目前暂时先开放GET请求方式,可能意味着后续还会根据业务需求对其他请求方式(如POST、PUT、DELETE等)进行相应的开发和配置。 + @RestController +// 该注解表明这个类是一个RESTful风格的控制器,Spring会自动将返回的对象转换为JSON等格式响应给客户端,并且处理HTTP请求与方法的映射关系。 @RequestMapping("/manage/category/") +// 用于定义这个控制器类中所有接口方法的基础请求路径,即该控制器下的接口URL都将以"/manage/category/"开头。 public class CategoryController { + @Autowired + // Spring的依赖注入注解,自动将实现了ICategoryService接口的实例注入到此处,方便在控制器方法中调用服务层的业务逻辑方法。 private ICategoryService categoryService; /** * 获取品类子节点(平级) + * 此方法用于处理获取品类子节点(平级关系)的请求,接收一个可选的品类ID参数,若未传入则默认值为0。 + * 它调用服务层的categoryService.getCategory方法来获取相应的品类子节点信息,并将服务层返回的ServerResponse对象直接返回给客户端, + * 由服务层负责具体的业务逻辑处理以及响应数据的封装等操作。 */ @RequestMapping("get_category.do") - public ServerResponse getCategory(@RequestParam(value = "categoryId",defaultValue = "0") Integer categoryId){ + public ServerResponse getCategory(@RequestParam(value = "categoryId", defaultValue = "0") Integer categoryId) { ServerResponse response = categoryService.getCategory(categoryId); return response; } /** * 增加节点 + * 该方法用于处理增加品类节点的请求,接收品类名称和父节点ID作为参数(父节点ID若未传入默认值为0)。 + * 通过调用服务层的categoryService.addCategory方法来执行增加品类节点的业务逻辑,然后将服务层返回的ServerResponse对象返回给客户端, + * 以便告知客户端增加节点操作的执行结果情况。 */ @RequestMapping("add_category.do") - public ServerResponse addCategory(String categoryName, @RequestParam(value = "parentId",defaultValue = "0")int parentId){ - ServerResponse response = categoryService.addCategory(categoryName,parentId); + public ServerResponse addCategory(String categoryName, @RequestParam(value = "parentId", defaultValue = "0") int parentId) { + ServerResponse response = categoryService.addCategory(categoryName, parentId); return response; } /** * 修改品类名称 + * 此方法负责处理修改品类名称的请求,接收要修改的品类名称和对应的品类ID作为参数。 + * 它调用服务层的categoryService.updateCategoryName方法来执行修改名称的业务逻辑,并直接返回服务层返回的ServerResponse对象给客户端, + * 这个返回对象中可能包含了修改操作的结果状态以及相关的提示信息等内容。 */ @RequestMapping("set_category_name.do") - public ServerResponse set_category_name(String categoryName,Integer categoryId){ - return categoryService.updateCategoryName(categoryName,categoryId); + public ServerResponse set_category_name(String categoryName, Integer categoryId) { + return categoryService.updateCategoryName(categoryName, categoryId); } /** * 递归获取自身和所有的子节点 + * 用于处理递归获取指定品类及其所有子节点的请求,接收一个可选的品类ID参数(默认值为0)。 + * 调用服务层的categoryService.selectCategoryAndDeepChildrenById方法来实现具体的递归查询业务逻辑, + * 并将服务层返回的ServerResponse对象返回给客户端,让客户端获取到完整的品类及其子节点相关信息。 */ @RequestMapping("get_deep_category.do") - public ServerResponse get_deep_category(@RequestParam(value = "categoryId",defaultValue = "0") Integer categoryId){ + public ServerResponse get_deep_category(@RequestParam(value = "categoryId", defaultValue = "0") Integer categoryId) { return categoryService.selectCategoryAndDeepChildrenById(categoryId); } /** * 这是为了给其他服务调用而新增的接口 + * 该方法对应的接口是为了方便其他服务调用获取品类详细信息而新增的,接收一个品类ID作为参数。 + * 通过调用服务层的categoryService.getCategoryDetail方法来获取相应的品类详细信息,并将服务层返回的ServerResponse对象返回给客户端, + * 满足其他服务对品类详细内容的获取需求。 */ @RequestMapping("get_category_detail.do") - public ServerResponse get_category_detail(Integer categoryId){ + public ServerResponse get_category_detail(Integer categoryId) { return categoryService.getCategoryDetail(categoryId); } - - - - -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/dao/CategoryMapper.java b/snailmall-category-service/src/main/java/com/njupt/swg/dao/CategoryMapper.java index be3f08a..112c7af 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/dao/CategoryMapper.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/dao/CategoryMapper.java @@ -1,24 +1,70 @@ package com.njupt.swg.dao; - import com.njupt.swg.entity.Category; import org.apache.ibatis.annotations.Mapper; - import java.util.List; +/** + * CategoryMapper接口是一个MyBatis的数据访问层接口,用于定义对数据库中与品类(Category)相关的数据进行操作的方法。 + * 它通过MyBatis提供的注解和相关机制,与数据库中的数据表进行交互,实现增删改查等基本的数据操作功能。 + * + * @Mapper注解用于标识这个接口是一个MyBatis的Mapper接口,MyBatis框架在扫描时会识别该注解, + * 并为这个接口创建对应的代理实现类,使得接口中的方法可以与数据库操作语句(如SQL语句,通常在对应的XML映射文件中配置)进行关联,进而执行数据库操作。 + */ @Mapper public interface CategoryMapper { + + /** + * 根据主键删除对应的品类记录。 + * 此方法接收一个表示品类记录主键(通常是唯一标识一条品类记录的ID)的整数参数, + * 在数据库中执行删除操作,删除对应主键的品类记录,并返回一个整数值表示受影响的行数, + * 通常如果删除成功,返回值为1,表示成功删除了一条记录;若返回值为0,则表示没有符合条件的记录被删除(例如主键对应的记录不存在)。 + */ int deleteByPrimaryKey(Integer id); + /** + * 插入一条新的品类记录。 + * 接收一个Category类型的对象作为参数,该对象包含了要插入到数据库中的品类的各项属性信息(如品类名称、父品类ID等), + * 通过MyBatis配置的SQL语句(一般在对应的XML映射文件中)将这个对象所代表的品类信息插入到数据库中相应的数据表内, + * 同样返回一个整数值表示受影响的行数,成功插入一条记录时返回值通常为1。 + */ int insert(Category record); + /** + * 插入一条新的品类记录,但只插入对象中不为null的属性对应的字段值。 + * 与insert方法类似,也是接收一个Category对象作为参数,不过该方法在执行插入操作时, + * 会根据对象属性值是否为null来决定是否将对应的字段插入到数据库中,这样更加灵活,避免插入不必要的null值到数据表中, + * 返回值也是表示受影响的行数,插入成功时一般返回1。 + */ int insertSelective(Category record); + /** + * 根据主键查询对应的品类记录。 + * 传入一个表示主键的整数参数,通过MyBatis执行查询操作,从数据库中查找对应主键的品类记录, + * 如果找到,则返回一个Category类型的对象,该对象封装了从数据库中获取到的品类的各项属性信息; + * 若未找到符合条件的记录,则返回null。 + */ Category selectByPrimaryKey(Integer id); + /** + * 根据主键更新品类记录,但只更新对象中不为null的属性对应的字段值。 + * 接收一个Category对象作为参数,此方法会根据对象中不为null的属性,通过MyBatis配置的SQL语句对数据库中对应主键的品类记录进行更新操作, + * 返回一个整数值表示受影响的行数,若成功更新了相应的字段,则返回值大于0(具体数值取决于实际更新的字段数量),若没有任何字段被更新(例如传入的对象属性值与数据库中已有的值相同),则返回0。 + */ int updateByPrimaryKeySelective(Category record); + /** + * 根据主键更新品类记录,会更新所有传入对象中对应的字段值,无论其是否为null。 + * 同样接收一个Category对象作为参数,利用该对象中的所有属性值,通过MyBatis配置的SQL语句对数据库中对应主键的品类记录进行全面更新, + * 返回值表示受影响的行数,更新成功时返回值大于0,具体取决于实际更新的字段情况。 + */ int updateByPrimaryKey(Category record); + /** + * 根据父品类ID查询其所有子品类记录。 + * 接收一个表示父品类ID的整数参数,通过MyBatis执行查询操作,从数据库中获取该父品类ID对应的所有子品类记录, + * 并以List的形式返回查询结果,列表中每个Category对象代表一条子品类记录,包含了相应子品类的详细属性信息。 + * 如果没有找到符合条件的子品类记录,则返回一个空的列表。 + */ List selectCategoryChildrenByParentId(Integer categoryId); } \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/entity/Category.java b/snailmall-category-service/src/main/java/com/njupt/swg/entity/Category.java index 7138ccc..bd8559e 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/entity/Category.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/entity/Category.java @@ -3,24 +3,51 @@ package com.njupt.swg.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; - import java.util.Date; +/** + * Category类是项目中的实体类,主要用于映射数据库中与品类相关的数据表结构, + * 也就是将数据库中品类表的各个字段对应到该类的成员变量上,方便在Java代码中以面向对象的方式对品类相关的数据进行操作和处理。 + */ @Data +// @Data注解是由Lombok库提供的一个强大的注解,它能够自动为类中的非-final字段生成对应的Getter和Setter方法, +// 同时还会重写toString()、hashCode()以及equals()等常用方法。这样可以极大地减少代码的冗余,让代码更加简洁,提高开发效率。 @AllArgsConstructor +// 此注解指示Lombok为该类自动生成一个包含所有参数的构造方法。这意味着可以使用所有成员变量的值来便捷地创建Category类的实例对象, +// 方便在初始化对象时一次性传入所有必要的属性值,例如:new Category(1, 2, "示例品类", true, 3, new Date(), new Date())。 @NoArgsConstructor +// 与@AllArgsConstructor相对应,@NoArgsConstructor注解让Lombok生成一个无参构造方法。 +// 在很多Java框架(如MyBatis在进行对象实例化等操作时)或者某些默认初始化场景下,无参构造方法是必不可少的, +// 确保了类具有默认的构造形式,能以更灵活的方式被实例化。 + public class Category { private Integer id; + // 该成员变量用于存储品类的唯一标识符,在数据库层面,它通常对应着数据表中的主键字段。 + // 通过这个唯一的ID值,可以在整个系统中准确无误地定位、区分每一个不同的品类记录, + // 无论是进行数据的查询、更新还是删除等操作,都依赖于这个关键的标识信息。 private Integer parentId; + // 代表该品类所属的父品类的唯一标识符,用于构建品类之间的层级关系,体现了品类的父子层级结构特点。 + // 例如,若存在“电子产品”品类(其ID为10),下属有“手机”品类(其parentId就可以设置为10),通过这样的关联可以清晰地梳理出整个品类的树形结构。 private String name; + // 存储品类的具体名称,是用于直观展示和区分不同品类的重要属性。 + // 比如在电商系统中,品类名称可以是“服装”“食品”“家居用品”等,用户可以通过这个名称快速识别品类所涵盖的商品范围。 private Boolean status; + // 这个布尔类型的变量用于表示品类当前所处的状态,常见的用途是标记该品类是否处于可用、启用等有效状态。 + // 例如,当status为true时,表示该品类在业务逻辑中是有效的,可以正常参与各类业务操作(如展示、销售等); + // 而当status为false时,则意味着该品类可能处于停用、隐藏等不可用状态。 private Integer sortOrder; + // 用于确定品类在展示或者排序过程中的顺序位置,在一些需要按照特定顺序展示品类列表的业务场景中发挥作用。 + // 例如,在电商系统的前端页面,品类列表可以根据sortOrder的值从小到大进行排序展示,数字越小的品类越靠前排列,方便用户浏览查找。 private Date createTime; + // 记录了品类记录在数据库中最初被创建的时间点,它在业务操作中有诸多用途, + // 比如可以用于数据的追溯,查看某个品类是什么时候添加到系统中的;也可以用于数据分析,统计不同时间段内品类的新增情况等。 private Date updateTime; + // 用于存储品类记录在数据库中最后一次被更新的时间,它能够帮助了解品类信息的更新历史和时效性。 + // 例如,业务人员可以通过这个时间来判断品类相关数据是否是最新的,或者在一些数据同步、备份的场景中,依据这个时间来确定操作的范围等。 } \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/entity/User.java b/snailmall-category-service/src/main/java/com/njupt/swg/entity/User.java index 4354b81..ccab137 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/entity/User.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/entity/User.java @@ -4,40 +4,73 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; - import java.io.Serializable; import java.util.Date; /** + * User类是项目中的用户实体类,用于映射数据库中与用户相关的数据表结构,将数据库里用户表的各个字段以面向对象的方式呈现出来, + * 方便在Java代码中对用户相关的数据进行操作、传递以及业务逻辑处理等工作,代表了系统中用户的基本信息概念。 + * * @Author swg. * @Date 2018/12/31 21:01 * @CONTACT 317758022@qq.com * @DESC 用户实体类 */ @Data +// @Data注解由Lombok提供,它会自动为类中的非-final字段生成对应的Getter和Setter方法,同时重写toString()、hashCode()以及equals()方法, +// 减少了手动编写这些常规代码的工作量,使代码更加简洁,便于在获取和设置对象属性以及对象打印输出等场景中使用。 @NoArgsConstructor +// @NoArgsConstructor注解指示Lombok为该类生成一个无参构造方法。在一些Java框架(例如MyBatis进行对象实例化时)或者默认初始化的情况下, +// 无参构造方法是必需的,确保类能以一种通用的、默认的方式被实例化。 @AllArgsConstructor +// 此注解让Lombok自动生成一个包含所有参数的构造方法,这样可以方便地使用所有成员变量的值来创建User类的实例对象, +// 比如:new User(1, "user1", "pass1", "user1@example.com", "123456789", "question1", "answer1", 1, new Date(), new Date()), +// 适用于需要一次性传入所有属性值来初始化对象的场景。 @ToString +// @ToString注解用于让Lombok自动重写toString()方法,使得在打印输出User类对象时,能够以一种清晰、易读的格式展示对象的各个属性值, +// 方便在调试等场景中查看对象的具体内容。 + public class User implements Serializable { + // 实现Serializable接口,表示该类的对象可以被序列化和反序列化,便于在网络传输(如在分布式系统中传递用户信息)或者持久化存储(如保存用户数据到文件等情况)时使用, + // 确保对象数据的完整性和可恢复性。 + private Integer id; + // 用于存储用户在系统中的唯一标识符,通常作为数据库中用户表的主键字段,通过这个ID可以在整个系统中准确地定位、区分每一个不同的用户, + // 是进行用户相关的数据查询、更新、删除等操作的重要依据。 private String username; + // 存储用户登录系统时所使用的用户名,是用户在系统中对外展示的身份标识之一,用户通过输入这个用户名以及对应的密码来进行登录操作, + // 并且在系统的很多交互场景中(如显示用户信息、进行权限判断等)都会用到该属性。 private String password; + // 存放用户登录系统的密码,是保障用户账号安全的关键信息,在用户进行登录验证以及修改密码等业务操作时会涉及到对该字段的处理, + // 需要进行妥善的加密存储以及安全验证等操作来保护用户的隐私和账号安全。 private String email; + // 用于记录用户的电子邮箱地址,在系统中可以用于多种用途,比如找回密码、接收系统通知、验证用户身份等,方便与用户进行非实时的信息沟通以及相关业务流程的推进。 private String phone; + // 存储用户的手机号码,同样也是一种重要的联系方式,可用于接收验证码、短信通知等,并且在一些需要手机号验证或者基于手机号进行服务拓展(如手机号登录等)的业务场景中发挥作用。 private String question; + // 代表用户设置的用于找回密码的密保问题,当用户忘记密码时,可以通过回答正确这个密保问题来重置密码, + // 是一种辅助保障用户账号可找回性和安全性的机制,需要用户在注册或者账号设置阶段进行合理设置。 private String answer; + // 对应密保问题的答案,与question字段配合使用,只有用户输入的答案与预先设置的答案一致时,才能成功进行密码重置等相关操作, + // 属于用户账号安全体系中的重要一环,同样需要妥善保护其保密性。 //角色0-管理员,1-普通用户 private Integer role; + // 用于标识用户在系统中所扮演的角色,通过不同的整数值来区分不同权限等级的用户,这里约定0表示管理员角色,具有系统的最高权限, + // 可以进行各种系统管理操作(如管理用户、配置系统参数等);1表示普通用户角色,其权限相对受限,只能进行一些普通的业务操作(如查看信息、下单购物等), + // 在系统进行权限判断和功能限制等业务逻辑处理时会依据这个角色属性来决定用户能否执行相应的操作。 private Date createTime; + // 记录用户账号在数据库中被创建的时间,有助于了解用户的注册历史情况,比如统计不同时间段内的用户新增数量、查看用户的注册顺序等, + // 并且在一些数据追溯以及业务分析场景中具有一定的参考价值。 private Date updateTime; - + // 用于存储用户信息最后一次在数据库中被更新的时间,通过这个时间可以判断用户信息的时效性,了解用户是否近期有过信息变更, + // 同时在一些数据同步、备份以及数据一致性维护等业务场景中也能起到辅助作用。 } \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/service/CategoryServiceImpl.java b/snailmall-category-service/src/main/java/com/njupt/swg/service/CategoryServiceImpl.java index 8549a28..837e90e 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/service/CategoryServiceImpl.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/service/CategoryServiceImpl.java @@ -16,115 +16,174 @@ import java.util.List; import java.util.Set; /** + * CategoryServiceImpl类是实现了ICategoryService接口的服务层实现类,主要负责处理与品类(Category)相关的业务逻辑。 + * 它通过调用数据访问层(CategoryMapper)的方法与数据库进行交互,并根据业务规则进行相应的处理,最终返回统一格式的响应结果(ServerResponse)给上层调用者(如控制器层)。 + * * @Author swg. * @Date 2019/1/2 12:54 * @CONTACT 317758022@qq.com * @DESC */ @Service +// @Service注解用于标记这个类是Spring框架中的一个服务层组件,Spring会自动扫描并将其纳入到容器管理中,方便进行依赖注入等操作。 @Slf4j -public class CategoryServiceImpl implements ICategoryService{ +// 使用Lombok的@Slf4j注解,自动生成一个名为log的SLF4J日志记录器,用于在类中记录日志信息,方便在业务处理过程中记录关键操作、异常情况等,便于后续排查问题。 +public class CategoryServiceImpl implements ICategoryService { + @Autowired + // Spring的依赖注入注解,通过该注解将CategoryMapper接口的实现类自动注入到此处,使得本服务类能够调用数据访问层的方法与数据库进行交互。 private CategoryMapper categoryMapper; - + /** + * 获取品类子节点(平级)的业务方法。 + * 该方法首先对传入的品类ID参数进行校验,若参数为空,则抛出相应的业务异常;然后根据传入的品类ID,从数据库中获取该品类下一级的所有子品类信息, + * 如果获取到的子品类列表为空,则记录相应日志信息;最后将包含子品类列表(若有)的成功响应返回给调用者。 + * + * @param categoryId 要获取子节点的品类的ID,可为null(但会进行校验),用于在数据库中定位对应的品类记录并查询其子品类信息。 + * @return ServerResponse 返回一个包含查询到的子品类列表(如果存在)的统一格式的响应对象,若没有子品类则返回包含相应提示信息的成功状态响应对象。 + */ @Override public ServerResponse getCategory(Integer categoryId) { - //1.校验参数 - if(categoryId == null){ + // 1.校验参数 + if (categoryId == null) { + // 如果品类ID为空,说明传入的参数不符合要求,抛出SnailmallException异常,提示“未找到该品类”,由上层统一处理异常情况并返回相应错误响应给客户端。 throw new SnailmallException("未找到该品类"); } - //2.根据父亲id获取这个父亲下一级所有子ID + // 2.根据父亲id获取这个父亲下一级所有子ID List categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); - if(CollectionUtils.isEmpty(categoryList)){ + if (CollectionUtils.isEmpty(categoryList)) { + // 如果获取到的品类列表为空,意味着该品类节点下没有任何子节点,记录一条信息级别的日志,方便后续查看业务执行情况以及排查问题。 log.info("该节点下没有任何子节点"); } return ServerResponse.createBySuccess(categoryList); } + /** + * 增加品类节点的业务方法。 + * 首先对传入的品类名称参数进行校验,若为空则抛出异常;然后创建一个新的品类对象,设置相应的属性(名称、父品类ID、状态等), + * 通过数据访问层将新的品类信息插入到数据库中,根据插入操作影响的行数判断插入是否成功,并返回相应的成功或失败提示信息的响应给调用者。 + * + * @param categoryName 要添加的品类的名称,不能为空,用于创建新的品类对象并设置其名称属性。 + * @param parentId 新添加品类的父品类的ID,用于设置新品类对象的父品类ID属性,确定其在品类层级结构中的位置。 + * @return ServerResponse 返回一个包含添加品类操作结果提示信息(成功或失败)的统一格式的响应对象,告知调用者操作是否成功执行。 + */ @Override public ServerResponse addCategory(String categoryName, int parentId) { - //1.校验参数 - if(StringUtils.isBlank(categoryName)){ + // 1.校验参数 + if (StringUtils.isBlank(categoryName)) { + // 如果品类名称为空字符串(包括null和空字符串情况),不符合业务要求,抛出异常提示“品类名字不能为空”。 throw new SnailmallException("品类名字不能为空"); } - //2.创建类目 + // 2.创建类目 Category category = new Category(); category.setName(categoryName); category.setParentId(parentId); category.setStatus(true); int resultCount = categoryMapper.insert(category); - if(resultCount > 0){ + if (resultCount > 0) { return ServerResponse.createBySuccessMessage("添加品类成功"); } return ServerResponse.createByErrorMessage("添加品类失败"); } + /** + * 修改品类名称的业务方法。 + * 先对传入的品类名称参数进行校验,若为空则抛出异常;接着根据传入的品类ID从数据库中获取对应的品类对象,若不存在则抛出异常; + * 然后创建一个新的品类对象,设置要更新的品类ID和新的品类名称属性,通过数据访问层执行更新操作,根据影响的行数判断更新是否成功, + * 并返回相应的成功或失败提示信息的响应给调用者。 + * + * @param categoryName 要修改成的品类新名称,不能为空,用于设置更新后的品类名称属性。 + * @param categoryId 要修改名称的品类的ID,用于从数据库中查找对应的品类记录以及确定要更新的目标记录,不能为空。 + * @return ServerResponse 返回一个包含修改品类名称操作结果提示信息(成功或失败)的统一格式的响应对象,若成功则响应状态为成功,否则为失败状态。 + */ @Override public ServerResponse updateCategoryName(String categoryName, Integer categoryId) { - //1.校验参数 - if(StringUtils.isBlank(categoryName)){ + // 1.校验参数 + if (StringUtils.isBlank(categoryName)) { throw new SnailmallException("品类名字不能为空"); } - //2.根据id获取品类 + // 2.根据id获取品类 Category tmpCat = categoryMapper.selectByPrimaryKey(categoryId); - if(tmpCat == null){ + if (tmpCat == null) { throw new SnailmallException("品类不存在"); } - //3.更新品类名称 + // 3.更新品类名称 Category category = new Category(); category.setId(categoryId); category.setName(categoryName); int resultCount = categoryMapper.updateByPrimaryKeySelective(category); - if(resultCount > 0){ + if (resultCount > 0) { return ServerResponse.createBySuccessMessage("更新品类名称成功"); } return ServerResponse.createByErrorMessage("更新品类名称失败"); } + /** + * 递归获取指定品类及其所有子品类(包括子品类的子品类等,形成树形结构的全部子节点)的业务方法。 + * 首先创建一个Set集合用于存放不重复的品类对象(起到去重作用,避免重复添加相同品类),然后通过递归方法查找所有子品类并添加到该集合中, + * 接着将集合中的品类ID提取出来放入列表中,最后返回包含品类ID列表的成功响应给调用者。 + * + * @param categoryId 要获取所有子品类的起始品类的ID,可为null(在方法内部会进行相应处理),作为递归查找的起点,用于在数据库中定位对应的品类记录并开始查找其子品类。 + * @return ServerResponse 返回一个包含所有子品类ID列表(如果存在)的统一格式的响应对象,若传入的品类ID为空等情况也会进行相应处理并返回合适的响应。 + */ @Override public ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId) { - //1、创建一个空Set用来存放不重复的品类对象--去重 + // 1、创建一个空Set用来存放不重复的品类对象--去重 Set categorySet = Sets.newHashSet(); - //2、递归获取所有的子节点(儿子、孙子、等等),包括自己也添加进去 - findChildCategory(categorySet,categoryId); - //3、将递归获取到的品类id取出来放进list中 + // 2、递归获取所有的子节点(儿子、孙子、等等),包括自己也添加进去 + findChildCategory(categorySet, categoryId); + // 3、将递归获取到的品类id取出来放进list中 List categoryIdList = new ArrayList<>(); - if(categoryId != null){ - for(Category category:categorySet){ + if (categoryId!= null) { + for (Category category : categorySet) { categoryIdList.add(category.getId()); } } return ServerResponse.createBySuccess(categoryIdList); } - private Set findChildCategory(Set categorySet,Integer categoryId){ - //4、如果自己不为空的话,首先把自己添加进去;如果自己为空,这个递归分支就结束,所以也是一个停止条件 + /** + * 私有递归方法,用于查找指定品类及其所有子品类(递归实现),并将找到的品类对象添加到传入的Set集合中(去重)。 + * 首先判断当前品类是否为空,如果不为空则添加到集合中;然后获取当前品类的所有子品类,再针对每个子品类递归调用本方法,继续查找其子品类, + * 直到所有子品类及其子品类等都被查找并添加到集合中为止,最后返回包含所有品类对象的集合。 + * + * @param categorySet 用于存放找到的不重复的品类对象的集合,在递归过程中不断添加品类对象进去,最终包含了指定品类及其所有子品类的对象。 + * @param categoryId 当前要查找子品类的品类的ID,作为递归查找的依据,用于在数据库中获取对应的品类及其子品类信息。 + * @return Set 返回包含了指定品类及其所有子品类对象的集合,已经进行了去重处理,方便后续提取ID等操作。 + */ + private Set findChildCategory(Set categorySet, Integer categoryId) { + // 4、如果自己不为空的话,首先把自己添加进去;如果自己为空,这个递归分支就结束,所以也是一个停止条件 Category category = categoryMapper.selectByPrimaryKey(categoryId); - if(category != null){ + if (category!= null) { categorySet.add(category); } - //5、根据父亲id获取下一级所有品类(即先获取儿子们) + // 5、根据父亲id获取下一级所有品类(即先获取儿子们) List categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); - //6、根据每一个儿子再获取儿子的儿子们,递归下去 - for(Category categoryItem:categoryList){ - findChildCategory(categorySet,categoryItem.getId()); + // 6、根据每一个儿子再获取儿子的儿子们,递归下去 + for (Category categoryItem : categoryList) { + findChildCategory(categorySet, categoryItem.getId()); } return categorySet; } - + /** + * 获取品类详细信息的业务方法。 + * 首先对传入的品类ID参数进行校验,若为空则返回相应的错误提示信息的响应;接着根据品类ID从数据库中获取对应的品类对象,若不存在则返回相应的错误提示信息的响应; + * 最后若获取到品类对象,则返回包含该品类对象的成功响应给调用者。 + * + * @param categoryId 要获取详细信息的品类的ID,可为null(会进行相应校验和处理),用于在数据库中查找对应的品类记录并获取其详细信息。 + * @return ServerResponse 返回一个包含品类详细信息(如果存在)的统一格式的响应对象,若品类ID为空或者对应的品类不存在等情况会返回相应的错误提示信息的响应。 + */ @Override public ServerResponse getCategoryDetail(Integer categoryId) { - if(categoryId == null){ + if (categoryId == null) { return ServerResponse.createByErrorMessage("参数不能为空"); } Category category = categoryMapper.selectByPrimaryKey(categoryId); - if(category == null){ + if (category == null) { return ServerResponse.createByErrorMessage("品类不存在"); } return ServerResponse.createBySuccess(category); } - -} +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/service/ICategoryService.java b/snailmall-category-service/src/main/java/com/njupt/swg/service/ICategoryService.java index 8d0d964..66b9502 100644 --- a/snailmall-category-service/src/main/java/com/njupt/swg/service/ICategoryService.java +++ b/snailmall-category-service/src/main/java/com/njupt/swg/service/ICategoryService.java @@ -4,6 +4,10 @@ import com.njupt.swg.common.resp.ServerResponse; import com.njupt.swg.entity.Category; /** + * ICategoryService接口定义了与品类(Category)相关的一系列业务操作方法,它作为服务层的抽象接口, + * 规定了具体服务实现类需要实现的功能,使得在项目中可以通过面向接口编程的方式来解耦不同层次(如控制器层与服务层)之间的依赖关系, + * 方便进行代码的维护、扩展以及替换具体的服务实现逻辑。 + * * @Author swg. * @Date 2019/1/2 12:54 * @CONTACT 317758022@qq.com @@ -11,19 +15,58 @@ import com.njupt.swg.entity.Category; */ public interface ICategoryService { - /** 根据类目id获取其下面所有的一级子类目 **/ + /** + * 根据类目id获取其下面所有的一级子类目。 + * 此方法接收一个品类的ID作为参数,用于在业务逻辑中定位到具体的某个品类,然后查询并获取该品类下一级(也就是直接子节点)的所有品类信息, + * 最终以统一的ServerResponse格式返回查询结果,方便调用者(如控制器层)获取并处理返回的数据以及响应状态等信息。 + * + * @param categoryId 表示要获取子类目所对应的品类的唯一标识符,通过这个ID可以在相关的数据存储(通常是数据库)中找到对应的品类记录,进而获取其子类目信息。 + * @return ServerResponse 返回包含了查询到的一级子类目相关信息的统一格式响应对象,若未查询到对应子类目等情况也会通过合适的响应状态和提示信息进行反馈。 + */ ServerResponse getCategory(Integer categoryId); - /** 新建一个商品类目 **/ + /** + * 新建一个商品类目。 + * 该方法用于创建一个新的商品品类,接收要创建的品类名称和其所属的父品类ID作为参数,在业务逻辑中会依据这些信息构造一个新的品类对象, + * 并将其保存到相应的数据存储(如数据库)中,最后以统一的ServerResponse格式返回操作结果,告知调用者新建品类操作是否成功执行以及相关提示信息。 + * + * @param categoryName 要创建的商品品类的名称,是新的品类对象的重要属性之一,用于在业务中标识和区分不同的品类,不能为空,需要符合业务规则设定的命名要求。 + * @param parentId 新创建的商品品类所属的父品类的ID,用于确定新品类在整个品类层级结构中的位置,建立品类之间的父子关联关系。 + * @return ServerResponse 返回包含新建品类操作结果信息(如成功或失败提示等)的统一格式响应对象,方便调用者知晓操作执行情况。 + */ ServerResponse addCategory(String categoryName, int parentId); - /** 更新品类名称 **/ + /** + * 更新品类名称。 + * 此方法用于对已存在的品类的名称进行修改更新操作,接收要更新成的新的品类名称以及对应的品类ID作为参数, + * 通过业务逻辑根据品类ID找到对应的品类记录,并将其名称更新为传入的新名称,最后以ServerResponse格式返回操作结果, + * 这里的泛型可以用于在成功更新后返回一些额外的相关信息(具体根据业务需求而定),同时也通过响应状态等反馈更新操作是否成功。 + * + * @param categoryName 要更新成的新的品类名称,必须符合业务规则设定的命名要求,不能为空,用于替换原有的品类名称属性值。 + * @param categoryId 要进行名称更新的品类的唯一标识符,通过这个ID可以在数据存储中准确找到对应的品类记录,进而执行更新操作。 + * @return ServerResponse 返回包含更新品类名称操作结果信息(如成功或失败提示等)的统一格式响应对象,若更新成功可能还会包含相关的额外信息。 + */ ServerResponse updateCategoryName(String categoryName, Integer categoryId); - /** 递归查询出所有品类 **/ + /** + * 递归查询出所有品类。 + * 接收一个品类的ID作为起始点,通过递归的方式在业务逻辑中查找该品类及其所有层级的子品类(包括子品类的子品类等,即整个树形结构下的所有品类)信息, + * 最后以统一的ServerResponse格式返回查询到的所有品类相关的结果信息(如品类ID列表等,具体根据业务实现而定),方便调用者获取和处理这些信息。 + * + * @param categoryId 表示递归查询的起始品类的唯一标识符,以这个品类为起点开始向下递归查找所有相关的子品类,若传入null等情况则根据业务实现进行相应处理。 + * @return ServerResponse 返回包含了递归查询到的所有品类相关结果信息的统一格式响应对象,用于反馈查询操作的结果以及相关数据给调用者。 + */ ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId); - /** 被其他服务调用的接口 **/ + /** + * 被其他服务调用的接口。 + * 此方法主要是为了满足项目中其他服务对品类详细信息的获取需求而定义的,接收一个品类的ID作为参数, + * 通过业务逻辑查找并获取对应ID的品类的详细信息(如品类的各种属性值等),最后以统一的ServerResponse格式返回查询结果, + * 以便其他服务能够获取到所需的品类详细内容并进行后续相关的业务处理。 + * + * @param categoryId 要获取详细信息的品类的唯一标识符,通过这个ID在相关的数据存储中定位并获取对应的品类详细记录信息。 + * @return ServerResponse 返回包含了指定品类详细信息的统一格式响应对象,若未查询到对应品类等情况也会通过合适的响应状态和提示信息进行反馈。 + */ ServerResponse getCategoryDetail(Integer categoryId); -} +} \ No newline at end of file diff --git a/snailmall-config-server/src/main/java/com/njupt/swg/SnailmallConfigServerApplication.java b/snailmall-config-server/src/main/java/com/njupt/swg/SnailmallConfigServerApplication.java index ab4f06b..ddb23e9 100644 --- a/snailmall-config-server/src/main/java/com/njupt/swg/SnailmallConfigServerApplication.java +++ b/snailmall-config-server/src/main/java/com/njupt/swg/SnailmallConfigServerApplication.java @@ -5,14 +5,36 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.config.server.EnableConfigServer; +/** + * SnailmallConfigServerApplication类是整个项目中配置服务器相关的Spring Boot应用的启动类, + * 它充当了启动配置服务器应用的入口点,负责协调各个组件进行初始化、配置加载等工作,从而使配置服务器能够正常运行并对外提供配置管理相关的服务。 + */ @SpringBootApplication +// @SpringBootApplication注解是一个复合注解,它整合了多个重要的Spring相关注解功能。 +// 其中,@Configuration注解表示这个类本身就是一个配置类,可以在里面定义各种Bean以及配置信息; +// @EnableAutoConfiguration注解用于开启Spring Boot的自动配置机制,会根据项目中添加的依赖自动配置相应的Spring组件和功能,极大地简化了配置过程; +// @ComponentScan注解则负责扫描指定包及其子包下的所有被Spring组件注解(如@Component、@Service、@Controller等)标记的类,将它们纳入Spring容器进行管理。 +// 通过使用这个复合注解,能够便捷地搭建起一个基础的Spring Boot应用框架。 + @EnableDiscoveryClient +// @EnableDiscoveryClient注解在微服务架构环境下有着重要作用。它用于启用服务发现客户端的功能, +// 意味着这个配置服务器应用可以将自身的服务信息注册到服务注册中心(例如Eureka、Consul等常用的服务注册与发现工具)上, +// 同时也能够发现其他已注册在服务注册中心的服务,方便在分布式系统中实现服务之间的相互调用与协作,确保配置服务器能更好地融入整个微服务生态系统。 + @EnableConfigServer +// @EnableConfigServer注解是Spring Cloud专门用于启用配置服务器功能的关键注解。 +// 当应用中添加了这个注解后,Spring Cloud会自动将该应用配置成一个配置服务器,它能够从各种配置源(如本地文件系统、Git仓库等)读取配置文件, +// 并对外提供配置信息的获取服务,使得其他微服务应用可以从这个配置服务器获取它们所需的配置内容,实现了配置的集中管理和动态更新等功能。 + public class SnailmallConfigServerApplication { + /** + * main方法是Java应用程序的标准入口方法,在这里它承担着启动Spring Boot应用的核心任务。 + * 通过调用SpringApplication.run方法,并传入当前启动类的Class对象(即SnailmallConfigServerApplication.class)以及命令行参数(args), + * Spring Boot框架会基于这些信息进行一系列复杂的操作,包括但不限于加载配置文件、扫描并初始化各种组件、启动嵌入式的Web服务器(如果有相关依赖配置)等, + * 最终使得整个配置服务器应用顺利启动并开始对外提供配置管理相关的服务,满足其他微服务应用获取配置的需求。 + */ public static void main(String[] args) { SpringApplication.run(SnailmallConfigServerApplication.class, args); } - -} - +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/SnailmallProductServiceApplication.java b/snailmall-product-service/src/main/java/com/njupt/swg/SnailmallProductServiceApplication.java index 5d90f3b..da5b777 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/SnailmallProductServiceApplication.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/SnailmallProductServiceApplication.java @@ -8,14 +8,51 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +/** + * SnailmallProductServiceApplication类是整个Spring Boot项目的启动类,它扮演着启动整个应用程序以及配置相关框架特性的关键角色。 + * 通过使用多个注解,集成了Spring Boot的自动配置功能、服务发现功能以及Feign客户端功能等,使得项目能够方便地构建为微服务架构,并且与其他微服务进行交互,同时加载自定义的配置文件属性等操作。 + */ @SpringBootApplication +// @SpringBootApplication是一个组合注解,它相当于同时使用了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 这三个注解。 +// @Configuration注解表明这个类是一个Java配置类,可用于定义Bean等配置信息;@EnableAutoConfiguration注解启用了Spring Boot的自动配置机制, +// 会根据项目中添加的依赖自动配置各种组件(如数据库连接、Web相关配置等),减少了手动配置的工作量;@ComponentScan注解用于指定Spring要扫描的组件所在的包路径, +// 默认会扫描该类所在包及其子包下的所有带有Spring相关注解(如 @Component、@Service、@Controller等)的类,将它们注册为Spring容器中的Bean,方便进行依赖注入等操作。 + @EnableDiscoveryClient +// @EnableDiscoveryClient注解用于启用服务发现功能,在微服务架构中,当项目作为一个服务运行时,这个注解会让服务能够注册到服务注册中心(如Eureka、Consul等), +// 并且可以从注册中心发现其他服务的信息,方便实现服务之间的调用和交互,使得各个微服务能够动态地感知到彼此的存在,便于构建分布式的系统架构。 + @EnableFeignClients +// @EnableFeignClients注解用于开启Feign客户端功能。Feign是一个声明式的HTTP客户端框架,它可以简化微服务之间的HTTP接口调用, +// 通过定义接口并使用注解来描述HTTP请求的相关信息(如请求方法、请求路径、请求参数等),就可以方便地调用其他微服务提供的接口, +// 而无需手动编写复杂的HTTP请求代码,提高了微服务之间通信的便利性和代码的可读性、可维护性。 + public class SnailmallProductServiceApplication { + /** + * 项目的主入口方法,通过调用SpringApplication的静态方法run来启动整个Spring Boot应用程序, + * 将当前类(SnailmallProductServiceApplication.class)作为配置类传递进去,同时传入命令行参数(args), + * Spring Boot会根据配置类中的注解以及相关依赖自动进行组件扫描、自动配置等操作,初始化并启动整个应用,最终运行在相应的Web容器(如Tomcat等)中。 + * + * @param args 表示从命令行传入的参数,在项目启动时可以通过命令行指定一些配置信息、运行模式等参数,例如指定应用的端口号、配置文件路径等, + * Spring Boot会解析这些参数并根据参数内容进行相应的初始化和配置调整,使得应用能够按照期望的方式启动和运行。 + */ public static void main(String[] args) { SpringApplication.run(SnailmallProductServiceApplication.class, args); } -} + /** + * 定义一个Bean方法,用于创建PropertySourcesPlaceholderConfigurer类型的Bean实例, + * 这个Bean在Spring框架中主要用于处理属性占位符的替换功能,特别是在加载外部配置文件(通过 @PropertySource注解指定的配置文件)时, + * 可以将配置文件中的属性值按照占位符的方式替换到对应的地方(如 @Value注解标注的字段、配置类中的属性等),保证配置文件中的属性能够正确应用到项目中, + * 提高了配置的灵活性和可维护性,避免硬编码一些配置值,方便在不同环境(如开发、测试、生产环境)下通过修改配置文件来调整项目的相关参数。 + * + * @return PropertySourcesPlaceholderConfigurer 返回一个PropertySourcesPlaceholderConfigurer类型的实例, + * 这个实例会被Spring容器管理,在需要进行属性占位符替换的地方会自动发挥作用,确保配置文件中的属性能够正确解析和应用。 + */ + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java b/snailmall-product-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java index 66c6499..3079d9a 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java @@ -8,48 +8,71 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** + * CommonCacheUtil类是一个用于操作Redis缓存的工具类,它提供了一些常用的缓存操作方法, + * 例如向缓存中存储数据、获取缓存中的数据、设置带过期时间的缓存以及删除缓存中的数据等功能, + * 在项目中方便其他地方统一调用这些方法来与Redis缓存进行交互,实现缓存相关的业务逻辑。 + * * @Author swg. * @Date 2019/1/1 15:03 * @CONTACT 317758022@qq.com * @DESC */ @Component +// @Component注解用于将这个类标记为Spring框架中的一个组件,使得Spring能够扫描并管理这个类,方便进行依赖注入等操作, +// 意味着可以在其他需要使用这个缓存工具类的地方通过依赖注入的方式获取其实例。 + @Slf4j +// 使用Lombok的@Slf4j注解,会自动生成一个名为log的SLF4J日志记录器,用于在类中记录各种操作的日志信息, +// 特别是在缓存操作出现异常等情况时,方便记录详细的错误信息,便于后续排查问题。 + public class CommonCacheUtil { @Autowired + // Spring的依赖注入注解,用于将JedisPoolWrapper类型的对象注入到当前类中, + // 通过这个被注入的对象可以获取到JedisPool实例,进而操作Redis缓存,实现了与缓存配置的解耦,方便灵活替换缓存相关的配置和实现。 private JedisPoolWrapper jedisPoolWrapper; - /** * 缓存永久key + * 该方法用于将指定的键值对存储到Redis缓存中,并且这个缓存数据是永久有效的(除非手动删除), + * 它通过获取JedisPool实例,从中获取Jedis资源来执行向Redis中设置键值对的操作,若操作过程中出现异常,会记录日志并抛出业务异常。 + * + * @param key 要存储到缓存中的数据的键,在Redis中是唯一标识一个数据项的字符串,通过这个键可以后续获取对应的缓存值,需保证唯一性和符合Redis键的命名规范。 + * @param value 要存储到缓存中的数据的值,是与键对应的具体数据内容,可以是字符串形式的各种数据(如序列化后的对象、JSON格式的数据等),符合Redis对值的存储要求。 */ public void cache(String key, String value) { try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { - try (Jedis Jedis = pool.getResource()) { - Jedis.select(0); - Jedis.set(key, value); + if (pool!= null) { + try (Jedis jedis = pool.getResource()) { + // 选择Redis的数据库编号,这里选择0号数据库,通常在一个Redis实例中可以配置多个数据库,通过编号进行区分,便于不同业务场景使用不同数据库来隔离数据。 + jedis.select(0); + jedis.set(key, value); } } } catch (Exception e) { log.error("redis存值失败", e); + // 如果在向Redis存储值的过程中出现异常,记录错误日志,并抛出SnailmallException异常,告知上层调用者Redis操作报错,由上层统一处理异常情况。 throw new SnailmallException("redis报错"); } } /** * 获取缓存key + * 此方法用于从Redis缓存中根据指定的键获取对应的值,同样是先获取JedisPool实例,再获取Jedis资源来执行获取操作, + * 若出现异常会记录日志并抛出业务异常,最终返回获取到的缓存值(若不存在则返回null)。 + * + * @param key 要从缓存中获取值所对应的键,需与之前存储数据时使用的键一致,用于在Redis中定位到相应的数据项并获取其值。 + * @return String 返回从Redis缓存中获取到的对应键的值,如果缓存中不存在该键对应的数据,则返回null,由调用者根据实际情况进行后续处理。 */ public String getCacheValue(String key) { String value = null; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { - try (Jedis Jedis = pool.getResource()) { - Jedis.select(0); - value = Jedis.get(key); + if (pool!= null) { + try (Jedis jedis = pool.getResource()) { + jedis.select(0); + value = jedis.get(key); } } } catch (Exception e) { @@ -61,12 +84,19 @@ public class CommonCacheUtil { /** * 过期key + * 该方法用于向Redis缓存中存储一个带有过期时间的键值对,首先尝试使用setnx命令(如果键不存在则设置成功)设置键值对, + * 然后为这个键设置过期时间,若操作过程中出现异常,会记录日志并抛出业务异常,最后返回setnx操作的结果(1表示设置成功,0表示键已存在设置失败)。 + * + * @param key 要存储到缓存中的数据的键,其作用与前面方法中的键类似,用于在Redis中唯一标识一个数据项,并且要符合Redis键的相关规范。 + * @param value 要存储到缓存中的数据的值,与键对应,是实际要缓存的数据内容,需符合Redis对值的存储要求。 + * @param expire 要设置的过期时间,单位为秒,表示缓存数据在Redis中存活的时长,超过这个时间后,对应的键值对将自动从Redis中删除。 + * @return long 返回setnx操作的结果,1表示成功向Redis中设置了不存在的键值对,0表示键已经存在,设置失败,供调用者根据返回值判断操作情况并进行后续处理。 */ public long cacheNxExpire(String key, String value, int expire) { long result = 0; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); result = jedis.setnx(key, value); @@ -83,10 +113,14 @@ public class CommonCacheUtil { /** * 删除缓存key + * 此方法用于从Redis缓存中删除指定的键对应的键值对,通过获取Jedis资源并执行删除操作来实现, + * 若在删除过程中出现异常,会记录日志并抛出业务异常,以告知上层调用者操作出现问题。 + * + * @param key 要从Redis缓存中删除的键,需是之前已经存储在Redis中的有效键,用于定位到要删除的键值对数据项。 */ public void delKey(String key) { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); try { @@ -98,7 +132,4 @@ public class CommonCacheUtil { } } } - - - -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java b/snailmall-product-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java index addfe24..086b618 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java @@ -5,38 +5,70 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; - import javax.annotation.PostConstruct; /** + * JedisPoolWrapper类主要用于封装JedisPool对象,负责对Jedis连接池进行初始化配置,并提供获取JedisPool实例的方法, + * 以便在项目中其他地方(如操作Redis缓存的相关类)能够方便地获取到配置好的Jedis连接池来与Redis进行交互。 + * 虽然当前代码只实现了针对单个Redis的配置,但文档中提到课程里实现了Redis客户端集群相关内容,这里需要掌握一致性Hash算法(暗示后续可能有相关扩展或对比学习的要求)。 + * * @Author swg. * @Date 2019/1/1 15:00 * @CONTACT 317758022@qq.com * @DESC 只做了单个redis,但是课程中实现的redis客户端集群,要掌握一致性hash算法 */ @Component +// @Component注解用于将该类标记为Spring框架中的一个组件,使得Spring能够扫描并管理这个类,这样就可以通过依赖注入等方式在其他类中使用它, +// 保证了类的实例化和生命周期由Spring容器进行管控,便于在项目中灵活集成和复用。 + @Slf4j +// 通过Lombok的@Slf4j注解,自动生成一个名为log的SLF4J日志记录器,用于在类中记录日志信息,方便记录如连接池初始化过程中的成功、失败等关键情况, +// 有助于后续排查问题以及查看系统运行状态。 + public class JedisPoolWrapper { + @Autowired + // Spring的依赖注入注解,用于将Parameters类型的对象注入到当前类中。这里的Parameters类应该是用于存放Redis相关配置参数的, + // 通过注入这个对象,本类能够获取到如Redis最大连接数、最大空闲连接数、最大等待时间等配置信息,进而对Jedis连接池进行合理配置。 private Parameters parameters; private JedisPool jedisPool = null; + // 用于存储JedisPool对象,JedisPool是Jedis客户端提供的连接池对象,通过它可以管理与Redis服务器的连接,避免频繁创建和销毁连接,提高性能, + // 初始化为null,会在后续的初始化方法中根据配置参数进行实例化。 @PostConstruct - public void init(){ + // @PostConstruct注解标记的方法会在类实例化后,依赖注入完成时自动被调用,用于执行一些初始化的操作。在这里就是用于初始化Jedis连接池的相关配置并创建JedisPool对象。 + public void init() { try { JedisPoolConfig config = new JedisPoolConfig(); + // 创建JedisPoolConfig对象,它用于配置Jedis连接池的各种属性,如连接池中的最大连接数、最大空闲连接数、获取连接的最大等待时间等。 + config.setMaxTotal(parameters.getRedisMaxTotal()); + // 通过注入的Parameters对象获取Redis最大连接数配置参数,并设置到JedisPoolConfig中,用于限制连接池中总共可以创建的最大连接数量, + // 避免因创建过多连接导致系统资源耗尽等问题。 + config.setMaxIdle(parameters.getRedisMaxIdle()); + // 获取Redis最大空闲连接数参数,并设置到JedisPoolConfig中,规定了连接池中允许空闲的最大连接数量,合理设置可以提高连接的复用效率,减少资源浪费。 + config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis()); - jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx"); + // 获取Redis获取连接的最大等待时间参数(单位为毫秒),设置到JedisPoolConfig中,当连接池中的连接耗尽时,若获取连接的等待时间超过这个设定值,就会抛出异常, + // 用于防止长时间等待获取连接而导致系统阻塞等情况。 + + jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx"); + // 根据配置好的JedisPoolConfig对象以及从Parameters对象获取到的Redis服务器的主机地址、端口号等信息,创建JedisPool对象, + // 其中2000表示连接超时时间(单位为毫秒),"xxx"这里应该是密码(如果Redis服务器设置了密码认证的话,实际使用中需替换为正确密码), + // 通过这个JedisPool对象就可以获取Jedis客户端连接与Redis服务器进行交互了。 + log.info("【初始化redis连接池成功】"); - }catch (Exception e){ - log.error("【初始化redis连接池失败】",e); + // 记录日志,表示Jedis连接池初始化成功,方便在查看日志时确认连接池是否正常创建,以便后续排查可能出现的与Redis连接相关的问题。 + } catch (Exception e) { + log.error("【初始化redis连接池失败】", e); + // 如果在初始化Jedis连接池过程中出现异常,记录错误日志,详细记录异常信息,方便后续查找问题根源,排查初始化失败的原因。 } } public JedisPool getJedisPool() { return jedisPool; } -} + // 对外提供获取JedisPool对象的方法,使得其他类(如操作Redis缓存的工具类等)能够获取到这个已经配置好的连接池对象,进而获取Jedis客户端连接来操作Redis。 +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/cache/Parameters.java b/snailmall-product-service/src/main/java/com/njupt/swg/cache/Parameters.java index 513fa27..aae34db 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/cache/Parameters.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/cache/Parameters.java @@ -5,24 +5,54 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** + * Parameters类是一个用于存储项目相关配置参数的实体类,在本案例中主要用于存放与Redis配置相关的参数信息。 + * 通过使用Spring框架提供的注解,能够从配置文件(如application.properties或application.yml等)中读取对应的配置值,并注入到相应的成员变量中, + * 方便在其他需要使用这些配置参数的地方进行获取和使用,实现了配置参数的集中管理和灵活注入。 + * * @Author swg. * @Date 2019/1/1 14:27 * @CONTACT 317758022@qq.com * @DESC */ @Component +// @Component注解用于将这个类标记为Spring框架中的一个组件,使得Spring能够扫描并管理这个类,将其纳入Spring容器中, +// 这样就可以通过依赖注入等方式在其他类中方便地使用这个类的实例,便于在项目中实现配置参数的统一管理和共享使用。 + @Data +// @Data注解是由Lombok库提供的一个便捷注解,它会自动为类中的非-final字段生成对应的Getter和Setter方法, +// 同时还会重写toString()、hashCode()以及equals()等方法,减少了手动编写这些常规代码的工作量,使代码更加简洁,方便对类中成员变量的访问和操作。 + public class Parameters { /*****redis config start*******/ @Value("${redis.host}") + // @Value注解用于从Spring的配置文件(例如application.properties或application.yml等)中按照指定的属性键读取对应的值, + // 并将其注入到被标注的成员变量中。这里表示从配置文件中读取名为"redis.host"的属性值,将其注入到redisHost变量中, + // 该属性值通常用于指定Redis服务器的主机地址,比如"localhost"或者具体的IP地址等,以便在代码中能够正确连接到Redis服务器。 private String redisHost; + @Value("${redis.port}") + // 同样通过@Value注解从配置文件中读取名为"redis.port"的属性值,注入到redisPort变量中,用于指定Redis服务器的端口号, + // 常见的Redis默认端口号是6379,通过配置文件可以灵活修改该端口号,以适应不同的部署环境或自定义设置。 private int redisPort; + @Value("${redis.max-idle}") + // 从配置文件中读取"redis.max-idle"属性值注入到redisMaxTotal变量中,不过从变量名来看,这里可能存在命名混淆, + // 按照一般的理解,"redis.max-idle"对应的应该是最大空闲连接数,而变量名却为redisMaxTotal(通常代表最大连接总数), + // 可能需要进一步确认配置文件中的属性含义与这里变量使用的一致性,暂且认为此处是按照实际配置文件的语义进行赋值,用于配置Jedis连接池的相关参数, + // 表示连接池中允许的最大空闲连接数量,合理设置该值可以提高连接的复用效率,避免过多空闲连接占用资源。 private int redisMaxTotal; + @Value("${redis.max-total}") + // 读取配置文件中"redis.max-total"属性值注入到redisMaxIdle变量中,同样存在变量名与常规语义不太匹配的情况, + // 正常"redis.max-total"一般表示连接池允许创建的最大连接总数,此处注入到名为redisMaxIdle的变量(通常理解为最大空闲连接数)中, + // 需检查配置文件与代码逻辑的对应关系,暂且按当前代码逻辑理解为用于设置Jedis连接池相关参数,这里设定连接池的最大连接总数, + // 限制了连接池中总共可以创建的连接数量,防止因创建过多连接导致系统资源紧张等问题。 private int redisMaxIdle; + @Value("${redis.max-wait-millis}") + // 从配置文件获取"redis.max-wait-millis"属性值注入到redisMaxWaitMillis变量中,这个属性用于指定获取连接时的最大等待时间(单位为毫秒), + // 在连接池中的连接耗尽时,如果获取连接的等待时间超过这个设定值,就会抛出异常,以此来避免长时间等待获取连接而导致系统阻塞等情况发生, + // 合理设置该参数有助于保障系统的响应性能和稳定性。 private int redisMaxWaitMillis; /*****redis config end*******/ -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/clients/CategoryClient.java b/snailmall-product-service/src/main/java/com/njupt/swg/clients/CategoryClient.java index dc591d7..b1c0b03 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/clients/CategoryClient.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/clients/CategoryClient.java @@ -7,16 +7,42 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** + * CategoryClient接口是一个基于Spring Cloud OpenFeign实现的客户端接口,用于声明对名为"category-service"的服务进行远程调用的相关接口方法。 + * 通过Feign的机制,它可以方便地以类似本地方法调用的方式去调用远程服务提供的接口,实现了服务之间的解耦以及简单便捷的远程通信,在微服务架构中常用于不同服务间的交互场景。 + * * @Author swg. * @Date 2019/1/3 16:56 * @CONTACT 317758022@qq.com * @DESC */ @FeignClient("category-service") +// @FeignClient注解用于指定要调用的远程服务的名称(这里是"category-service"),Spring Cloud会根据这个名称去服务注册中心(如Eureka等)查找对应的服务实例, +// 并通过动态代理等机制生成实现该接口的代理类,使得接口中的方法调用能够被转发到远程服务对应的接口上进行实际执行,从而实现远程服务调用。 + public interface CategoryClient { + /** + * 远程调用获取品类详细信息的接口方法。 + * 该方法对应远程"category-service"服务中提供的用于获取品类详细信息的接口,通过发送HTTP请求(具体请求方式由Feign根据注解等配置确定), + * 并传递品类ID参数,获取相应的品类详细信息,最终以ServerResponse格式返回远程服务的响应结果,方便调用者处理返回的数据以及响应状态等信息。 + * + * @param categoryId 表示要获取详细信息的品类的唯一标识符,通过这个参数传递给远程服务,使其能够在对应的业务逻辑中查找并返回指定品类的详细内容, + * 例如品类的各种属性值等信息,该参数不能为空,需符合远程服务接口对于参数的要求。 + * @return ServerResponse 返回包含了远程服务返回的品类详细信息(如果查询成功)以及相应响应状态等内容的统一格式对象, + * 若远程服务调用出现问题或者未查询到对应品类等情况也会通过合适的响应状态和提示信息进行反馈。 + */ @RequestMapping("/manage/category/get_category_detail.do") ServerResponse getCategoryDetail(@RequestParam("categoryId") Integer categoryId); + /** + * 远程调用递归获取品类及其所有子品类信息的接口方法。 + * 对应远程"category-service"服务中提供的用于递归查询品类及其所有层级子品类(包括子品类的子品类等,即整个树形结构下的所有品类)信息的接口, + * 发送HTTP请求并传入品类ID参数,接收远程服务返回的查询结果,以ServerResponse格式返回给调用者,方便后续进行处理和展示等操作。 + * + * @param categoryId 表示递归查询的起始品类的唯一标识符,作为参数传递给远程服务,以这个品类为起点开始向下递归查找所有相关的子品类, + * 若传入null等情况则根据远程服务的业务实现进行相应处理,该参数需符合远程服务接口对参数的定义要求。 + * @return ServerResponse 返回包含了远程服务返回的递归查询到的所有品类相关结果信息(如品类ID列表等,具体根据业务实现而定)以及响应状态等内容的统一格式对象, + * 用于反馈远程查询操作的结果以及相关数据给调用者,若出现异常或未查询到有效数据等情况也会通过相应的响应状态和提示信息体现。 + */ @RequestMapping("/manage/category/get_deep_category.do") ServerResponse getDeepCategory(@RequestParam(value = "categoryId") Integer categoryId); -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/constants/Constants.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/constants/Constants.java index 3e43480..c657925 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/constants/Constants.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/constants/Constants.java @@ -4,41 +4,78 @@ import com.google.common.collect.Sets; import java.util.Set; /** + * Constants类是项目中的常量类,用于集中定义和管理项目中各种常用的常量值,方便在整个项目的不同地方统一引用这些常量, + * 增强代码的可读性、可维护性以及避免魔法数字(直接使用硬编码的数值而不清楚其含义)等问题,使得代码的语义更加清晰明确。 + * * @Author swg. * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com * @DESC */ public class Constants { - /**自定义状态码 start**/ + /** + * 自定义状态码 start + * 以下这部分定义了一组表示不同响应状态的自定义状态码常量,它们类似于HTTP状态码,在项目中用于标识各种请求处理后的结果状态, + * 方便在不同的业务逻辑中统一判断和处理响应情况,使得代码中对于响应状态的处理更加规范和清晰。 + */ public static final int RESP_STATUS_OK = 200; + // RESP_STATUS_OK常量表示请求处理成功的状态码,对应常见的HTTP 200状态码,表示客户端发起的请求已被服务器成功处理并返回了预期的结果, + // 在业务逻辑中可以通过判断响应的状态码是否等于该常量来确定操作是否顺利完成。 public static final int RESP_STATUS_NOAUTH = 401; + // RESP_STATUS_NOAUTH常量代表未授权的状态码,类似于HTTP 401状态码,意味着客户端发起的请求需要用户进行身份认证,但当前用户未提供有效的认证凭据或者认证失败, + // 在涉及需要权限验证的业务场景中,当收到该状态码的响应时,可以提示用户进行登录或重新认证等操作。 public static final int RESP_STATUS_INTERNAL_ERROR = 500; + // RESP_STATUS_INTERNAL_ERROR常量表示服务器内部错误的状态码,等同于HTTP 500状态码,说明服务器在处理请求时发生了内部错误,无法正常完成请求的处理, + // 当业务逻辑中接收到该状态码的响应时,通常可以记录错误日志并向用户提示服务器出现问题,建议稍后再试等信息。 public static final int RESP_STATUS_BADREQUEST = 400; + // RESP_STATUS_BADREQUEST常量用于表示请求参数错误的状态码,类似HTTP 400状态码,表明客户端发送的请求存在格式错误、参数缺失或者不符合要求等问题, + // 导致服务器无法正确解析和处理该请求,在进行请求参数校验等业务逻辑中可以根据该状态码来反馈参数错误相关的提示信息给用户。 - /**自定义状态码 end**/ + /** + * 自定义状态码 end + */ - - /** 产品的状态 **/ - public interface Product{ + /** + * 产品的状态 + * 以下通过内部接口的形式定义了与产品状态相关的常量,用于表示产品在不同阶段或者不同情况下的状态值, + * 在涉及产品管理、查询等业务逻辑中,可以通过这些常量来判断产品的当前状态,进行相应的业务处理。 + */ + public interface Product { int PRODUCT_ON = 1; + // PRODUCT_ON常量表示产品处于正常上线、可用的状态,通常意味着该产品可以被展示、销售等,在产品状态判断的业务逻辑中可以用该常量来标识产品是否处于可操作状态。 + int PRODUCT_OFF = 2; + // PRODUCT_OFF常量代表产品处于下线、停用的状态,比如产品暂时缺货、不符合销售条件等情况时,可将其状态设置为该值, + // 在业务中可以根据这个状态值来决定是否隐藏该产品或者不允许对其进行某些操作。 + int PRODUCT_DELETED = 3; + // PRODUCT_DELETED常量表示产品已被删除的状态,当产品从系统中彻底移除后,可以用这个常量来标记其最终状态, + // 在数据查询、展示等业务逻辑中可以依据该状态值过滤掉已删除的产品信息。 } - public interface ProductListOrderBy{ - Set PRICE_ASC_DESC = Sets.newHashSet("price_desc","price_asc"); + public interface ProductListOrderBy { + // ProductListOrderBy内部接口用于定义产品列表排序相关的常量集合,这里定义了一个包含按照价格升序和降序排序标识的集合, + // 在进行产品列表排序的业务场景中,可以通过判断传入的排序参数是否在这个集合中来确定是否是合法的价格排序方式,并按照相应规则进行排序操作。 + Set PRICE_ASC_DESC = Sets.newHashSet("price_desc", "price_asc"); } - - /***redis product**/ + /** + * redis product + * 以下这部分定义了与Redis中存储产品相关的一些常量,用于在使用Redis进行产品数据缓存等操作时,作为缓存键的前缀以及设置缓存过期时间等用途, + * 方便统一管理和区分不同用途的Redis缓存数据,以及控制缓存数据的时效性。 + */ public static final String PRODUCT_TOKEN_PREFIX = "product__"; + // PRODUCT_TOKEN_PREFIX常量定义了在Redis中存储产品相关数据时使用的键的前缀,通过添加这个前缀可以方便地对产品相关的缓存数据进行分类和查找, + // 例如存储产品详情信息时,缓存键可能是"product__产品ID"这样的格式,便于在Redis中统一管理产品相关的缓存项。 + public static final int PRODUCT_EXPIRE_TIME = 60 * 60 * 24 * 300; + // PRODUCT_EXPIRE_TIME常量设定了产品相关数据在Redis缓存中的过期时间,单位为秒,这里计算后表示缓存有效期为300天, + // 通过设置合适的过期时间可以保证缓存数据的时效性,避免长时间使用过期数据而导致业务逻辑出现问题,同时也能合理利用缓存资源。 public static final String PRODUCT_TOKEN_STOCK_PREFIX = "product__stock_"; - - -} + // PRODUCT_TOKEN_STOCK_PREFIX常量定义了在Redis中存储产品库存相关数据时使用的键的前缀,与前面的产品数据前缀类似, + // 只是用于专门区分和标识与产品库存相关的缓存数据,方便在操作产品库存缓存时准确地进行键的定位和数据的处理。 +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java index cef87ac..2d28cef 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java @@ -1,6 +1,5 @@ package com.njupt.swg.common.exception; - import com.njupt.swg.common.constants.Constants; import com.njupt.swg.common.resp.ServerResponse; import lombok.extern.slf4j.Slf4j; @@ -9,25 +8,59 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** + * ExceptionHandlerAdvice类是一个用于全局异常处理的类,它基于Spring框架提供的相关机制,能够捕获项目中不同地方抛出的异常, + * 并统一进行处理,返回合适的响应结果给客户端,避免因未处理的异常导致系统出现不友好的错误页面或者中断服务等情况, + * 增强了系统的稳定性和用户体验,同时也方便对异常情况进行统一的日志记录和监控。 + * * @Author swg. * @Date 2019/1/1 13:21 * @CONTACT 317758022@qq.com * @DESC 全局异常处理 */ @ControllerAdvice +// @ControllerAdvice注解用于标识这个类是一个全局的异常处理类,它可以对整个项目中的控制器层(@Controller注解标记的类)抛出的异常进行统一处理, +// 相当于一个全局的异常拦截器,能够捕获多种类型的异常并按照定义的方法进行相应的处理操作。 + @ResponseBody +// @ResponseBody注解表示将方法的返回值直接作为响应体返回给客户端,而不是去寻找对应的视图进行渲染, +// 在这里用于确保异常处理方法返回的ServerResponse对象能够以JSON等格式直接响应给客户端,符合RESTful风格的接口返回要求。 + @Slf4j +// 使用Lombok的@Slf4j注解,自动生成一个名为log的SLF4J日志记录器,用于在异常处理过程中记录详细的异常信息, +// 方便后续查看日志来排查问题、分析系统出现异常的原因以及进行相关的监控统计等工作。 + public class ExceptionHandlerAdvice { + /** + * 处理所有未被特定异常处理器捕获的异常(即通用异常处理方法)。 + * 当项目中抛出的异常类型没有被其他更具体的@ExceptionHandler方法匹配处理时,就会进入这个方法进行处理。 + * 它会记录异常的详细信息到日志中,然后返回一个表示系统内部错误的ServerResponse响应对象给客户端,告知用户系统出现异常,建议稍后再试, + * 使用了在Constants类中定义的表示内部错误的状态码常量来统一表示这种错误情况。 + * + * @param e 捕获到的Exception类型的异常对象,代表了各种未被其他异常处理器处理的通用异常情况,包含了异常的详细信息,如异常消息、堆栈信息等, + * 通过这个对象可以获取到具体的异常原因,以便记录日志和进行相应的处理。 + * @return ServerResponse 返回一个包含错误状态码(系统内部错误状态码)和相应提示信息(告知用户系统异常,请稍后再试)的统一格式的响应对象, + * 用于反馈给客户端当前系统出现了异常情况以及相应的提示内容,让客户端能够做出合适的响应,比如显示错误提示给用户等。 + */ @ExceptionHandler(Exception.class) - public ServerResponse handleException(Exception e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); + public ServerResponse handleException(Exception e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试"); } + /** + * 处理SnailmallException类型的异常。 + * 当项目中抛出SnailmallException异常时,会进入这个方法进行针对性处理,它同样会先记录异常的详细信息到日志中, + * 然后根据SnailmallException对象中存储的异常状态码和异常消息,返回一个对应的ServerResponse响应对象给客户端, + * 能够更精准地反馈这个自定义异常所代表的具体错误情况给客户端。 + * + * @param e 捕获到的SnailmallException类型的异常对象,这是项目中自定义的异常类型,其包含了自定义的异常状态码以及详细的异常消息等信息, + * 通过获取其状态码和消息,可以准确地向客户端传达具体的错误情况以及对应的提示内容。 + * @return ServerResponse 返回一个包含自定义异常对应的状态码和异常消息的统一格式的响应对象,用于向客户端详细反馈该自定义异常所代表的错误情况, + * 让客户端能够根据具体的错误提示进行相应的处理,比如展示具体的错误原因给用户等。 + */ @ExceptionHandler(SnailmallException.class) - public ServerResponse handleException(SnailmallException e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); + public ServerResponse handleException(SnailmallException e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage()); } - -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java index 363f19d..c983ac6 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java @@ -4,22 +4,50 @@ import com.njupt.swg.common.resp.ResponseEnum; import lombok.Getter; /** + * SnailmallException类是项目中自定义的异常类,它继承自Java的RuntimeException,属于运行时异常类型, + * 用于在项目特定的业务逻辑中抛出并表示一些符合业务场景的异常情况,相比于Java内置的通用异常,它能更精准地传达业务相关的错误信息, + * 方便在全局异常处理等地方进行针对性的捕获和处理,增强了项目对异常情况处理的灵活性和业务针对性。 + * * @Author swg. * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC */ @Getter -public class SnailmallException extends RuntimeException{ +// @Getter注解由Lombok库提供,它会自动为类中的私有成员变量(这里是exceptionStatus)生成对应的Getter方法, +// 方便外部代码获取该变量的值,遵循了Java的封装原则,同时减少了手动编写Getter方法的工作量,使代码更加简洁。 + +public class SnailmallException extends RuntimeException { private int exceptionStatus = ResponseEnum.ERROR.getCode(); + // 定义一个私有成员变量exceptionStatus用于存储异常对应的状态码,默认初始化为ResponseEnum.ERROR.getCode(), + // 这里的ResponseEnum应该是一个枚举类,用于定义各种响应状态相关的枚举值,默认情况下该异常的状态码采用这个默认的错误状态码值, + // 当然也可以通过特定的构造方法来重新设置这个状态码,以便更准确地表示不同业务场景下的异常情况。 - public SnailmallException(String msg){ + /** + * 构造方法,用于创建一个只传入异常消息的SnailmallException实例。 + * 该构造方法接收一个表示异常消息的字符串参数,调用父类(RuntimeException)的构造方法将这个消息传递上去, + * 同时使用默认的异常状态码(即ResponseEnum.ERROR.getCode())来表示这个异常情况,适用于那些只需传达简单错误消息, + * 且可以使用默认状态码来标识的业务异常场景。 + * + * @param msg 表示异常的详细消息内容,用于描述出现异常的具体原因,比如“品类名字不能为空”等业务相关的错误提示信息, + * 会在异常被捕获处理时展示给开发人员或者用户,方便了解出现问题的具体情况。 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 构造方法,用于创建一个可以同时传入异常状态码和异常消息的SnailmallException实例。 + * 此构造方法接收一个表示异常状态码的整数参数和一个表示异常消息的字符串参数,先调用父类(RuntimeException)的构造方法传递异常消息, + * 然后将传入的状态码赋值给exceptionStatus变量,用于覆盖默认的异常状态码,这样可以更灵活地根据不同业务场景的具体要求, + * 设定准确的异常状态码来代表不同的异常情况,比如不同模块下的特定错误可以对应不同的状态码进行区分。 + * + * @param code 表示异常对应的状态码,通过传入具体的整数值,可以自定义该异常所对应的状态码,使其能准确匹配业务中不同的错误情况, + * 该状态码可以与项目中定义的各种状态码枚举值(如ResponseEnum中的枚举值)相对应,方便统一管理和判断异常类型。 + * @param msg 表示异常的详细消息内容,与前面的构造方法中的msg作用相同,用于描述出现异常的具体原因,方便在处理异常时获取详细信息。 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java index e4e59c7..804230b 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java @@ -3,23 +3,49 @@ package com.njupt.swg.common.resp; import lombok.Getter; /** + * ResponseEnum类是一个枚举类型,用于定义项目中基本的返回状态描述以及对应的状态码,它在整个项目的响应处理机制中起着关键作用, + * 可以让不同的业务逻辑在返回结果时统一使用这些预定义的枚举值来表示操作的成功、失败或者其他特定的状态情况, + * 增强了代码的可读性、可维护性以及返回状态的规范性,方便客户端根据返回的状态码和描述信息准确理解服务端操作的结果。 + * * @Author swg. * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com * @DESC 基本的返回状态描述 */ @Getter +// @Getter注解由Lombok库提供,它会自动为枚举类中的私有成员变量(这里是code和desc)生成对应的Getter方法, +// 方便外部代码获取这些变量的值,遵循了Java的封装原则,同时减少了手动编写Getter方法的工作量,使代码更加简洁。 + public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + SUCCESS(0, "SUCCESS"), + // SUCCESS枚举值表示操作成功的状态,对应的状态码为0,描述信息为"SUCCESS",在业务逻辑中当某个操作顺利完成并需要返回成功的响应时, + // 可以使用这个枚举值来构建相应的返回对象(如ServerResponse对象),向客户端传达操作成功的消息以及对应的状态码,方便客户端进行相应处理。 + + ERROR(1, "ERROR"), + // ERROR枚举值代表操作出现错误的一般情况,状态码设定为1,描述为"ERROR",用于在业务逻辑发生一般性错误(没有更具体的错误分类时), + // 通过返回包含该枚举值相关信息的响应对象告知客户端操作失败,客户端可以根据这个统一的错误标识进行相应的提示展示等操作。 + + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + // ILLEGAL_ARGUMENTS枚举值用于表示传入的参数不合法的情况,状态码为2,描述为"ILLEGAL_ARGUMENTS", + // 当业务逻辑中对客户端传入的请求参数进行校验发现不符合要求(如参数缺失、格式错误等)时,就可以使用这个枚举值返回相应的错误响应, + // 让客户端知晓是参数方面出现了问题,并提示用户修正参数后重新发起请求。 + + NEED_LOGIN(10, "NEED_LOGIN"); + // NEED_LOGIN枚举值表示需要用户登录的状态,状态码为10,描述为"NEED_LOGIN",在涉及需要权限验证且用户未登录的业务场景中, + // 通过返回包含该枚举值信息的响应对象,提示客户端当前操作需要用户先进行登录,引导用户去登录页面完成登录操作后再继续。 private int code; + // 用于存储每个枚举值对应的状态码,通过这些状态码可以在项目的不同地方(如全局异常处理、返回结果判断等场景)统一识别和区分不同的响应状态, + // 便于进行逻辑处理和向客户端准确反馈具体的情况。 + private String desc; + // 存放每个枚举值对应的描述信息,是对相应状态的一种文字性描述,更加直观易懂,方便客户端在接收到响应后展示给用户或者开发人员查看, + // 以便更好地理解服务端操作的结果以及出现的情况。 - ResponseEnum(int code,String desc){ + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} + // 枚举类的构造方法,用于初始化每个枚举值对应的状态码和描述信息,在定义枚举值(如SUCCESS(0, "SUCCESS")等)时会调用这个构造方法, + // 将传入的状态码和描述信息赋值给对应的成员变量,确保每个枚举值都有正确的状态码和描述与之关联。 +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java index 53f3a65..8dfa46e 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java @@ -4,75 +4,135 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Getter; import lombok.NoArgsConstructor; - import java.io.Serializable; /** + * ServerResponse类是本项目的通用的返回封装类,用于统一封装服务端返回给客户端的响应数据, + * 它将响应的状态码、提示消息以及具体的数据内容(如果有)整合在一起,使得返回结果更加规范、易于理解和处理, + * 无论是成功的响应还是失败的响应,都可以通过这个类的不同构造方法和静态方法来创建合适的返回对象,方便在整个项目的各个业务逻辑中使用, + * 增强了代码的一致性和可维护性,同时也便于与前端(客户端)进行交互,前端可以根据统一的格式来解析和展示响应信息。 + * * @Author swg. * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com * @DESC 作为本项目的通用的返回封装类 */ @Getter +// @Getter注解由Lombok库提供,它会自动为类中的非-final成员变量(这里是status、msg和data)生成对应的Getter方法, +// 方便外部代码获取这些变量的值,遵循了Java的封装原则,同时减少了手动编写Getter方法的工作量,使代码更加简洁。 + @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// @JsonSerialize注解用于配置Jackson(用于JSON序列化和反序列化的库)的序列化行为,这里设置了include属性为JsonSerialize.Inclusion.NON_NULL, +// 表示在将ServerResponse对象序列化为JSON格式时,只包含那些非null值的属性,避免返回的JSON数据中出现不必要的null字段,优化了数据传输量并且使返回的JSON结构更简洁明了。 + public class ServerResponse implements Serializable { + // 实现Serializable接口,表示该类的对象可以被序列化和反序列化,这在网络传输(如将响应结果从服务端发送到客户端)或者持久化存储等场景中是必要的, + // 确保对象数据能够完整地在不同环境中传递和恢复。 + private int status; + // 用于存储响应的状态码,通过这个状态码可以表示操作的结果是成功还是失败以及具体是哪种类型的成功或失败情况, + // 通常会与项目中定义的状态码枚举(如ResponseEnum)中的值相对应,方便统一管理和判断响应状态。 + private String msg; + // 存放响应的提示消息,是对操作结果的一种文字性描述,用于更直观地告知客户端(如前端页面或者其他调用服务的客户端)操作的情况, + // 例如成功时可以是“操作成功”之类的消息,失败时可以是具体的错误原因提示等,方便用户理解响应内容。 + private T data; + // 泛型成员变量,用于存储具体的业务数据,如果操作成功并且有需要返回的数据(如查询操作返回的查询结果集等),就可以将这些数据存放在这里, + // 通过泛型可以适应不同类型的数据返回需求,提高了类的通用性和灵活性。 - public ServerResponse(){} + public ServerResponse() { + } + // 无参构造方法,主要用于在一些需要默认初始化ServerResponse对象的场景中,比如后续可能通过Setter方法等方式再去设置具体的状态码、消息和数据内容, + // 提供了一种灵活的对象创建方式,虽然在当前代码中没有看到直接使用该无参构造方法后再设置属性的情况,但保留它可以增加代码的扩展性。 - public ServerResponse(int status){ + public ServerResponse(int status) { this.status = status; } - public ServerResponse(int status,String msg){ + // 只传入状态码的构造方法,用于创建一个只指定状态码的ServerResponse对象,适用于一些只需要关注响应状态码的场景, + // 例如在某些简单的错误提示或者初步判断响应是否成功的情况下,可以先使用这个构造方法创建对象,后续再根据需要决定是否添加消息和数据内容。 + + public ServerResponse(int status, String msg) { this.status = status; this.msg = msg; } - public ServerResponse(int status,T data){ + // 传入状态码和消息的构造方法,方便创建一个带有特定状态码和相应提示消息的ServerResponse对象, + // 常用于需要明确告知客户端操作结果(成功或失败以及具体原因)但暂时没有具体数据需要返回的情况,比如一些验证失败、权限不足等提示场景。 + + public ServerResponse(int status, T data) { this.status = status; this.data = data; } - public ServerResponse(int status,String msg,T data){ + // 传入状态码和数据的构造方法,用于当操作成功并且有具体业务数据要返回给客户端时创建相应的ServerResponse对象, + // 通过泛型指定具体的数据类型,将数据封装到对象中,使得客户端可以获取到操作成功后的相关数据内容,例如查询接口返回查询结果等情况会使用到这个构造方法。 + + public ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + // 完整传入状态码、消息和数据的构造方法,提供了最全面的创建ServerResponse对象的方式,适用于各种需要详细指定响应状态、提示消息以及返回数据的业务场景, + // 可以根据具体的业务逻辑需求灵活运用这个构造方法来构建准确的返回对象。 @JsonIgnore - public boolean isSuccess(){ + // @JsonIgnore注解用于指示Jackson在序列化和反序列化过程中忽略被标注的方法或成员变量,在这里标注在isSuccess方法上, + // 意味着该方法不会被包含在序列化后的JSON数据中,因为它只是一个用于在服务端内部判断响应是否成功的工具方法,不需要传递给客户端。 + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } + // 用于判断当前ServerResponse对象表示的响应是否为成功状态的方法,通过比较存储的状态码与ResponseEnum枚举中定义的表示成功的状态码(SUCCESS.getCode())是否相等, + // 来返回一个布尔值表示是否成功,方便在业务逻辑中(如控制器层接收到服务层返回的ServerResponse对象后)快速判断操作是否成功执行,进而进行后续的处理操作。 /** * 成功的方法 + * 以下这组静态方法用于方便快捷地创建表示成功状态的ServerResponse对象,它们都返回一个状态为成功(使用ResponseEnum.SUCCESS.getCode()作为状态码)的对象, + * 只是在提示消息和返回数据方面有所不同,可以根据具体的业务需求选择合适的方法来创建成功响应的返回对象。 */ - public static ServerResponse createBySuccess(){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); + public static ServerResponse createBySuccess() { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc()); } - public static ServerResponse createBySuccessMessage(String message){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); + // 创建一个只包含默认成功状态码和默认成功描述信息的ServerResponse对象的静态方法,适用于那些不需要额外返回消息和数据, + // 只需简单告知客户端操作成功的基本情况的业务场景,返回的对象可以直接作为响应返回给客户端,体现了操作的成功结果。 + + public static ServerResponse createBySuccessMessage(String message) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); + // 创建一个带有自定义提示消息的成功状态的ServerResponse对象的静态方法,通过传入一个字符串参数作为提示消息, + // 可以在操作成功的基础上向客户端传达更具体、个性化的成功信息,比如“添加品类成功”等业务相关的成功提示内容,增强了返回信息的针对性和实用性。 + + public static ServerResponse createBySuccess(T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data); } - public static ServerResponse createBySuccess(String message,T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); + // 创建一个包含具体业务数据的成功状态的ServerResponse对象的静态方法,利用泛型传入要返回的数据内容, + // 适用于查询等操作成功后需要将查询结果返回给客户端的业务场景,将查询到的数据封装在返回对象中传递给客户端供其进一步处理和展示。 + + public static ServerResponse createBySuccess(String message, T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } + // 创建一个既包含自定义提示消息又包含具体业务数据的成功状态的ServerResponse对象的静态方法,是最全面的创建成功响应的方式, + // 可以同时向客户端传达详细的成功信息以及相关的数据内容,满足各种复杂的业务逻辑中对成功返回结果的多样化需求。 /** * 失败的方法 + * 以下这组静态方法用于创建表示失败状态的ServerResponse对象,同样通过不同的参数设置来满足各种失败场景下的返回需求, + * 可以根据具体的错误类型和需要传达给客户端的信息选择合适的方法来构建相应的失败响应对象。 */ - public static ServerResponse createByError(){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); - } - public static ServerResponse createByErrorMessage(String msg){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); + public static ServerResponse createByError() { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); - } - + // 创建一个只包含默认错误状态码和默认错误描述信息的ServerResponse对象的静态方法,用于一般性的错误情况, + // 当没有更具体的错误分类或者不需要详细说明错误原因时,可以使用这个方法返回一个简单表示操作失败的对象给客户端,让客户端知晓操作未成功。 + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } + // 创建一个带有自定义错误提示消息的失败状态的ServerResponse对象的静态方法,通过传入具体的错误消息字符串, + // 可以更精准地向客户端传达操作失败的具体原因,比如“品类名字不能为空”等业务相关的错误提示,方便用户了解问题所在并进行相应处理。 -} + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + return new ServerResponse<>(code, msg); + } + // 创建一个可以自定义状态码和错误提示消息的失败状态的ServerResponse对象的静态方法,最为灵活, + // 适用于需要根据不同业务模块或者不同错误类型来指定特定的状态码(可能与项目中定义的各种错误状态码枚举对应)以及相应错误消息的场景, + // 使得返回的失败响应能够准确地反映具体的错误情况,便于客户端进行针对性的处理。 +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java index 0233e8d..9fcc928 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java @@ -2,48 +2,92 @@ package com.njupt.swg.common.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; - import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * cookie读写 + * CookieUtil类是一个用于操作HTTP Cookie的工具类,主要聚焦于项目中与用户登录相关的Cookie处理, + * 提供了写入登录Token的Cookie、读取登录Cookie以及注销时删除登录Cookie等功能,方便在Web应用的不同业务场景中对登录状态相关的Cookie信息进行管理, + * 保障用户登录认证等相关流程的顺利进行,同时通过设置合理的Cookie属性来增强安全性以及控制其有效期和作用范围等。 + * + * @Slf4j注解用于自动生成一个名为log的SLF4J日志记录器,方便在类中记录与Cookie操作相关的日志信息,便于后续排查问题以及了解Cookie操作的执行情况。 */ @Slf4j public class CookieUtil { private final static String COOKIE_DOMAIN = "oursnail.cn"; - private final static String COOKIE_NAME = "snailmall_login_token"; + // 定义一个表示Cookie作用域名的常量,指定了该Cookie在哪个域名下有效,这里设置为"oursnail.cn",意味着只有在访问该域名及其子域名下的页面时,浏览器才会携带这个Cookie, + // 用于限定Cookie的作用范围,使其符合项目的域名部署要求,保障Cookie的使用安全性和有效性。 + private final static String COOKIE_NAME = "snailmall_login_token"; + // 定义一个表示Cookie名称的常量,用于唯一标识与登录相关的这个特定Cookie,在后续的读取、写入和删除操作中通过这个名称来定位对应的Cookie, + // 项目中其他地方如果需要操作这个登录相关的Cookie,就可以依据这个统一的名称来进行,增强了代码的一致性和可维护性。 /** * 登陆的时候写入cookie - * @param response - * @param token + * 该方法用于在用户登录成功时,向客户端(浏览器)写入包含登录Token的Cookie,通过设置Cookie的各种属性来规定其作用范围、有效期以及安全性等, + * 最后将设置好的Cookie添加到HttpServletResponse对象中,使得浏览器能够接收到并存储这个Cookie,以便后续请求时携带该Cookie用于验证登录状态等操作。 + * + * @param response HttpServletResponse对象,用于向客户端发送响应信息,包括添加要写入的Cookie,它是Servlet规范中用于响应客户端请求的重要接口实例, + * 通过这个对象可以操作响应相关的各种属性和内容,如设置响应头、添加Cookie等。 + * @param token 表示登录Token的字符串,通常是经过加密等处理后的用于标识用户登录状态的唯一标识信息,会被存储到Cookie的值中, + * 后续在验证用户登录等业务逻辑中可以通过读取该Cookie的值获取到这个Token进行相应的验证操作。 */ - public static void writeLoginToken(HttpServletResponse response,String token){ - Cookie ck = new Cookie(COOKIE_NAME,token); + public static void writeLoginToken(HttpServletResponse response, String token) { + Cookie ck = new Cookie(COOKIE_NAME, token); + // 创建一个新的Cookie对象,使用预定义的COOKIE_NAME作为Cookie的名称,将传入的登录Token作为Cookie的值,这样就构建了一个与登录相关的特定Cookie实例, + // 后续对这个Cookie设置属性后添加到响应中,就可以发送给客户端浏览器进行存储和后续使用。 + ck.setDomain(COOKIE_DOMAIN); - ck.setPath("/");//设值在根目录 - ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击 - ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒,maxage不设置的话,cookie就不会写入硬盘,只会写在内存,只在当前页面有效 - log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); + // 设置Cookie的作用域名,将其设置为之前定义的COOKIE_DOMAIN常量值,确保该Cookie只在指定的域名("oursnail.cn"及其子域名)下有效, + // 避免Cookie在不相关的域名下被误使用,增强了Cookie使用的安全性和针对性。 + + ck.setPath("/"); + // 设置Cookie的路径为根目录("/"),表示该Cookie在整个域名下的所有路径页面请求时都会被浏览器自动携带发送给服务器, + // 例如访问"oursnail.cn"下的任何子路径页面(如"/user", "/product"等)时,浏览器都会带上这个Cookie,方便在整个Web应用中统一验证用户登录状态等操作。 + + ck.setHttpOnly(true); + // 设置Cookie的HttpOnly属性为true,这意味着该Cookie不能通过JavaScript等脚本语言进行访问,只能在HTTP请求和响应中由浏览器自动处理, + // 这样可以有效避免跨站脚本攻击(XSS攻击),防止恶意脚本获取到登录相关的Cookie信息,保障用户登录信息的安全性。 + + ck.setMaxAge(60 * 60 * 24 * 365); + // 设置Cookie的最大存活时间,这里设置为一年(通过计算得出的秒数,60秒 * 60分钟 * 24小时 * 365天),单位是秒, + // 表示该Cookie在客户端浏览器上存储的有效时长,超过这个时间后浏览器会自动删除该Cookie,-1表示永久有效, + // 如果不设置这个属性(或者设置为0以外的负数),Cookie就不会写入硬盘持久化存储,只会临时保存在内存中,且只在当前页面有效,关闭页面后就会消失。 + + log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue()); + // 使用自动生成的日志记录器记录一条信息,记录即将写入的Cookie的名称和值,方便后续查看日志了解Cookie写入操作的具体情况,有助于排查可能出现的与Cookie相关的问题。 + response.addCookie(ck); + // 将设置好的Cookie添加到HttpServletResponse对象中,这样在响应发送给客户端(浏览器)时,浏览器就会接收到这个Cookie并按照设置的属性进行存储和后续使用。 } /** * 读取登陆的cookie - * @param request - * @return + * 此方法用于从客户端发送过来的HTTP请求中读取之前写入的与登录相关的Cookie值,通过遍历请求中的所有Cookie, + * 根据预定义的Cookie名称(COOKIE_NAME)来查找对应的Cookie,并返回其值(即登录Token),如果未找到则返回null, + * 以便在后续的业务逻辑中(如验证用户登录状态)根据获取到的Token进行相应的处理。 + * + * @param request HttpServletRequest对象,它是Servlet规范中用于接收客户端请求的接口实例,包含了请求的各种信息, + * 如请求头、请求参数以及这里要读取的Cookie信息等,通过这个对象可以获取到客户端发送过来的所有Cookie数据进行查找操作。 + * @return String 返回从请求中找到的与登录相关的Cookie的值(即登录Token),如果没有找到对应的Cookie,则返回null, + * 由调用者根据返回值判断是否获取到了有效的登录Token,进而决定后续的业务逻辑处理流程。 */ - public static String readLoginToken(HttpServletRequest request){ + public static String readLoginToken(HttpServletRequest request) { Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks){ - log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ - log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + // 从HttpServletRequest对象中获取所有的Cookie数组,这些Cookie是客户端(浏览器)在发送请求时自动携带过来的, + // 如果没有Cookie则返回null,所以需要先进行非空判断后再进行后续的查找操作。 + + if (cks!= null) { + for (Cookie ck : cks) { + log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 遍历所有获取到的Cookie,对于每个Cookie都记录其名称和值到日志中,方便查看请求中携带的Cookie具体情况,有助于排查问题以及了解请求中的Cookie信息全貌。 + + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); return ck.getValue(); + // 通过使用StringUtils的equals方法比较当前Cookie的名称与预定义的COOKIE_NAME是否相等,如果相等,说明找到了与登录相关的Cookie, + // 则记录该Cookie的名称和值到日志中,并返回其值(即登录Token),以便后续业务逻辑使用这个Token进行登录状态验证等操作。 } } } @@ -52,23 +96,43 @@ public class CookieUtil { /** * 注销的时候进行删除 - * @param request - * @param response + * 该方法用于在用户执行注销操作时,从客户端浏览器上删除之前存储的与登录相关的Cookie,通过遍历请求中的Cookie, + * 找到对应的登录Cookie后,设置其最大存活时间为0,并将修改后的Cookie添加回响应中,使得浏览器接收到后会删除该Cookie,从而实现注销时清除登录状态相关Cookie的功能。 + * + * @param request HttpServletRequest对象,用于获取客户端发送过来的请求中携带的Cookie信息,以便查找要删除的登录Cookie, + * 它包含了客户端请求的各种详细内容,是进行注销操作中Cookie删除处理的重要依据。 + * @param response HttpServletResponse对象,用于向客户端发送响应信息,在找到要删除的登录Cookie后,通过修改其属性并添加到这个响应对象中, + * 使得浏览器能够接收到更新后的Cookie信息并执行删除操作,完成注销时Cookie的清理工作。 */ - public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ + public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) { Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks) { - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ + // 首先从HttpServletRequest对象中获取客户端发送过来的所有Cookie数组,以便后续查找要删除的登录Cookie,同样需要先判断是否为空再进行遍历操作。 + + if (cks!= null) { + for (Cookie ck : cks) { + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { ck.setDomain(COOKIE_DOMAIN); + // 在找到名称与预定义的COOKIE_NAME相等的登录Cookie后,先重新设置其作用域名,确保与之前设置的一致,保证删除操作的准确性和有效性, + // 使其作用范围仍然限定在指定的域名下,避免误删其他无关的Cookie或者出现删除操作不符合预期的情况。 + ck.setPath("/"); - ck.setMaxAge(0);//0表示消除此cookie - log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + // 同样设置Cookie的路径为根目录("/"),保持与写入时的设置一致,确保在整个域名下的所有路径页面请求时都能正确删除该Cookie, + // 使得无论在哪个页面执行注销操作,都能对该登录Cookie进行有效的删除处理。 + + ck.setMaxAge(0); + // 设置Cookie的最大存活时间为0,表示让浏览器立即删除这个Cookie,当浏览器接收到这个属性设置后的Cookie响应时,会自动将其从本地存储中移除, + // 从而实现注销时清除登录相关Cookie的目的,达到清除用户登录状态的效果。 + + log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 记录要删除的Cookie的名称和值到日志中,方便查看注销操作中Cookie删除的具体情况,有助于后续排查可能出现的与Cookie删除相关的问题。 + response.addCookie(ck); + // 将修改后的Cookie添加到HttpServletResponse对象中,这样在响应发送给客户端(浏览器)时,浏览器会根据设置的属性(最大存活时间为0)删除对应的Cookie, + // 完成注销操作中对登录Cookie的删除流程,之后浏览器再发送请求时就不会携带这个已删除的登录Cookie了。 + return; } } } } - -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java index 8655b2a..2b9d0ac 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java @@ -4,65 +4,151 @@ import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** - * @DESC 时间转换的工具类 + * DateTimeUtil类是一个用于时间转换的工具类,它借助 `joda-time` 库以及Java标准的日期时间处理类(如 `SimpleDateFormat`), + * 提供了多种将字符串与 `Date` 对象之间相互转换的功能,还支持将 `Date` 对象转换为时间戳,方便在项目的不同业务场景中进行时间相关数据的处理和格式统一, + * 避免了在多个地方重复编写类似的时间转换代码,提高了代码的复用性和可维护性。 */ public class DateTimeUtil { - //joda-time + // joda-time + // 引入 `joda-time` 库来进行更方便、灵活的日期时间处理操作,相比Java原生的日期时间类(如 `java.util.Date` 和 `java.text.SimpleDateFormat`), + // `joda-time` 提供了更简洁、易读且不易出错的API,用于日期时间的格式化、解析以及各种运算等操作。 - //str->Date - //Date->str + // str->Date + // Date->str public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + // 定义了一个表示标准日期时间格式的常量字符串,格式为 "yyyy-MM-dd HH:mm:ss",用于在没有指定具体格式的情况下,作为默认的日期时间格式化和解析的格式模板, + // 确保整个项目中对于常见的日期时间表示形式能够统一进行处理,方便数据的交互和展示等操作。 - - - public static Date strToDate(String dateTimeStr, String formatStr){ + /** + * 将指定格式的日期时间字符串转换为 `Date` 对象的方法。 + * 通过使用 `joda-time` 库的 `DateTimeFormat` 和 `DateTime` 类,按照传入的格式字符串对输入的日期时间字符串进行解析,最终得到对应的 `Date` 对象。 + * + * @param dateTimeStr 表示要转换的日期时间字符串,需符合传入的 `formatStr` 所指定的格式要求,例如传入格式为 "yyyy-MM-dd" 的 `formatStr`, + * 那么 `dateTimeStr` 就需要是类似 "2024-01-01" 这样符合该格式的字符串,用于提供具体的日期时间信息进行转换操作。 + * @param formatStr 用于指定日期时间字符串的格式模板,通过这个参数告诉程序如何解析 `dateTimeStr`,例如 "yyyy-MM-dd HH:mm:ss" 等格式字符串, + * 需确保与实际传入的 `dateTimeStr` 的格式相匹配,否则会解析失败抛出异常。 + * @return Date 返回解析后的 `Date` 对象,如果解析成功,该对象代表了与输入的日期时间字符串对应的具体日期时间点,可用于后续的日期时间相关业务操作, + * 若解析失败(如格式不匹配等原因)会抛出相应的解析异常(由 `joda-time` 库内部机制抛出),需要调用者进行适当的异常处理。 + */ + public static Date strToDate(String dateTimeStr, String formatStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr); + // 根据传入的格式字符串创建 `DateTimeFormatter` 对象,它是 `joda-time` 库中用于定义日期时间格式化和解析规则的关键类, + // 可以按照指定的格式模式对日期时间字符串进行解析或者将日期时间对象格式化为字符串。 + DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); + // 使用创建好的 `DateTimeFormatter` 对象对传入的日期时间字符串进行解析,得到 `DateTime` 对象,`DateTime` 是 `joda-time` 库中表示具体日期时间的核心类, + // 它包含了丰富的日期时间操作方法,并且可以方便地与Java原生的 `Date` 对象进行转换。 + return dateTime.toDate(); + // 将 `DateTime` 对象转换为Java原生的 `Date` 对象并返回,使得转换后的结果可以在基于Java标准日期时间处理的业务逻辑中进行使用,例如存储到数据库等操作。 } - public static String dateToStr(Date date,String formatStr){ - if(date == null){ + /** + * 将 `Date` 对象按照指定格式转换为日期时间字符串的方法。 + * 首先判断传入的 `Date` 对象是否为 `null`,若为 `null` 则返回空字符串;否则使用 `joda-time` 库的 `DateTime` 类将 `Date` 对象进行包装, + * 再按照传入的格式字符串将其转换为对应的字符串表示形式并返回。 + * + * @param date 要转换的 `Date` 对象,代表了一个具体的日期时间点,如果传入 `null`,则按照业务逻辑返回空字符串, + * 正常情况下它包含了需要转换为字符串的日期时间信息,例如从数据库中查询出来的日期时间数据等。 + * @param formatStr 用于指定转换后的日期时间字符串的格式模板,例如 "yyyy-MM-dd" 可以将日期转换为只包含年月日的字符串形式, + * 通过这个参数可以灵活控制输出的日期时间字符串的格式,满足不同业务场景下对日期时间展示格式的需求。 + * @return String 返回按照指定格式转换后的日期时间字符串,如果传入的 `Date` 对象为 `null`,则返回空字符串, + * 否则返回符合指定格式的表示对应日期时间的字符串,方便在界面展示、数据传输等场景中使用该字符串形式的日期时间数据。 + */ + public static String dateToStr(Date date, String formatStr) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); + // 使用 `joda-time` 库的 `DateTime` 类将传入的 `Date` 对象进行包装,创建一个对应的 `DateTime` 对象, + // 以便后续利用 `DateTime` 类提供的格式化方法将其转换为字符串。 + return dateTime.toString(formatStr); + // 调用 `DateTime` 对象的 `toString` 方法,按照传入的格式字符串将其转换为对应的日期时间字符串并返回,实现了从 `Date` 对象到指定格式字符串的转换功能。 } - //固定好格式 - public static Date strToDate(String dateTimeStr){ + /** + * 将日期时间字符串按照固定的标准格式("yyyy-MM-dd HH:mm:ss")转换为 `Date` 对象的方法。 + * 此方法使用了类中定义的 `STANDARD_FORMAT` 常量作为默认的格式模板,调用 `strToDate` 方法进行具体的解析操作, + * 方便在项目中当日期时间字符串符合这个标准格式时,快速进行转换而无需每次都传入格式字符串参数。 + * + * @param dateTimeStr 要转换的日期时间字符串,需符合 "yyyy-MM-dd HH:mm:ss" 这个标准格式要求,例如 "2024-01-01 12:00:00", + * 它包含了具体的日期时间信息,通过该方法将其转换为对应的 `Date` 对象用于后续业务操作。 + * @return Date 返回解析后的 `Date` 对象,如果输入的日期时间字符串符合标准格式要求且解析成功,该对象代表了对应的具体日期时间点, + * 若不符合格式要求则会解析失败抛出相应异常(由 `joda-time` 库内部机制抛出),需要调用者进行适当的异常处理。 + */ + public static Date strToDate(String dateTimeStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT); + // 根据预定义的标准格式字符串创建 `DateTimeFormatter` 对象,用于后续按照这个固定格式对日期时间字符串进行解析操作。 + DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); + // 使用创建好的 `DateTimeFormatter` 对象对传入的日期时间字符串进行解析,得到 `DateTime` 对象。 + return dateTime.toDate(); + // 将 `DateTime` 对象转换为Java原生的 `Date` 对象并返回,使得可以在项目的相关业务逻辑中使用这个转换后的 `Date` 对象,比如进行日期时间比较、存储等操作。 } - public static String dateToStr(Date date){ - if(date == null){ + /** + * 将 `Date` 对象按照固定的标准格式("yyyy-MM-dd HH:mm:ss")转换为日期时间字符串的方法。 + * 同样使用了类中定义的 `STANDARD_FORMAT` 常量作为默认格式,先判断传入的 `Date` 对象是否为 `null`,若为 `null` 则返回空字符串, + * 否则调用 `dateToStr` 方法进行具体的转换操作,方便在项目中统一按照标准格式将 `Date` 对象转换为字符串进行展示、传输等操作。 + * + * @param date 要转换的 `Date` 对象,代表了一个具体的日期时间点,如果传入 `null`,则按照业务逻辑返回空字符串, + * 正常情况下用于提供需要转换为标准格式字符串的日期时间信息,例如获取系统当前时间并转换为标准格式字符串进行展示等场景。 + * @return String 返回按照标准格式转换后的日期时间字符串,如果传入的 `Date` 对象为 `null`,则返回空字符串, + * 否则返回符合 "yyyy-MM-dd HH:mm:ss" 格式的表示对应日期时间的字符串,便于在项目中统一展示日期时间数据,保持格式的一致性。 + */ + public static String dateToStr(Date date) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); + // 使用 `joda-time` 库的 `DateTime` 类将传入的 `Date` 对象进行包装,创建对应的 `DateTime` 对象,以便后续按照标准格式进行字符串转换操作。 + return dateTime.toString(STANDARD_FORMAT); + // 调用 `DateTime` 对象的 `toString` 方法,按照预定义的标准格式("yyyy-MM-dd HH:mm:ss")将其转换为对应的日期时间字符串并返回, + // 实现了将 `Date` 对象转换为标准格式字符串的功能,满足项目中对日期时间数据格式统一展示的需求。 } - //Date -> 时间戳 + /** + * 将 `Date` 对象转换为时间戳(从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数)的方法。 + * 通过使用Java标准的 `SimpleDateFormat` 类,按照指定的标准格式("yyyy-MM-dd HH:mm:ss")对 `Date` 对象进行格式化, + * 再将格式化后的字符串解析为 `Date` 对象(主要是为了确保格式的准确性以及兼容一些底层对 `Date` 处理的要求),最后获取其对应的时间戳并返回。 + * 注意,该方法可能会抛出 `ParseException` 异常,需要调用者进行适当的异常处理,例如在业务逻辑中捕获该异常并进行相应的提示或者日志记录等操作。 + * + * @param date 要转换为时间戳的 `Date` 对象,代表了一个具体的日期时间点,例如某个业务操作发生的具体时间等,通过这个对象来获取对应的时间戳数值, + * 如果传入 `null`,则按照业务逻辑返回 `null`,表示没有有效的日期时间信息可用于转换为时间戳。 + * @return Long 返回对应 `Date` 对象的时间戳数值(从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数),如果传入的 `Date` 对象为 `null`,则返回 `null`, + * 否则返回代表对应日期时间的时间戳,可用于一些需要基于时间戳进行比较、计算或者存储等的业务场景,例如在缓存设置过期时间(以时间戳为依据)等操作中使用。 + * @throws ParseException 该方法在使用 `SimpleDateFormat` 进行字符串解析为 `Date` 对象时,若格式不匹配等原因可能会抛出此异常, + * 需要调用者在调用该方法的地方使用 `try-catch` 块进行异常捕获和处理,以保证程序的稳定性。 + */ public static Long dateToChuo(Date date) throws ParseException { - if(date == null){ + if (date == null) { return null; } - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 创建一个 `SimpleDateFormat` 对象,使用预定义的标准格式("yyyy-MM-dd HH:mm:ss")作为格式化模板, + // 用于将 `Date` 对象格式化为对应的字符串,确保日期时间格式的准确性以及符合后续解析为时间戳的要求。 + return format.parse(String.valueOf(date)).getTime(); + // 先将传入的 `Date` 对象转换为字符串(通过 `String.valueOf` 方法),再使用创建好的 `SimpleDateFormat` 对象对这个字符串进行解析,重新得到 `Date` 对象, + // 这一步主要是为了确保日期时间格式符合预期以及兼容一些底层对 `Date` 处理的要求,然后通过调用 `getTime` 方法获取这个 `Date` 对象对应的时间戳(毫秒数)并返回, + // 实现了将 `Date` 对象转换为时间戳的功能。 } public static void main(String[] args) throws ParseException { - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); - String time="1970-01-06 11:45:55"; + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String time = "1970-01-06 11:45:55"; Date date = format.parse(time); - System.out.print("Format To times:"+date.getTime()); + System.out.print("Format To times:" + date.getTime()); + // 主方法用于简单测试 `dateToChuo` 方法(虽然这里实际并没有直接调用该方法进行测试,但功能类似,都是将日期时间字符串转换为 `Date` 对象后获取时间戳), + // 通过创建 `SimpleDateFormat` 对象,按照标准格式解析传入的日期时间字符串,得到 `Date` 对象,然后输出该 `Date` 对象对应的时间戳数值, + // 可以在本地运行该主方法来验证日期时间转换为时间戳的功能是否正常,方便在开发过程中进行简单的功能测试和调试。 } -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/FtpUtil.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/FtpUtil.java index ea43669..560a818 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/FtpUtil.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/FtpUtil.java @@ -10,73 +10,193 @@ import java.io.IOException; import java.util.List; /** - * @Author 【swg】. + * FtpUtil类是一个用于与FTP(File Transfer Protocol,文件传输协议)服务器进行交互,实现文件上传功能的工具类。 + * 它通过封装FTP连接、文件上传等相关操作,提供了方便的接口来将本地文件上传到指定的FTP服务器,同时利用日志记录上传过程中的关键信息和异常情况,便于调试和问题排查。 + * + * @Author 【swg】 * @Date 2018/1/11 14:32 * @DESC * @CONTACT 317758022@qq.com */ @Data +// @Data注解由Lombok库提供,会自动为类中的非-final字段生成对应的Getter和Setter方法, +// 并且重写toString()、hashCode()以及equals()等方法,减少了手动编写这些常规代码的工作量,使代码更加简洁,方便操作类中的成员变量。 @Slf4j +// 使用Lombok的@Slf4j注解,自动生成一个名为log的SLF4J日志记录器,用于在类中记录FTP文件上传操作相关的日志信息, +// 比如记录连接服务器、上传文件过程中的成功、失败以及出现的异常等情况,方便后续查看日志来追踪问题、分析操作流程。 + public class FtpUtil { private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip"); + // 以下三个静态变量用于存储从配置文件中读取的FTP服务器相关配置信息,通过PropertiesUtil工具类(此处未展示其代码,但推测用于读取配置属性)来获取相应的值。 + // 这种方式使得FTP服务器的配置可以灵活配置在外部文件中,方便根据不同的部署环境(如开发环境、测试环境、生产环境)进行修改,而无需改动代码本身。 + private static String ftpUser = PropertiesUtil.getProperty("ftp.user"); - private static String ftpPass = PropertiesUtil.getProperty("ftp.pass"); + // 存储FTP服务器的IP地址,通过配置文件获取,确定要连接的FTP服务器所在的网络位置,是建立FTP连接的基础信息之一。 + private static String ftpPass = PropertiesUtil.getProperty("ftp.pass"); + // 存储FTP服务器的用户名,用于后续连接FTP服务器时进行身份验证,确保只有合法授权的用户能够登录并操作FTP服务器。 + /** + * 构造方法,用于创建FtpUtil类的实例,接收FTP服务器的IP地址、端口号、用户名和密码作为参数, + * 将这些参数分别赋值给对应的成员变量,以便后续在与FTP服务器进行连接和文件上传操作时使用。 + * + * @param ip FTP服务器的IP地址,用于指定要连接的具体服务器,传入的值将覆盖类中通过配置文件读取的默认IP地址(如果需要使用不同的IP地址进行连接时)。 + * @param port FTP服务器的端口号,通常FTP默认端口是21,但也可以根据实际服务器配置进行修改,这里传入具体的端口号以确保能正确连接到FTP服务器。 + * @param user FTP服务器的用户名,用于进行身份验证,需是FTP服务器上已配置且有权限进行文件上传等操作的合法用户名。 + * @param pwd FTP服务器的密码,与用户名对应,用于完成登录验证,确保访问的安全性和合法性。 + */ public FtpUtil(String ip,int port,String user,String pwd){ this.ip = ip; this.port = port; this.user = user; this.pwd = pwd; } + + /** + * 静态方法,用于将给定的文件列表上传到FTP服务器。 + * 该方法首先使用从配置文件中获取的默认FTP服务器配置信息(IP地址、用户名、密码以及默认端口21)创建一个FtpUtil实例, + * 然后调用实例的uploadFile方法进行实际的文件上传操作,最后返回上传结果,方便外部代码简单地调用此方法实现文件上传功能,无需关心底层的具体操作细节。 + * + * @param fileList 要上传到FTP服务器的文件列表,是一个包含多个File对象的集合,每个File对象代表一个本地文件, + * 这些文件将被上传到FTP服务器的指定路径下,要求本地文件存在且具有可读权限,否则可能导致上传失败。 + * @return boolean 返回文件上传操作的结果,true表示所有文件都成功上传到FTP服务器,false表示在上传过程中出现了异常,导致部分或全部文件未能上传成功, + * 调用者可以根据返回值来判断文件上传是否成功,并进行相应的后续处理,例如提示用户上传成功或失败等操作。 + * @throws IOException 在文件上传过程中涉及到文件读取、FTP服务器连接等操作,这些操作可能会抛出IOException异常, + * 比如文件不存在、无法连接FTP服务器、网络传输错误等情况,需要调用者在调用此方法的地方进行适当的异常处理, + * 例如使用try-catch块捕获异常并进行相应的错误提示或日志记录等操作,以保证程序的稳定性。 + */ public static boolean uploadFile(List fileList) throws IOException { FtpUtil ftpUtil = new FtpUtil(ftpIp,21,ftpUser,ftpPass); + // 使用默认的FTP服务器配置信息创建FtpUtil实例,为后续连接服务器并上传文件做准备,这里使用配置文件中获取的IP地址、默认端口21以及对应的用户名和密码。 + log.info("开始连接ftp服务器"); + // 记录日志信息,提示开始尝试连接FTP服务器,方便后续查看日志来了解文件上传操作的执行顺序以及排查可能出现的连接问题。 + boolean result = ftpUtil.uploadFile("img",fileList); + // 调用实例的uploadFile方法,传入默认的远程路径(这里是"img",可推测是FTP服务器上用于存放上传文件的某个文件夹路径)和要上传的文件列表, + // 执行实际的文件上传操作,并获取上传结果(以布尔值表示成功与否),将结果存储在result变量中。 + log.info("开始连接ftp服务器,结束上传,上传结果:{}",result); + // 再次记录日志,告知文件上传操作已结束,并显示上传的结果,方便后续查看整个文件上传过程的情况,若上传失败可根据日志进一步查找具体原因。 + return result; } - + /** + * 私有方法,用于将给定的文件列表上传到指定远程路径的FTP服务器。 + * 此方法是实际执行文件上传操作的核心逻辑所在,它先尝试连接FTP服务器,若连接成功,则对FTP客户端进行一系列的配置设置(如工作目录、缓冲区大小、编码、文件类型、传输模式等), + * 然后遍历文件列表,逐个将文件通过文件输入流的方式上传到FTP服务器上,在上传过程中若出现异常会记录详细的错误日志,并将上传结果标记为失败, + * 最后无论上传是否成功,都会关闭文件输入流并断开与FTP服务器的连接,返回最终的上传结果。 + * + * @param remotePath FTP服务器上的远程路径,指定了要将文件上传到FTP服务器的具体文件夹位置,例如"img"表示将文件上传到FTP服务器名为"img"的文件夹下, + * 该路径需要在FTP服务器上实际存在且具有可写入权限,否则文件上传操作可能会失败,不同的业务场景可以根据需求传入相应的有效路径。 + * @param fileList 要上传到FTP服务器的文件列表,与前面方法中的fileList参数含义相同,是一个包含多个本地文件的集合, + * 每个文件都将按照顺序逐个上传到指定的远程路径下,要求本地文件存在且具有可读权限,以保证能够顺利读取文件内容进行上传。 + * @return boolean 返回文件上传操作的最终结果,true表示所有文件都成功上传到FTP服务器指定的远程路径下,false表示在上传过程中出现了异常,导致部分或全部文件未能上传成功, + * 由调用者根据返回值判断文件上传是否成功,进而进行相应的后续处理,比如更新本地文件状态、提示用户上传结果等操作。 + * @throws IOException 在执行文件上传相关操作时,如连接FTP服务器、设置FTP客户端参数、读取本地文件以及向FTP服务器传输文件等过程中, + * 都可能会抛出IOException异常,例如文件不存在、FTP服务器连接中断、权限不足等情况,需要调用者在调用此方法的地方使用try-catch块进行异常捕获和处理, + * 以保证程序在出现异常时能够正常响应,避免因异常导致程序崩溃等问题。 + */ private boolean uploadFile(String remotePath,List fileList) throws IOException { boolean uploaded = true; + // 初始化上传结果变量为true,表示默认情况下假设文件上传操作能够成功完成,后续如果在上传过程中出现异常情况,会将该变量修改为false来表示上传失败。 + FileInputStream fis = null; + // 定义一个文件输入流对象,用于读取本地文件的内容,初始化为null,在后续遍历文件列表上传文件时,会对其进行实例化操作,用于将文件内容通过FTP客户端上传到服务器, + // 最后需要在适当的地方关闭该输入流,以释放系统资源,避免资源泄漏。 + //连接FTP服务器 log.info("【开始连接文件服务器】"); + // 记录日志信息,提示开始进行连接FTP服务器的操作,便于后续查看日志了解文件上传操作的流程以及排查连接相关的问题。 + if(connectServer(this.ip,this.port,this.user,this.pwd)){ + // 调用connectServer方法尝试连接FTP服务器,并使用传入的IP地址、端口号、用户名和密码进行登录验证, + // 如果连接和登录成功(即connectServer方法返回true),则进入下面的代码块执行后续的文件上传相关操作,否则直接返回uploaded变量的值(此时为false,表示连接失败导致无法上传文件)。 + try { ftpClient.changeWorkingDirectory(remotePath); + // 通过FTP客户端对象(ftpClient)将工作目录切换到指定的远程路径下,确保后续上传的文件会被放置到这个正确的路径中, + // 如果指定的远程路径不存在或者当前用户没有权限切换到该目录等情况,将会抛出IOException异常,需要在后续的异常处理中进行相应的处理。 + ftpClient.setBufferSize(1024); + // 设置FTP客户端的缓冲区大小为1024字节,缓冲区用于在文件传输过程中临时存储数据,合理设置缓冲区大小可以提高文件传输效率, + // 根据实际的网络环境和文件大小等因素可以适当调整这个值,不过一般使用默认的或者常见的合适大小即可。 + ftpClient.setControlEncoding("UTF-8"); + // 设置FTP客户端的控制编码为"UTF-8",确保在与FTP服务器进行命令交互等过程中使用统一的字符编码,避免出现编码不一致导致的命令解析错误等问题, + // 特别是在处理包含中文等多字节字符的文件名等情况时,合适的编码设置尤为重要。 + ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); + // 设置FTP客户端传输文件的类型为二进制文件类型,适用于上传各种类型的文件(如图像、视频、文档等), + // 与ASCII文件类型(主要用于纯文本文件传输)区分开来,确保文件在传输过程中不会出现格式损坏等问题,保证文件的完整性。 + ftpClient.enterLocalPassiveMode(); + // 将FTP客户端设置为被动模式,在这种模式下,FTP服务器会主动向客户端发起数据连接,适用于客户端位于防火墙后面等网络环境, + // 可以提高文件传输的成功率,避免因网络限制导致无法建立数据连接而影响文件上传操作。 + for(File fileItem : fileList){ fis = new FileInputStream(fileItem); + // 对于文件列表中的每一个文件,创建一个文件输入流对象,用于读取该文件的内容,以便后续通过FTP客户端将文件内容上传到服务器, + // 如果文件不存在或者当前用户没有权限读取该文件等情况,将会抛出IOException异常,需要在异常处理中进行相应的处理。 + ftpClient.storeFile(fileItem.getName(),fis); + // 使用FTP客户端对象将从本地文件读取的内容上传到FTP服务器,以文件的实际名称(通过fileItem.getName()获取)作为在服务器上存储的文件名, + // 如果在上传过程中出现权限问题、网络传输错误等情况,可能会抛出IOException异常,影响文件上传的结果,后续会在异常处理中进行相应处理。 } } catch (IOException e) { log.error("上传文件异常",e); uploaded = false; e.printStackTrace(); + // 如果在文件上传相关的操作(如切换目录、设置参数、上传文件等过程)中出现IOException异常,记录详细的错误日志, + // 将表示上传结果的uploaded变量设置为false,表示文件上传失败,同时打印异常的堆栈信息,方便更深入地排查问题所在。 } finally { fis.close(); ftpClient.disconnect(); + // 在无论文件上传是否成功的情况下,都需要关闭文件输入流(避免资源泄漏)以及断开与FTP服务器的连接(释放网络资源等), + // 通过在finally块中执行这些操作,确保资源能够被正确释放,即使在出现异常的情况下也能保证程序的资源管理的正确性。 } } return uploaded; } - - + /** + * 私有方法,用于连接FTP服务器并进行登录验证操作。 + * 创建一个FTPClient对象,尝试连接到指定IP地址和端口号的FTP服务器,然后使用提供的用户名和密码进行登录, + * 根据登录结果返回是否连接成功的布尔值,若在连接或登录过程中出现异常,会记录详细的错误日志,方便后续排查连接失败的原因。 + * + * @param ip FTP服务器的IP地址,明确要连接的FTP服务器所在的网络位置,需要确保IP地址的准确性以及网络可达性, + * 可以是本地IP(如在本地测试环境中)或者远程服务器的IP(如生产环境中的FTP服务器IP),根据实际业务场景传入正确的IP地址值。 + * @param port FTP服务器的端口号,通常FTP服务器默认端口为21,但也可以根据实际服务器配置进行修改,传入准确的端口号才能确保与FTP服务器建立连接, + * 如果端口号设置错误或者被防火墙等限制,可能导致无法连接到FTP服务器,需要根据实际情况传入正确的端口值。 + * @param user FTP服务器的用户名,用于进行用户身份认证,必须是在FTP服务器上已经配置且具有相应权限(如文件上传权限等)的合法用户名, + * 通过从配置文件或者构造方法传入正确的用户名来确保能够成功登录服务器进行后续操作。 + * @param pwd FTP服务器的密码,与用户名对应,用于验证用户身份,保障只有授权用户能够连接并操作FTP服务器, + * 需要确保密码的准确性以及安全性(例如在配置文件中妥善保存,避免明文暴露在代码中),根据实际的FTP服务器用户配置传入正确的密码值。 + * @return boolean 返回是否成功连接并登录到FTP服务器的结果,true表示连接和登录操作成功,可以进行后续的文件上传等操作, + * false表示在连接或登录过程中出现了异常(如网络问题、用户名或密码错误等),导致无法正常登录FTP服务器, + * 由调用者根据返回值判断是否可以继续进行文件上传相关的操作,比如在uploadFile方法中根据这个返回值决定是否执行后续的上传流程。 + */ private boolean connectServer(String ip,int port,String user,String pwd){ boolean isSuccess = false; + // 初始化连接成功的结果变量为false,表示默认情况下假设连接操作可能会失败,后续根据实际的连接和登录情况来更新这个变量的值。 + ftpClient = new FTPClient(); + // 创建一个FTPClient对象,它是Apache Commons Net库提供的用于操作FTP服务器的核心类,通过这个对象可以进行连接服务器、登录、文件传输等一系列操作。 + try { ftpClient.connect(ip); + // 使用FTPClient对象尝试连接到指定IP地址的FTP服务器,若无法连接(比如IP地址不可达、服务器未启动等原因),会抛出IOException异常, + // 需要在后续的异常处理中进行相应的记录和处理,以判断连接失败的原因。 + isSuccess = ftpClient.login(user,pwd); + // 在连接成功的基础上,使用提供的用户名和密码尝试登录FTP服务器,通过FTPClient对象的login方法进行登录操作, + // 如果用户名或密码错误、用户没有相应权限等情况,登录操作会失败,返回false,并将其赋值给isSuccess变量, + // 若登录成功则将isSuccess设置为true,表示可以进行后续的文件上传等操作。 } catch (IOException e) { log.error("连接FTP服务器异常",e); + // 如果在连接FTP服务器或者登录过程中出现IOException异常,记录详细的错误日志,方便后续查看日志排查是网络问题还是认证问题等导致的连接异常情况。 } return isSuccess; } @@ -84,8 +204,23 @@ public class FtpUtil { private String ip; + // 用于存储FTP服务器的IP地址,通过构造方法或者默认配置(从配置文件读取)进行赋值,在连接FTP服务器等操作中会使用到这个IP地址信息, + // 确保与实际要连接的FTP服务器的网络位置相对应,是进行FTP操作的关键参数之一。 + private int port; + // 表示FTP服务器的端口号,同样可以通过构造方法传入或者使用默认值(通常为21),用于指定与FTP服务器建立连接时的端口, + // 不同的FTP服务器部署环境可能会使用不同的端口,通过这个成员变量可以灵活配置端口号,保证能够准确连接到目标FTP服务器。 + private String user; + // 存储FTP服务器的用户名,用于在连接FTP服务器时进行用户身份认证,必须是FTP服务器上合法有效的用户名, + // 通过合适的方式(如配置文件读取、构造方法传入等)获取正确的用户名,以确保能够成功登录服务器进行文件上传等操作。 + private String pwd; + // 存储的是与上述用户名对应的FTP服务器密码。它与用户名配合使用,作为登录FTP服务器的凭证,用于验证用户的身份合法性, + // 保障只有授权的用户能够访问和操作FTP服务器。同样,其值可通过构造方法设定或者依据配置文件中的默认配置来获取,并且要注意密码的保密性,避免在代码中明文暴露。 + private FTPClient ftpClient; + // FTPClient类型的成员变量,它是Apache Commons Net库中用于操作FTP服务器的核心类对象。 + // 通过这个对象,可以实现与FTP服务器建立连接(如调用其connect方法)、进行用户登录(login方法)、切换工作目录(changeWorkingDirectory方法)、设置文件传输相关参数(如缓冲区大小、文件类型等)以及执行文件上传(storeFile方法)等一系列操作, + // 是整个FTP文件上传功能实现的关键对象,在类中的多个方法中都会对其进行操作来完成与FTP服务器的交互过程。 } diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java index 12daca4..e5940ee 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java @@ -8,119 +8,190 @@ import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.annotate.JsonSerialize; import org.codehaus.jackson.type.JavaType; import org.codehaus.jackson.type.TypeReference; - import java.io.IOException; import java.text.SimpleDateFormat; /** - * jackson的序列化和反序列化 + * JsonUtil类是一个用于处理JSON数据序列化和反序列化操作的工具类,它基于Jackson库(这里使用的是Codehaus的Jackson版本), + * 通过对ObjectMapper进行一系列配置,实现了将Java对象转换为JSON字符串(序列化)以及将JSON字符串转换为Java对象(反序列化)的功能, + * 并且提供了多种不同场景下适用的序列化和反序列化方法,方便在项目中统一进行JSON数据处理,同时通过记录日志来对转换过程中出现的异常情况进行提示,便于排查问题。 + * + * @Slf4j注解用于自动生成一个名为log的SLF4J日志记录器,用于在JSON序列化和反序列化操作出现问题时记录警告信息以及异常堆栈信息等, + * 方便后续查看日志来了解转换失败的原因以及出现的相关情况,有助于调试和维护代码。 */ @Slf4j public class JsonUtil { private static ObjectMapper objectMapper = new ObjectMapper(); + // 创建一个静态的ObjectMapper对象,ObjectMapper是Jackson库中用于进行JSON序列化和反序列化的核心类, + // 通过这个对象可以配置各种序列化和反序列化的规则,并执行具体的转换操作,将其定义为静态成员变量,方便在整个类的各个静态方法中共享使用。 static { //所有字段都列入进行转换 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); + // 通过设置ObjectMapper的SerializationInclusion属性为JsonSerialize.Inclusion.ALWAYS,指定在序列化Java对象为JSON字符串时, + // 将对象的所有字段都包含进转换结果中,无论字段的值是否为null,这样可以保证完整的对象数据结构能在JSON中体现,避免遗漏字段信息。 + //取消默认转换timestamp形式 - objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); + objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); + // 配置ObjectMapper取消默认将日期类型字段转换为时间戳形式的行为,通常Jackson默认会把日期对象转换为时间戳进行序列化, + // 这里设置为false后,会按照后续配置的日期格式(通过setDateFormat方法)进行日期的序列化,使得日期在JSON中的表示更符合常规的日期格式要求,便于阅读和理解。 + //忽略空bean转json的错误 - objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); + objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false); + // 设置ObjectMapper在序列化空的Java Bean(即没有任何属性值的对象)为JSON时忽略可能出现的错误,默认情况下,序列化空Bean可能会抛出异常, + // 通过将此配置设置为false,使得在遇到这种情况时能够正常返回空的JSON对象(如"{}"),而不会导致程序中断,增强了序列化操作的容错性。 + //统一时间的格式 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); + // 为ObjectMapper设置统一的日期格式,这里使用了DateTimeUtil类中定义的STANDARD_FORMAT常量(格式为"yyyy-MM-dd HH:mm:ss"), + // 确保在序列化和反序列化过程中,日期类型的数据都按照这个标准格式进行处理,使得整个项目中对于日期的JSON表示形式保持一致,方便数据的交互和处理。 + //忽略json存在属性,但是java对象不存在属性的错误 - objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); + objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); + // 配置ObjectMapper在进行反序列化(将JSON字符串转换为Java对象)时,忽略JSON字符串中存在但Java对象中不存在对应属性的情况,默认情况下这种情况会抛出异常, + // 通过将此配置设置为false,使得反序列化过程能够更加灵活,即使JSON数据比Java对象定义的属性多一些,也可以正常进行反序列化,只处理Java对象中定义的属性对应的JSON数据,避免不必要的错误抛出。 } /** * 序列化方法,将对象转为字符串 - * @param obj - * @param - * @return + * 该方法用于将给定的Java对象转换为JSON字符串表示形式,首先判断传入的对象是否为null,如果为null则直接返回null, + * 若对象不为null,则尝试使用ObjectMapper将其转换为JSON字符串,若转换过程中出现IOException异常,会记录警告日志并返回null, + * 若对象本身就是String类型,则直接返回该字符串对象,否则执行常规的序列化操作。 + * + * @param obj 要进行序列化的Java对象,它可以是任意类型的Java对象,只要该对象的类结构能够被Jackson库正确处理(即类中的属性、方法等符合Jackson序列化的要求), + * 例如常见的POJO(Plain Old Java Object)类对象、集合对象等都可以传入进行序列化操作,若传入null则表示没有有效的对象需要序列化,直接返回null。 + * @param 泛型参数,用于表示传入对象的类型以及返回JSON字符串对应的对象类型,在方法调用时会根据实际传入的对象类型自动推断出具体的泛型类型, + * 保证了方法的通用性,能够处理各种类型的对象序列化需求。 + * @return String 返回与传入的Java对象对应的JSON字符串表示形式,如果对象为null或者序列化过程中出现异常则返回null, + * 正常情况下返回的JSON字符串符合ObjectMapper中配置的序列化规则(如日期格式、包含所有字段等),可用于数据传输、存储等场景, + * 例如可以将返回的JSON字符串发送给前端页面进行展示或者存储到文件、数据库等地方。 */ - public static String obj2String(T obj){ - if(obj == null){ + public static String obj2String(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); + return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** * 序列化方法,同上,只是输出的格式是美化的,便于测试 - * @param obj - * @param - * @return + * 与obj2String方法功能类似,也是将Java对象转换为JSON字符串,不同之处在于该方法会输出格式化后的、更易于阅读的JSON字符串, + * 主要用于在开发测试阶段,方便查看JSON数据的结构和内容,同样会先判断对象是否为null,若为null则返回null, + * 在转换过程中若出现IOException异常会记录警告日志并返回null,若对象本身是String类型则直接返回该字符串对象,否则执行美化格式的序列化操作。 + * + * @param obj 要进行序列化的Java对象,其要求和作用与obj2String方法中的obj参数相同,是任意符合Jackson序列化要求的Java对象, + * 可以是不同类型的POJO、集合等对象,用于生成对应的JSON字符串表示形式,若传入null则直接返回null,不进行序列化操作。 + * @param 泛型参数,与obj2String方法中的泛型参数作用一致,用于表示传入对象的类型以及返回JSON字符串对应的对象类型, + * 根据实际传入的对象自动推断具体类型,以支持各种类型对象的序列化并保证方法的通用性。 + * @return String 返回经过美化格式后的与传入Java对象对应的JSON字符串,若对象为null或者转换过程中出现异常则返回null, + * 美化后的JSON字符串具有缩进、换行等格式,更方便人工查看和阅读,有助于在测试过程中快速确认JSON数据的结构和内容是否符合预期, + * 例如在调试接口返回的JSON数据时可以使用该方法输出更清晰的JSON内容进行查看。 */ - public static String obj2StringPretty(T obj){ - if(obj == null){ + public static String obj2StringPretty(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** * 比较简单的反序列化的方法,将字符串转为单个对象 - * @param str - * @param clazz - * @param - * @return + * 此方法用于将给定的JSON字符串转换为指定类型的Java对象,首先判断传入的JSON字符串是否为空以及指定的目标Java类是否为null, + * 若两者有一个为null则直接返回null,若都不为null,则尝试使用ObjectMapper将JSON字符串反序列化为目标类型的Java对象, + * 在反序列化过程中若出现IOException异常,会记录警告日志并返回null,若目标类型就是String类,则直接将JSON字符串转换为对应的String对象返回, + * 否则执行常规的反序列化操作将JSON数据转换为指定类型的Java对象。 + * + * @param str 要进行反序列化的JSON字符串,需符合ObjectMapper配置的JSON格式要求(如日期格式、属性对应等)以及目标Java对象的结构要求, + * 例如JSON字符串中的属性名称和类型要与目标Java类中的属性相匹配(忽略配置中允许的未知属性情况),若传入空字符串则表示没有有效的JSON数据需要反序列化,直接返回null。 + * @param clazz 目标Java对象的类类型,通过传入具体的Class对象指定要将JSON字符串反序列化为哪种类型的Java对象, + * 例如传入User.class表示要将JSON字符串转换为User类型的对象,这个类需要符合Jackson反序列化的要求(如包含合适的构造方法、属性的Getter和Setter等), + * 若传入null则无法确定目标对象类型,直接返回null,不进行反序列化操作。 + * @param 泛型参数,用于表示返回的Java对象类型,会根据传入的clazz参数自动推断出具体的类型,保证了方法能够根据不同的目标类型进行反序列化操作, + * 使得可以灵活地将JSON字符串转换为各种类型的Java对象,满足项目中不同业务场景下的反序列化需求。 + * @return 返回与传入的JSON字符串对应的、指定类型的Java对象,如果JSON字符串为空、目标类为null或者反序列化过程中出现异常则返回null, + * 正常情况下返回的Java对象包含了从JSON字符串中解析出来的数据,可用于后续的业务逻辑操作,比如在接收到前端发送的JSON数据后, + * 通过该方法将其转换为后端对应的Java对象进行数据处理、存储等操作。 */ - public static T String2Obj(String str,Class clazz){ - if(StringUtils.isEmpty(str) || clazz == null){ + public static T String2Obj(String str, Class clazz) { + if (StringUtils.isEmpty(str) || clazz == null) { return null; } try { - return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz); + return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** * 复杂对象的反序列化(通用) - * @param str - * @param typeReference - * @param - * @return + * 该方法用于处理复杂类型对象的反序列化,特别是当需要将JSON字符串转换为一些复杂的、带有泛型结构的Java对象(如包含嵌套的集合、自定义泛型类等)时使用, + * 首先判断传入的JSON字符串是否为空以及指定的TypeReference是否为null,若有一个为null则直接返回null, + * 若都不为null,则尝试根据TypeReference指定的类型信息使用ObjectMapper将JSON字符串反序列化为对应的复杂Java对象, + * 在反序列化过程中若出现IOException异常,会记录警告日志并返回null,若TypeReference指定的类型就是String类,则直接将JSON字符串作为对应的String对象返回, + * 否则执行常规的反序列化操作将JSON数据转换为复杂的Java对象。 + * + * @param str 要进行反序列化的JSON字符串,同样需符合ObjectMapper配置的JSON格式要求以及对应复杂Java对象的结构要求, + * 由于是处理复杂对象,JSON字符串的结构可能更复杂,例如包含多层嵌套的对象、集合等内容,若传入空字符串则直接返回null,不进行反序列化操作。 + * @param typeReference 用于指定复杂Java对象的类型信息的TypeReference对象,通过创建特定的TypeReference实例,可以准确地描述包含泛型等复杂结构的对象类型, + * 例如对于List类型的对象,可以通过创建合适的TypeReference实例来告诉ObjectMapper如何将JSON字符串反序列化为这种复杂的类型, + * 若传入null则无法确定目标复杂对象的类型,直接返回null,不进行反序列化操作。 + * @param 泛型参数,用于表示返回的复杂Java对象的类型,会根据typeReference中指定的类型自动推断具体类型, + * 以支持各种复杂类型结构的反序列化,满足项目中对复杂JSON数据转换为Java对象的多样化需求,比如处理接口返回的复杂嵌套结构的JSON数据等情况。 + * @return 返回与传入的JSON字符串对应的、指定复杂类型的Java对象,如果JSON字符串为空、typeReference为null或者反序列化过程中出现异常则返回null, + * 正常情况下返回的复杂Java对象包含了从JSON字符串中解析出来的复杂数据结构,可用于后续的业务逻辑操作,比如在处理复杂的业务数据交互场景中, + * 将接收到的复杂JSON数据转换为后端对应的Java对象进行深层次的数据处理、存储以及业务逻辑运算等操作。 */ - public static T Str2Obj(String str, TypeReference typeReference){ - if(StringUtils.isEmpty(str) || typeReference == null){ + public static T Str2Obj(String str, TypeReference typeReference) { + if (StringUtils.isEmpty(str) || typeReference == null) { return null; } try { - return (T) (typeReference.getType().equals(String.class)?str:objectMapper.readValue(str,typeReference)); + return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference)); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** * 第二种方式实现复杂对象的反序列化 - * @param str - * @param collectionClass - * @param elementClasses - * @param - * @return + * 这是另一种实现复杂对象反序列化的方法,通过传入集合类类型以及元素类类型信息,利用ObjectMapper的类型工厂(TypeFactory)构建JavaType对象, + * 来准确描述复杂的、带有泛型结构的集合类型等Java对象的类型信息,然后使用ObjectMapper将传入的JSON字符串反序列化为对应的复杂Java对象, + * 在反序列化过程中若出现IOException异常,会记录警告日志并返回null,通过这种方式可以灵活处理多种复杂的集合类型等对象的反序列化需求。 + * + * @param str 要进行反序列化的JSON字符串,需要满足ObjectMapper配置的JSON格式要求以及对应复杂Java对象的结构要求, + * 由于是针对复杂对象的反序列化,JSON字符串结构通常较复杂,可能包含多层嵌套的集合、对象等内容,若传入空字符串则直接返回null,不进行反序列化操作。 + * @param collectionClass 表示集合类型的Java类,例如List.class、Set.class等,用于指定要反序列化的复杂对象是哪种集合类型的结构, + * 通过传入具体的集合类类型,告诉ObjectMapper目标对象的最外层结构是哪种集合,为构建复杂对象的类型信息做准备, + * 若传入null则无法确定集合类型,直接返回null,不进行反序列化操作。 + * @param elementClasses 可变参数,用于指定集合中元素的类型信息,是一个或多个Java类类型,按照顺序依次表示集合中元素的类型, + * 例如对于List这样的结构(假设在业务中有这种需求),可以传入User.class和Role.class作为elementClasses参数, + * 配合collectionClass准确地描述复杂对象的完整类型结构,若不传或传入null等情况则无法准确构建类型信息,直接返回null,不进行反序列化操作。 + * @param 泛型参数,用于表示返回的复杂Java对象的类型,会根据传入的collectionClass和elementClasses参数构建的JavaType自动推断具体类型, + * 以支持各种复杂的集合类型以及包含多种元素类型的复杂对象的反序列化,满足项目中不同业务场景下对复杂JSON数据转换为Java对象的多样化需求。 + * @return 返回与传入的JSON字符串对应的、指定复杂类型的Java对象,如果JSON字符串为空、collectionClass为null或者反序列化过程中出现异常则返回null, + * 正常情况下返回的复杂Java对象包含了从JSON字符串中解析出来的复杂数据结构,可用于后续的业务逻辑操作,比如在处理包含复杂集合结构的业务数据交互场景中, + * 将接收到的JSON数据转换为后端对应的Java对象进行深层次的数据处理、存储以及业务逻辑运算等操作。 */ - public static T Str2Obj(String str,Class collectionClass,Class... elementClasses){ - JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); + public static T Str2Obj(String str, Class collectionClass, Class... elementClasses) { + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); try { - return objectMapper.readValue(str,javaType); + return objectMapper.readValue(str, javaType); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java index e6e5c8a..7b0901e 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java @@ -3,47 +3,115 @@ package com.njupt.swg.common.utils; import java.security.MessageDigest; /** - * MD5加密工具类 + * MD5Util类是一个用于进行MD5加密操作的工具类,提供了将输入字符串转换为MD5加密后的字符串的功能。 + * MD5(Message-Digest Algorithm 5)是一种常用的哈希函数,用于生成消息摘要,通常用于对数据(如用户密码等敏感信息)进行加密处理,以保障数据的安全性和完整性, + * 在本类中通过一系列方法实现了MD5加密,并可以指定字符编码以及提供了以UTF-8编码进行加密的便捷方法,同时在主方法中提供了简单的测试示例。 */ public class MD5Util { + /** + * 将字节数组转换为十六进制字符串的私有方法。 + * 该方法遍历传入的字节数组,将每个字节通过byteToHexString方法转换为对应的十六进制表示形式,并依次追加到StringBuffer对象中, + * 最后返回拼接好的十六进制字符串,这个字符串就是字节数组对应的十六进制形式的表示,常用于将加密后的字节数组结果转换为可读的十六进制字符串形式展示。 + * + * @param b 要转换的字节数组,通常是经过加密算法(如MD5加密)处理后得到的字节数组结果,需要将其转换为十六进制字符串以便于查看和后续使用, + * 例如在MD5加密过程中,对原始数据加密后得到的字节数组就会通过这个方法进行转换,使其变为常见的十六进制形式的加密结果字符串。 + * @return String 返回由字节数组转换得到的十六进制字符串,代表了对应字节数组的十六进制表示形式,可作为加密后的最终结果展示或者进一步处理等操作, + * 例如在MD5加密中作为加密后的字符串返回给调用者,用于验证数据完整性或者存储加密后的密码等用途。 + */ private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); + // 创建一个StringBuffer对象,用于拼接字节对应的十六进制字符,StringBuffer是可变的字符序列,适合在循环中频繁追加字符的操作, + // 相比String的不可变特性,使用它可以提高性能,避免过多的字符串拼接产生的临时对象开销。 + for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); + // 遍历传入的字节数组,对于每个字节调用byteToHexString方法将其转换为十六进制表示形式,并追加到resultSb对象中, + // 通过循环处理完整个字节数组后,resultSb就包含了所有字节对应的十六进制字符的拼接结果。 return resultSb.toString(); + // 将StringBuffer对象转换为不可变的String对象并返回,得到最终的十六进制字符串,完成字节数组到十六进制字符串的转换过程。 } + /** + * 将单个字节转换为十六进制字符串的私有方法。 + * 首先对传入的字节进行处理,如果字节值为负数(在Java中字节是有符号的,范围是 -128 到 127),则将其转换为无符号整数(通过加上256), + * 然后分别计算该整数对应的十六进制数的高位和低位数字,通过查找预定义的十六进制字符数组(hexDigits)获取对应的十六进制字符, + * 最后将这两个十六进制字符拼接起来返回,得到单个字节对应的十六进制字符串表示形式。 + * + * @param b 要转换的单个字节,通常是字节数组中的一个元素,在byteArrayToHexString方法中会对字节数组中的每个字节依次调用这个方法进行转换, + * 例如在处理MD5加密后的字节数组中的每个字节时,会通过这个方法将其转换为十六进制形式,便于后续组成完整的十六进制加密结果字符串。 + * @return String 返回传入字节对应的十六进制字符串,长度为2,代表了该字节的十六进制表示形式,是组成最终十六进制加密结果字符串的基本单元, + * 例如字节值为10对应的十六进制字符串为"0a",字节值为15对应的十六进制字符串为"0f"等,这些单个字节的十六进制字符串会拼接起来形成完整的加密结果字符串。 + */ private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; + // 由于Java中的字节是有符号的,取值范围是 -128 到 127,而在十六进制表示中我们需要使用无符号整数(0 到 255), + // 所以当字节值为负数时,通过加上256将其转换为无符号整数范围,以便后续正确计算十六进制表示形式。 + int d1 = n / 16; int d2 = n % 16; + // 分别计算转换后的无符号整数对应的十六进制数的高位和低位数字,通过除以16取整得到高位数字(十六进制的十位),通过取模16得到低位数字(十六进制的个位), + // 例如对于整数20,除以16得到高位数字1,取模16得到低位数字4,对应十六进制表示就是"14"。 + return hexDigits[d1] + hexDigits[d2]; + // 通过查找预定义的十六进制字符数组(hexDigits),获取对应高位和低位数字的十六进制字符,然后将它们拼接起来返回, + // 得到单个字节对应的十六进制字符串表示形式,例如对于字节值为10,经过计算和查找字符数组后返回的十六进制字符串就是"0a"。 } /** - * 返回大写MD5 + * 执行MD5加密并返回大写MD5加密结果字符串的私有方法。 + * 首先创建一个新的字符串对象复制传入的原始字符串(这里其实可以优化,直接使用原字符串即可,创建新对象有额外开销), + * 然后获取MD5加密算法的MessageDigest实例,根据传入的字符编码情况(如果为空或空字符串则使用默认字符编码,否则使用指定字符编码), + * 将原始字符串转换为字节数组并传入MessageDigest进行MD5加密计算,得到加密后的字节数组,最后通过byteArrayToHexString方法将字节数组转换为十六进制字符串, + * 并将结果转换为大写形式返回,得到最终的MD5加密结果字符串。 * - * @param origin - * @param charsetname - * @return + * @param origin 要进行MD5加密的原始字符串,通常是需要加密的数据,比如用户密码等敏感信息,传入的字符串内容会经过MD5算法处理转换为加密后的表示形式, + * 其内容应该符合指定的字符编码要求(如果传入了字符编码参数),否则可能导致编码转换异常等问题。 + * @param charsetname 用于指定对原始字符串进行编码转换时采用的字符编码名称,例如"utf-8"、"gbk"等, + * 如果传入null或者空字符串,则使用默认的字符编码(取决于系统环境等因素)将原始字符串转换为字节数组进行MD5加密操作, + * 通过这个参数可以灵活控制字符编码,确保在不同编码环境下都能正确进行MD5加密处理。 + * @return String 返回经过MD5加密后的十六进制字符串表示形式的结果,并且转换为大写字母形式,方便统一格式展示以及在一些需要固定格式对比验证等场景中使用, + * 例如在验证用户登录密码时,将用户输入的密码加密后与存储的加密后的密码(通常也是大写MD5加密结果)进行对比验证,以判断密码是否正确。 */ private static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); + // 这里创建了一个新的字符串对象复制传入的原始字符串,其实可以直接使用origin,创建新对象会有额外的内存开销,不过在当前代码逻辑下功能上是等效的。 + MessageDigest md = MessageDigest.getInstance("MD5"); + // 通过Java的安全框架获取MD5加密算法对应的MessageDigest实例,MessageDigest是用于计算消息摘要(如MD5、SHA等算法)的抽象类, + // 这里指定"MD5"算法名称来获取用于MD5加密操作的具体实例,后续可以通过这个实例对数据进行MD5加密计算。 + if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString.getBytes())); else resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname))); + // 根据传入的字符编码情况进行不同的处理,如果字符编码参数为空或空字符串,就使用默认字符编码将原始字符串转换为字节数组, + // 然后传入MessageDigest实例的digest方法进行MD5加密计算,得到加密后的字节数组,再通过byteArrayToHexString方法将字节数组转换为十六进制字符串, + // 如果传入了有效的字符编码名称,则按照指定的字符编码将原始字符串转换为字节数组后进行MD5加密和后续的转换操作,最终得到加密后的十六进制字符串表示形式的结果。 } catch (Exception exception) { + // 当前代码中捕获了异常但没有进行任何处理,这可能不太合适,在实际应用中建议根据具体的业务需求进行相应的异常处理, + // 比如记录日志、抛出更具体的业务异常等操作,以便更好地排查问题和反馈给调用者加密操作出现了异常情况。 } return resultString.toUpperCase(); + // 将得到的MD5加密后的十六进制字符串结果转换为大写形式返回,这样在不同环境下返回的加密结果格式统一,便于后续的比较、验证等操作, + // 例如在存储用户密码的MD5加密值或者验证密码是否匹配等场景中,统一使用大写形式可以避免因大小写差异导致的验证错误等问题。 } + /** + * 以UTF-8编码对输入字符串进行MD5加密并返回结果的公共方法。 + * 该方法直接调用MD5Encode方法,传入原始字符串和"utf-8"字符编码参数,实现了以UTF-8编码对输入字符串进行MD5加密的便捷操作, + * 并且在实际应用中可以考虑在此方法内添加“加盐”操作(虽然当前代码中只是简单调用了MD5Encode方法),“加盐”可以增加密码等加密信息的安全性, + * 常用于对用户密码等敏感信息进行加密处理,返回加密后的十六进制字符串结果,方便在业务逻辑中进行后续的使用,比如存储加密后的密码、验证密码等操作。 + * + * @param origin 要进行MD5加密的原始字符串,与MD5Encode方法中的origin参数含义相同,通常是需要加密的敏感数据,如用户密码等, + * 这里要求传入的字符串内容符合UTF-8编码规范,以便正确进行加密操作,避免出现编码相关的异常情况。 + * @return String 返回以UTF-8编码对原始字符串进行MD5加密后的十六进制字符串结果,可用于后续业务逻辑中与存储的加密值对比验证或者其他需要使用加密结果的场景, + * 例如在用户登录验证密码时,将用户输入密码通过此方法加密后的结果与数据库中存储的加密后的密码进行对比,判断密码是否正确。 + */ public static String MD5EncodeUtf8(String origin) { //这里可以加盐 return MD5Encode(origin, "utf-8"); @@ -51,9 +119,13 @@ public class MD5Util { public static void main(String[] args) { System.out.println(MD5EncodeUtf8("123456")); + // 主方法提供了一个简单的测试示例,调用MD5EncodeUtf8方法对字符串"123456"进行MD5加密,并将加密后的结果输出到控制台, + // 方便在本地运行代码时快速查看MD5加密功能是否正常,验证加密方法是否按照预期对输入字符串进行了加密处理, + // 在实际开发过程中可以通过修改传入的字符串参数来测试不同内容的MD5加密结果,也可以在此基础上添加更多的测试逻辑,比如验证加密结果的格式等。 } - private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; -} + // 定义一个私有的静态字符串数组,用于存储十六进制的数字和字母对应的字符表示,在byteToHexString方法中通过查找这个数组来获取字节对应的十六进制字符, + // 数组的索引对应十六进制数的高位或低位数字,例如索引0对应十六进制字符"0",索引10对应十六进制字符"a"等,方便将字节值转换为十六进制字符串表示形式。 +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/PropertiesUtil.java b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/PropertiesUtil.java index 79f8335..7a7f066 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/PropertiesUtil.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/common/utils/PropertiesUtil.java @@ -2,45 +2,98 @@ package com.njupt.swg.common.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; - import java.io.IOException; import java.io.InputStreamReader; import java.util.Properties; /** - * @Author 【swg】. + * PropertiesUtil类是一个用于读取配置文件属性的工具类,它基于Java的 `Properties` 类,提供了方便的方法来获取配置文件中指定键对应的属性值, + * 并且在类初始化时会尝试加载指定的配置文件,如果加载过程中出现异常会进行日志记录,方便后续排查问题。常用于项目中从配置文件获取各种配置参数(如数据库连接信息、FTP服务器配置等), + * 使得配置信息能够与代码分离,便于修改和维护。 + * + * @Author 【swg】 * @Date 2018/1/10 14:56 * @DESC * @CONTACT 317758022@qq.com */ @Slf4j +// 使用Lombok的@Slf4j注解,自动生成一个名为log的SLF4J日志记录器,用于在配置文件读取出现异常时记录错误信息,方便后续查看日志来了解配置文件加载失败的原因等情况, +// 保证在出现问题时能够快速定位并排查相关的异常情况。 public class PropertiesUtil { private static Properties props; + // 定义一个静态的 `Properties` 类对象,`Properties` 类是Java中用于处理配置文件(通常是 `.properties` 格式)的常用类, + // 它可以存储键值对形式的配置信息,这里将其定义为静态成员变量,方便在整个类的不同方法中共享使用,用于存储从配置文件中读取的所有属性数据。 static { String fileName = "parameter.properties"; + // 指定要读取的配置文件的名称,这里固定为 "parameter.properties",表示程序默认会尝试从类路径下查找并读取这个名称的配置文件, + // 当然,根据实际需求也可以考虑通过其他方式传入不同的文件名,使得读取的配置文件更具灵活性,不过当前代码是使用固定的这个文件名进行操作。 + props = new Properties(); + // 创建一个 `Properties` 对象实例,用于后续加载配置文件中的属性数据,在内存中创建一个空的属性集合,等待从文件中读取数据填充进去。 + try { - props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8")); + props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName), "UTF-8")); + // 通过类加载器(ClassLoader)获取指定配置文件(fileName)的输入流(getResourceAsStream方法),并使用 `InputStreamReader` 将其包装为字符流, + // 指定字符编码为 "UTF-8",确保能够正确读取配置文件中的中文等多字节字符内容,然后调用 `Properties` 对象的 `load` 方法,将配置文件中的键值对数据加载到 `props` 对象中, + // 如果配置文件不存在、格式错误或者读取过程中出现其他I/O相关的问题,会抛出 `IOException` 异常,需要在 `catch` 块中进行相应的处理。 } catch (IOException e) { - log.error("配置文件读取异常",e); + log.error("配置文件读取异常", e); + // 如果在加载配置文件过程中出现 `IOException` 异常,使用自动生成的日志记录器记录详细的错误信息,包括异常堆栈信息, + // 方便后续查看日志来排查是文件路径问题、编码问题还是其他I/O异常导致的配置文件读取失败情况,不过当前代码只是记录了日志,没有进行其他额外的恢复或提示操作, + // 在实际应用中可以根据具体需求考虑添加更多的处理逻辑,比如尝试使用默认配置或者提示用户配置文件读取失败等情况。 } } - public static String getProperty(String key){ + /** + * 获取配置文件中指定键对应的属性值的方法,如果属性值为空(空白字符串或者不存在对应的键)则返回 `null`。 + * 首先通过 `Properties` 对象获取指定键对应的属性值,然后对获取到的值进行空白字符串判断,若为空则返回 `null`,否则返回去除首尾空白字符后的属性值, + * 这样可以确保获取到的属性值在使用时更加规范,避免因空白字符导致的一些潜在问题,比如在后续拼接字符串、进行比较等操作时出现不符合预期的情况。 + * + * @param key 要获取属性值的键,在配置文件中以键值对形式存在,通过传入这个键,可以从之前加载的配置文件(`props` 对象中存储的内容)查找对应的属性值, + * 需要确保键的名称准确无误,并且与配置文件中定义的键保持一致,否则将无法获取到期望的属性值,可能返回 `null` 或者错误的值, + * 同时在传入前最好对键进行必要的格式处理(如去除多余空白字符等),虽然当前代码内部也会进行 `trim` 操作,但统一格式可以减少不必要的逻辑开销。 + * @return String 返回配置文件中与指定键对应的属性值,如果属性值为空(空白字符串或者不存在对应的键)则返回 `null`, + * 正常情况下返回去除首尾空白字符后的属性值字符串,可用于后续在项目中作为各种配置参数进行使用,比如数据库连接的用户名、密码,服务器的IP地址等配置信息的获取。 + */ + public static String getProperty(String key) { String value = props.getProperty(key.trim()); - if(StringUtils.isBlank(value)){ + // 调用 `Properties` 对象的 `getProperty` 方法,传入去除首尾空白字符后的键(通过 `trim` 方法处理),尝试获取对应的属性值, + // 从之前加载的配置文件数据(存储在 `props` 对象中)中查找与该键匹配的属性值,如果找到则返回对应的字符串值,若不存在则返回 `null`。 + + if (StringUtils.isBlank(value)) { return null; } return value.trim(); + // 使用 `StringUtils` 的 `isBlank` 方法判断获取到的属性值是否为空(空白字符串或者 `null`),如果是则返回 `null`, + // 若属性值不为空,则再次调用 `trim` 方法去除首尾空白字符后返回,确保返回的属性值在格式上更加规范,避免因空白字符带来的潜在问题,方便后续使用。 } - public static String getProperty(String key,String defaultValue){ + /** + * 获取配置文件中指定键对应的属性值的方法,如果属性值为空(空白字符串或者不存在对应的键)则返回默认值。 + * 同样先通过 `Properties` 对象获取指定键对应的属性值,然后判断其是否为空,如果为空则使用传入的默认值进行赋值,最后返回去除首尾空白字符后的属性值(可能是从配置文件获取的或者是传入的默认值), + * 这种方式可以在配置文件中对应键的属性值缺失或者为空时,提供一个默认的备用值,保证在获取配置参数时不会因为配置文件的不完善而导致程序出现异常或者不符合预期的行为。 + * + * @param key 要获取属性值的键,与 `getProperty` 方法中的键参数作用相同,用于从配置文件中查找对应的属性值, + * 需要保证键的准确性以及格式规范,确保能够正确获取到期望的属性值或者正确应用默认值,同样建议传入前进行必要的格式处理(如去除空白字符等)。 + * @param defaultValue 当配置文件中指定键对应的属性值为空(空白字符串或者不存在该键)时要返回的默认值,是一个字符串类型的参数, + * 可以根据不同的配置项需求传入合适的默认值,例如对于数据库连接的端口号配置,如果配置文件中未设置,则可以传入默认的端口号(如 "3306")作为备用值, + * 确保程序在配置文件不完善的情况下依然能够按照合理的默认配置进行运行。 + * @return String 返回配置文件中与指定键对应的属性值,如果属性值为空(空白字符串或者不存在对应的键)则返回传入的默认值, + * 最后返回的结果都会经过去除首尾空白字符的处理,使得返回的属性值格式规范,便于后续在项目中作为配置参数进行使用,保证程序的正常运行以及配置的合理性。 + */ + public static String getProperty(String key, String defaultValue) { String value = props.getProperty(key.trim()); - if(StringUtils.isBlank(value)){ + // 首先调用 `Properties` 对象的 `getProperty` 方法,传入去除首尾空白字符后的键,尝试从配置文件中获取对应的属性值,操作与 `getProperty` 方法中的获取值步骤类似, + // 如果找到对应的值则返回该字符串值,若不存在则返回 `null`。 + + if (StringUtils.isBlank(value)) { value = defaultValue; } return value.trim(); + // 使用 `StringUtils` 的 `isBlank` 方法判断获取到的属性值是否为空(空白字符串或者 `null`),如果为空则将传入的默认值赋给 `value` 变量, + // 最后再次调用 `trim` 方法去除 `value` 的首尾空白字符后返回,确保返回的属性值无论是从配置文件获取的还是使用默认值,都具有规范的格式,便于后续在项目中使用, + // 比如作为数据库连接的相关配置参数、服务器相关配置等使用,避免因格式问题导致的配置错误以及程序异常等情况。 } -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/controller/ProductController.java b/snailmall-product-service/src/main/java/com/njupt/swg/controller/ProductController.java index 97b6b4d..be7d0f3 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/controller/ProductController.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/controller/ProductController.java @@ -11,52 +11,138 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** + * ProductController类是一个Spring MVC框架下的RESTful风格的控制器类,用于处理与商品(Product)相关的各种HTTP请求, + * 它通过依赖注入(@Autowired注解)获取IProductService接口的实现类实例,进而调用对应的业务逻辑方法来处理不同的请求, + * 并将业务层返回的结果(以ServerResponse包装)直接返回给客户端,实现了前后端的数据交互,为前端提供了获取商品详情、商品列表等相关功能的接口。 + * * @Author swg. * @Date 2019/1/2 17:32 * @CONTACT 317758022@qq.com * @DESC */ @RestController +// @RestController注解是Spring 4.0引入的一个组合注解,它等同于同时使用了@Controller和@ResponseBody注解。 +// @Controller用于标记该类是一个Spring MVC的控制器类,负责处理HTTP请求;@ResponseBody表示该类中所有的方法返回值都会直接写入HTTP响应体中, +// 通常用于返回JSON数据等格式的响应内容,这里将整个类标记为 @RestController,意味着这个类中的方法主要用于提供RESTful API接口,返回的数据格式适合直接被客户端(如前端页面、移动端应用等)消费。 + @RequestMapping("/product") +// @RequestMapping注解用于将HTTP请求映射到对应的控制器类的方法上,这里将类级别的请求路径设置为"/product", +// 意味着该类中所有的方法处理的请求路径都是在"/product"这个基础路径之下,方便对一组相关的接口进行统一的路径管理和归类, +// 例如后续的接口方法的请求路径就是在"/product"后面再添加具体的子路径来区分不同的功能接口。 + public class ProductController { @Autowired private IProductService productService; + // 使用Spring的依赖注入机制,通过 @Autowired注解自动装配IProductService接口的实现类实例到当前变量中, + // IProductService应该是定义了一系列与商品相关的业务逻辑方法的接口,其具体实现类会在Spring的配置中被实例化并注入到这里, + // 这样在控制器的各个方法中就可以直接调用该服务层接口的方法来处理具体的业务,实现了控制层与业务层的解耦,方便业务逻辑的扩展和替换。 + /** + * 处理获取商品详情的HTTP请求的方法,对应请求路径为"/product/detail.do"。 + * 接收一个表示商品ID的整数参数productId,将其传递给业务层的getPortalProductDetail方法,获取商品详情信息(以ProductDetailVo对象包装), + * 并将业务层返回的包含商品详情的ServerResponse对象直接返回给客户端,客户端可以根据返回的结果(成功与否以及详情数据内容)进行相应的展示或处理操作, + * 例如在前端页面展示商品的详细介绍、图片等信息。 + * + * @param productId 表示要获取详情的商品的唯一标识符,是一个整数类型的参数,通过HTTP请求传递过来, + * 前端在请求商品详情时需要传入正确的商品ID值,该ID对应数据库中存储的商品记录的主键等唯一标识, + * 业务层会根据这个ID从数据库或其他数据源查询并组装对应的商品详情信息返回。 + * @return ServerResponse 返回一个ServerResponse类型的对象,其泛型参数为ProductDetailVo, + * ServerResponse是一种统一的响应包装类,用于包装业务逻辑执行的结果(成功与否以及具体的数据内容), + * ProductDetailVo应该是一个包含了商品详细信息(如商品名称、描述、价格、图片等各种属性)的视图对象(VO,View Object), + * 若查询成功,返回的ServerResponse对象中会包含有效的ProductDetailVo对象以及表示成功的状态码等信息, + * 若查询失败(如商品不存在等原因),则返回的ServerResponse对象中会包含相应的错误信息和表示失败的状态码,方便客户端进行相应的处理。 + */ @RequestMapping("detail.do") - public ServerResponse detail(Integer productId){ + public ServerResponse detail(Integer productId) { return productService.getPortalProductDetail(productId); } + /** + * 处理获取商品列表的HTTP请求的方法,对应请求路径为"/product/list.do"。 + * 接收多个请求参数,包括关键词(keyword)、分类ID(categoryId)、页码(pageNum)、每页数量(pageSize)以及排序规则(orderBy), + * 将这些参数传递给业务层的portalList方法,获取符合条件的商品列表信息(以PageInfo对象包装,包含分页相关的数据), + * 并将业务层返回的包含商品列表的ServerResponse对象直接返回给客户端,客户端可以根据返回结果展示商品列表,例如在电商网站的商品展示页面进行分页展示商品等操作。 + * + * @param keyword 表示搜索商品的关键词,是一个字符串类型的参数,可选(通过 @RequestParam注解的required = false指定), + * 如果前端传入了关键词,业务层会根据这个关键词在商品名称、描述等相关字段中进行模糊查询,筛选出符合关键词的商品, + * 若未传入关键词,则表示查询所有商品(不进行关键词筛选),方便实现商品搜索功能。 + * @param categoryId 表示商品所属分类的ID,是一个整数类型的参数,同样可选(required = false), + * 如果传入了分类ID,业务层会筛选出属于该分类下的商品列表,若未传入,则表示不按照分类进行筛选,查询所有分类的商品, + * 用于实现按照商品分类查看商品的功能,例如查看某一品类下的所有商品。 + * @param pageNum 表示要获取的商品列表所在的页码,是一个整数类型的参数,默认值为 "1"(通过 @RequestParam注解的defaultValue = "1"指定), + * 前端可以传入具体的页码值来获取指定页的商品列表,方便实现分页展示商品的功能,例如查看第2页、第3页的商品等情况, + * 如果不传则默认获取第一页的商品列表。 + * @param pageSize 表示每页显示的商品数量,是一个整数类型的参数,默认值为 "10"(defaultValue = "10"), + * 前端可以传入期望每页显示的商品个数,用于灵活控制每页商品展示数量,若不传则按照默认每页10个商品进行分页展示。 + * @param orderBy 表示商品列表的排序规则,是一个字符串类型的参数,默认值为空字符串(defaultValue = ""), + * 前端可以传入具体的排序字段和排序方式(如 "price desc"表示按照价格降序排序),业务层会根据传入的排序规则对查询到的商品列表进行排序后返回, + * 若不传则按照默认的排序方式(可能是数据库默认排序或者业务层定义的默认顺序)返回商品列表,方便实现商品列表按照不同条件排序展示的功能。 + * @return ServerResponse 返回一个ServerResponse类型的对象,其泛型参数为PageInfo, + * ServerResponse同样用于包装业务逻辑执行结果,PageInfo是PageHelper框架提供的用于包装分页数据的类, + * 它包含了总记录数、总页数、当前页数据列表等分页相关的信息以及查询到的商品列表数据(通常是一个商品对象的集合), + * 若查询成功,返回的ServerResponse对象中会包含有效的PageInfo对象以及表示成功的状态码等信息,方便客户端进行分页展示商品等操作, + * 若查询失败(如参数错误、数据库查询异常等原因),则返回的ServerResponse对象中会包含相应的错误信息和表示失败的状态码,供客户端处理。 + */ @RequestMapping("list.do") - public ServerResponse list(@RequestParam(value = "keyword",required = false)String keyword, - @RequestParam(value = "categoryId",required = false)Integer categoryId, - @RequestParam(value = "pageNum",defaultValue = "1")int pageNum, - @RequestParam(value = "pageSize",defaultValue = "10")int pageSize, - @RequestParam(value = "orderBy",defaultValue = "")String orderBy){ - return productService.portalList(keyword,categoryId,orderBy,pageNum,pageSize); + public ServerResponse list( + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "categoryId", required = false) Integer categoryId, + @RequestParam(value = "pageNum", defaultValue = "1") int pageNum, + @RequestParam(value = "categoryId", defaultValue = "10") int pageSize, + @RequestParam(value = "orderBy", defaultValue = "") String orderBy) { + return productService.portalList(keyword, categoryId, orderBy, pageNum, pageSize); } + /** + * 处理查询单个商品的HTTP请求的方法,对应请求路径为"/product/queryProduct.do"。 + * 接收一个表示商品ID的整数参数productId,将其传递给业务层的queryProduct方法,获取商品相关信息, + * 并将业务层返回的包含商品信息的ServerResponse对象直接返回给客户端,客户端可以根据返回结果进行相应的处理, + * 例如在某些特定场景下查看商品的基本信息等操作,与detail方法不同的是返回的数据结构可能更简单或者更侧重于某些特定方面的商品信息展示。 + * + * @param productId 表示要查询的商品的唯一标识符,是一个整数类型的参数,通过HTTP请求传递过来, + * 其作用与detail方法中的productId参数类似,用于定位数据库中对应的商品记录,业务层会根据这个ID查询并返回相应的商品信息, + * 前端需要传入准确的商品ID值来获取期望的商品信息,若商品不存在等情况会在业务层返回相应的错误信息。 + * @return ServerResponse 返回一个ServerResponse类型的对象,这里没有指定泛型参数,说明返回的数据结构相对比较灵活, + * 根据业务层的queryProduct方法具体实现,可能返回包含部分商品属性的简单对象或者其他形式的数据包装在ServerResponse中, + * 若查询成功,返回的ServerResponse对象中会包含有效的商品信息以及表示成功的状态码等信息,方便客户端进行相应的处理, + * 若查询失败则返回相应的错误信息和表示失败的状态码。 + */ @RequestMapping("/queryProduct.do") - public ServerResponse queryProduct(@RequestParam("productId") Integer productId){ + public ServerResponse queryProduct(@RequestParam("productId") Integer productId) { return productService.queryProduct(productId); } /** * 补充接口1:预置每个商品库存到redis中 + * 处理将每个商品库存预先初始化到Redis缓存中的HTTP请求的方法,对应请求路径为"/product/preInitProductStcokToRedis.do"。 + * 该方法调用业务层的preInitProductStcokToRedis方法来执行具体的将商品库存存入Redis的业务逻辑, + * 并将业务层返回的ServerResponse对象直接返回给客户端,客户端可以根据返回结果判断操作是否成功, + * 例如在需要提前预热缓存,确保商品库存信息能够快速被获取的场景下使用,提高系统性能和响应速度。 + * + * @return ServerResponse 返回一个ServerResponse类型的对象,用于包装业务层执行将商品库存存入Redis操作的结果, + * 若操作成功,返回的ServerResponse对象中会包含表示成功的状态码等信息, + * 若操作失败(如Redis连接问题、数据格式问题等原因),则返回的ServerResponse对象中会包含相应的错误信息和表示失败的状态码, + * 方便客户端进行相应的处理,例如提示用户缓存初始化失败等情况。 */ @RequestMapping("/preInitProductStcokToRedis.do") - public ServerResponse preInitProductStcokToRedis(){ + public ServerResponse preInitProductStcokToRedis() { return productService.preInitProductStcokToRedis(); } - /** * 补充接口2:预置所有商品到redis中 + * 处理将所有商品预先初始化到Redis缓存中的HTTP请求的方法,对应请求路径为"/product/preInitProductListToRedis.do"。 + * 调用业务层的preInitProductListToRedis方法来实现将所有商品相关信息存入Redis的业务逻辑, + * 并将业务层返回的ServerResponse对象直接返回给客户端,客户端可以依据返回结果知晓操作是否成功, + * 常用于系统启动或者商品数据有更新后,提前将商品数据加载到缓存中,加快后续对商品信息的查询速度,提升用户体验。 + * + * @return ServerResponse 返回一个ServerResponse类型的对象,用于包装业务层执行将所有商品存入Redis操作的结果, + * 若操作成功,返回的ServerResponse对象中会包含表示成功的状态码等信息, + * 若操作失败(如Redis容量不足、数据传输错误等原因),则返回的ServerResponse对象中会包含相应的错误信息和表示失败的状态码, + * 方便客户端进行相应的处理,例如提示用户商品缓存初始化失败等情况。 */ @RequestMapping("/preInitProductListToRedis.do") - public ServerResponse preInitProductListToRedis(){ + public ServerResponse preInitProductListToRedis() { return productService.preInitProductListToRedis(); } - - -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/controller/ProductManageController.java b/snailmall-product-service/src/main/java/com/njupt/swg/controller/ProductManageController.java index a31df1c..f8a8cf0 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/controller/ProductManageController.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/controller/ProductManageController.java @@ -27,125 +27,177 @@ import javax.servlet.http.HttpServletResponse; import java.util.Map; /** + * ProductManageController类是一个Spring MVC框架下的RESTful风格的控制器类,用于处理后台商品相关的各种HTTP请求, + * 提供了诸如获取商品列表、搜索商品、图片上传、管理商品上下架以及保存商品等功能接口,通过依赖注入相关服务层接口实现业务逻辑调用, + * 并结合缓存工具、工具类等进行数据处理和响应构建,同时使用日志记录关键操作信息及异常情况,方便后续的调试与维护。 + * * @Author swg. * @Date 2019/1/2 17:32 * @CONTACT 317758022@qq.com * @DESC 后台商品服务 */ @RestController +// 表明该类是一个Spring RESTful风格的控制器,意味着类中的方法返回值会直接作为HTTP响应体的内容返回,通常用于返回JSON格式的数据等, +// 适用于构建API接口,方便前后端分离架构下与前端进行数据交互。 @RequestMapping("/manage/product") +// 将该控制器类下所有方法对应的请求路径统一设置在 "/manage/product" 前缀之下,便于对后台商品相关接口进行统一管理和分类, +// 例如后续各个具体方法的请求路径都是基于这个前缀进行拓展的。 @Slf4j +// 使用Lombok的 @Slf4j注解自动生成名为log的SLF4J日志记录器,用于在方法执行过程中记录关键信息、异常情况等,方便后续查看日志排查问题、跟踪操作流程。 public class ProductManageController { @Autowired private IProductService productService; + // 通过Spring的依赖注入机制,自动装配IProductService接口的实现类实例,IProductService应该定义了众多与商品业务逻辑相关的方法, + // 如查询商品列表、获取商品详情、更新商品信息等,在本控制器的多个方法中会调用其相应方法来处理具体的业务操作,实现控制层与业务层的解耦。 + @Autowired private IFileService fileService; + // 注入IFileService接口的实现类实例,该接口大概率是用于处理文件相关操作的服务,比如文件上传功能,在本类的图片上传相关方法中会调用其方法来完成实际的文件上传逻辑。 + @Autowired private CommonCacheUtil commonCacheUtil; + // 注入CommonCacheUtil实例,这应该是一个用于操作缓存(可能是Redis等缓存系统)的工具类,在获取用户信息等需要缓存数据支持的操作中会用到, + // 通过它可以方便地从缓存中读取、写入数据,提高系统性能,减少重复查询数据库等操作。 /** * 产品list + * 处理获取商品列表的HTTP请求的方法,对应请求路径为 "/manage/product/list.do"。 + * 接收页码(pageNum)和每页数量(pageSize)两个参数,默认值分别为1和10,将这两个参数传递给业务层的list方法,获取相应分页的商品列表信息, + * 并把业务层返回的包含商品列表信息的ServerResponse对象直接返回给客户端,客户端可根据返回结果进行分页展示商品等操作,适用于后台管理系统查看商品列表的场景。 + * + * @param pageNum 表示要获取的商品列表所在的页码,整数类型,默认值为 "1"(通过 @RequestParam注解指定), + * 前端可传入具体页码值获取对应页的商品列表,若不传则默认获取第一页商品列表,方便实现分页功能,满足不同查看需求。 + * @param pageSize 表示每页显示的商品数量,整数类型,默认值为 "10",前端可传入期望每页显示的商品个数来灵活控制每页展示数量,若不传则按默认每页10个商品进行分页展示。 + * @return ServerResponse 返回一个ServerResponse类型的对象,用于包装业务层执行获取商品列表操作的结果, + * 若查询成功,返回的ServerResponse对象中会包含有效的商品列表数据以及表示成功的状态码等信息, + * 若查询失败(如数据库查询异常、参数错误等原因),则包含相应的错误信息和表示失败的状态码,方便客户端进行相应处理。 */ @RequestMapping("/list.do") - public ServerResponse list(@RequestParam(value = "pageNum",defaultValue = "1") int pageNum, - @RequestParam(value = "pageSize",defaultValue = "10") int pageSize){ - return productService.list(pageNum,pageSize); + public ServerResponse list(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum, + @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) { + return productService.list(pageNum, pageSize); } /** * 产品搜索 + * 处理商品搜索的HTTP请求的方法,对应请求路径为 "/manage/product/search.do"。 + * 接收商品名称(productName)、商品ID(productId)、页码(pageNum)和每页数量(pageSize)四个参数, + * 将这些参数传递给业务层的search方法,获取符合搜索条件的分页商品列表信息(以PageInfo对象包装),并把业务层返回的ServerResponse对象返回给客户端, + * 客户端可根据返回结果展示符合搜索条件的商品列表,便于后台管理系统按名称或ID等条件查找特定商品。 + * + * @param productName 表示要搜索的商品名称,字符串类型,用于在业务层进行模糊搜索(通常会根据商品名称字段进行匹配查找), + * 若传入具体名称,业务层会筛选出名称包含该关键字的商品列表,若不传则可视为不按名称进行筛选,根据其他条件查找商品。 + * @param productId 表示商品的唯一标识符,整数类型,可用于精确查找特定ID的商品,若传入具体ID值,业务层会优先根据该ID查找对应的商品, + * 若不传则可结合其他条件(如名称、分页等)进行商品查找,可单独使用也可与其他参数配合来精确或模糊搜索商品。 + * @param pageNum 表示要获取的商品列表所在的页码,整数类型,默认值为 "1",用于实现分页搜索结果展示,前端传入页码值获取对应页的搜索结果,不传则默认获取第一页。 + * @param pageSize 表示每页显示的商品数量,整数类型,默认值为 "10",用于控制每页展示的搜索到的商品个数,前端可传入期望每页数量,不传按默认每页10个商品分页展示。 + * @return ServerResponse 返回一个ServerResponse类型的对象,其泛型参数为PageInfo, + * ServerResponse用于包装业务层执行搜索操作的结果,PageInfo包含了分页相关信息(总记录数、总页数等)以及查询到的商品列表数据, + * 若搜索成功,返回的ServerResponse对象中会包含有效的PageInfo对象及表示成功的状态码等信息,方便客户端分页展示搜索结果, + * 若搜索失败则包含相应错误信息和表示失败的状态码,供客户端处理。 */ @RequestMapping("search.do") public ServerResponse search(String productName, Integer productId, - @RequestParam(value = "pageNum",defaultValue = "1") int pageNum, - @RequestParam(value = "pageSize",defaultValue = "10") int pageSize){ + @RequestParam(value = "pageNum", defaultValue = "1") int pageNum, + @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) { - return productService.search(productName,productId,pageNum,pageSize); + return productService.search(productName, productId, pageNum, pageSize); } /** * 图片上传 + * 处理图片上传的HTTP请求的方法,对应请求路径为 "/manage/product/upload.do"。 + * 接收一个MultipartFile类型的文件对象(file)以及HttpServletRequest对象(用于获取服务器相关路径信息), + * 通过调用fileService的upload方法将文件上传到指定路径,然后构建包含文件相关信息(文件名和文件访问URL)的Map对象, + * 并将其包装在ServerResponse对象中返回给客户端,同时记录上传的图片路径等关键信息到日志中,方便后续查看操作情况及调试。 + * + * @param file 表示要上传的文件对象,通过 @RequestParam注解指定参数名及可选性(这里非必填,不过实际使用中通常需要上传文件), + * 是MultipartFile类型,它是Spring提供的用于处理文件上传的接口,可获取文件的各种属性(如文件名、文件内容等),并将文件传输到服务器端进行后续处理。 + * @param request HttpServletRequest类型,用于获取服务器相关的上下文信息,在这里主要是获取服务器上用于存放上传文件的真实路径(通过获取Servlet上下文路径等方式), + * 以便确定文件上传的具体位置,保证文件能够正确存储在服务器指定的目录下。 + * @return ServerResponse 返回一个ServerResponse类型的对象,用于包装图片上传操作的结果及相关文件信息, + * 若上传成功,返回的ServerResponse对象中会包含表示成功的状态码以及包含文件路径(uri)和文件访问URL(url)的Map对象等信息, + * 若上传失败(如文件格式不支持、服务器路径问题等原因),则包含相应的错误信息和表示失败的状态码,方便客户端知晓上传情况并进行相应处理。 */ @RequestMapping("upload.do") - public ServerResponse upload(@RequestParam(value = "upload_file",required = false) MultipartFile file, HttpServletRequest request){ + public ServerResponse upload(@RequestParam(value = "upload_file", required = false) MultipartFile file, HttpServletRequest request) { String path = request.getSession().getServletContext().getRealPath("upload"); - String targetFileName = fileService.upload(file,path); - String url = "http://img.oursnail.cn/"+targetFileName; + // 获取服务器上用于存放上传文件的真实路径,通过HttpServletRequest对象获取当前会话(Session)的Servlet上下文(ServletContext), + // 再获取名为 "upload" 的目录的真实路径,该路径就是后续文件要上传保存的位置,确保文件能够正确存储在服务器指定的地方。 + + String targetFileName = fileService.upload(file, path); + // 调用fileService的upload方法,将接收到的文件对象和文件保存路径传递进去,执行实际的文件上传操作, + // 该方法会返回上传后文件在服务器上的目标文件名(可能经过了重命名等处理),方便后续构建文件的访问URL等操作。 + + String url = "http://img.oursnail.cn/" + targetFileName; + // 构建文件的访问URL,根据业务需求,将服务器域名(这里是 "http://img.oursnail.cn/")与上传后的目标文件名拼接起来, + // 得到完整的可用于在浏览器等客户端访问该文件的URL地址,便于后续在前端展示图片或者其他相关操作中使用这个URL来引用上传的文件。 - log.info("【上传的图片路径为:{}】",url); + log.info("【上传的图片路径为:{}】", url); + // 使用日志记录上传的图片的访问URL信息,方便后续查看文件上传的结果以及在调试时确认文件是否上传到了正确的位置,同时也有助于排查可能出现的文件访问问题。 Map fileMap = Maps.newHashMap(); - fileMap.put("uri",targetFileName); - fileMap.put("url",url); - log.info("【返回数据为:{}】",fileMap); + fileMap.put("uri", targetFileName); + fileMap.put("url", url); + // 创建一个Map对象,用于存放文件相关的关键信息,将上传后文件的目标文件名(uri)和构建好的文件访问URL(url)放入Map中, + // 方便将这些信息统一包装在ServerResponse对象中返回给客户端,使得客户端能够获取到文件的相关详细信息,便于后续展示等操作。 + + log.info("【返回数据为:{}】", fileMap); + // 再次使用日志记录要返回给客户端的包含文件信息的Map对象内容,便于后续查看返回的数据结构以及排查数据传递过程中可能出现的问题,确保返回的数据符合预期。 + return ServerResponse.createBySuccess(fileMap); + // 通过ServerResponse的静态方法createBySuccess创建一个表示成功的ServerResponse对象,并将包含文件信息的fileMap作为成功结果数据传入, + // 最终将这个ServerResponse对象返回给客户端,告知客户端图片上传操作已成功完成,并传递相关文件信息供客户端使用。 } /** * 产品详情 + * 处理获取商品详情的HTTP请求的方法,对应请求路径为 "/manage/product/detail.do"。 + * 接收一个表示商品ID的整数参数productId,将其传递给业务层的detail方法,获取商品详情信息(以ProductDetailVo对象包装), + * 并将业务层返回的包含商品详情的ServerResponse对象直接返回给客户端,客户端可根据返回结果展示商品详细信息,常用于后台查看商品具体详情的场景。 + * + * @param productId 表示要获取详情的商品的唯一标识符,整数类型,通过HTTP请求传递过来, + * 业务层会依据这个ID从数据库或其他数据源查询并组装对应的商品详情信息(如商品名称、描述、价格、库存等各种属性), + * 前端需传入准确的商品ID值,若商品不存在等情况会在业务层返回相应的错误信息,包含在ServerResponse对象中返回给客户端。 + * @return ServerResponse 返回一个ServerResponse类型的对象,其泛型参数为ProductDetailVo, + * ServerResponse用于包装业务层执行获取商品详情操作的结果,ProductDetailVo是包含商品详细信息的视图对象, + * 若查询成功,返回的ServerResponse对象中会包含有效的ProductDetailVo对象以及表示成功的状态码等信息,方便客户端展示商品详情, + * 若查询失败则包含相应错误信息和表示失败的状态码,供客户端处理。 */ @RequestMapping("detail.do") - public ServerResponse detail(Integer productId){ + public ServerResponse detail(Integer productId) { return productService.detail(productId); } /** * 产品上下架 + * 处理设置商品销售状态(上下架)的HTTP请求的方法,对应请求路径为 "/manage/product/set_sale_status.do"。 + * 接收商品ID(productId)和状态(status)两个整数参数,将它们传递给业务层的set_sale_status方法,执行商品上下架的业务逻辑操作, + * 并把业务层返回的包含操作结果信息(以字符串形式表示,可能是操作成功与否的提示等)的ServerResponse对象返回给客户端, + * 客户端可根据返回结果知晓商品上下架操作是否成功,便于后台管理系统对商品的售卖状态进行管控。 + * + * @param productId 表示要设置销售状态的商品的唯一标识符,整数类型,用于定位数据库中对应的商品记录, + * 业务层会根据这个ID来更新商品的销售状态,前端需传入准确的商品ID值,确保操作的是正确的商品,若商品不存在等情况会返回相应错误信息。 + * @param status 表示商品的销售状态值,整数类型,具体的取值含义(如1表示上架,0表示下架等)应该由业务层根据业务规则来定义和处理, + * 通过传入这个参数来告知业务层要将商品设置为对应的销售状态,不同的取值会使业务层执行相应的数据库更新等操作来改变商品的售卖情况。 + * @return ServerResponse 返回一个ServerResponse类型的对象,其泛型参数为String, + * ServerResponse用于包装业务层执行设置商品销售状态操作的结果,返回的字符串内容可能是操作成功的提示、失败原因等信息, + * 若操作成功,返回的ServerResponse对象中会包含表示成功的状态码以及相应的成功提示信息等,方便客户端知晓操作情况, + * 若操作失败则包含相应的错误信息和表示失败的状态码,供客户端处理。 */ @RequestMapping("set_sale_status.do") - public ServerResponse set_sale_status(Integer productId,Integer status){ - return productService.set_sale_status(productId,status); - } - - /** - * 新增OR更新产品 - */ - @RequestMapping("save.do") - public ServerResponse productSave(Product product){ - return productService.saveOrUpdateProduct(product); - } - - /** - * 富文本上传图片 - * 由于这里如果没有管理员权限,需要回复特定形式的信息,所以校验单独放在这里,zuul过滤器对其直接放过 - */ - @RequestMapping("richtext_img_upload.do") - public Map richtextImgUpload(@RequestParam(value = "upload_file",required = false) MultipartFile file, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { - String loginToken = CookieUtil.readLoginToken(httpServletRequest); - if(StringUtils.isEmpty(loginToken)){ - throw new SnailmallException("用户未登录,无法获取当前用户信息"); - } - //2.从redis中获取用户信息 - String userStr = commonCacheUtil.getCacheValue(loginToken); - if(userStr == null){ - throw new SnailmallException("用户未登录,无法获取当前用户信息"); - } - - User user = JsonUtil.Str2Obj(userStr,User.class); - Map resultMap = Maps.newHashMap(); - if(user == null){ - resultMap.put("success",false); - resultMap.put("msg","请登录管理员"); - return resultMap; - } - - String path = httpServletRequest.getSession().getServletContext().getRealPath("upload"); - String targetFileName = fileService.upload(file, path); - if (StringUtils.isBlank(targetFileName)) { - resultMap.put("success", false); - resultMap.put("msg", "上传失败"); - return resultMap; - } - String url = PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.oursnail.cn/")+targetFileName; - resultMap.put("success", true); - resultMap.put("msg", "上传成功"); - resultMap.put("file_path", url); - log.info("【返回数据为:{}】",resultMap); - httpServletResponse.addHeader("Access-Control-Allow-Headers", "X-File-Name"); - return resultMap; + public ServerResponse set_sale_status(Integer productId, Integer status) { + return productService.set_sale_status(productId, status); } - - - } +/** + * 新增OR更新产品 + * 处理新增或更新商品的HTTP请求的方法,对应请求路径为 "/manage/product/save.do"。 + * 接收一个Product类型的商品对象参数,将其传递给业务层的saveOrUpdateProduct方法,执行商品的新增或更新的业务逻辑操作, + * 并把业务层返回的包含操作结果信息(以字符串形式表示,可能是操作成功与否的提示等)的ServerResponse对象返回给客户端, + * 客户端可根据返回结果知晓商品新增或更新操作是否成功,便于后台管理系统对商品信息进行维护管理。 + * + * @param product 表示要新增或更新的商品对象,是Product实体类类型,包含了商品的各种属性信息(如名称、描述、价格、库存等), + * 前端需要按照业务要求构建并传入完整或部分有效的商品对象信息,业务层会根据对象的属性情况(如是否包含ID等)判断 + */ \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/dao/ProductMapper.java b/snailmall-product-service/src/main/java/com/njupt/swg/dao/ProductMapper.java index 28b9fba..14e4686 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/dao/ProductMapper.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/dao/ProductMapper.java @@ -5,26 +5,180 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; - +/** + * ProductMapper接口是基于MyBatis框架定义的用于操作数据库中商品(Product)相关数据的持久层接口。 + * 它定义了一系列与商品数据的增删改查(CRUD)操作对应的方法,通过MyBatis的映射机制,这些方法会关联到对应的SQL语句(通常在XML映射文件或者使用注解形式定义), + * 从而实现与数据库的交互,对商品数据表进行相应的操作,例如插入新商品记录、更新商品信息、查询商品详情以及根据不同条件筛选商品列表等操作。 + * + * @Mapper注解是MyBatis框架提供的用于标记该接口是一个MyBatis的Mapper接口, + * 在Spring与MyBatis整合的项目中,这个注解会被扫描并自动创建对应的代理实现类,使得可以在Service层等地方直接注入并调用这些方法来操作数据库, + * 简化了持久层与业务层之间的交互流程,方便进行数据库相关的业务逻辑开发。 + */ @Mapper public interface ProductMapper { + /** + * 根据商品的主键(通常是唯一标识符,如商品ID)删除对应的商品记录的方法。 + * 该方法会在数据库的商品表中,依据传入的商品ID,执行删除操作,删除对应的一条商品记录, + * 并返回受影响的行数,若返回值大于0,表示成功删除了对应ID的商品记录,若返回0,则表示未找到对应的记录或者删除操作未实际执行(例如传入的ID不存在等情况)。 + * + * @param id 表示要删除的商品记录的主键值,通常为商品的唯一标识符(如数据库表中的商品ID字段对应的整数值), + * 通过传入这个参数,MyBatis会将其作为条件,构造对应的SQL删除语句(例如:DELETE FROM product_table WHERE id = #{id}), + * 用于定位并删除数据库中相应的商品记录,需要确保传入的ID值准确对应数据库中存在的商品记录,否则无法实现删除操作。 + * @return int 返回值表示数据库中受该删除操作影响的行数,即成功删除的商品记录的条数,一般情况下,成功删除一条记录时返回1,若未找到对应记录等情况则返回0, + * 可以根据这个返回值在业务层判断删除操作是否成功,进而进行后续的处理(如提示用户删除成功或失败等操作)。 + */ int deleteByPrimaryKey(Integer id); - + /** + * 向数据库的商品表中插入一条完整的商品记录的方法。 + * 该方法接收一个Product类型的对象(record),对象中包含了要插入的商品的各种属性信息(如商品名称、描述、价格、库存等), + * MyBatis会将这个对象的属性值提取出来,按照预定义的映射规则(可以是基于XML映射文件或者注解方式定义的字段与表列的对应关系), + * 构造对应的SQL插入语句(例如:INSERT INTO product_table (column1, column2,...) VALUES (#{property1}, #{property2},...)), + * 将商品记录插入到数据库中,并返回受影响的行数,若返回值大于0,表示成功插入了一条商品记录,若返回0,则表示插入操作出现问题(如违反数据库约束等情况)。 + * + * @param record 表示要插入到数据库的商品记录对象,是Product实体类类型,包含了商品的各种属性信息, + * 这些属性信息需要符合数据库表中对应列的定义和约束(如数据类型、长度、非空约束等),以确保能够正确插入到数据库中, + * 在调用此方法时,需要构建一个完整且合法的Product对象传递进来,以便MyBatis进行数据提取和插入操作。 + * @return int 返回值表示数据库中受该插入操作影响的行数,正常情况下,成功插入一条记录时返回1,若因数据不符合数据库约束(如必填字段为空、数据类型不匹配等)导致插入失败则返回0, + * 业务层可以根据这个返回值判断插入操作是否成功,进而进行后续的提示用户、记录日志等处理操作。 + */ int insert(Product record); - + /** + * 向数据库的商品表中插入一条商品记录的可选方式(只插入非空属性对应的列)的方法。 + * 与insert方法类似,接收一个Product类型的对象,不同之处在于,此方法会根据对象中属性值是否为空,选择性地将非空属性插入到数据库中, + * 通过MyBatis的动态SQL功能(通常基于注解或者XML配置实现),只会构造包含非空属性对应的列及其值的SQL插入语句, + * 这样可以更灵活地处理插入操作,避免因某些属性为空而导致违反数据库非空约束等问题,同样返回受影响的行数来表示插入操作是否成功。 + * + * @param record 表示要插入到数据库的商品记录对象,是Product实体类类型,包含了商品的各种属性信息, + * 但在插入时只会将对象中不为空的属性对应的列插入到数据库中,例如如果商品对象中的某个描述字段为空,那么在插入时对应的数据库列就不会插入该空值, + * 提高了插入操作的灵活性和数据插入的成功率,减少因部分属性为空而导致插入失败的情况,调用时需传入合法的Product对象供MyBatis进行处理。 + * @return int 返回值表示数据库中受该插入操作影响的行数,成功插入时返回相应的行数(通常插入一条记录返回1),若插入失败(如违反其他数据库约束等情况)则返回0, + * 业务层依据这个返回值判断插入操作的结果,进行后续相关的业务处理(如提示用户插入情况等操作)。 + */ int insertSelective(Product record); - + /** + * 根据商品的主键(通常是商品ID)从数据库中查询并获取对应的一条商品记录的方法。 + * 该方法接收一个表示商品主键的整数参数(id),MyBatis会将其作为查询条件,构造对应的SQL查询语句(例如:SELECT * FROM product_table WHERE id = #{id}), + * 从数据库的商品表中查找并返回对应的商品记录信息,返回值是一个Product类型的对象,包含了从数据库中查询到的商品的详细属性信息(如名称、描述、价格、库存等), + * 若未找到对应的商品记录(即数据库中不存在该主键对应的记录),则返回null,方便在业务层进行后续的处理(如判断商品是否存在等操作)。 + * + * @param id 表示要查询的商品记录的主键值,是一个整数类型的参数,通过传入这个参数,MyBatis会在数据库的商品表中定位对应的商品记录, + * 需要确保传入的ID值准确对应数据库中可能存在的商品记录,否则将查询不到相应的数据,返回null给业务层, + * 业务层可以根据返回的Product对象是否为null来判断商品是否存在,并进行相应的逻辑处理(如展示商品详情或者提示商品不存在等操作)。 + * @return Product 返回值是一个Product类型的对象,代表从数据库中查询到的对应主键的商品记录,包含了该商品的各种详细属性信息, + * 若数据库中不存在该主键对应的商品记录,则返回null,供业务层根据返回结果进行不同的业务逻辑处理,比如判断商品是否存在等情况。 + */ Product selectByPrimaryKey(Integer id); - + /** + * 根据商品的主键(通常是商品ID)更新数据库中对应的商品记录的可选方式(只更新非空属性对应的列)的方法。 + * 接收一个Product类型的对象(record),对象中包含了要更新的商品的各种属性信息,此方法会根据对象中属性值是否为空,选择性地更新数据库中对应商品记录的非空属性列, + * 通过MyBatis的动态SQL功能(基于注解或者XML配置实现),构造包含只更新非空属性的SQL更新语句(例如:UPDATE product_table SET column1 = #{property1}, column2 = #{property2} WHERE id = #{id},其中property1、property2等为非空属性值), + * 实现对数据库中商品记录的部分更新操作,返回受影响的行数来表示更新操作是否成功,若返回值大于0,表示成功更新了对应商品记录的部分属性,若返回0,则可能表示未找到对应的记录或者没有实际执行更新操作。 + * + * @param record 表示要用于更新数据库中商品记录的对象,是Product实体类类型,包含了商品的各种属性信息, + * 但只会将对象中不为空的属性对应的列更新到数据库中对应的商品记录上,例如如果商品对象中的某个价格字段有新值,而其他字段为空,则只会更新价格字段的值, + * 提高了更新操作的灵活性,避免将空值覆盖原有有效的数据,调用时需传入包含要更新的有效属性信息的Product对象供MyBatis进行处理。 + * @return int 返回值表示数据库中受该更新操作影响的行数,成功更新部分属性时返回相应的行数(通常更新了一条记录的部分属性返回大于0的值),若未找到对应记录或者更新失败(如违反数据库约束等情况)则返回0, + * 业务层依据这个返回值判断更新操作的结果,进而进行后续的相关业务处理(如提示用户更新情况等操作)。 + */ int updateByPrimaryKeySelective(Product record); - + /** + * 根据商品的主键(通常是商品ID)更新数据库中对应的商品记录的方法(会更新所有属性对应的列)。 + * 与updateByPrimaryKeySelective方法类似,接收一个Product类型的对象(record),不同之处在于,此方法会将对象中的所有属性值, + * 按照预定义的映射规则(通过XML或注解定义的字段与表列对应关系)构造SQL更新语句(例如:UPDATE product_table SET column1 = #{property1}, column2 = #{property2},... WHERE id = #{id}), + * 无论属性值是否为空,都会更新数据库中对应商品记录的所有列,返回受影响的行数来表示更新操作是否成功,若返回值大于0,表示成功更新了对应商品记录的所有属性,若返回0,则可能表示未找到对应的记录或者更新操作出现问题。 + * + * @param record 表示要用于更新数据库中商品记录的对象,是Product实体类类型,包含了商品的各种属性信息, + * 该对象中的所有属性值都会被用来更新数据库中对应的商品记录的所有列,无论属性值是否为空,都将覆盖原有数据库中的数据, + * 在调用此方法时需要谨慎传入完整且合法的Product对象,确保更新的数据符合数据库表中列的定义和约束,避免因数据问题导致更新失败, + * 业务层可根据返回的受影响行数判断更新操作是否成功,进而进行后续的相关处理(如提示用户更新情况等操作)。 + * @return int 返回值表示数据库中受该更新操作影响的行数,成功更新一条记录的所有属性时返回1(通常情况),若未找到对应记录或者更新失败(如违反数据库约束等情况)则返回0, + * 业务层依据这个返回值判断更新操作的结果,以便进行后续的业务逻辑处理(如提示用户更新成功或失败等操作)。 + */ int updateByPrimaryKey(Product record); - + /** + * 查询数据库中所有商品记录的方法。 + * 该方法会构造对应的SQL查询语句(例如:SELECT * FROM product_table),从数据库的商品表中获取所有的商品记录信息, + * 返回值是一个包含多个Product类型对象的List集合,每个Product对象代表一条商品记录,包含了该商品的详细属性信息(如名称、描述、价格、库存等), + * 若商品表中没有任何记录,则返回一个空的List集合,方便在业务层进行遍历、展示等后续操作(如在后台管理系统中展示所有商品列表等情况)。 + * + * @return List 返回值是一个List集合,其中元素类型为Product,代表从数据库中查询到的所有商品记录, + * 若数据库中不存在商品记录,则返回一个空的List集合,业务层可以通过遍历这个集合来获取每个商品的详细信息, + * 进而进行相应的业务逻辑操作(如展示商品列表、进行数据统计等操作)。 + */ List selectList(); - + /** + * 根据商品名称和商品ID查询商品记录的方法。 + * 该方法接收商品名称(productName)和商品ID(productId)两个参数,MyBatis会将这两个参数作为查询条件, + * 通过动态SQL(基于注解或者XML配置实现)构造对应的SQL查询语句(例如:SELECT * FROM product_table WHERE product_name LIKE '%#{productName}%' AND id = #{productId},这里假设商品名称采用模糊查询方式), + * 从数据库的商品表中查找并返回符合条件的商品记录信息,返回值是一个包含多个Product类型对象的List集合,每个Product对象代表一条符合条件的商品记录, + * 若未找到符合条件的商品记录,则返回一个空的List集合,方便业务层根据返回结果进行后续处理(如展示查询到的商品列表或者提示未找到相关商品等操作)。 + * + * @param productName 表示要查询的商品的名称,是一个字符串类型的参数,通常可以用于模糊查询(具体查询方式由MyBatis的SQL配置决定,可能是包含关键字的模糊匹配等), + * 通过传入商品名称,MyBatis会将其作为条件在数据库的商品表中查找名称包含该关键字的商品记录(或者按照其他定义的名称匹配方式查找), + * 若传入空字符串或者null,则在名称这个条件上相当于不做限制(具体取决于SQL语句的逻辑),可以结合商品ID等其他条件一起查找商品记录。 + * @param productId 表示要查询的商品的唯一标识符(如商品ID),是一个整数类型的参数,通过传入这个参数,MyBatis会将其作为条件在数据库的商品表中定位对应的商品记录, + * 若传入null或者不存在对应的商品ID,则在ID这个条件上相当于不做限制(具体取决于SQL语句的逻辑),可以结合商品名称等其他条件一起查找商品记录, + * 业务层可以根据实际需求传入不同的商品名称和商品ID组合来查询符合特定条件的商品记录。 + * @return List 返回值是一个List集合,其中元素类型为Product,代表从数据库中查询到的符合商品名称和商品ID条件的商品记录, + * 若未找到符合条件的商品记录,则返回一个空的List集合,业务层可以对返回的集合进行遍历等操作,获取每个商品的详细信息, + * 进而进行相应的业务逻辑处理(如展示查询到的商品列表、判断是否找到特定商品等操作)。 + */ List selectProductByNameAndId(@Param("productName") String productName, @Param("productId") Integer productId); - + /** + * 根据商品名称和商品ID查询商品记录的方法。 + * 此方法旨在从数据库中检索满足特定名称和ID条件的商品信息,通过接收商品名称(productName)与商品ID(productId)作为查询条件, + * 利用MyBatis的参数传递和SQL映射机制,构造合适的查询语句来获取相应的商品记录集合。 + * + * @Param("productName") String productName:用于指定要查询的商品名称,是一个字符串类型参数。 + * 在实际的SQL查询中,它可以用于进行模糊查询(具体取决于MyBatis中对应的SQL语句配置,常见的是使用 LIKE 关键字结合通配符进行模糊匹配,例如 '%#{productName}%'), + * 以便查找名称中包含指定关键字的商品记录。若传入的productName为空字符串或者null值,那么在基于名称的查询条件上就相当于没有限制, + * 此时会结合传入的productId(如果有值的话)来筛选商品记录,这种灵活性方便在不同场景下进行商品查找,比如只根据ID查找、或者结合部分名称关键字和ID查找等情况。 + * + * @Param("productId") Integer productId:表示要查询的商品的唯一标识符,即商品ID,是一个整数类型参数。 + * 它在SQL查询语句中充当精确匹配的条件(例如通过 WHERE id = #{productId} 这样的语句片段),用于定位特定的商品记录。 + * 若传入的productId为null值,那么在基于ID的这个条件上就相当于不做限制(同样取决于具体的SQL逻辑),可以单纯依据传入的productName(如果非空的话)进行商品查找, + * 业务层可以根据实际业务场景,灵活传入不同的productName和productId组合,来获取期望的符合条件的商品记录集合。 + * + * @return List:返回值是一个List集合,其元素类型为Product实体类。 + * 这个集合中包含了从数据库中查询到的所有符合传入的商品名称和商品ID条件的商品记录。 + * 如果数据库中不存在满足给定条件的商品记录,那么将返回一个空的List集合,便于在业务层通过遍历该集合等操作来进一步处理查询到的商品信息, + * 例如在界面上展示查询结果、进行后续的业务逻辑判断(如是否找到特定商品等),从而为用户提供相应的反馈或者执行后续的流程操作。 + */ List selectByNameAndCategoryIds(@Param("productName") String productName, @Param("categoryIdList") List categoryIdList); - + /** + * 根据商品名称和商品分类ID列表查询商品记录的方法。 + * 该方法主要用于从数据库中查找那些符合特定商品名称以及所属分类ID在给定列表中的商品记录,通过接收商品名称(productName)和商品分类ID列表(categoryIdList)作为查询条件, + * 借助MyBatis的动态SQL功能(依据配置生成不同的SQL语句片段),构建合适的查询语句以获取符合条件的商品记录集合。 + * + * @Param("productName") String productName:代表要查询的商品名称,属于字符串类型参数。 + * 其作用与在 `selectProductByNameAndId` 方法中的类似,通常可用于模糊查询(取决于MyBatis配置的具体SQL语句写法,常采用 LIKE 结合通配符的方式进行模糊匹配,如 '%#{productName}%'), + * 以此来筛选出名称包含指定关键字的商品记录。当传入的productName为空字符串或者null时,在基于名称的查询限制上就相当于不存在, + * 会依据传入的categoryIdList(如果非空的话)来进一步筛选商品记录,这种设置便于应对不同的业务查找需求,例如只按照分类查找、或者结合部分名称关键字和分类进行查找等情况。 + * + * @Param("categoryIdList") List categoryIdList:这是一个存储整数类型的列表参数,用于指定要查询的商品所属的分类ID集合。 + * 在SQL查询语句中,一般会通过 IN 操作符(例如 WHERE category_id IN (#{categoryIdList}) 这样的语句结构,具体由MyBatis配置决定)来匹配属于这些分类的商品记录, + * 即查找那些分类ID在这个列表中的商品。若传入的categoryIdList为空列表,那么在基于分类的这个查询条件上就相当于没有限制(取决于具体SQL逻辑), + * 此时可以结合传入的productName(如果非空的话)来查找商品记录,业务层可以根据实际业务情况,灵活传入合适的productName和categoryIdList组合, + * 从而获取期望的符合条件的商品记录集合。 + * + * @return List:返回值是一个List集合,其元素类型为Product实体类。 + * 这个集合包含了从数据库中查询到的所有符合传入的商品名称以及商品分类ID列表条件的商品记录。 + * 倘若数据库中不存在满足给定条件的商品记录,那么将会返回一个空的List集合,方便业务层后续通过遍历该集合等操作来处理查询到的商品信息, + * 例如用于展示符合条件的商品列表、进行相关业务判断(如是否找到期望分类下的商品等),进而为用户提供相应的反馈或者执行后续的流程操作。 + */ Integer selectStockByProductId(Integer id); + /** + * 根据商品ID查询商品库存数量的方法。 + * 此方法的目的是从数据库中获取指定商品的库存数量,通过接收商品的唯一标识符,也就是商品ID(id)作为查询条件, + * 利用MyBatis的SQL映射机制,构造相应的查询语句去数据库中查找并获取对应的库存数量信息。 + * + * @Param("id") Integer id:表示要查询库存数量的商品的唯一标识符,是一个整数类型参数。 + * 在SQL查询语句中,它将作为精确匹配的条件(例如通过类似 WHERE product_id = #{id} 的语句结构,具体取决于MyBatis配置的实际SQL语句), + * 用于定位到数据库中对应的商品记录,进而获取其库存数量相关的数据。需要确保传入的id值准确对应数据库中存在的商品记录,否则可能查询不到期望的库存数量信息。 + * + * @return Integer:返回值是一个整数类型,表示查询到的商品的库存数量。 + * 如果数据库中存在对应商品ID的记录,并且库存数量字段有合法的值,那么将返回该商品实际的库存数量数值。 + * 若数据库中不存在该商品ID对应的记录,或者由于其他原因(比如库存数量字段数据异常等情况)无法获取到有效的库存数量,那么可能返回null值, + * 业务层可以根据返回的结果进行相应的处理,例如判断库存是否充足、展示库存信息给用户等操作。 + */ } \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/entity/Category.java b/snailmall-product-service/src/main/java/com/njupt/swg/entity/Category.java index 9e80994..3ad8376 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/entity/Category.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/entity/Category.java @@ -3,20 +3,43 @@ package com.njupt.swg.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; - import java.util.Date; +/** + * Category类是用于表示商品分类的实体类,它在项目中对应数据库中的商品分类表结构,用于存储和传递商品分类相关的信息, + * 通过使用Lombok提供的注解,简化了代码编写,自动生成了常用的方法(如Getter、Setter、构造方法等),方便在业务逻辑中对商品分类对象进行操作。 + */ @Data +// @Data注解是Lombok提供的一个便捷注解,它会自动为类中的所有非静态、非final字段生成Getter、Setter方法,同时还会生成toString、equals和hashCode方法, +// 这样就无需手动编写这些重复的代码,提高了代码开发效率,使得在其他类中可以方便地获取和设置该类对象的属性值,以及进行对象之间的比较、打印等操作。 + @AllArgsConstructor +// @AllArgsConstructor注解会为类生成一个包含所有参数的构造方法,这个构造方法接受类中所有属性作为参数,方便在创建对象时一次性初始化所有属性, +// 例如可以通过 new Category(1, 0, "电子产品", true, 1) 这样的方式来创建一个Category对象,传入对应的属性值进行初始化,适用于需要完整初始化对象的场景。 + @NoArgsConstructor +// @NoArgsConstructor注解会为类生成一个无参构造方法,在一些框架(如Spring在进行依赖注入、MyBatis在创建对象实例等场景)或者需要默认创建对象的情况下,无参构造方法是必要的, +// 它提供了一种简单的创建对象的方式,后续可以再通过Setter方法等去设置具体的属性值,保证了类在不同使用场景下的灵活性。 + public class Category { private Integer id; + // 用于存储商品分类的唯一标识符,通常对应数据库表中的主键字段,通过这个ID可以唯一确定一个商品分类,在数据库操作(如查询、更新、删除等)以及业务逻辑中, + // 可以依据这个ID来定位和处理特定的商品分类记录,例如通过分类ID查找该分类下的所有商品等操作。 private Integer parentId; + // 表示当前商品分类的父分类的ID,用于构建商品分类的层级关系,若parentId为0或者null,通常表示该分类是顶级分类,没有上级分类, + // 通过这个字段可以实现分类的树形结构,方便进行分类的层级展示、查询子分类、查找上级分类等相关操作,例如在电商平台中展示商品分类目录时, + // 可以依据parentId来展示不同层级的分类以及它们之间的包含关系。 private String name; + // 存储商品分类的名称,是用于直观展示和区分不同商品分类的重要属性,例如"电子产品"、"服装"、"食品"等,用户在浏览商品或者后台管理分类时, + // 通过这个名称可以清楚地了解每个分类所涵盖的商品范围,在业务逻辑中也常根据分类名称进行模糊查询、匹配等操作,比如查找名称包含特定关键字的分类等情况。 private Boolean status; + // 用于表示商品分类的状态,一般是布尔类型,常见的取值含义可以是true表示分类可用、处于激活状态,false表示分类不可用、被禁用等情况, + // 在业务中可以根据这个状态来决定是否展示该分类下的商品、是否允许对该分类进行编辑等操作,例如在后台管理系统中,只展示状态为true的分类给用户操作。 private Integer sortOrder; + // 用于确定商品分类在展示或者排序时的顺序,整数类型的排序序号,数值越小通常表示在列表中越靠前的位置,例如可以按照sortOrder的值从小到大排列分类, + // 在前端展示分类列表或者后台管理分类顺序时,可以依据这个字段来调整分类的展示顺序,方便用户查看和操作分类,使其更符合业务需求和用户体验。 } \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/entity/Product.java b/snailmall-product-service/src/main/java/com/njupt/swg/entity/Product.java index e2eeee0..047f184 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/entity/Product.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/entity/Product.java @@ -4,37 +4,75 @@ import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; - import java.math.BigDecimal; import java.util.Date; +/** + * Product类是用于表示商品信息的实体类,它对应着数据库中存储商品相关数据的表结构,在整个项目的业务逻辑中扮演着关键角色, + * 用于承载和传递商品各个方面的详细信息,通过Lombok提供的注解简化了代码编写,自动生成了常用的方法,方便对商品对象进行操作和数据交互。 + */ @Data +// @Data注解是Lombok提供的一个实用注解,它会自动为类中的所有非静态、非final字段生成Getter、Setter方法,同时还会生成toString、equals和hashCode方法。 +// 这样一来,在其他类中就能便捷地获取和设置该类对象的各个属性值,并且在进行对象比较、打印输出等操作时也更加方便,无需手动编写这些重复的代码,提高了开发效率。 + @AllArgsConstructor +// @AllArgsConstructor注解会为类生成一个包含所有参数的构造方法,这个构造方法接受类中所有属性作为参数。 +// 例如,可以通过类似 new Product(1, 100, "商品名称", "副标题", "主图路径", "子图路径列表", "商品详情", new BigDecimal("9.99"), 100, 1, new Date(), new Date()) 这样的方式创建一个Product对象, +// 一次性传入所有属性值进行初始化,适用于在明确知道所有属性值且需要完整初始化对象的场景下使用。 + @NoArgsConstructor +// @NoArgsConstructor注解会为类生成一个无参构造方法,在很多场景下(比如Spring框架进行依赖注入、MyBatis框架创建对象实例等),无参构造方法是必不可少的。 +// 它提供了一种简单创建对象的方式,后续可以再通过Setter方法等去逐个设置具体的属性值,使得类在不同的使用场景下更加灵活,能满足多样化的需求。 + public class Product { private Integer id; + // 用于存储商品的唯一标识符,通常对应数据库表中的主键字段,通过这个ID可以在整个系统中唯一确定一个商品, + // 在数据库操作(如查询、更新、删除商品记录等)以及各种业务逻辑处理中,都依靠这个ID来精准定位和操作特定的商品,例如根据商品ID获取商品详情、更新商品信息等操作。 private Integer categoryId; + // 表示该商品所属的分类的ID,用于建立商品与商品分类之间的关联关系,通过这个字段可以知道商品属于哪一个分类, + // 在业务逻辑中,常用于根据分类查找商品、展示分类下的商品列表等操作,比如在电商平台中按照分类展示不同类型的商品,或者通过分类ID筛选出该分类下的所有商品信息等情况。 private String name; + // 存储商品的名称,这是用于直观展示和区分不同商品的重要属性,用户在浏览商品、搜索商品或者后台管理商品时, + // 通过商品名称能够快速了解商品的大致内容,在业务逻辑里也常常会根据商品名称进行模糊查询、精确匹配等操作,例如搜索名称包含特定关键字的商品等情况。 private String subtitle; + // 用于存放商品的副标题,一般可以对商品名称进行补充说明,提供更多关于商品特点、优势等方面的简要信息, + // 虽然不像商品名称那样是必填且唯一标识商品的关键属性,但可以帮助用户更全面地了解商品内容,在展示商品详情或者商品列表时,可以作为辅助信息展示给用户。 private String mainImage; + // 存储商品的主图路径信息,通常指向服务器上存储该商品主图片的具体位置(可能是相对路径或者完整的URL地址,具体取决于项目配置), + // 在前端页面展示商品时,会依据这个路径来加载并显示商品的主图片,让用户能够直观地看到商品的外观等特征,是商品展示环节中很重要的一个属性。 private String subImages; + // 用于保存商品的子图片路径信息,一般是以某种特定格式(如逗号分隔的字符串等)存储多个子图片的路径,同样指向服务器上对应的图片存储位置, + // 这些子图片可以从不同角度、细节展示商品,丰富用户对商品的视觉认知,在前端展示商品详情页面时,会根据这些路径加载并展示相应的子图片,增强商品展示效果。 private String detail; + // 存储商品的详细描述信息,包含了商品的功能、参数、使用方法、材质等各方面详细的文字介绍内容, + // 用户在查看商品详情时,通过这个属性可以深入了解商品的具体情况,以便做出购买决策,在后台管理商品时,也会对这个属性进行编辑、更新等操作来完善商品的介绍内容。 private BigDecimal price; + // 用于表示商品的价格,采用BigDecimal类型是为了更精确地处理数值,避免浮点数运算带来的精度损失问题,尤其是在涉及货币计算等对精度要求较高的场景下, + // 这个属性明确了商品的售价,在业务逻辑中会用于计算订单总价、展示商品价格、进行价格比较等各种与价格相关的操作。 private Integer stock; + // 表示商品的库存数量,即当前可售卖的商品数量,在电商业务中,库存管理是很重要的一部分,会根据这个库存数量来判断商品是否还有货、能否进行销售等情况, + // 例如在用户下单时需要检查库存是否充足,后台管理系统也会对库存数量进行更新操作(如进货增加库存、销售减少库存等)。 private Integer status; + // 用于体现商品的状态信息,一般可以通过不同的整数值来定义不同的状态含义(具体含义由业务规则确定),例如常见的可以是1表示商品上架、可售卖状态,0表示商品下架、不可售卖状态等, + // 在业务逻辑中会根据这个状态来决定是否在前端展示商品、是否允许用户购买等操作,后台管理系统也会对商品的状态进行修改操作(如上下架商品)。 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS") private Date createTime; + // 使用 @JsonFormat注解对日期类型的创建时间属性进行格式化设置,指定其在序列化和反序列化过程中的格式为 "yyyy-MM-dd HH:mm:ss.SSS", + // 也就是精确到毫秒的年月日时分秒格式,这样在将商品对象转换为JSON字符串(如接口返回数据给前端)或者从JSON字符串转换为商品对象(如接收前端传入的数据)时, + // 日期属性能够按照统一的、易读的格式进行处理,便于数据的传输和展示,createTime属性记录了商品在系统中被创建的时间,常用于记录商品的历史信息、查询商品创建顺序等操作。 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS") private Date updateTime; + // 同样使用 @JsonFormat注解进行格式化的日期属性,用于记录商品信息最后一次被更新的时间,格式也是精确到毫秒的 "yyyy-MM-dd HH:mm:ss.SSS", + // 在业务逻辑中,当商品的任何属性发生修改时,一般会更新这个时间字段,方便跟踪商品信息的变更历史,例如查看商品最近一次修改是什么时候等情况。 } \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/entity/User.java b/snailmall-product-service/src/main/java/com/njupt/swg/entity/User.java index 4354b81..b067198 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/entity/User.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/entity/User.java @@ -4,40 +4,77 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; - import java.io.Serializable; import java.util.Date; /** + * User类是用于表示用户信息的实体类,在整个项目中对应着数据库中存储用户相关数据的表结构,承载着用户各个方面的关键信息, + * 是实现用户管理、权限控制、登录验证等众多业务逻辑的基础,通过使用Lombok提供的多个注解,简化了代码编写,方便对用户对象进行操作和数据交互。 + * * @Author swg. * @Date 2018/12/31 21:01 * @CONTACT 317758022@qq.com * @DESC 用户实体类 */ @Data +// @Data注解是Lombok提供的一个强大的注解,它会自动为类中的所有非静态、非final字段生成Getter、Setter方法,同时还会生成toString、equals和hashCode方法。 +// 这样一来,在其他类中就能够方便快捷地获取和设置该类对象的各个属性值,并且在进行对象比较、打印输出等操作时也无需手动编写这些重复的代码,极大地提高了开发效率。 + @NoArgsConstructor +// @NoArgsConstructor注解会为类生成一个无参构造方法,在很多场景下(例如Spring框架进行依赖注入、MyBatis框架创建对象实例等),无参构造方法是必不可少的。 +// 它提供了一种简单创建用户对象的方式,后续可以再通过Setter方法等去逐个设置具体的属性值,使得类在不同的使用场景下更加灵活,能满足多样化的业务需求。 + @AllArgsConstructor +// @AllArgsConstructor注解会为类生成一个包含所有参数的构造方法,这个构造方法接受类中所有属性作为参数。 +// 例如,可以通过类似 new User(1, "user1", "password1", "user1@example.com", "1234567890", "question1", "answer1", 1, new Date(), new Date()) 这样的方式创建一个User对象, +// 一次性传入所有属性值进行初始化,适用于在明确知道所有属性值且需要完整初始化对象的场景下使用。 + @ToString +// @ToString注解会自动为类生成一个toString方法,默认情况下它会包含类名以及所有非静态、非transient字段的值,方便在调试、日志输出等场景中快速查看用户对象的具体内容, +// 例如在打印用户对象时,能够直观地看到各个属性的值,便于排查问题和了解对象的状态。 + public class User implements Serializable { + // 实现Serializable接口表示该类的对象可以被序列化和反序列化,这在很多场景下是非常重要的,比如将用户对象存储到文件、在网络中传输(如分布式系统中的远程调用等情况), + // 通过序列化可以将对象转换为字节流进行持久化或传输,反序列化则可以从字节流重新恢复为对象,确保用户对象能够在不同的环境和操作中正确地保存和恢复其状态。 + private Integer id; + // 用于存储用户的唯一标识符,通常对应数据库表中的主键字段,通过这个ID可以在整个系统中唯一确定一个用户, + // 在数据库操作(如查询、更新、删除用户记录等)以及各种业务逻辑处理中,都依靠这个ID来精准定位和操作特定的用户,例如根据用户ID获取用户详细信息、更新用户资料等操作。 private String username; + // 存储用户的用户名,这是用户在登录系统、进行各种操作时用于标识自己的重要属性,通常要求具有唯一性(具体取决于业务规则), + // 用户在注册时会设置自己的用户名,后续登录或者系统内的交互操作中,通过这个用户名来区分不同的用户,在业务逻辑里也常常会根据用户名进行查找、验证等操作,例如验证用户名是否存在、根据用户名查找用户信息等情况。 private String password; + // 用于存放用户的登录密码,密码通常会经过加密处理(如使用MD5等加密算法)后存储,以保障用户账户的安全性, + // 在用户登录时,会将输入的密码进行同样方式的加密后与存储的加密密码进行比对,以此来验证用户身份,同时在修改密码等业务场景中,也会对这个属性进行更新操作。 private String email; + // 存储用户的电子邮箱地址,可用于用户注册验证、找回密码、接收系统通知等功能,例如在用户注册时可能会发送一封验证邮件到这个邮箱地址, + // 在业务逻辑中,有时也会根据邮箱地址来查找用户、验证邮箱的唯一性等操作,确保每个用户的邮箱地址在系统中是唯一且有效的。 private String phone; + // 用于保存用户的手机号码,手机号码同样在很多业务场景中有重要作用,比如用于手机验证码登录、绑定账号、接收短信通知等功能, + // 在一些需要验证用户身份或者与用户进行即时通讯的场景下,手机号码是很关键的信息,并且也可能会验证其唯一性以及格式的合法性。 private String question; + // 存储用户设置的密保问题,密保问题常用于用户找回密码等安全验证场景,当用户忘记密码时,可以通过回答预先设置的密保问题来证明自己的身份,进而重置密码, + // 在业务逻辑中,会在用户设置密保、找回密码等相关操作时涉及对这个属性的处理,确保密保问题的合理性以及与用户答案的匹配性。 private String answer; + // 对应于用户设置的密保问题的答案,是与question属性配合使用的关键信息,用于在找回密码等安全验证环节中,与用户输入的答案进行比对, + // 只有当用户输入的答案与存储的这个答案一致时,才允许进行后续的密码重置等操作,保障用户账户的安全性和找回密码流程的合法性。 //角色0-管理员,1-普通用户 private Integer role; + // 用于表示用户在系统中的角色,通过整数值来区分不同的角色类型,这里约定0表示管理员角色,1表示普通用户角色(具体的角色划分和对应数值可以根据业务规则调整), + // 在权限控制相关的业务逻辑中,会依据这个角色属性来决定用户能够访问哪些资源、执行哪些操作,例如管理员可能具有更多的系统管理权限,而普通用户只能进行一些常规的操作。 private Date createTime; + // 记录用户在系统中被创建的时间,一般在用户注册成功时会自动设置这个时间字段,通过存储创建时间,可以方便地查询用户注册的先后顺序、统计不同时间段内的新增用户数量等, + // 在数据统计、用户行为分析等业务场景中,这个属性是很有价值的基础数据,并且在进行数据库操作时,也需要注意对这个字段的正确维护(如在插入新用户记录时赋值等情况)。 private Date updateTime; - + // 用于记录用户信息最后一次被更新的时间,当用户修改了自己的用户名、密码、联系方式等任何属性信息时,一般会同步更新这个时间字段, + // 它有助于跟踪用户信息的变更历史,例如查看用户最近一次修改资料是什么时候,也方便在一些数据一致性检查、审计等业务场景中使用,了解用户信息的动态变化情况。 } \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/service/FileServiceImpl.java b/snailmall-product-service/src/main/java/com/njupt/swg/service/FileServiceImpl.java index db9681a..67c6ffb 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/service/FileServiceImpl.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/service/FileServiceImpl.java @@ -11,50 +11,107 @@ import java.io.IOException; import java.util.UUID; /** + * FileServiceImpl类实现了IFileService接口,是用于处理文件上传相关业务逻辑的服务实现类。 + * 它负责接收上传的文件(MultipartFile类型)以及指定的文件上传路径,完成将文件保存到本地服务器临时目录,并进一步上传到FTP服务器(通过FtpUtil工具类)的操作, + * 最后删除本地临时文件,返回上传后文件在服务器上对应的文件名(可用于后续访问文件等操作),整个过程中通过日志记录关键步骤和异常情况,方便后续的调试与维护。 + * * @Author swg. * @Date 2019/1/3 19:13 * @CONTACT 317758022@qq.com * @DESC */ @Service +// @Service注解用于标记该类是Spring框架中的一个服务层组件,意味着这个类会被Spring容器管理,在其他地方(如控制层)可以通过依赖注入的方式使用这个类的实例, +// 同时Spring会对其进行相关的生命周期管理以及一些配置和增强操作(如AOP切面等,若有配置的话),方便业务逻辑的组织和复用。 + @Slf4j -public class FileServiceImpl implements IFileService{ +// 使用Lombok的 @Slf4j注解自动生成名为log的SLF4J日志记录器,用于在文件上传的各个步骤中记录关键信息(如文件名称、路径等)以及出现异常情况时记录详细的错误信息, +// 方便后续查看日志来了解文件上传的执行情况,排查可能出现的问题,例如文件上传失败的原因、是否成功传输到FTP服务器等情况。 + +public class FileServiceImpl implements IFileService { + /** + * 实现文件上传功能的方法,接收一个MultipartFile类型的文件对象以及表示文件上传路径的字符串参数,完成文件上传相关的一系列操作。 + * + * @param file 表示要上传的文件对象,是MultipartFile类型,它是Spring框架中用于处理文件上传的接口类型, + * 可以获取文件的原始名称、文件内容等信息,通过这个对象传递从客户端上传过来的文件数据,方便后续进行保存、传输等操作。 + * @param path 表示文件上传的目标路径,是一个字符串类型的参数,指定了文件要保存到本地服务器以及后续上传到FTP服务器的基础路径, + * 例如可以是服务器上的某个特定目录(如 "/upload" 等),需要确保这个路径在服务器上是可写的,并且符合项目对于文件存储位置的规划和要求。 + * @return String 返回值是一个字符串,代表上传后文件在服务器上对应的文件名(通常经过了重命名等处理), + * 这个文件名可用于后续在系统中构建文件的访问链接、记录文件存储信息等操作,若文件上传过程中出现异常情况,则返回null,表示上传失败。 + */ @Override public String upload(MultipartFile file, String path) { String fileName = file.getOriginalFilename(); + // 获取上传文件的原始文件名,这个文件名是客户端上传文件时原本的名称,例如客户端上传了一个名为 "abc.jpg" 的图片文件,这里获取到的就是 "abc.jpg", + // 通过这个原始文件名可以提取文件的扩展名等信息,用于后续生成新的文件名以及进行相关的文件操作判断等情况。 + //扩展名 //abc.jpg - String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1); - String uploadFileName = UUID.randomUUID().toString()+"."+fileExtensionName; - log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}",fileName,path,uploadFileName); + String fileExtensionName = fileName.substring(fileName.lastIndexOf(".") + 1); + // 从原始文件名中提取文件的扩展名,通过查找文件名中最后一个 "." 出现的位置,然后取其后面的字符串部分,得到文件的扩展名, + // 例如对于文件名 "abc.jpg",通过该操作获取到的扩展名就是 "jpg",用于后续构建新的文件名(确保新文件名保留正确的文件类型扩展名),便于识别文件类型以及正确的访问和处理文件。 + + String uploadFileName = UUID.randomUUID().toString() + "." + fileExtensionName; + // 使用Java的UUID(通用唯一识别码)生成一个随机的字符串,并与获取到的文件扩展名拼接起来,组成新的文件名, + // 这样做的目的是为了避免文件名冲突(特别是在多用户同时上传文件或者重复上传同名文件的情况下),确保每个上传的文件在服务器上有一个唯一的标识名称, + // 例如生成的新文件名可能类似 "550e8400-e29b-41d4-a716-446655440000.jpg",方便后续文件的存储、管理以及访问操作。 + + log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}", fileName, path, uploadFileName); + // 使用日志记录器记录文件上传操作开始时的关键信息,包括原始文件名、上传的目标路径以及生成的新文件名,方便后续查看日志了解文件上传的初始情况, + // 同时在出现问题时也可以通过这些记录来排查是哪个文件在哪个路径下上传出现了异常等情况。 File fileDir = new File(path); - if(!fileDir.exists()){ + if (!fileDir.exists()) { fileDir.setWritable(true); fileDir.mkdirs(); } - log.info("【文件上传路径为:{}】",fileDir); - File targetFile = new File(path,uploadFileName); + // 根据传入的文件上传路径创建一个File对象,用于表示对应的目录,如果该目录不存在,则先设置其可写权限(确保后续可以创建文件等操作), + // 然后通过mkdirs方法创建该目录及其所有必要的父目录(如果不存在的话),保证文件上传时有对应的本地存储目录可用,避免因目录不存在导致文件保存失败的情况。 + + log.info("【文件上传路径为:{}】", fileDir); + // 使用日志记录创建好的文件上传路径对应的File对象信息,方便后续查看实际使用的文件存储目录情况,确认目录是否创建正确以及是否符合预期等情况, + // 也有助于排查可能因目录问题导致的文件上传异常情况。 + + File targetFile = new File(path, uploadFileName); + // 根据文件上传路径和新生成的文件名创建一个用于保存上传文件的目标File对象,这个对象代表了文件在本地服务器上最终要保存的位置和对应的文件名, + // 后续会将上传的文件内容写入到这个目标文件中,完成文件在本地服务器的临时存储操作。 try { file.transferTo(targetFile); + // 将上传的MultipartFile对象中的文件内容传输并保存到之前创建的本地目标文件(targetFile)中, + // 这个操作会将客户端上传的文件数据实际写入到服务器本地的文件系统中,完成文件在本地服务器的临时存储,如果这个过程出现I/O异常等问题,会抛出IOException异常, + // 例如文件权限不足、磁盘空间不足等原因可能导致传输失败,需要在catch块中进行相应的异常处理。 + //文件已经上传成功了 log.info("【文件上传本地服务器成功】"); - + // 使用日志记录文件成功上传到本地服务器的信息,方便后续查看文件上传的执行进度以及确认本地存储这一步是否成功完成, + // 若后续出现问题(如无法上传到FTP服务器等情况),可以通过这个记录来判断是否是本地存储环节之后出现的问题。 FtpUtil.uploadFile(Lists.newArrayList(targetFile)); + // 调用FtpUtil工具类的uploadFile方法,将包含目标文件(targetFile)的列表传递进去,执行将文件上传到FTP服务器的操作, + // FtpUtil类应该是专门用于处理FTP文件传输相关逻辑的工具类,通过它实现与FTP服务器的连接、文件上传等功能,若这个过程出现异常(如FTP连接失败、权限问题等), + // 会在FtpUtil内部进行相应的处理(可能记录日志、抛出异常等情况,具体取决于FtpUtil的实现),这里只是调用其方法来触发上传到FTP服务器的操作。 + //已经上传到ftp服务器上 log.info("【文件上传到文件服务器成功】"); + // 使用日志记录文件成功上传到FTP服务器的信息,表明文件已经从本地服务器进一步传输到了FTP服务器上,完成了整个文件上传流程中的关键步骤, + // 通过这个记录可以方便后续查看文件是否完整地按照预期上传到了指定的FTP服务器,若出现问题(如文件在FTP服务器上不可访问等情况),可以据此排查是FTP上传环节出现的问题。 targetFile.delete(); log.info("【删除本地文件】"); + // 在文件成功上传到FTP服务器后,删除本地临时保存的文件,以释放本地服务器的磁盘空间,避免不必要的文件冗余存储, + // 通过这个操作,本地服务器只起到一个临时中转存储的作用,最终文件存储在FTP服务器上,而本地只保留相关的文件上传记录(如文件名等信息,用于后续访问等操作)。 } catch (IOException e) { - log.error("上传文件异常",e); + log.error("上传文件异常", e); + // 如果在文件上传过程(包括本地保存或者上传到FTP服务器等操作)中出现IOException异常,使用日志记录器记录详细的错误信息,包括异常堆栈信息, + // 方便后续查看日志来排查具体是哪个环节出现了I/O相关的问题,例如是文件传输失败、FTP连接异常还是其他文件操作异常等情况,同时返回null表示文件上传失败。 return null; } //A:abc.jpg //B:abc.jpg return targetFile.getName(); + // 返回上传后文件在服务器上对应的文件名(这里是经过重命名后的新文件名,即之前生成的uploadFileName), + // 这个文件名可以在其他地方(如数据库中记录、返回给前端用于构建文件访问链接等)使用,用于标识和访问已经上传到FTP服务器上的文件,完成文件上传操作并返回相应的文件名结果。 } -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/service/IFileService.java b/snailmall-product-service/src/main/java/com/njupt/swg/service/IFileService.java index 17e707c..9d0bcfc 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/service/IFileService.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/service/IFileService.java @@ -3,11 +3,30 @@ package com.njupt.swg.service; import org.springframework.web.multipart.MultipartFile; /** + * IFileService接口定义了文件上传相关操作的抽象方法,它作为一种契约,规定了实现类需要具备的文件上传功能, + * 在项目的架构中处于服务层,用于解耦文件上传的具体实现逻辑与其他调用者(如控制层)之间的关系,使得不同的实现类可以根据具体需求来实现文件上传的业务逻辑, + * 同时方便进行依赖注入,提高代码的可维护性和扩展性。 + * * @Author swg. * @Date 2019/1/3 19:12 * @CONTACT 317758022@qq.com * @DESC */ public interface IFileService { + /** + * 文件上传的抽象方法,规定了实现类需要实现的文件上传功能的基本规范。 + * 该方法接收一个MultipartFile类型的文件对象和一个表示文件上传路径的字符串参数,用于将指定的文件上传到指定的路径位置, + * 具体的上传逻辑(如文件保存到本地服务器、再上传到远程服务器、文件名处理等操作)由实现类去具体实现,不同的实现场景(比如上传到不同的文件服务器、采用不同的文件命名策略等)可以有不同的具体实现方式。 + * + * @param file 表示要上传的文件对象,是MultipartFile类型,它是Spring框架中用于处理文件上传的接口类型, + * 通过这个对象可以获取文件的原始名称、文件内容、文件大小等信息,传递从客户端上传过来的文件数据,方便后续进行各种文件操作, + * 实现类需要根据这个文件对象中的数据来进行实际的文件上传处理,例如将文件内容保存到服务器指定位置等操作。 + * @param path 表示文件上传的目标路径,是一个字符串类型的参数,指定了文件要保存到的具体位置(可以是本地服务器的某个目录路径,也可以是远程文件服务器相关的路径等,具体取决于实现方式), + * 实现类需要依据这个路径将文件正确地上传到对应的位置,并且需要考虑路径的合法性、可写性等因素,确保文件能够成功保存到指定的路径下, + * 例如路径可以是 "/upload" 表示本地服务器上的一个名为 "upload" 的目录,或者是FTP服务器的某个特定目录的访问路径等情况。 + * @return String 返回值是一个字符串,代表上传后文件在服务器上对应的文件名(可能经过了重命名等处理), + * 这个文件名可用于后续在系统中构建文件的访问链接、记录文件存储信息等操作,不同的实现类根据自身的文件处理逻辑返回相应的文件名结果, + * 如果文件上传过程中出现异常情况或者上传失败,返回值应该符合相应的错误处理规范(比如返回null或者特定的错误提示字符串等,具体由实现类和业务需求决定)。 + */ String upload(MultipartFile file, String path); -} +} \ No newline at end of file diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/service/IProductService.java b/snailmall-product-service/src/main/java/com/njupt/swg/service/IProductService.java index 01313f2..3f31d9f 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/service/IProductService.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/service/IProductService.java @@ -6,6 +6,11 @@ import com.njupt.swg.entity.Product; import com.njupt.swg.vo.ProductDetailVo; /** + * IProductService接口定义了一系列与商品(Product)相关的业务操作方法,它作为服务层的抽象接口, + * 规定了处理商品业务逻辑的规范和契约,具体的实现类需要按照这些方法定义去实现相应的业务功能, + * 从而实现了业务逻辑层与控制层、数据持久层等其他层之间的解耦,方便在不同的业务场景下灵活替换或扩展具体的业务实现方式, + * 同时也使得控制层等调用者可以统一按照接口约定来调用商品相关的服务,无需关心具体的实现细节。 + * * @Author swg. * @Date 2019/1/2 17:36 * @CONTACT 317758022@qq.com diff --git a/snailmall-product-service/src/main/java/com/njupt/swg/service/ProductServiceImpl.java b/snailmall-product-service/src/main/java/com/njupt/swg/service/ProductServiceImpl.java index 57d1713..b633017 100644 --- a/snailmall-product-service/src/main/java/com/njupt/swg/service/ProductServiceImpl.java +++ b/snailmall-product-service/src/main/java/com/njupt/swg/service/ProductServiceImpl.java @@ -25,288 +25,637 @@ import java.util.Date; import java.util.List; /** + * ProductServiceImpl类实现了IProductService接口,是整个项目中处理商品相关业务逻辑的核心服务类, + * 它整合了数据持久层(通过ProductMapper)、缓存操作(通过CommonCacheUtil)以及与其他服务的交互(通过CategoryClient)等功能, + * 为前端(包括后台管理系统前端和前台门户前端)提供了丰富的商品操作服务,如获取商品列表、查询商品详情、更新商品状态、新增/更新商品等功能, + * 并且在操作过程中注重数据的格式转换、缓存的合理使用以及与其他模块的协同工作,确保商品业务逻辑的完整性和高效性。 + * * @Author swg. * @Date 2019/1/2 17:36 * @CONTACT 317758022@qq.com * @DESC */ @Service +// @Service注解用于标记该类是Spring框架中的服务层组件,意味着这个类会被Spring容器管理,其他地方(如控制层)可以通过依赖注入的方式使用这个类的实例, +// 同时Spring会对其进行相关的生命周期管理以及一些配置和增强操作(如AOP切面等,若有配置的话),方便业务逻辑的组织和复用。 + @Slf4j +// 使用Lombok的 @Slf4j注解自动生成名为log的SLF4J日志记录器,用于在商品业务逻辑处理的各个步骤中记录关键信息(如操作的参数、执行结果等)以及出现异常情况时记录详细的错误信息, +// 方便后续查看日志来了解商品业务的执行情况,排查可能出现的问题,例如商品查询失败的原因、更新状态不成功的原因等情况。 + public class ProductServiceImpl implements IProductService{ @Autowired private ProductMapper productMapper; + // 通过Spring的依赖注入机制注入ProductMapper接口的实现类实例,用于与数据库进行交互,执行如查询商品记录、更新商品信息等持久化操作, + // ProductMapper中定义了一系列针对商品数据表的操作方法,这个实例在本类的多个业务方法中都会被调用,以获取或更新商品相关的数据。 + @Autowired private CategoryClient categoryClient; + // 注入CategoryClient实例,CategoryClient通常是基于Feign框架实现的用于调用商品分类相关服务的客户端接口, + // 通过它可以远程调用其他服务(可能是独立的商品分类服务微服务)提供的接口,获取商品分类的详细信息等内容,在一些需要涉及商品分类信息处理的业务方法中会用到它。 + @Autowired private CommonCacheUtil commonCacheUtil; +// 注入CommonCacheUtil实例,用于操作缓存(通常是Redis缓存等情况),实现如缓存商品数据、从缓存中获取商品数据、删除缓存中的商品相关键值对等功能, + // 在整个商品业务逻辑中,通过合理使用缓存可以提高数据获取的效率,减少对数据库的频繁访问,提升系统的整体性能。 + /** + * 后台获取产品分页列表的业务方法实现,用于获取后台管理系统中展示的商品分页列表信息。 + * + * @param pageNum 表示要获取的商品列表所在的页码,是一个整数类型参数,用于指定获取第几页的商品列表,例如传入1表示获取第一页的商品列表, + * 通常默认从第一页开始展示商品列表,调用者(如后台管理系统的控制器)可以根据用户操作或者业务需求传入不同的页码值来获取相应页的商品信息。 + * @param pageSize 表示每页显示的商品数量,也是整数类型参数,用于控制每页展示的商品个数,例如传入10表示每页展示10个商品, + * 调用者可以根据页面布局、用户查看习惯等因素传入合适的每页数量值。 + * @return ServerResponse 返回一个ServerResponse类型的对象,该对象用于包装获取商品分页列表操作的结果, + * 若操作成功,返回的ServerResponse对象中会包含表示成功的状态码以及分页后的商品列表数据等信息,方便调用者展示商品列表, + * 若操作失败(如数据库查询异常、参数错误等原因),则会包含相应的错误信息和表示失败的状态码,供调用者进行相应的错误处理,例如提示用户获取列表失败等操作。 + */ @Override public ServerResponse list(int pageNum, int pageSize) { //1.pagehelper对下一行取出的集合进行分页 PageHelper.startPage(pageNum,pageSize); + // 使用PageHelper插件启动分页功能,它会拦截后续执行的查询语句(通过ProductMapper进行的查询),并根据传入的页码和每页数量参数对查询结果进行分页处理, + // 例如,如果查询到的总商品数量为100条,传入pageNum为2,pageSize为10,那么将会获取到第11 - 20条商品记录,方便在后台展示分页的商品列表。 + List productList = productMapper.selectList(); + // 通过ProductMapper的selectList方法从数据库中获取所有的商品记录列表,这里获取到的是原始的Product实体对象列表,后续需要对其进行一些格式转换等操作, + // 以适配返回给前端展示的需求,例如将其转换为包含部分商品属性的视图对象列表等情况。 + List productListVoList = Lists.newArrayList(); for(Product product:productList){ ProductListVo productListVo = assembleProductListVo(product); productListVoList.add(productListVo); } + // 遍历从数据库获取到的商品列表,调用assembleProductListVo方法将每个Product实体对象转换为ProductListVo视图对象, + // ProductListVo对象可能只包含了前端展示所需的部分商品属性,这样可以减少返回给前端的数据量,同时保证展示的信息是前端关心的关键内容, + // 将转换后的视图对象依次添加到productListVoList列表中,用于后续构建包含分页信息的返回结果。 + //2.返回给前端的还需要一些其他的分页信息,为了不丢失这些信息,需要进行下面的处理 PageInfo pageInfo = new PageInfo(productList); pageInfo.setList(productListVoList); + // 创建PageInfo对象,它会自动封装从数据库查询到的商品列表的分页相关信息(如总记录数、总页数、当前页数据列表等), + // 然后将转换后的视图对象列表(productListVoList)设置到PageInfo对象中,替换掉原本的包含所有商品属性的列表,确保返回给前端的分页信息是准确且符合展示需求的, + // 最后将包含分页商品列表信息的PageInfo对象包装在ServerResponse对象中返回给调用者(后台管理系统前端)。 + return ServerResponse.createBySuccess(pageInfo); } - + /** + * 后台的搜索,根据id或者name模糊查询的业务方法实现,用于在后台管理系统中实现商品的搜索功能,并返回分页的搜索结果。 + * + * @param productName 表示要搜索的商品名称,是一个字符串类型参数,用于进行模糊查询,若传入具体的商品名称关键字, + * 实现类需要在数据库中查找名称包含该关键字的商品记录(常见的是使用 LIKE '%关键字%' 这样的方式进行模糊匹配), + * 若不传该参数或者传入空字符串,则在搜索时可视为不限制商品名称条件,仅依据其他参数(如商品ID等)进行查询,方便实现多种搜索场景,例如只按ID搜索或者结合名称和ID进行搜索等情况。 + * @param productId 表示商品的唯一标识符,即商品ID,是一个整数类型参数,用于精确查找特定的商品,若传入具体的商品ID值, + * 实现类会优先根据该ID查找对应的商品记录,若不传该参数或者传入null值,则在搜索时可视为不限制商品ID条件, + * 可以结合商品名称等其他参数进行商品查找,通过与productName等参数的灵活组合,实现不同条件下的商品搜索功能。 + * @param pageNum 表示要获取的商品列表所在的页码,整数类型,用于指定获取搜索结果中第几页的商品列表,其作用与在list方法中的类似, + * 调用者可以传入具体页码值获取对应页的搜索到的商品信息,实现类需要根据这个页码值进行分页查询操作,获取相应页的符合搜索条件的商品数据。 + * @param pageSize 表示每页显示的商品数量,整数类型,用于控制每页展示的搜索到的商品个数,其作用同list方法中的pageSize参数, + * 调用者可传入期望的每页数量值,实现类依据此参数对搜索到的商品数据进行分页处理,确保返回的商品列表符合每页显示数量的要求。 + * @return ServerResponse 返回一个ServerResponse类型的对象,其泛型参数为PageInfo, + * ServerResponse用于包装业务操作的结果,PageInfo是用于包装分页相关信息(如总记录数、总页数、当前页数据列表等)以及查询到的商品列表数据的对象, + * 若搜索操作成功,返回的ServerResponse对象中会包含表示成功的状态码以及有效的PageInfo对象(包含符合条件的商品列表和分页信息)等信息,方便调用者分页展示搜索结果, + * 若搜索失败(如数据库查询异常、参数错误等原因),则会包含相应的错误信息和表示失败的状态码,供调用者进行相应的错误处理,例如提示用户搜索失败等操作。 + */ @Override public ServerResponse search(String productName, Integer productId, int pageNum, int pageSize) { //开始准备分页 PageHelper.startPage(pageNum,pageSize); + // 同样使用PageHelper插件启动分页功能,为后续的商品搜索结果进行分页准备,后续查询到的符合条件的商品记录将会按照传入的页码和每页数量参数进行分页处理。 + //如果有内容,可以先在这里封装好,直接传到sql中去 if(StringUtils.isNotBlank(productName)){ productName = new StringBuilder().append("%").append(productName).append("%").toString(); } + // 如果传入的商品名称参数不为空字符串,对其进行模糊查询格式的处理,通过在名称前后添加 "%" 符号,将其转换为 LIKE 语句中可用的模糊查询格式, + // 例如,原本传入的商品名称为 "手机",处理后变为 "%手机%",这样在数据库查询时就能查找名称中包含 "手机" 关键字的所有商品记录了。 + List productList = productMapper.selectProductByNameAndId(productName,productId); + // 通过ProductMapper的selectProductByNameAndId方法,传入处理后的商品名称和商品ID参数,从数据库中查询符合条件的商品记录列表, + // 这个方法内部会根据传入的参数构建相应的SQL查询语句(基于MyBatis的映射机制),执行数据库查询操作,获取满足条件的商品记录。 + //转换一下传给前端显示 List productListVoList = Lists.newArrayList(); for(Product product:productList){ ProductListVo productListVo = assembleProductListVo(product); productListVoList.add(productListVo); } + // 遍历查询到的商品列表,调用assembleProductListVo方法将每个Product实体对象转换为ProductListVo视图对象, + // 目的同样是为了提取前端展示所需的部分商品属性,减少返回给前端的数据量,将转换后的视图对象依次添加到productListVoList列表中,用于后续构建返回结果。 + PageInfo pageInfo = new PageInfo(productList); pageInfo.setList(productListVoList); + // 创建PageInfo对象来封装分页相关信息,将包含商品列表的PageInfo对象的原始商品列表替换为转换后的视图对象列表, + // 确保返回给前端的分页信息中包含的是经过处理、符合展示需求的商品数据,最后将包含分页商品列表信息的PageInfo对象包装在ServerResponse对象中返回给调用者(后台管理系统前端)。 + return ServerResponse.createBySuccess(pageInfo); } - + /** + * 后台查看商品详情的业务方法实现,用于在后台管理系统中获取指定商品的详细信息。 + * + * @param productId 表示要查看详情的商品的唯一标识符,是一个整数类型参数,通过传入这个参数,实现类能够定位到数据库中对应的商品记录, + * 进而获取该商品的详细信息,如商品名称、描述、价格、库存等各种属性信息,调用者需要确保传入准确的商品ID值, + * 若传入的ID对应的商品不存在等情况,实现类需要在返回的ServerResponse对象中包含相应的错误信息和表示失败的状态码,方便调用者进行相应的处理,例如提示用户商品不存在等操作。 + * @return ServerResponse 返回一个ServerResponse类型的对象,其泛型参数为ProductDetailVo, + * ServerResponse用于包装业务操作的结果,ProductDetailVo是一个包含商品详细信息的视图对象(VO,View Object), + * 若查询操作成功,返回的ServerResponse对象中会包含表示成功的状态码以及有效的ProductDetailVo对象(包含丰富的商品详细属性信息)等信息,方便调用者展示商品详情, + * 若查询失败(如商品不存在、数据库查询异常等原因),则会包含相应的错误信息和表示失败的状态码,供调用者进行相应的错误处理,例如提示用户查看详情失败等操作。 + */ @Override public ServerResponse detail(Integer productId) { //1,校验参数 if(productId == null){ throw new SnailmallException("参数不正确"); } + // 首先对传入的商品ID参数进行校验,如果参数为null,说明传入的参数不合法,抛出SnailmallException异常, + // 这个异常可能会在更上层(如控制层)被捕获并处理,最终给前端返回相应的错误提示信息,告知用户参数不正确,无法进行商品详情查询操作。 + //1-在售 2-下架 3-删除 Product product = productMapper.selectByPrimaryKey(productId); if(product == null){ return ServerResponse.createByErrorMessage("商品不存在"); } + // 通过ProductMapper的selectByPrimaryKey方法,根据传入的商品ID从数据库中查询对应的商品记录,如果查询结果为null, + // 表示数据库中不存在该ID对应的商品,此时返回一个包含错误信息的ServerResponse对象,告知前端商品不存在,调用者(如后台管理系统前端)可以根据返回结果进行相应的提示展示。 + ProductDetailVo productDetailVo = assembleProductDetailVo(product); + // 如果查询到了商品记录,调用assembleProductDetailVo方法,将查询到的Product实体对象转换为ProductDetailVo视图对象, + // ProductDetailVo对象包含了更丰富、更适合前端展示的商品详细属性信息,用于后续返回给前端展示商品详情。 + return ServerResponse.createBySuccess(productDetailVo); + // 最后,将包含转换好的ProductDetailVo视图对象的ServerResponse对象以成功状态返回,这个响应对象会被传递到前端(如后台管理系统的对应界面), + // 前端可以从ServerResponse中获取到表示成功的状态码以及ProductDetailVo对象中的详细商品信息,进而在页面上进行商品详情的展示,完成整个查看商品详情的业务操作流程, + // 向用户提供了期望的商品详细信息展示功能,同时通过前面的参数校验和商品存在性判断等操作,保证了整个流程的健壮性和对各种情况的合理处理。 } @Override public ServerResponse set_sale_status(Integer productId, Integer status) { - //1.校验参数 - if(productId == null || status == null){ + // 1.校验参数 + if (productId == null || status == null) { return ServerResponse.createByErrorMessage("参数不正确"); } - //2.更新状态 + // 首先对传入的参数进行合法性校验,商品ID(productId)和要设置的销售状态(status)在这个业务操作中都是必不可少的。 + // 如果其中任何一个参数为null,说明传入的参数不符合要求,无法进行后续的商品销售状态更新操作, + // 此时直接返回一个包含错误提示信息(“参数不正确”)的ServerResponse对象,告知调用者(可能是前端页面或者其他调用该服务的模块)参数存在问题, + // 这样可以保证业务操作在正确的参数输入前提下进行,避免因参数缺失或错误导致的异常情况,提高系统的健壮性。 + + // 2.更新状态 Product product = new Product(); product.setId(productId); product.setStatus(status); - //3.删除该商品缓存 + // 创建一个新的Product对象,并通过Setter方法设置其ID和状态属性。这里的目的是构建一个只包含要更新的关键信息(商品ID和新的销售状态)的商品对象, + // 用于后续传递给数据持久层(通过productMapper)进行数据库更新操作,采用这种方式可以灵活地指定要更新的字段,而不需要获取整个商品对象的所有属性再去更新, + // 尤其在只需要更新部分字段(这里就是状态字段)的场景下,更加高效且符合业务逻辑,减少不必要的数据操作。 + + // 3.删除该商品缓存 commonCacheUtil.delKey(Constants.PRODUCT_TOKEN_PREFIX); + // 在更新商品销售状态之前,先调用commonCacheUtil的delKey方法删除与商品相关的缓存。 + // 原因是商品状态发生了改变,之前缓存中的商品信息可能已经不符合最新情况了,为了避免后续读取缓存时获取到旧的、不准确的商品状态数据, + // 需要先将对应的缓存数据删除,使得下次获取该商品信息时能够从数据库重新加载最新的数据并更新缓存,保证数据的一致性和准确性。 + // Constants.PRODUCT_TOKEN_PREFIX应该是一个定义好的常量字符串,用于标识商品相关缓存的键值的前缀部分,通过这个前缀可以定位到所有与商品相关的缓存项,进行统一的删除操作。 + int rowCount = productMapper.updateByPrimaryKeySelective(product); - if(rowCount > 0){ - commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+productId,JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME); + // 通过productMapper(数据持久层接口,通常基于MyBatis等框架实现)的updateByPrimaryKeySelective方法,将构建好的包含新状态信息的Product对象传递进去, + // 执行根据商品主键(这里就是productId)更新商品记录中指定字段(这里就是状态字段,因为使用的是updateByPrimaryKeySelective方法,只会更新传入对象中非空的字段)的操作, + // 该方法会返回受影响的行数(即实际更新的商品记录行数),如果返回值大于0,表示数据库中对应的商品记录的状态字段已成功更新,否则表示更新操作未成功执行。 + + if (rowCount > 0) { + commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + productId, JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME); return ServerResponse.createBySuccessMessage("更新产品状态成功"); } + // 如果受影响的行数大于0,说明商品状态在数据库中更新成功了,接下来需要重新将更新后的商品信息缓存起来,方便后续快速获取。 + // 调用commonCacheUtil的cacheNxExpire方法,以商品ID(productId)拼接上之前定义的商品缓存键值前缀(Constants.PRODUCT_TOKEN_PREFIX)作为缓存的键, + // 将更新后的商品对象转换为字符串(通过JsonUtil的obj2String方法,可能是将Java对象序列化为JSON字符串形式,方便存储在缓存中)作为缓存的值, + // 并设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME(应该是一个预定义的表示缓存有效时长的常量),确保缓存数据在一定时间后会自动失效,避免缓存数据长期占用内存且过时的问题。 + // 最后返回一个包含成功提示信息(“更新产品状态成功”)的ServerResponse对象,表示商品销售状态更新操作成功完成,调用者(如前端页面)可以根据这个响应进行相应的提示展示等操作。 + return ServerResponse.createByErrorMessage("更新产品状态失败"); + // 如果数据库更新操作中受影响的行数不大于0,即更新商品状态失败了,返回一个包含错误提示信息(“更新产品状态失败”)的ServerResponse对象, + // 告知调用者商品销售状态更新操作没有成功,方便调用者(比如前端页面可以给用户展示相应的提示,告知用户状态更新失败的情况)进行相应的处理,保证业务流程的完整性以及对操作结果的合理反馈。 } @Override public ServerResponse saveOrUpdateProduct(Product product) { - //1.校验参数 - if(product == null || product.getCategoryId()==null){ + // 1.校验参数 + if (product == null || product.getCategoryId() == null) { return ServerResponse.createByErrorMessage("参数不正确"); } - //2.设置一下主图,主图为子图的第一个图 - if(StringUtils.isNotBlank(product.getSubImages())){ + // 首先进行参数的合法性校验。在这里,传入的Product对象以及其中的商品分类ID(categoryId)是关键参数, + // 如果Product对象为null,说明没有接收到有效的商品信息,或者商品分类ID为null,意味着缺少必要的商品分类关联信息, + // 这两种情况都不符合业务逻辑要求,无法进行后续的保存或更新操作,所以直接返回一个包含错误提示信息(“参数不正确”)的ServerResponse对象, + // 告知调用者(可能是前端页面或者其他调用该服务的模块)参数存在问题,以此保证业务操作在正确的参数输入前提下进行,避免因参数缺失或错误导致的异常情况,增强系统的健壮性。 + + // 2.设置一下主图,主图为子图的第一个图 + if (StringUtils.isNotBlank(product.getSubImages())) { String[] subImages = product.getSubImages().split(","); - if(subImages.length > 0){ + if (subImages.length > 0) { product.setMainImage(subImages[0]); } } - //3.看前端传过来的产品id是否存在,存在则为更新,否则为新增 - if(product.getId() != null){ - //删除该商品缓存 + // 这段代码的目的是处理商品主图的设置逻辑。如果传入的商品对象中的子图信息(subImages)不为空字符串,说明存在子图相关信息, + // 首先通过逗号将子图信息字符串分割为字符串数组(假设子图信息是以逗号分隔的多个图片路径等情况),然后判断数组长度是否大于0, + // 如果大于0,表示存在至少一个子图,按照业务规则,将第一个子图作为商品的主图,通过设置商品对象的主图属性(setMainImage方法)来完成主图的赋值操作, + // 这样可以保证商品在展示等场景下有合理的主图信息,同时体现了一种常见的业务逻辑处理方式,即根据已有的子图来确定主图。 + + // 3.看前端传过来的产品id是否存在,存在则为更新,否则为新增 + if (product.getId()!= null) { + // 删除该商品缓存 commonCacheUtil.delKey(Constants.PRODUCT_TOKEN_PREFIX); int rowCount = productMapper.updateByPrimaryKeySelective(product); - if(rowCount > 0){ - commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+product.getId(),JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME); + if (rowCount > 0) { + commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + product.getId(), JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME); return ServerResponse.createBySuccessMessage("更新产品成功"); } return ServerResponse.createByErrorMessage("更新产品失败"); - }else { - //新增 + } else { + // 新增 product.setCreateTime(new Date());//这两句可能多余,因为xml中已经保证了,就先放这里 product.setUpdateTime(new Date()); int rowCount = productMapper.insert(product); - if(rowCount > 0){ - commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+product.getId(),JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME); + if (rowCount > 0) { + commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + product.getId(), JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME); return ServerResponse.createBySuccessMessage("新增产品成功"); } return ServerResponse.createByErrorMessage("新增产品失败"); } + // 这部分代码通过判断传入商品对象的ID是否存在来区分是执行更新操作还是新增操作: + // - 如果商品ID(product.getId())不为null,说明前端传递过来的是已存在商品的相关信息,要执行更新操作。 + // - 首先调用commonCacheUtil的delKey方法删除该商品对应的缓存,原因是商品信息即将被更新,之前缓存中的商品数据已经过时, + // 为了保证后续获取商品信息时能获取到最新的数据,需要先清除旧的缓存,Constants.PRODUCT_TOKEN_PREFIX是用于标识商品缓存键的前缀常量,通过它可以定位并删除对应的缓存项。 + // - 接着通过productMapper的updateByPrimaryKeySelective方法,将传入的商品对象传递进去执行更新操作,该方法会根据商品的主键(即商品ID)更新数据库中对应的商品记录, + // 并且只会更新传入对象中非空的字段(这种方式更灵活,可以只更新部分需要修改的字段),然后获取实际更新的行数(rowCount)。 + // - 如果rowCount大于0,表示数据库中商品记录更新成功,此时需要将更新后的商品信息重新缓存起来,方便后续快速获取。 + // 通过调用commonCacheUtil的cacheNxExpire方法,以商品ID拼接上缓存键前缀作为缓存的键,将商品对象转换为字符串(通过JsonUtil的obj2String方法,可能是序列化为JSON字符串方便存储在缓存中)作为缓存的值, + // 并设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME,最后返回一个包含成功提示信息(“更新产品成功”)的ServerResponse对象,表示商品更新操作成功完成,调用者(如前端页面)可据此进行相应提示展示等操作。 + // - 如果rowCount不大于0,说明商品更新操作失败了,返回一个包含错误提示信息(“更新产品失败”)的ServerResponse对象,告知调用者更新操作未成功,方便前端进行相应提示告知用户情况。 + // - 如果商品ID(product.getId())为null,说明前端传递的是一个新的商品信息,要执行新增操作。 + // - 先设置商品的创建时间(createTime)和更新时间(updateTime)为当前时间(虽然代码中备注了这两句可能多余,因为在对应的xml配置中可能已经做了相关时间的默认设置,但这里先保留设置操作)。 + // - 然后通过productMapper的insert方法将商品对象插入到数据库中,获取实际插入的行数(rowCount)。 + // - 如果rowCount大于0,表示商品新增操作成功,同样需要将新插入的商品信息缓存起来,操作方式与更新成功后的缓存设置类似,通过commonCacheUtil的cacheNxExpire方法进行缓存设置, + // 最后返回一个包含成功提示信息(“新增产品成功”)的ServerResponse对象,告知调用者商品新增操作已成功,方便前端进行相应展示等操作。 + // - 如果rowCount不大于0,说明商品新增操作失败了,返回一个包含错误提示信息(“新增产品失败”)的ServerResponse对象,告知调用者新增操作未成功,以便前端提示用户相应情况。 } @Override public ServerResponse getPortalProductDetail(Integer productId) { - if(productId == null){ + // 首先进行参数校验,判断传入的商品ID是否为null。在前台门户获取商品详情的业务场景中, + // 商品ID是定位特定商品的关键依据,如果传入的商品ID为null,就无法明确要查询详情的具体商品,不符合正常的业务逻辑, + // 所以直接返回一个包含错误提示信息(“参数不正确”)的ServerResponse对象,告知调用者(可能是前台页面或者其他调用该服务的模块)参数存在问题, + // 保证业务操作在参数合法有效的前提下进行,避免因错误参数引发后续不必要的操作和异常情况,增强系统的健壮性。 + if (productId == null) { return ServerResponse.createByErrorMessage("参数不正确"); } + Product product = productMapper.selectByPrimaryKey(productId); - if(product == null){ + // 通过ProductMapper接口的selectByPrimaryKey方法,依据传入的商品ID从数据库中精确查询对应的商品记录。 + // ProductMapper通常是基于数据持久层框架(如MyBatis)生成的接口,其定义了与数据库中商品表交互的相关方法,selectByPrimaryKey方法就是按照主键(即商品ID)来查找一条商品记录, + // 这一步是获取商品详情的核心数据库查询操作,只有先从数据库获取到商品的原始数据,才能进行后续的判断以及数据转换等工作,为最终返回适合前台展示的商品详情信息做准备。 + + if (product == null) { return ServerResponse.createByErrorMessage("商品不存在"); } - if(product.getStatus() != Constants.Product.PRODUCT_ON){ + // 对从数据库查询到的商品记录进行判断,如果返回的product对象为null,说明在数据库中并没有找到与传入的productId对应的商品, + // 这种情况下,直接返回一个包含“商品不存在”错误提示信息的ServerResponse对象,告知调用者(比如前台页面)当前要查看详情的商品不存在, + // 使得前台可以根据这个响应结果向用户展示相应的提示内容,保证业务流程在面对商品不存在这种情况时能合理反馈,维持系统的完整性。 + + if (product.getStatus()!= Constants.Product.PRODUCT_ON) { return ServerResponse.createByErrorMessage("产品已下架或删除"); } + // 当查询到商品记录(即product对象不为null)后,进一步检查商品的状态。Constants.Product.PRODUCT_ON应该是一个预定义的表示商品上架状态的常量, + // 如果商品的状态不等于这个上架状态常量,意味着商品可能已经下架或者被删除了,此时不符合在前台门户展示商品详情的业务需求, + // 所以返回一个包含“产品已下架或删除”错误提示信息的ServerResponse对象,告知调用者(例如前台页面)该商品不能展示详情, + // 让前台能够相应地提示用户,避免展示不可用的商品信息给用户,确保前台展示的商品信息都是有效的、可供查看的。 + ProductDetailVo productDetailVo = assembleProductDetailVo(product); + // 当商品存在(product不为null)且处于上架状态(product.getStatus()符合要求)时,调用assembleProductDetailVo方法对查询到的Product实体对象进行处理, + // assembleProductDetailVo方法的作用是将从数据库获取的Product实体类对象转换为ProductDetailVo视图对象。ProductDetailVo作为视图对象, + // 它会提取、封装那些适合在前台门户展示给用户查看商品详情的属性信息,可能会对Product实体类中的一些属性进行筛选、格式转换或者添加一些方便前台展示的辅助信息等操作, + // 例如对日期类型的属性进行格式化处理,使其更符合前台展示的格式要求,以此来优化展示给用户的商品详情内容,提升用户体验。 + return ServerResponse.createBySuccess(productDetailVo); + // 最后,将包含转换好的ProductDetailVo视图对象的ServerResponse对象以成功状态返回,这个ServerResponse对象会传递到前台(比如前台门户的对应页面), + // 前台可以从中获取到表示成功的状态码以及ProductDetailVo对象里详细的商品信息,进而在页面上展示商品详情给用户查看,完成整个前台门户获取商品详情的业务操作流程, + // 为用户提供了准确且符合业务要求的商品详情展示功能,同时通过前面的参数校验、商品存在性判断以及状态检查等操作,保障了整个流程的健壮性和对各种情况的合理处理。 } @Override public ServerResponse portalList(String keyword, Integer categoryId, String orderBy, int pageNum, int pageSize) { - //准备盛放categoryIds + // 准备盛放categoryIds List categoryIdList = Lists.newArrayList(); - //如果categoryId不为空 - if(categoryId != null){ - //对于这里,直接强转出错了,所以我就序列化处理了一下 + // 创建一个空的整数列表categoryIdList,用于后续存放商品分类ID相关的数据,它的作用是在根据分类筛选商品时,存储符合条件的多个分类ID, + // 比如当查询某个父分类下的所有商品(包括子分类商品)时,这个列表会存储该父分类及其所有子分类的ID,方便后续进行基于分类的商品查询操作。 + + // 如果categoryId不为空 + if (categoryId!= null) { + // 对于这里,直接强转出错了,所以我就序列化处理了一下 ServerResponse response = categoryClient.getCategoryDetail(categoryId); + // 通过categoryClient(通常是基于Feign等远程调用框架实现的用于与商品分类服务进行交互的客户端)的getCategoryDetail方法, + // 根据传入的categoryId尝试获取对应的商品分类详细信息,返回的是一个ServerResponse对象,它包装了操作结果(成功与否以及相关的数据等信息)。 + Object object = response.getData(); String objStr = JsonUtil.obj2String(object); - Category category = JsonUtil.Str2Obj(objStr,Category.class); - if(category == null && StringUtils.isBlank(keyword)){ - ////直接返回空 - PageHelper.startPage(pageNum,pageSize); + Category category = JsonUtil.Str2Obj(objStr, Category.class); + // 从ServerResponse对象中获取返回的数据部分(getData方法),这个数据可能是一个对象,先将其转换为字符串(通过JsonUtil的obj2String方法,可能是序列化为JSON字符串格式,方便后续处理), + // 然后再将这个字符串反序列化为Category类型的对象(通过JsonUtil的Str2Obj方法,用于将JSON字符串转换回Java对象),这样就能获取到具体的商品分类对象了,方便后续判断和使用。 + + if (category == null && StringUtils.isBlank(keyword)) { + // 如果根据categoryId获取到的商品分类对象为null,并且传入的关键词(keyword)也是空字符串,意味着既没有有效的分类信息,也没有关键词信息来筛选商品, + // 在这种情况下,直接返回一个空的商品列表分页信息。 + + PageHelper.startPage(pageNum, pageSize); List productListVoList = Lists.newArrayList(); PageInfo pageInfo = new PageInfo(productListVoList); return ServerResponse.createBySuccess(pageInfo); + // 首先使用PageHelper启动分页功能,传入当前页码(pageNum)和每页显示数量(pageSize),虽然此时没有实际的商品数据,但分页相关的设置还是需要进行的, + // 然后创建一个空的ProductListVo类型的列表(ProductListVo是用于前台展示的商品列表视图对象类型,包含部分商品属性),将其封装到PageInfo对象中, + // 最后通过ServerResponse的createBySuccess方法将包含空列表的PageInfo对象包装起来,以成功状态返回,表示没有符合条件的商品数据,调用者(如前台页面)可以根据这个响应进行相应的展示,比如显示一个空的商品列表页面。 } - //说明category还是存在的 + + // 说明category还是存在的 categoryIdList = (List) categoryClient.getDeepCategory(categoryId).getData(); + // 如果获取到的商品分类对象不为null,说明categoryId对应的分类是存在的,此时调用categoryClient的getDeepCategory方法, + // 这个方法可能是用于获取指定分类及其所有子分类的ID列表(根据业务逻辑推测),从返回的ServerResponse对象中获取数据部分,并强转成List类型, + // 赋值给categoryIdList,后续就可以基于这个包含多个分类ID的列表去数据库中查询属于这些分类的商品了。 } - //如果keyword不为空 - if(StringUtils.isNotBlank(keyword)){ + + // 如果keyword不为空 + if (StringUtils.isNotBlank(keyword)) { keyword = new StringBuilder().append("%").append(keyword).append("%").toString(); } - //如果orderBy不为空 - if(StringUtils.isNotBlank(orderBy)){ - if(Constants.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)){ + // 当传入的关键词(keyword)不为空字符串时,对其进行模糊查询格式的处理,通过在关键词前后添加 "%" 符号,将其转换为 LIKE 语句中可用的模糊查询格式, + // 例如,原本传入的关键词为 "手机",处理后变为 "%手机%",这样在数据库查询时就能查找名称中包含 "手机" 关键字的所有商品记录了,方便实现根据关键词模糊搜索商品的功能。 + + // 如果orderBy不为空 + if (StringUtils.isNotBlank(orderBy)) { + if (Constants.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) { String[] orderByArray = orderBy.split("_"); - //特定的格式 - PageHelper.orderBy(orderByArray[0]+" "+orderByArray[1]); + // 特定的格式 + PageHelper.orderBy(orderByArray[0] + " " + orderByArray[1]); } } - PageHelper.startPage(pageNum,pageSize); - //模糊查询 - List productList = productMapper.selectByNameAndCategoryIds(StringUtils.isBlank(keyword)?null:keyword, - categoryIdList.size()==0?null:categoryIdList); - //封装返回对象 + // 当传入的排序规则(orderBy)字符串不为空时,先进行合法性校验,判断它是否在预定义的合法排序规则集合(Constants.ProductListOrderBy.PRICE_ASC_DESC,应该是定义了允许的排序规则内容的常量集合)中, + // 如果在集合中,说明是合法的排序规则,将orderBy字符串按照下划线("_")进行分割,得到一个包含排序字段和排序方式(如 "price" 和 "asc" 或者 "price" 和 "desc" 等情况)的字符串数组, + // 然后通过PageHelper的orderBy方法,按照特定格式(排序字段 + " " + 排序方式)传入参数,设置后续数据库查询结果的排序规则,例如按照价格升序或者降序排列商品列表等,以满足前台展示商品列表时不同的排序需求。 + + PageHelper.startPage(pageNum, pageSize); + // 使用PageHelper启动分页功能,传入当前页码(pageNum)和每页显示数量(pageSize)参数,这样后续通过ProductMapper进行的商品查询操作将会按照设定的分页规则返回对应页的商品数据, + // 便于在前台分页展示商品列表,提升用户查看商品时的体验,避免一次性返回大量数据影响性能和展示效果。 + + // 模糊查询 + List productList = productMapper.selectByNameAndCategoryIds(StringUtils.isBlank(keyword)? null : keyword, + categoryIdList.size() == 0? null : categoryIdList); + // 通过ProductMapper的selectByNameAndCategoryIds方法进行模糊查询,根据处理后的关键词(如果keyword为空则传入null)以及分类ID列表(如果categoryIdList为空则传入null), + // 从数据库中查询符合条件的商品记录列表,这个方法内部会根据传入的参数构建相应的SQL查询语句(基于MyBatis的映射机制),执行数据库查询操作,获取满足条件的商品记录,实现根据关键词和分类筛选商品的功能。 + + // 封装返回对象 List productListVoList = Lists.newArrayList(); - for(Product product : productList){ + for (Product product : productList) { ProductListVo productListVo = assembleProductListVo(product); productListVoList.add(productListVo); } - //返回 + // 遍历查询到的商品列表,调用assembleProductListVo方法将每个Product实体对象转换为ProductListVo视图对象, + // ProductListVo对象是专门为了前台展示而设计的,它可能只包含了部分商品属性(如商品名称、主图、价格等前台关心的关键信息),通过这样的转换可以减少返回给前台的数据量,同时保证展示的信息是符合前台展示需求的, + // 将转换后的视图对象依次添加到productListVoList列表中,用于后续构建包含分页信息的返回结果。 + + // 返回 PageInfo pageInfo = new PageInfo(productList); pageInfo.setList(productListVoList); return ServerResponse.createBySuccess(pageInfo); + // 创建PageInfo对象来封装分页相关信息(如总记录数、总页数、当前页数据列表等),它会根据传入的原始商品列表(productList)自动填充这些信息, + // 然后将包含商品列表的PageInfo对象的原始商品列表替换为转换后的视图对象列表(productListVoList),确保返回给前台的分页信息中包含的是经过处理、符合展示需求的商品数据, + // 最后将包含分页商品列表信息的PageInfo对象包装在ServerResponse对象中,通过createBySuccess方法以成功状态返回,调用者(如前台页面)可以接收到这个响应, + // 从中获取分页的商品列表信息并展示在页面上,完成整个前台门户获取商品分页列表的业务操作流程,为用户提供了可根据关键词、分类、排序规则分页查看商品列表的功能。 } @Override public ServerResponse queryProduct(Integer productId) { - //1.校验参数 - if(productId == null){ + // 1.校验参数 + if (productId == null) { return ServerResponse.createByErrorMessage("参数错误"); } - //2.去redis中查询,没有则把商品重新添加进redis中 - String redisProductStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+productId); - if (redisProductStr == null){ + // 首先进行参数校验,判断传入的商品ID(productId)是否为null。在查询商品的业务场景中,商品ID是定位特定商品的关键依据, + // 如果传入的商品ID为null,就无法明确要查询的具体商品,不符合正常的业务逻辑,所以直接返回一个包含“参数错误”提示信息的ServerResponse对象, + // 告知调用者(可能是其他业务模块或者前端页面等)参数存在问题,以此保证业务操作在正确的参数输入前提下进行,避免因错误参数引发后续不必要的操作和异常情况,增强系统的健壮性。 + + // 2.去redis中查询,没有则把商品重新添加进redis中 + String redisProductStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + productId); + // 通过commonCacheUtil(这应该是一个用于操作缓存的工具类实例)的getCacheValue方法,尝试从Redis缓存中获取指定键(由Constants.PRODUCT_TOKEN_PREFIX与传入的商品ID拼接而成的字符串作为键)对应的缓存值, + // 缓存值在这里预期是存储的商品相关信息(可能是经过序列化后的字符串形式),将获取到的缓存值赋值给redisProductStr变量,后续根据这个值来判断商品信息是否已存在于缓存中。 + + if (redisProductStr == null) { Product product = productMapper.selectByPrimaryKey(productId); - if(product == null){ + if (product == null) { return ServerResponse.createByErrorMessage("商品不存在"); } - if(product.getStatus() != Constants.Product.PRODUCT_ON){ + // 如果从缓存中获取到的值为null,说明缓存中不存在该商品的信息,此时需要从数据库中查询商品信息。 + // 通过ProductMapper的selectByPrimaryKey方法,依据传入的商品ID从数据库中精确查询对应的商品记录,这是获取商品原始数据的关键步骤, + // 如果查询到的product对象为null,意味着数据库中也不存在该商品,直接返回一个包含“商品不存在”提示信息的ServerResponse对象,告知调用者商品不存在,方便调用者(如前端页面)进行相应的提示展示等操作。 + + if (product.getStatus()!= Constants.Product.PRODUCT_ON) { return ServerResponse.createByErrorMessage("商品已经下架或者删除"); } - commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+productId,JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME); + // 当从数据库中查询到商品记录(即product不为null)后,进一步检查商品的状态。Constants.Product.PRODUCT_ON应该是一个预定义的表示商品上架状态的常量, + // 如果商品的状态不等于这个上架状态常量,意味着商品已经下架或者被删除了,此时不符合正常查询并返回可用商品信息的业务需求, + // 所以返回一个包含“商品已经下架或者删除”提示信息的ServerResponse对象,告知调用者该商品不可用,让调用者(例如前端页面)能够相应地提示用户,避免返回无效的商品信息。 + + commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + productId, JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME); + // 当商品存在(product不为null)且处于上架状态(product.getStatus()符合要求)时,需要将从数据库获取到的商品信息缓存到Redis中,方便后续查询时能够直接从缓存获取,提高查询效率。 + // 通过调用commonCacheUtil的cacheNxExpire方法,以商品ID拼接上缓存键前缀(Constants.PRODUCT_TOKEN_PREFIX)作为缓存的键, + // 将商品对象转换为字符串(通过JsonUtil的obj2String方法,可能是将Java对象序列化为JSON字符串形式,方便存储在缓存中)作为缓存的值, + // 并设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME(应该是一个预定义的表示缓存有效时长的常量),确保缓存数据在一定时间后会自动失效,避免缓存数据长期占用内存且过时的问题。 } - //2.获取商品 - Product product = JsonUtil.Str2Obj(commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+productId),Product.class); + // 2.获取商品 + Product product = JsonUtil.Str2Obj(commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + productId), Product.class); + // 无论之前是从缓存中获取到了商品信息(即redisProductStr不为null的情况),还是刚刚将数据库中查询到的商品信息缓存后,这里都需要从缓存中获取商品信息并转换为Product对象。 + // 通过JsonUtil的Str2Obj方法,将从缓存中获取到的缓存值(通过commonCacheUtil的getCacheValue方法再次获取,确保获取到最新的缓存内容)转换为Product类型的Java对象, + // 这样就能得到适合后续业务处理或返回给调用者的商品对象形式了。 + return ServerResponse.createBySuccess(product); + // 最后,将包含查询到的商品对象的ServerResponse对象以成功状态返回,这个ServerResponse对象会被传递给调用者(比如其他业务模块或者前端页面等), + // 调用者可以从中获取到表示成功的状态码以及商品对象中的详细商品信息,进而进行相应的业务处理或展示操作,完成整个查询商品的业务操作流程, + // 实现了先从缓存查询商品信息,缓存不存在时从数据库获取并缓存,最终返回可用商品信息的功能,提高了商品查询的效率以及数据的可用性。 } private ProductListVo assembleProductListVo(Product product) { + // 创建一个新的ProductListVo对象,ProductListVo是专门设计用于在前端展示商品列表时使用的视图对象, + // 它包含了部分商品属性,这些属性是经过筛选的,符合前端展示商品列表时通常所需要展示的关键信息,通过将Product实体对象转换为ProductListVo对象, + // 可以减少传递给前端的数据量,同时保证前端获取到的是真正需要展示给用户查看的信息,提升性能以及用户体验。 ProductListVo productListVo = new ProductListVo(); + productListVo.setId(product.getId()); + // 将传入的Product实体对象的ID属性赋值给ProductListVo对象的ID属性,商品ID是唯一标识商品的关键信息,在前端展示商品列表时, + // 通常需要展示每个商品对应的ID,方便后续进行一些与商品相关的操作(如查看详情、编辑等操作时可以通过ID来定位具体商品)。 + productListVo.setSubtitle(product.getSubtitle()); + // 把Product实体对象中的副标题(subtitle)属性赋值给ProductListVo对象的副标题属性,副标题可以对商品进行进一步的补充说明, + // 在商品列表展示中,能够帮助用户更详细地了解商品的一些特点或者额外信息,丰富展示内容。 + productListVo.setMainImage(product.getMainImage()); + // 将Product实体对象的主图(mainImage)属性赋值给ProductListVo对象的主图属性,主图是商品在列表中最直观展示给用户的视觉元素, + // 用户可以通过主图快速识别商品的大致外观等信息,所以在商品列表视图对象中需要包含主图信息,方便前端进行展示。 + productListVo.setPrice(product.getPrice()); + // 把Product实体对象的价格(price)属性传递给ProductListVo对象的价格属性,价格是用户在浏览商品列表时非常关注的信息之一, + // 展示价格能够让用户快速了解商品的价值情况,便于用户进行比较和筛选商品。 + productListVo.setCategoryId(product.getCategoryId()); + // 将Product实体对象的商品分类ID(categoryId)属性赋值给ProductListVo对象的分类ID属性,商品分类信息有助于用户了解商品所属的类别, + // 在一些有分类筛选或者导航功能的前端页面中,展示分类ID可以方便后续基于分类进行相关操作(如筛选同一分类下的其他商品等)。 + productListVo.setName(product.getName()); + // 把Product实体对象的商品名称(name)属性赋值给ProductListVo对象的商品名称属性,商品名称是最基本且重要的展示信息, + // 用户主要通过商品名称来识别和区分不同的商品,所以必须包含在用于前端展示的视图对象中。 + productListVo.setStatus(product.getStatus()); + // 将Product实体对象的商品状态(status)属性传递给ProductListVo对象的商品状态属性,商品状态信息(例如是否上架等状态)可以让用户了解商品当前的可用性, + // 方便用户知晓哪些商品是可以进行购买等操作的,哪些是不可用的。 + productListVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://image.snail.com/")); + // 通过PropertiesUtil工具类的getProperty方法获取图片服务器的HTTP前缀地址(默认值为 "http://image.snail.com/"), + // 并将其赋值给ProductListVo对象的ImageHost属性。这个属性的作用是与商品的图片路径(如主图、子图等路径)相结合, + // 构成完整的图片URL地址,方便前端能够正确地展示商品的图片,因为在存储图片路径时可能只是相对路径或者部分路径,需要加上这个前缀才能形成可访问的完整URL。 + return productListVo; + // 最后返回组装好的ProductListVo对象,这个对象包含了适合前端展示商品列表的关键属性信息,可供后续在业务逻辑中返回给前端进行展示使用。 } - private ProductDetailVo assembleProductDetailVo(Product product){ + private ProductDetailVo assembleProductDetailVo(Product product) { ProductDetailVo productDetailVo = new ProductDetailVo(); + // 创建一个新的ProductDetailVo对象,ProductDetailVo是用于在前端展示商品详细信息时使用的视图对象, + // 它相比ProductListVo包含了更丰富、更全面的商品属性信息,旨在为用户提供查看商品所有重要细节的功能,同样通过将Product实体对象转换为这个视图对象, + // 可以对数据进行整理和格式转换,使其更符合前端展示详细信息的需求。 + productDetailVo.setId(product.getId()); + // 将传入的Product实体对象的ID属性赋值给ProductDetailVo对象的ID属性,商品ID用于唯一标识商品,在展示商品详细信息时, + // 依然需要展示这个关键标识,方便用户在查看详情页面与其他相关页面(如列表页返回等操作)进行关联和定位该商品。 + productDetailVo.setSubtitle(product.getSubtitle()); + // 把Product实体对象中的副标题(subtitle)属性赋值给ProductDetailVo对象的副标题属性,副标题能进一步补充说明商品的特点等信息, + // 在详细信息页面展示可以让用户更全面地了解商品情况。 + productDetailVo.setMainImage(product.getMainImage()); + // 将Product实体对象的主图(mainImage)属性赋值给ProductDetailVo对象的主图属性,主图在商品详情页面也是重要的展示元素, + // 方便用户直观地看到商品外观,辅助用户了解商品。 + productDetailVo.setSubImages(product.getSubImages()); + // 把Product实体对象的子图(subImages)属性赋值给ProductDetailVo对象的子图属性,子图可以从更多角度展示商品的外观、细节等情况, + // 在商品详情页面完整展示商品图片信息能够让用户更全面地了解商品的样子,所以需要包含子图信息在视图对象中。 + productDetailVo.setPrice(product.getPrice()); + // 把Product实体对象的价格(price)属性传递给ProductDetailVo对象的价格属性,价格是用户在查看商品详情时关注的重要信息之一, + // 明确商品价格有助于用户决定是否购买该商品。 + productDetailVo.setCategoryId(product.getCategoryId()); + // 将Product实体对象的商品分类ID(categoryId)属性赋值给ProductDetailVo对象的分类ID属性,展示商品所属分类信息, + // 能让用户了解商品在整个商品体系中的归类情况,同时方便用户通过分类导航查看同类的其他商品等操作。 + productDetailVo.setDetail(product.getDetail()); + // 把Product实体对象的详细描述(detail)属性赋值给ProductDetailVo对象的详细描述属性,详细描述通常包含了商品的各种详细规格、功能特点、使用说明等内容, + // 在商品详情页面展示这些详细信息能够满足用户深入了解商品的需求。 + productDetailVo.setName(product.getName()); + // 把Product实体对象的商品名称(name)属性赋值给ProductDetailVo对象的商品名称属性,商品名称始终是重要的展示信息, + // 用户通过名称来快速识别商品,在详情页面同样需要展示明确的商品名称。 + productDetailVo.setStatus(product.getStatus()); + // 将Product实体对象的商品状态(status)属性传递给ProductDetailVo对象的商品状态属性,展示商品当前的状态(如是否上架等), + // 让用户清楚该商品是否可进行购买等操作,提供必要的商品可用性信息。 + productDetailVo.setStock(product.getStock()); + // 把Product实体对象的库存(stock)属性赋值给ProductDetailVo对象的库存属性,库存信息对于用户来说也是比较关注的内容, + // 特别是在一些限量销售或者用户考虑购买数量的场景下,了解商品的库存情况有助于用户做出购买决策。 productDetailVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://image.snail.com/")); - //返回给前端还需要一下该商品所处品类的父品类id,所以需要去品类服务中去查询一下,这里就要用到Feign - if(categoryClient.getCategoryDetail(product.getCategoryId()).isSuccess()){ + // 通过PropertiesUtil工具类获取图片服务器的HTTP前缀地址(默认值为 "http://image.snail.com/"),并赋值给ProductDetailVo对象的ImageHost属性, + // 作用与在ProductListVo中类似,是为了与商品的图片路径结合形成完整可访问的图片URL,确保前端能够正确展示商品的所有图片。 + + // 返回给前端还需要一下该商品所处品类的父品类id,所以需要去品类服务中去查询一下,这里就要用到Feign + if (categoryClient.getCategoryDetail(product.getCategoryId()).isSuccess()) { ServerResponse response = categoryClient.getCategoryDetail(product.getCategoryId()); Object object = response.getData(); String objStr = JsonUtil.obj2String(object); - Category category = (Category) JsonUtil.Str2Obj(objStr,Category.class); + Category category = (Category) JsonUtil.Str2Obj(objStr, Category.class); productDetailVo.setParentCategoryId(category.getParentId()); - }else { + } else { productDetailVo.setParentCategoryId(0); } + // 通过categoryClient(通常是基于Feign框架实现的用于调用商品分类服务的客户端)的getCategoryDetail方法,根据商品的分类ID去查询对应的商品分类详细信息, + // 如果查询操作成功(通过isSuccess方法判断),从返回的ServerResponse对象中获取数据部分,先将其转换为字符串(通过JsonUtil的obj2String方法), + // 再将字符串反序列化为Category类型的对象(通过JsonUtil的Str2Obj方法),然后获取该分类对象的父品类ID,并赋值给ProductDetailVo对象的ParentCategoryId属性, + // 这样在前端展示商品详情时就能展示商品所属品类的父品类信息了,方便用户了解商品在分类层级中的位置等情况。 + // 如果查询失败,将ProductDetailVo对象的ParentCategoryId属性设置为0(这里0可能是一个表示默认或者无父品类的约定值),保证该属性有一个合理的默认值,避免出现空值等异常情况。 + productDetailVo.setCreateTime(DateTimeUtil.dateToStr(product.getCreateTime())); + // 使用DateTimeUtil工具类的dateToStr方法将Product实体对象中的创建时间(createTime,通常是Date类型)转换为字符串格式, + // 并赋值给ProductDetailVo对象的CreateTime属性,将日期类型转换为字符串是为了方便前端直接展示时间信息,符合前端展示的格式要求,让用户能直观地看到商品的创建时间。 + productDetailVo.setUpdateTime(DateTimeUtil.dateToStr(product.getUpdateTime())); + // 同样地,通过DateTimeUtil工具类将Product实体对象中的更新时间(updateTime,通常是Date类型)转换为字符串格式, + // 赋值给ProductDetailVo对象的UpdateTime属性,使得前端能够展示商品信息的最后更新时间,方便用户了解商品信息的时效性。 + return productDetailVo; + // 最后返回组装好的ProductDetailVo对象,这个对象包含了丰富且适合前端展示商品详细信息的各种属性,可供后续在业务逻辑中返回给前端进行商品详情展示使用。 } - @Override public ServerResponse preInitProductStcokToRedis() { + // 通过productMapper(数据持久层接口,通常基于MyBatis等框架实现,用于与数据库中的商品表进行交互)的selectList方法, + // 从数据库中查询获取所有的商品记录列表,这个列表包含了系统中所有商品的相关信息(以Product实体对象形式存在),后续将基于这个列表来筛选并缓存商品的库存信息到Redis中。 List productList = productMapper.selectList(); - for(Product product:productList){ + + for (Product product : productList) { Integer productId = product.getId(); Integer stock = product.getStock(); - if(productId != null && stock != null && product.getStatus().equals(Constants.Product.PRODUCT_ON)){ - commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_STOCK_PREFIX+String.valueOf(productId),String.valueOf(stock),Constants.PRODUCT_EXPIRE_TIME); + // 遍历从数据库获取到的商品列表,对于每一个商品对象,获取其商品ID(productId)和库存(stock)属性值, + // 商品ID用于唯一标识商品,是后续在Redis缓存中构建缓存键以及关联商品相关信息的关键,库存信息则是要缓存到Redis中的核心数据内容,方便后续业务快速获取商品库存情况。 + + if (productId!= null && stock!= null && product.getStatus().equals(Constants.Product.PRODUCT_ON)) { + commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_STOCK_PREFIX + String.valueOf(productId), String.valueOf(stock), Constants.PRODUCT_EXPIRE_TIME); } + // 对每个商品进行条件判断,如果商品ID不为null,库存也不为null,并且商品的状态(通过Constants.Product.PRODUCT_ON常量来判断,该常量应该表示商品处于上架等可用状态)符合要求, + // 说明这个商品的库存信息是有效的、需要缓存到Redis中的。此时调用commonCacheUtil(这是一个用于操作Redis缓存的工具类实例)的cacheNxExpire方法, + // 以特定的缓存键(由Constants.PRODUCT_TOKEN_STOCK_PREFIX与商品ID的字符串形式拼接而成,Constants.PRODUCT_TOKEN_STOCK_PREFIX应该是一个预定义的用于标识商品库存缓存键前缀的常量字符串)作为键, + // 将库存值转换为字符串形式(通过String.valueOf方法)作为缓存的值,同时设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME(这也是一个预定义的表示缓存有效时长的常量), + // 这样就把商品的库存信息缓存到了Redis中,并且在过期时间后缓存会自动失效,避免缓存数据长期占用内存且过时的问题,方便后续业务场景(如商品销售、库存查询等操作)能快速从缓存获取准确的库存信息,提高系统性能。 } + return ServerResponse.createBySuccessMessage("预置库存成功"); + // 当完成对所有符合条件商品的库存信息缓存操作后,返回一个包含成功提示信息(“预置库存成功”)的ServerResponse对象, + // 告知调用者(可能是其他业务模块或者系统管理相关的操作触发了这个方法调用)库存信息预置到Redis缓存的操作已顺利完成,方便调用者根据这个返回结果进行相应的后续处理(如记录日志、提示用户操作成功等)。 } @Override public ServerResponse preInitProductListToRedis() { + // 同样先通过productMapper的selectList方法从数据库中获取所有的商品记录列表,这个列表包含了系统中全部商品的详细信息, + // 后续将基于这个列表来筛选并缓存商品的详细信息(以Product实体对象形式表示的各种属性信息)到Redis中,方便后续业务场景快速获取商品的完整信息,减少数据库查询压力,提升系统响应速度。 List productList = productMapper.selectList(); - for(Product product:productList){ + + for (Product product : productList) { Integer productId = product.getId(); - if(productId != null && product.getStatus().equals(Constants.Product.PRODUCT_ON)){ - commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+String.valueOf(productId),JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME); + // 遍历商品列表,获取每个商品的商品ID(productId),商品ID用于唯一标识商品,在缓存操作中是构建缓存键以及关联商品对应信息的关键依据, + // 通过它可以准确地在Redis中定位和存储、获取特定商品的详细信息。 + + if (productId!= null && product.getStatus().equals(Constants.Product.PRODUCT_ON)) { + commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + String.valueOf(productId), JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME); } + // 对每个商品进行条件判断,如果商品ID不为null,并且商品的状态(依据Constants.Product.PRODUCT_ON常量判断是否处于可用状态)符合要求, + // 说明这个商品的详细信息是需要缓存到Redis中的。此时调用commonCacheUtil的cacheNxExpire方法, + // 以特定的缓存键(由Constants.PRODUCT_TOKEN_PREFIX与商品ID的字符串形式拼接而成,Constants.PRODUCT_TOKEN_PREFIX是用于标识商品相关缓存键前缀的常量字符串)作为键, + // 将整个商品对象转换为字符串形式(通过JsonUtil的obj2String方法,可能是将Java对象序列化为JSON字符串形式,方便存储在缓存中)作为缓存的值, + // 同时设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME,以此将商品的详细信息缓存到Redis中,确保缓存数据在一定时间后会自动失效,维持缓存数据的时效性和内存占用的合理性, + // 方便后续如商品详情查询等业务操作能优先从缓存获取信息,提升系统的整体性能和响应效率。 } + return ServerResponse.createBySuccessMessage("预置商品信息成功"); + // 当完成对所有符合条件商品的详细信息缓存操作后,返回一个包含成功提示信息(“预置商品信息成功”)的ServerResponse对象, + // 告知调用者(例如系统初始化过程中触发此操作或者管理员手动执行预置信息操作等情况)商品详细信息预置到Redis缓存的操作已成功完成, + // 调用者可以根据这个返回结果进行相应的后续处理(如记录日志、展示操作成功提示给用户等),保证业务流程的完整性以及对操作结果的合理反馈。 } -- 2.34.1 From 43d9f36c65983d0a1a3c8f8e496523cecda3a5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E8=90=B1?= <2593634984@qq.com> Date: Wed, 18 Dec 2024 19:05:56 +0800 Subject: [PATCH 2/3] 3cx --- snailmall-product-service/src/3.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 snailmall-product-service/src/3.txt diff --git a/snailmall-product-service/src/3.txt b/snailmall-product-service/src/3.txt new file mode 100644 index 0000000..d28d40b --- /dev/null +++ b/snailmall-product-service/src/3.txt @@ -0,0 +1 @@ +add \ No newline at end of file -- 2.34.1 From 1cfa4a25dac767b0e542e811806855f3c02eed5c Mon Sep 17 00:00:00 2001 From: litingting <3316043814@qq.com> Date: Wed, 18 Dec 2024 19:11:03 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/njupt/swg/cache/CommonCacheUtil.java | 54 ++- .../com/njupt/swg/cache/JedisPoolWrapper.java | 46 ++- .../java/com/njupt/swg/common/Parameters.java | 23 +- .../java/com/njupt/swg/config/CorsConfig.java | 28 +- .../com/njupt/swg/constants/Constants.java | 49 ++- .../swg/controller/ErrorHandleController.java | 20 +- .../main/java/com/njupt/swg/entity/User.java | 18 +- .../swg/exception/SnailmallException.java | 28 +- .../com/njupt/swg/filter/AdminUserFilter.java | 107 +++-- .../com/njupt/swg/filter/RateLimitFilter.java | 42 +- .../java/com/njupt/swg/resp/ResponseEnum.java | 33 +- .../com/njupt/swg/resp/ServerResponse.java | 142 +++++-- .../java/com/njupt/swg/utils/CookieUtil.java | 104 +++-- .../com/njupt/swg/utils/DateTimeUtil.java | 80 +++- .../java/com/njupt/swg/utils/JsonUtil.java | 124 +++--- .../com/njupt/swg/cache/CommonCacheUtil.java | 42 +- .../com/njupt/swg/cache/JedisPoolWrapper.java | 39 +- .../java/com/njupt/swg/cache/Parameters.java | 16 +- .../com/njupt/swg/clients/ProductClient.java | 18 +- .../njupt/swg/common/constants/Constants.java | 34 +- .../exception/ExceptionHandlerAdvice.java | 32 +- .../common/exception/SnailmallException.java | 24 +- .../njupt/swg/common/resp/ResponseEnum.java | 32 +- .../njupt/swg/common/resp/ServerResponse.java | 144 +++++-- .../swg/common/utils/BigDecimalUtil.java | 48 ++- .../njupt/swg/common/utils/CookieUtil.java | 102 +++-- .../njupt/swg/common/utils/DateTimeUtil.java | 76 +++- .../com/njupt/swg/common/utils/JsonUtil.java | 125 +++--- .../com/njupt/swg/common/utils/MD5Util.java | 42 +- .../swg/common/utils/PropertiesUtil.java | 37 +- .../njupt/swg/controller/BaseController.java | 30 +- .../njupt/swg/controller/CartController.java | 255 ++++++++---- .../java/com/njupt/swg/dao/CartMapper.java | 32 +- .../main/java/com/njupt/swg/entity/Cart.java | 20 +- .../java/com/njupt/swg/entity/Product.java | 37 +- .../main/java/com/njupt/swg/entity/User.java | 27 +- .../njupt/swg/message/MessageReceiver.java | 36 +- .../njupt/swg/service/CartServiceImpl.java | 373 ++++++++++++++---- .../java/com/njupt/swg/vo/CartProductVo.java | 56 ++- .../main/java/com/njupt/swg/vo/CartVo.java | 24 +- .../com/njupt/swg/cache/CommonCacheUtil.java | 71 +++- .../com/njupt/swg/cache/JedisPoolWrapper.java | 44 ++- .../java/com/njupt/swg/cache/Parameters.java | 27 +- .../njupt/swg/common/constants/Constants.java | 67 +++- .../exception/ExceptionHandlerAdvice.java | 45 ++- .../common/exception/SnailmallException.java | 33 +- .../njupt/swg/common/resp/ResponseEnum.java | 27 +- .../njupt/swg/common/resp/ServerResponse.java | 84 ++-- .../njupt/swg/common/utils/CookieUtil.java | 88 +++-- .../njupt/swg/common/utils/DateTimeUtil.java | 90 ++++- .../com/njupt/swg/common/utils/JsonUtil.java | 147 ++++--- .../com/njupt/swg/common/utils/MD5Util.java | 58 ++- .../com/njupt/swg/common/utils/ZkClient.java | 27 +- 53 files changed, 2614 insertions(+), 793 deletions(-) diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java index 385face..5a24b4a 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java @@ -12,44 +12,61 @@ import redis.clients.jedis.JedisPool; * @Date 2019/1/1 15:03 * @CONTACT 317758022@qq.com * @DESC + * 这个类是一个通用的缓存工具类,用于与Redis进行交互,实现缓存相关的操作, + * 例如缓存数据、获取缓存数据、设置带过期时间的缓存以及删除缓存等操作。 + * 它依赖于JedisPoolWrapper来获取Jedis连接池。 */ @Component @Slf4j public class CommonCacheUtil { + // 自动注入JedisPoolWrapper,用于获取Jedis连接池,通过它来操作Redis @Autowired private JedisPoolWrapper jedisPoolWrapper; - /** * 缓存永久key + * 此方法用于将给定的键值对以永久有效的方式存入Redis缓存中。 + * 如果在操作过程中出现异常,会记录错误日志并抛出SnailmallException异常表示Redis操作报错。 + * @param key 要存入缓存的键 + * @param value 要存入缓存对应键的值 */ public void cache(String key, String value) { try { + // 获取Jedis连接池 JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { - try (Jedis Jedis = pool.getResource()) { - Jedis.select(0); - Jedis.set(key, value); + if (pool!= null) { + // 从连接池中获取Jedis资源,使用try-with-resources语句自动关闭资源 + try (Jedis jedis = pool.getResource()) { + // 选择Redis的第0个数据库(通常可以根据实际情况选择不同的数据库) + jedis.select(0); + // 将键值对存入Redis + jedis.set(key, value); } } } catch (Exception e) { + // 记录Redis存值失败的错误日志 log.error("redis存值失败", e); + // 抛出SnailmallException异常表示Redis报错 throw new SnailmallException("redis报错"); } } /** * 获取缓存key + * 此方法用于从Redis缓存中根据给定的键获取对应的值。 + * 如果出现异常,会记录错误日志并抛出SnailmallException异常表示Redis操作报错,最后返回获取到的值(可能为null)。 + * @param key 要获取值的缓存键 + * @return 缓存中对应键的值,如果不存在则返回null */ public String getCacheValue(String key) { String value = null; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { - try (Jedis Jedis = pool.getResource()) { - Jedis.select(0); - value = Jedis.get(key); + if (pool!= null) { + try (Jedis jedis = pool.getResource()) { + jedis.select(0); + value = jedis.get(key); } } } catch (Exception e) { @@ -61,12 +78,19 @@ public class CommonCacheUtil { /** * 过期key + * 此方法用于向Redis缓存中存入键值对,并设置该键的过期时间(以秒为单位)。 + * 先尝试使用SETNX命令设置键值对(只有键不存在时才设置成功),然后设置过期时间。 + * 如果操作过程中出现异常,会记录错误日志并抛出SnailmallException异常表示Redis操作报错,最后返回SETNX操作的结果。 + * @param key 要存入缓存的键 + * @param value 要存入缓存对应键的值 + * @param expire 键的过期时间,单位为秒 + * @return SETNX操作的结果,1表示设置成功(键不存在时),0表示键已存在设置失败 */ public long cacheNxExpire(String key, String value, int expire) { long result = 0; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); result = jedis.setnx(key, value); @@ -83,10 +107,13 @@ public class CommonCacheUtil { /** * 删除缓存key + * 此方法用于从Redis缓存中删除指定的键。 + * 如果在删除过程中出现异常,会记录错误日志并抛出SnailmallException异常表示Redis操作报错。 + * @param key 要删除的缓存键 */ public void delKey(String key) { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); try { @@ -98,7 +125,4 @@ public class CommonCacheUtil { } } } - - - -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java index 52cd0f7..a76f0fe 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java @@ -13,31 +13,65 @@ import javax.annotation.PostConstruct; * @Author swg. * @Date 2019/1/1 15:00 * @CONTACT 317758022@qq.com - * @DESC 只做了单个redis,但是课程中实现的redis客户端集群,要掌握一致性hash算法 + * @DESC 本类旨在对JedisPool进行封装管理,当前实现仅针对单个Redis实例进行连接池的配置与初始化操作。 + * 不过在相关课程中涉及了redis客户端集群相关内容,在那种场景下需要掌握一致性hash算法来更好地进行数据分布等操作。 + * 本类作为Spring的一个组件,通过依赖注入获取配置参数,并在初始化阶段构建JedisPool连接池,后续提供获取该连接池的方法供其他类使用。 */ @Component @Slf4j public class JedisPoolWrapper { + + // 通过Spring的依赖注入机制,自动注入Parameters对象。 + // Parameters类应该是用于存放各类配置参数的,在这里主要用于获取与Redis连接相关的配置信息。 @Autowired private Parameters parameters; + // 用于保存JedisPool实例,初始化为null,会在后续的初始化方法中根据配置情况进行实例化。 + // 这个连接池对象是与Redis服务器建立连接、获取Jedis客户端实例的关键所在。 private JedisPool jedisPool = null; + /** + * @PostConstruct注解修饰的方法会在当前类实例化且依赖注入完成后自动执行。 + * 此方法主要负责初始化JedisPool连接池,通过读取配置参数来设置连接池的各项属性,并创建JedisPool实例。 + * 如果在初始化过程中出现任何异常,会记录详细的错误日志信息,方便排查问题。 + */ @PostConstruct - public void init(){ + public void init() { try { + // 创建JedisPoolConfig对象,它用于配置JedisPool连接池的各种行为和参数限制。 JedisPoolConfig config = new JedisPoolConfig(); + + // 设置JedisPool连接池允许创建的最大连接数量。 + // 通过从注入的Parameters对象中获取对应的配置参数值来进行设置,确保连接池的规模符合预期配置。 config.setMaxTotal(parameters.getRedisMaxTotal()); + + // 设置JedisPool连接池中最大的空闲连接数量。 + // 空闲连接可以被重复利用,合理设置该值有助于提高资源利用效率,同样从Parameters获取配置值进行设置。 config.setMaxIdle(parameters.getRedisMaxIdle()); + + // 设置当从连接池中获取连接时,如果没有可用连接,最长等待的时间(单位为毫秒)。 + // 防止因为长时间等待连接导致应用程序阻塞,这里也是依据Parameters中的配置来设定。 config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis()); - jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx"); + + // 使用配置好的JedisPoolConfig对象,以及从Parameters获取的Redis服务器主机地址、端口号、超时时间(这里设置为2000毫秒)和密码(此处示例密码为"xxx",实际应替换为真实密码)等信息,创建JedisPool实例。 + // 这个实例将作为后续与Redis服务器进行交互的基础,通过它可以获取Jedis客户端来执行各种Redis操作。 + jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx"); + + // 如果初始化过程顺利,记录一条信息日志,表明Redis连接池初始化成功,方便在运行时查看初始化状态。 log.info("【初始化redis连接池成功】"); - }catch (Exception e){ - log.error("【初始化redis连接池失败】",e); + } catch (Exception e) { + // 如果在初始化JedisPool连接池过程中出现异常,记录一条错误日志,包含详细的异常信息(通过e参数传递异常堆栈等内容)。 + // 这样在排查问题时可以快速定位到是在初始化连接池时出现的错误,并根据异常详情分析具体原因。 + log.error("【初始化redis连接池失败】", e); } } + /** + * 对外提供获取JedisPool连接池对象的方法。 + * 其他需要与Redis进行交互的类可以调用此方法来获取JedisPool实例,进而从连接池中获取Jedis客户端来执行诸如数据读写、键值操作等各种Redis相关操作。 + * @return 返回已经初始化好的JedisPool对象,如果初始化过程中出现问题导致jedisPool仍为null,则返回null。 + */ public JedisPool getJedisPool() { return jedisPool; } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/common/Parameters.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/common/Parameters.java index f147b5c..d030897 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/common/Parameters.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/common/Parameters.java @@ -11,23 +11,44 @@ import java.util.List; * @Date 2019/1/1 14:27 * @CONTACT 317758022@qq.com * @DESC + * 这个类(Parameters)主要用于存放应用程序中的各类配置参数,通过Spring的依赖注入机制(@Value注解)从配置文件(通常是application.properties或application.yml等)中读取相应的值进行赋值。 + * 它被标记为Spring的组件(@Component注解),方便在其他需要使用这些配置参数的地方通过依赖注入获取该类的实例。 + * 同时使用了Lombok的@Data注解,自动生成了常用的Getter、Setter、toString等方法,方便对类中的属性进行操作和展示。 */ @Component @Data public class Parameters { + + /** + * Redis配置相关参数部分开始 + * 以下是用于配置与Redis服务器连接相关的各项参数,通过@Value注解结合配置文件中的对应属性键来获取具体的值并赋值给相应的成员变量。 + */ /*****redis config start*******/ + // 从配置文件中读取Redis服务器的主机地址配置,对应的配置文件中的属性键为"redis.host" @Value("${redis.host}") private String redisHost; + + // 从配置文件中读取Redis服务器的端口号配置,对应的配置文件中的属性键为"redis.port",会被自动解析并赋值为整数类型 @Value("${redis.port}") private int redisPort; + + // 此处变量名可能存在混淆,按照语义理解应该是获取Redis连接池的最大空闲连接数,但变量名写的是redisMaxTotal,可能需要确认是否准确。 + // 从配置文件中读取对应配置,配置文件中的属性键为"redis.max-idle",赋值为整数类型 @Value("${redis.max-idle}") private int redisMaxTotal; + + // 此处同样变量名可能存在混淆,按照语义理解应该是获取Redis连接池的最大连接数,但变量名写的是redisMaxIdle,可能需要核对准确性。 + // 从配置文件中读取对应配置,配置文件中的属性键为"redis.max-total",赋值为整数类型 @Value("${redis.max-total}") private int redisMaxIdle; + + // 从配置文件中读取获取Redis连接时的最大等待时间(单位为毫秒)配置,对应的配置文件中的属性键为"redis.max-wait-millis",赋值为整数类型 @Value("${redis.max-wait-millis}") private int redisMaxWaitMillis; /*****redis config end*******/ + // 用于存放不需要安全验证的管理员路径列表配置,从配置文件中读取以逗号分隔的字符串,并通过split方法解析为List类型。 + // 配置文件中对应的属性键为"security.noneSecurityAdminPaths" @Value("#{'${security.noneSecurityAdminPaths}'.split(',')}") private List noneSecurityAdminPaths; -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/config/CorsConfig.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/config/CorsConfig.java index 0acfa03..232023b 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/config/CorsConfig.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/config/CorsConfig.java @@ -9,24 +9,44 @@ import org.springframework.web.filter.CorsFilter; import java.util.Arrays; /** - * 跨域配置 允许所有的接口都可以跨域 - * C - Cross O - Origin R - Resource S - Sharing + * 跨域配置类,其目的是允许所有的接口都可以跨域访问。 + * CORS是"Cross-Origin Resource Sharing"(跨域资源共享)的缩写,通过配置相关规则来控制不同源之间的资源共享情况。 + * 此类使用Spring的配置相关注解,用于在Spring应用中定义跨域相关的配置逻辑,并将其作为一个Bean注入到Spring容器中供其他组件使用。 */ @Configuration public class CorsConfig { + /** + * @Bean注解用于将方法返回的对象作为一个Bean注册到Spring容器中,在这里返回的是CorsFilter对象。 + * 这个CorsFilter对象用于处理跨域请求时的过滤逻辑,基于配置好的跨域规则来决定是否允许跨域访问等操作。 + */ @Bean public CorsFilter corsFilter() { + // 创建一个基于URL的CorsConfigurationSource对象,它用于根据不同的URL路径来配置对应的CorsConfiguration(跨域配置)。 final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + // 创建一个CorsConfiguration对象,用于具体设置跨域相关的各项配置参数,比如允许的源、请求头、请求方法等。 final CorsConfiguration config = new CorsConfiguration(); + // 设置是否允许跨域请求中包含认证信息(如Cookie等),这里设置为true,表示允许携带认证信息进行跨域访问。 config.setAllowCredentials(true); - config.setAllowedOrigins(Arrays.asList("*")); //http:www.a.com + + // 设置允许的跨域请求源(Origin),这里使用Arrays.asList("*")表示允许所有来源的请求进行跨域访问。 + // 在实际更严格的场景下,通常应该明确指定具体的域名等来源,例如Arrays.asList("http://www.a.com")只允许来自该域名的跨域请求。 + config.setAllowedOrigins(Arrays.asList("*")); + + // 设置允许的请求头信息,使用Arrays.asList("*")表示允许所有类型的请求头进行跨域请求,不过在实际应用中,也可以根据需求明确列出允许的具体请求头列表。 config.setAllowedHeaders(Arrays.asList("*")); + + // 设置允许的请求方法(如GET、POST、PUT等),同样通过Arrays.asList("*")表示允许所有的请求方法进行跨域访问,也可以按需具体指定。 config.setAllowedMethods(Arrays.asList("*")); + + // 设置预检请求(OPTIONS请求)的有效期,单位为秒,这里设置为300秒,表示在这个时间范围内,相同配置的跨域请求不需要再次发送预检请求。 config.setMaxAge(300l); + // 将配置好的CorsConfiguration应用到所有路径("/**"表示匹配所有的URL路径)上,也就是对整个应用的所有接口都应用此跨域配置。 source.registerCorsConfiguration("/**", config); + + // 创建并返回CorsFilter对象,这个对象会在Spring Web应用中对进入的请求进行过滤,根据配置好的跨域规则来处理跨域相关的逻辑。 return new CorsFilter(source); } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/constants/Constants.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/constants/Constants.java index f721714..782a365 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/constants/Constants.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/constants/Constants.java @@ -5,42 +5,75 @@ package com.njupt.swg.constants; * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com * @DESC + * 这个类(Constants)主要用于定义整个应用程序中常用的常量。 + * 通过将这些常量集中在此类中进行统一管理,方便在代码的各个地方进行引用,提高代码的可读性和可维护性,避免了硬编码一些常数值的情况。 */ public class Constants { + + /** + * 自定义状态码相关常量部分开始 + * 以下定义了一系列在应用中用于表示不同响应状态的自定义状态码,方便在处理请求返回结果等场景下统一使用和判断。 + */ /**自定义状态码 start**/ + // 表示请求成功的状态码,对应HTTP状态码中的200,表示操作顺利完成,符合预期。 public static final int RESP_STATUS_OK = 200; + // 表示未授权的状态码,对应HTTP状态码中的401,通常用于用户在没有提供有效认证信息(如未登录或者权限不足等情况)访问受保护资源时返回该状态码。 public static final int RESP_STATUS_NOAUTH = 401; + // 表示服务器内部错误的状态码,对应HTTP状态码中的500,意味着服务器在处理请求过程中出现了不可预期的错误情况。 public static final int RESP_STATUS_INTERNAL_ERROR = 500; + // 表示请求参数错误的状态码,对应HTTP状态码中的400,常用于客户端发送的请求参数不符合要求、格式错误等场景。 public static final int RESP_STATUS_BADREQUEST = 400; - /**自定义状态码 end**/ + /** + * Redis中与用户相关的键(key)的前缀定义,后续在Redis中存储用户相关的数据时,键名通常会以此前缀开头,便于统一管理和识别用户相关的缓存数据。 + */ /***redis user相关的key以这个打头**/ public static final String TOKEN_PREFIX = "user_"; + /** + * 用于定义与用户登录信息在Redis中缓存的过期时间相关的常量。 + * 通过接口内部定义常量的方式,使得这个过期时间相关的值在逻辑上更加清晰、独立,方便后续可能的修改和维护。 + */ /** * 用户登陆redis的过期时间 */ - public interface RedisCacheExtime{ - int REDIS_SESSION_EXTIME = 60* 30;//30分钟 + public interface RedisCacheExtime { + // 定义用户登录信息在Redis中的缓存过期时间为30分钟,这里通过计算将分钟换算成秒(60秒 * 30分钟)进行表示。 + int REDIS_SESSION_EXTIME = 60 * 30; // 30分钟 } + /** + * 用户注册判断重复时涉及的参数类型相关常量部分开始 + * 以下定义了在用户注册过程中,用于判断哪些参数是否重复的相关常量字符串,比如判断邮箱、用户名是否已被使用等情况。 + */ /** 用户注册判断重复的参数类型 start **/ + // 表示用于判断用户注册时邮箱是否重复的参数类型对应的字符串常量,方便在相关验证逻辑中统一使用该标识。 public static final String EMAIL = "email"; + // 表示用于判断用户注册时用户名是否重复的参数类型对应的字符串常量,同样便于在用户名重复性验证逻辑中统一引用。 public static final String USERNAME = "username"; /** 用户注册判断重复的参数类型 end **/ + /** + * 用户角色相关的常量定义部分开始 + * 以下通过接口内部定义常量的方式,定义了不同用户角色对应的整数值,方便在权限判断、用户分类等场景下进行使用,以区分不同类型的用户角色。 + */ /** 用户角色 **/ - public interface Role{ - int ROLE_CUSTOME = 0;//普通用户 - int ROLE_ADMIN = 1;//管理员用户 + public interface Role { + // 表示普通用户角色对应的整数值为0,用于在系统中标识具有普通权限的用户类型。 + int ROLE_CUSTOME = 0; // 普通用户 + + // 表示管理员用户角色对应的整数值为1,用于区分具有管理权限、可进行系统管理操作的用户类型。 + int ROLE_ADMIN = 1; // 管理员用户 } + /** + * 用于定义用户注册过程中分布式锁相关的路径字符串常量,在涉及到分布式环境下对用户注册操作进行并发控制时,通过这个路径来标识对应的分布式锁资源。 + */ /**用户注册分布式锁路径***/ public static final String USER_REGISTER_DISTRIBUTE_LOCK_PATH = "/user_reg"; - -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/controller/ErrorHandleController.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/controller/ErrorHandleController.java index fdac6bd..d7fc586 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/controller/ErrorHandleController.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/controller/ErrorHandleController.java @@ -11,17 +11,31 @@ import org.springframework.web.bind.annotation.RestController; * @Date 2019/1/2 21:21 * @CONTACT 317758022@qq.com * @DESC + * 这个类(ErrorHandleController)是一个Spring MVC中的控制器类,用于处理应用程序中的错误情况。 + * 它实现了Spring Boot提供的ErrorController接口,意味着它负责定义和处理应用中发生错误时的相关逻辑,例如当用户未登录或者权限不足等情况导致的错误。 + * 通过使用@RestController注解,表明这个类中的方法返回的结果会直接以JSON等格式响应给客户端,而不是返回视图页面等。 */ @RestController public class ErrorHandleController implements ErrorController { + + /** + * 实现ErrorController接口中定义的方法,用于指定处理错误的路径。 + * 返回的路径("/error")就是当应用出现错误时,请求会被转发到的默认路径,后续会在这个路径对应的方法中进行具体的错误处理逻辑。 + * @return 返回处理错误的默认路径字符串,即"/error"。 + */ @Override public String getErrorPath() { return "/error"; } + /** + * 使用@RequestMapping注解将这个方法映射到"/error"路径上,意味着当请求访问到"/error"这个路径时,就会执行此方法。 + * 此方法主要用于构建并返回一个ServerResponse对象,来告知客户端出现的错误情况,这里是返回一个表示用户未登录或者权限不足的错误响应。 + * 通过调用ServerResponse的静态方法createByErrorCodeMessage,使用从ResponseEnum中获取的错误代码以及对应的错误提示信息来构造响应对象。 + * @return 返回一个ServerResponse对象,其中包含了错误代码以及对应的提示信息,告知客户端用户未登录或者权限不足的错误状态。 + */ @RequestMapping("/error") public ServerResponse error() { - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆或者权限不足"); + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆或者权限不足"); } - -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/entity/User.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/entity/User.java index 4354b81..078f8f9 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/entity/User.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/entity/User.java @@ -12,32 +12,44 @@ import java.util.Date; * @Author swg. * @Date 2018/12/31 21:01 * @CONTACT 317758022@qq.com - * @DESC 用户实体类 + * @DESC 用户实体类,用于在应用程序中对用户相关信息进行建模和封装。 + * 它对应着数据库中存储用户数据的表结构,每个属性代表了用户的一项具体信息,方便在业务逻辑处理中进行操作、传递以及持久化等操作。 + * 通过实现Serializable接口,使得该类的对象可以被序列化,便于在网络传输、对象存储(比如存入文件或缓存等场景)时进行相应的处理,确保数据的完整性和可恢复性。 + * 同时使用了Lombok提供的几个注解来简化代码编写,自动生成常用的方法,提高代码的简洁性和可读性。 */ @Data @NoArgsConstructor @AllArgsConstructor @ToString public class User implements Serializable { + + // 用户的唯一标识符,通常对应数据库表中的主键字段,用于区分不同的用户个体。 private Integer id; + // 用户登录时使用的用户名,具有唯一性,方便用户进行身份认证和在系统中标识自己。 private String username; + // 用户登录时使用的密码,用于验证用户身份的重要信息,需要进行妥善的加密存储和安全处理。 private String password; + // 用户的电子邮箱地址,可用于账号找回、信息通知等功能,也需要保证其唯一性以及格式的有效性。 private String email; + // 用户的手机号码,同样可用于多种业务场景,比如短信验证、联系用户等,格式上需要符合手机号码的规范要求。 private String phone; + // 用户设置的密保问题,用于在忘记密码等情况下辅助验证用户身份,增加账号的安全性。 private String question; + // 用户针对密保问题设置的答案,与question属性配合使用,用于验证用户身份的真实性。 private String answer; - //角色0-管理员,1-普通用户 + // 用于标识用户在系统中的角色,0表示管理员角色,拥有较高的系统管理权限;1表示普通用户角色,权限相对受限,只能进行普通的业务操作。 private Integer role; + // 用户账号创建的时间,记录了用户首次注册进入系统的时间节点,通常由系统自动生成并赋值。 private Date createTime; + // 用户信息最近一次更新的时间,每当用户修改个人信息等操作后,该时间会相应更新,便于跟踪用户信息的变更情况。 private Date updateTime; - } \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/exception/SnailmallException.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/exception/SnailmallException.java index af7918a..2fe0d6b 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/exception/SnailmallException.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/exception/SnailmallException.java @@ -8,18 +8,36 @@ import lombok.Getter; * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC + * 这个类(SnailmallException)是一个自定义的异常类,继承自Java的RuntimeException,属于运行时异常。 + * 它主要用于在应用程序特定的业务逻辑中抛出和处理异常情况,使得异常信息能够更好地贴合业务需求,方便进行统一的异常处理和错误提示展示等操作。 + * 通过使用Lombok的@Getter注解,自动生成了获取exceptionStatus属性的Getter方法,方便在其他地方获取该异常类中定义的异常状态码。 */ @Getter -public class SnailmallException extends RuntimeException{ +public class SnailmallException extends RuntimeException { + + // 用于存储异常对应的状态码,默认初始化为ResponseEnum.ERROR.getCode(),也就是使用预定义的错误状态码值,不过可以通过构造方法进行重新赋值。 private int exceptionStatus = ResponseEnum.ERROR.getCode(); - public SnailmallException(String msg){ + /** + * 构造方法之一,接收一个字符串类型的参数msg,用于创建一个SnailmallException异常实例。 + * 这个msg参数会传递给父类(RuntimeException)的构造方法,作为异常的详细描述信息,方便在日志或者错误提示中展示具体的错误原因等内容。 + * 此构造方法创建的异常实例会使用默认的异常状态码。 + * @param msg 异常的详细描述信息,用于说明出现异常的具体原因等情况。 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 另一个构造方法,接收两个参数,一个整数类型的code和一个字符串类型的msg。 + * code参数用于指定该异常对应的状态码,可根据不同的业务异常情况设置不同的状态码,以便于准确区分和处理各种异常。 + * msg参数同样传递给父类(RuntimeException)的构造方法,作为异常的详细描述信息。 + * 通过这个构造方法创建的异常实例可以自定义异常状态码,更灵活地适应业务中的不同异常场景。 + * @param code 异常对应的状态码,用于区分不同类型的业务异常情况。 + * @param msg 异常的详细描述信息,用于说明出现异常的具体原因等情况。 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/AdminUserFilter.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/AdminUserFilter.java index 037213b..58ede15 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/AdminUserFilter.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/AdminUserFilter.java @@ -26,43 +26,67 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst * @Author swg. * @Date 2019/1/3 10:21 * @CONTACT 317758022@qq.com - * @DESC 关于后台管理系统,登陆不需要拦截,对于需要认证的富文本上传,需要特定的格式,所以,这里先对富文本放开,到controller里面进行特别处理 - * 前台,就不再在这里进行处理了,因为前台需要防止用户同级的攻击,所以需要userID,放到这里,controller那边不好处理 + * @DESC 关于后台管理系统,登陆不需要拦截,对于需要认证的富文本上传,需要特定的格式,所以,这里先对富文本放开,到controller里面进行特别处理。 + * 前台,就不再在这里进行处理了,因为前台需要防止用户同级的攻击,所以需要userID,放到这里,controller那边不好处理。 + * 这个类(AdminUserFilter)是一个ZuulFilter的实现类,用于在Zuul网关中对请求进行过滤处理,主要针对进入后台管理系统的请求进行权限校验等相关操作。 + * 通过Spring的@Component注解将其注册为一个Spring组件,方便进行依赖注入等操作,并且使用了@Slf4j注解来记录相关的日志信息,便于跟踪请求处理过程和排查问题。 */ @Slf4j @Component public class AdminUserFilter extends ZuulFilter { + + // 自动注入CommonCacheUtil,用于从缓存(如Redis)中获取相关数据,比如用户信息等,辅助进行权限校验等操作。 @Autowired private CommonCacheUtil commonCacheUtil; + + // 自动注入Parameters对象,用于获取应用程序的配置参数,例如获取不需要进行安全校验的路径等配置信息。 @Autowired private Parameters parameters; + /** + * 定义过滤器的类型,这里返回PRE_TYPE,表示这是一个前置过滤器,会在请求路由之前执行,用于在请求到达目标服务之前进行一些预处理操作,比如权限校验等。 + * @return 返回过滤器类型,固定为PRE_TYPE,表示前置过滤器。 + */ @Override public String filterType() { return PRE_TYPE; } + /** + * 定义过滤器的执行顺序,通过返回一个整数值来指定其在同类型(这里是前置过滤器)过滤器中的执行先后顺序。 + * 返回的值是在预定义的PRE_DECORATION_FILTER_ORDER基础上减1,表示此过滤器在相关的前置过滤器序列中相对靠前执行,以便尽早进行权限校验等操作。 + * @return 返回过滤器的执行顺序值。 + */ @Override public int filterOrder() { - return PRE_DECORATION_FILTER_ORDER-1; + return PRE_DECORATION_FILTER_ORDER - 1; } + /** + * 判断当前过滤器是否需要对请求进行过滤处理。 + * 首先获取当前请求的上下文以及对应的HttpServletRequest对象,然后获取请求的URL路径。 + * 根据不同的URL条件来决定是否需要进行权限校验:如果请求的URL不包含"manage"(意味着不是后台管理系统相关的请求),则直接放过,不需要进行后续的权限校验; + * 如果URL包含"upload"(可能是特定的富文本上传等不需要权限校验的请求情况),也直接放过; + * 对于从配置文件获取的需要校验的路径,如果当前请求URL包含在特定的不需要校验的路径中,同样放过。 + * 其他情况下(即属于后台管理系统且不在特定放过列表中的请求),则需要进行权限校验,返回true。 + * @return 返回布尔值,true表示需要进行过滤处理(进行权限校验等),false表示直接放过当前请求,不进行后续过滤操作。 + */ @Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); - //获取当前请求的url + // 获取当前请求的url String url = request.getRequestURI(); - //前端的路径不在这里校验,直接放过 - if (!url.contains("manage")){ - log.info("【{}不需要进行权限校验】",url); + // 前端的路径不在这里校验,直接放过 + if (!url.contains("manage")) { + log.info("【{}不需要进行权限校验】", url); return false; } - if(url.contains("upload")){ - log.info("【{}不需要进行权限校验】",url); + if (url.contains("upload")) { + log.info("【{}不需要进行权限校验】", url); return false; } - //从配置文件获取所有门户需要校验的路径 + // 从配置文件获取所有门户需要校验的路径 // String[] passUrls = parameters.getNoneSecurityAdminPaths().toArray(new String[parameters.getNoneSecurityAdminPaths().size()]); // for(String str:passUrls){ // //指定的路径比较特殊,也不在这里校验 @@ -71,47 +95,57 @@ public class AdminUserFilter extends ZuulFilter { // return false; // } // } - log.info("【{}----需要进行权限校验,必须是管理员身份才可以进入】",url); + log.info("【{}----需要进行权限校验,必须是管理员身份才可以进入】", url); return true; } + /** + * 过滤器的核心执行逻辑方法,当shouldFilter方法返回true时,此方法会被执行,用于进行实际的权限校验等操作。 + * 首先获取请求上下文和HttpServletRequest对象,然后校验是否为管理员身份: + * 先从Cookie中读取登录令牌(loginToken),如果令牌为空,表示用户未登录,进行相应的错误处理(如设置不进行路由、返回错误状态码、返回错误提示信息等); + * 如果令牌不为空,则从Redis缓存中通过该令牌获取用户信息,如果获取不到用户信息,同样进行错误处理; + * 如果获取到了用户信息,并且请求的是后台管理系统相关的URL(包含"manage"),则进一步校验当前用户是否为管理员角色,如果不是管理员角色,则进行相应的错误处理。 + * 如果所有校验都通过,则返回null,表示请求可以正常路由到目标服务进行后续处理。 + * @return 返回ServerResponse对象(这里在出现错误情况时返回相应的错误提示响应,校验通过则返回null),或者抛出ZuulException异常(当前代码中未抛出,不过保留了相关机制)。 + * @throws ZuulException 可抛出此异常用于处理Zuul网关相关的异常情况,当前代码实现中暂未抛出此异常。 + */ @Override public ServerResponse run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); - //校验是否为管理员身份 + // 校验是否为管理员身份 String loginToken = CookieUtil.readLoginToken(request); - log.info("【获取cookie----{}】",loginToken); - if(StringUtils.isEmpty(loginToken)){ + log.info("【获取cookie----{}】", loginToken); + if (StringUtils.isEmpty(loginToken)) { // // 过滤该请求,不对其进行路由 // requestContext.setSendZuulResponse(false); // //返回错误代码 // requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH); // return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); this.returnMsg(requestContext); - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); - //throw new SnailmallException("用户未登录,无法获取当前用户信息"); + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息"); + // throw new SnailmallException("用户未登录,无法获取当前用户信息"); } - //2.从redis中获取用户信息 + // 2.从redis中获取用户信息 String userStr = commonCacheUtil.getCacheValue(loginToken); - log.info("【从redis中获取用户信息:{}】",userStr); - if(userStr == null){ + log.info("【从redis中获取用户信息:{}】", userStr); + if (userStr == null) { // // 过滤该请求,不对其进行路由 // requestContext.setSendZuulResponse(false); // //返回错误代码 // requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH); // return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); //SnailmallException(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); this.returnMsg(requestContext); - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息"); } String url = request.getRequestURI(); - log.info("【获取当前url:{}】",url); - if(url.contains("manage")){ + log.info("【获取当前url:{}】", url); + if (url.contains("manage")) { log.info("【来到了管理后台,需要校验权限】"); - User currentUser = JsonUtil.Str2Obj(userStr,User.class); - log.info("【当前登陆的用户为:{}】",currentUser); - if(!currentUser.getRole().equals(Constants.Role.ROLE_ADMIN)){ - //不是管理员报错 + User currentUser = JsonUtil.Str2Obj(userStr, User.class); + log.info("【当前登陆的用户为:{}】", currentUser); + if (!currentUser.getRole().equals(Constants.Role.ROLE_ADMIN)) { + // 不是管理员报错 log.error("【当前登陆用户不是管理员身份】"); // 过滤该请求,不对其进行路由 // requestContext.setSendZuulResponse(false); @@ -119,18 +153,25 @@ public class AdminUserFilter extends ZuulFilter { // requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH); // return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); this.returnMsg(requestContext); - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); - //throw new SnailmallException("用户权限不够"); + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息"); + // throw new SnailmallException("用户权限不够"); } } return null; } - //返回没权限消息 - private void returnMsg(RequestContext ctx){ + /** + * 用于返回没有权限相关的提示消息的私有方法,主要进行一些响应相关的设置操作。 + * 设置响应的内容类型为JSON格式("application/json; charset=utf-8"),然后令Zuul过滤该请求,不进行路由(即阻止请求继续向后端服务转发)。 + * 设置响应的状态码(这里设置为Constants.RESP_STATUS_OK,不过从语义上可能需要进一步确认是否合适,也许应该设置为对应的权限错误状态码等), + * 最后将包含错误提示信息的ServerResponse对象转换为JSON字符串,并设置为响应体内容,以便返回给客户端相应的错误提示。 + * @param ctx 请求上下文对象,用于设置响应相关的各种属性信息。 + */ + // 返回没权限消息 + private void returnMsg(RequestContext ctx) { ctx.getResponse().setContentType("application/json; charset=utf-8"); - ctx.setSendZuulResponse(false); //令zuul过滤该请求,不对其进行路由 + ctx.setSendZuulResponse(false); // 令zuul过滤该请求,不对其进行路由 ctx.setResponseStatusCode(Constants.RESP_STATUS_OK); - ctx.setResponseBody(JsonUtil.obj2String(ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"))); + ctx.setResponseBody(JsonUtil.obj2String(ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息"))); } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/RateLimitFilter.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/RateLimitFilter.java index 4f1944b..4911029 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/RateLimitFilter.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/filter/RateLimitFilter.java @@ -15,38 +15,64 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst * @Author swg. * @Date 2019/1/3 11:21 * @CONTACT 317758022@qq.com - * @DESC 令牌桶法限流 具体测试没做 + * @DESC 这个类(RateLimitFilter)是一个基于ZuulFilter实现的过滤器,用于在Zuul网关中实现限流功能,采用的是令牌桶算法来控制请求流量。 + * 不过需要注意的是,注释中提到具体测试没做,意味着其实际限流效果可能还未经过充分验证。它被注册为Spring的一个组件(通过@Component注解),方便在Spring应用中被自动扫描并实例化。 */ @Component public class RateLimitFilter extends ZuulFilter { - //放100个令牌 - private static final RateLimiter RATE_LIMITER = RateLimiter.create(100); + // 使用Google Guava库中的RateLimiter来创建一个令牌桶,这里设置令牌桶的初始令牌数量为100个,意味着一开始桶里有100个令牌可供请求获取,用于控制请求速率。 + // RateLimiter是基于令牌桶算法实现的一个限流器,通过控制令牌的发放和获取来限制请求的频率。 + private static final RateLimiter RATE_LIMITER = RateLimiter.create(100); + /** + * 定义过滤器的类型,返回PRE_TYPE,表示这是一个前置过滤器,会在请求路由之前执行。 + * 前置过滤器适合用于在请求到达目标服务之前进行一些预处理操作,在这里就是进行限流相关的判断,决定请求是否能够继续向后端服务发送。 + * @return 返回表示过滤器类型的字符串,固定为PRE_TYPE,表示前置过滤器。 + */ @Override public String filterType() { return PRE_TYPE; } + /** + * 定义过滤器的执行顺序,通过返回一个整数值来指定其在同类型(这里是前置过滤器)过滤器中的执行先后顺序。 + * 返回的值是在预定义的SERVLET_DETECTION_FILTER_ORDER基础上减1,表示此过滤器在相关的前置过滤器序列中相对靠前执行,以便尽早进行限流判断,避免不必要的后续处理。 + * @return 返回过滤器的执行顺序值。 + */ @Override public int filterOrder() { return SERVLET_DETECTION_FILTER_ORDER - 1; } + /** + * 判断当前过滤器是否需要对请求进行过滤处理,这里直接返回true,表示对所有请求都要进行限流相关的检查, + * 即每一个经过Zuul网关的请求都会进入到这个过滤器的run方法中进行是否获取到令牌的判断,来决定请求是否能继续路由。 + * @return 返回布尔值,true表示需要进行过滤处理(进行限流判断等),false表示直接放过当前请求,不进行后续过滤操作,这里始终返回true。 + */ @Override public boolean shouldFilter() { return true; } + /** + * 过滤器的核心执行逻辑方法,当shouldFilter方法返回true时,此方法会被执行,用于进行实际的限流操作判断。 + * 首先获取当前请求的上下文以及对应的HttpServletRequest对象,然后尝试从令牌桶(通过RATE_LIMITER对象表示)中获取一个令牌, + * 如果能够获取到令牌(即RATE_LIMITER.tryAcquire()返回true),说明当前请求在速率限制范围内,请求可以继续向后端服务路由; + * 如果没有获取到令牌(RATE_LIMITER.tryAcquire()返回false),则表示请求过于频繁,超过了限流设置,此时可以进行相应的错误处理, + * 这里简单地在请求上下文中设置了一些错误相关的信息(如状态码和错误消息),用于后续可能的处理(比如返回给前端相应的提示信息等),不过当前代码并没有完整地将错误信息返回给前端的逻辑,可能需要进一步完善。 + * @return 返回null,表示如果请求通过限流判断(获取到令牌),则正常进行后续路由等操作,不需要额外返回特定的对象;如果未通过限流判断(没获取到令牌),当前代码只是设置了错误信息,也返回null。 + * @throws ZuulException 可抛出此异常用于处理Zuul网关相关的异常情况,当前代码实现中暂未抛出此异常,只是进行了简单的错误信息设置。 + */ @Override public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); - if(!RATE_LIMITER.tryAcquire()){ - //没有取到一个令牌的话,可以这样返回信息给前端 - context.set("状态码",401); - context.set("error.message","用户没有获取到令牌"); + if (!RATE_LIMITER.tryAcquire()) { + // 没有取到一个令牌的话,可以这样返回信息给前端(当前只是简单设置了上下文信息,还需完善后续返回给前端的完整逻辑) + context.set("状态码", 401); + context.set("error.message", "用户没有获取到令牌"); } return null; } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ResponseEnum.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ResponseEnum.java index 428850a..76e74df 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ResponseEnum.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ResponseEnum.java @@ -6,20 +6,39 @@ import lombok.Getter; * @Author swg. * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com - * @DESC 基本的返回状态描述 + * @DESC 这个枚举类(ResponseEnum)主要用于定义应用程序中基本的返回状态描述,将不同的状态码及其对应的描述信息进行统一管理。 + * 通过使用枚举的方式,可以方便地在整个代码中引用这些预定义的状态,增强代码的可读性和可维护性,避免了硬编码状态码和描述信息的情况。 + * 同时使用了Lombok的@Getter注解,自动生成了获取code和desc属性的Getter方法,方便在其他地方获取枚举实例中定义的状态码和描述信息。 */ @Getter public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + // 表示操作成功的状态,状态码为0,对应的描述信息为"SUCCESS",通常用于在业务逻辑处理成功后返回给客户端等场景,表示请求顺利完成。 + SUCCESS(0, "SUCCESS"), + + // 表示通用的错误状态,状态码为1,描述信息为"ERROR",可以用于各种未明确细分的错误情况,作为一个笼统的错误标识返回给客户端等。 + ERROR(1, "ERROR"), + + // 表示请求参数不合法的状态,状态码为2,描述信息为"ILLEGAL_ARGUMENTS",常用于客户端发送的请求参数不符合要求、格式错误等场景,方便进行参数校验相关的错误提示。 + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + + // 表示需要用户登录的状态,状态码为10,描述信息为"NEED_LOGIN",一般用于当用户未提供有效登录信息却访问需要认证的资源时,提示客户端用户需要先登录才能继续操作。 + NEED_LOGIN(10, "NEED_LOGIN"); + + // 用于存储每个枚举实例对应的状态码,不同的枚举值有不同的状态码,用于区分不同的返回情况。 private int code; + + // 用于存储每个枚举实例对应的状态描述信息,直观地展示该状态所代表的含义,方便客户端等使用者理解具体的情况。 private String desc; - ResponseEnum(int code,String desc){ + /** + * 枚举的构造方法,用于初始化每个枚举实例的状态码和描述信息。 + * 当定义每个枚举值(如SUCCESS、ERROR等)时,会调用这个构造方法来传入对应的状态码和描述信息进行实例化。 + * @param code 状态码,用于唯一标识该种返回状态,在业务逻辑中可以根据这个状态码进行不同的处理。 + * @param desc 状态描述信息,用于向客户端等说明当前返回状态所代表的具体含义。 + */ + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ServerResponse.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ServerResponse.java index 2dd46a2..41b65de 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ServerResponse.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/resp/ServerResponse.java @@ -10,66 +10,156 @@ import java.io.Serializable; * @Author swg. * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com - * @DESC 作为本项目的通用的返回封装类 + * @DESC 这个类(ServerResponse)作为本项目的通用的返回封装类,用于对服务端返回给客户端的数据进行统一的格式封装。 + * 它可以包含响应的状态码、提示消息以及具体的业务数据等信息,方便客户端清晰地解析和处理服务端返回的结果,同时提高了整个项目返回数据格式的规范性和一致性。 + * 通过实现Serializable接口,使得该类的对象可以被序列化,便于在网络传输、缓存存储等场景下进行操作,确保数据能够正确地传递和保存。 + * 并且使用了Lombok的@Getter注解自动生成获取属性的Getter方法,以及使用了Jackson相关的注解来控制JSON序列化时的行为,例如排除值为null的字段等。 */ @Getter @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) public class ServerResponse implements Serializable { + + // 用于存储响应的状态码,通过不同的状态码来告知客户端请求的处理结果情况,例如成功、失败以及具体的错误类型等。 private int status; + + // 用于存储响应的提示消息,是对状态码含义的一种文字描述,方便客户端直观地了解请求处理的相关情况,比如操作成功的提示或者错误原因说明等。 private String msg; + + // 用于存储具体的业务数据,其类型是泛型参数T,意味着可以根据不同的业务场景返回不同类型的数据,比如可以是一个用户对象、商品列表等具体的业务相关实体数据。 private T data; - private ServerResponse(int status){ + /** + * 私有构造方法之一,只接收状态码作为参数,用于创建一个ServerResponse实例,通常在已知状态码且不需要额外设置提示消息和业务数据时使用, + * 比如在一些内部逻辑中根据固定的状态码来构造返回对象,后续可能会根据需要再进一步完善其他属性。 + * @param status 响应的状态码,用于标识请求处理的结果情况。 + */ + private ServerResponse(int status) { this.status = status; } - private ServerResponse(int status,String msg){ + + /** + * 私有构造方法之二,接收状态码和提示消息作为参数,用于创建一个包含特定状态码和对应提示消息的ServerResponse实例, + * 在需要明确告知客户端某个状态以及对应的详细说明时使用,例如返回错误状态并附上具体的错误原因描述等情况。 + * @param status 响应的状态码,用于标识请求处理的结果情况。 + * @param msg 响应的提示消息,用于详细说明状态码所代表的含义。 + */ + private ServerResponse(int status, String msg) { this.status = status; this.msg = msg; } - private ServerResponse(int status,T data){ + + /** + * 私有构造方法之三,接收状态码和业务数据作为参数,用于创建一个包含特定状态码以及对应业务数据的ServerResponse实例, + * 常用于请求处理成功并且有具体数据需要返回给客户端的场景,比如查询操作成功后返回查询到的结果数据等情况。 + * @param status 响应的状态码,用于标识请求处理的结果情况。 + * @param data 具体的业务数据,类型为泛型参数T,根据不同业务场景可以是不同类型的数据。 + */ + private ServerResponse(int status, T data) { this.status = status; this.data = data; } - private ServerResponse(int status,String msg,T data){ + + /** + * 私有构造方法之四,接收状态码、提示消息和业务数据作为参数,用于创建一个完整包含状态码、提示消息以及业务数据的ServerResponse实例, + * 这种情况可以更全面地向客户端传达请求处理的结果,既包含了处理状态、详细说明又有具体的数据内容,适用于多种复杂的业务返回场景。 + * @param status 响应的状态码,用于标识请求处理的结果情况。 + * @param msg 响应的提示消息,用于详细说明状态码所代表的含义。 + * @param data 具体的业务数据,类型为泛型参数T,根据不同业务场景可以是不同类型的数据。 + */ + private ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + /** + * 使用@JsonIgnore注解标记此方法,意味着在进行JSON序列化时,该方法不会被包含在序列化结果中。 + * 这个方法用于判断当前ServerResponse实例所表示的响应是否为成功状态,通过比较实例中的状态码与ResponseEnum中定义的成功状态码(SUCCESS的状态码)是否相等来判断。 + * @return 返回布尔值,true表示响应是成功状态,false表示响应是失败或者其他非成功的状态。 + */ @JsonIgnore - public boolean isSuccess(){ + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } /** - * 成功的方法 + * 以下是一系列静态方法,用于方便地创建表示成功状态的ServerResponse实例,根据不同的业务需求提供了多种创建方式,增强了灵活性。 + + * 创建一个表示成功状态的ServerResponse实例,使用ResponseEnum中定义的默认成功状态码和对应的描述信息进行实例化, + * 适用于简单告知客户端操作成功,不需要额外传递具体消息和业务数据的场景。 + * @param 泛型参数,表示可以返回不同类型的业务数据,这里创建实例时暂未指定具体数据类型。 + * @return 返回一个表示成功状态的ServerResponse实例,包含默认的成功状态码和描述信息。 */ - public static ServerResponse createBySuccess(){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); - } - public static ServerResponse createBySuccessMessage(String message){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); - } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); - } - public static ServerResponse createBySuccess(String message,T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); + public static ServerResponse createBySuccess() { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc()); } /** - * 失败的方法 + * 创建一个表示成功状态的ServerResponse实例,使用ResponseEnum中定义的默认成功状态码,并传入自定义的提示消息进行实例化, + * 适用于操作成功但需要向客户端传递一些额外的说明信息的场景,比如操作成功后的一些提示语等。 + * @param 泛型参数,表示可以返回不同类型的业务数据,这里创建实例时暂未指定具体数据类型。 + * @param message 自定义的提示消息,用于向客户端传达更详细的成功相关信息。 + * @return 返回一个表示成功状态的ServerResponse实例,包含默认的成功状态码和传入的提示消息。 */ - public static ServerResponse createByError(){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); + public static ServerResponse createBySuccessMessage(String message) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createByErrorMessage(String msg){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); + + /** + * 创建一个表示成功状态的ServerResponse实例,使用ResponseEnum中定义的默认成功状态码,并传入具体的业务数据进行实例化, + * 适用于操作成功且有具体业务数据需要返回给客户端的场景,比如查询操作成功后返回查询到的数据等情况。 + * @param 泛型参数,表示可以返回不同类型的业务数据,这里创建实例时指定了具体的数据类型,由传入的参数data决定。 + * @param data 具体的业务数据,类型为泛型参数T,根据不同业务场景可以是不同类型的数据。 + * @return 返回一个表示成功状态的ServerResponse实例,包含默认的成功状态码和传入的具体业务数据。 + */ + public static ServerResponse createBySuccess(T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data); } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); + + /** + * 创建一个表示成功状态的ServerResponse实例,使用ResponseEnum中定义的默认成功状态码,并传入自定义的提示消息和具体的业务数据进行实例化, + * 适用于操作成功且既有需要向客户端传递的额外说明信息,又有具体业务数据需要返回的复杂场景,比如查询操作成功后返回查询到的数据以及一些相关的提示等情况。 + * @param 泛型参数,表示可以返回不同类型的业务数据,这里创建实例时指定了具体的数据类型,由传入的参数data决定。 + * @param message 自定义的提示消息,用于向客户端传达更详细的成功相关信息。 + * @param data 具体的业务数据,类型为泛型参数T,根据不同业务场景可以是不同类型的数据。 + * @return 返回一个表示成功状态的ServerResponse实例,包含默认的成功状态码、传入的提示消息和具体业务数据。 + */ + public static ServerResponse createBySuccess(String message, T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } + /** + * 以下是一系列静态方法,用于方便地创建表示失败状态的ServerResponse实例,同样根据不同的业务需求提供了多种创建方式,便于在不同的错误场景下返回合适的错误信息。 + + * 创建一个表示通用失败状态的ServerResponse实例,使用ResponseEnum中定义的默认失败状态码和对应的描述信息进行实例化, + * 适用于一些未明确细分的错误情况,作为一个笼统的错误返回给客户端,告知客户端请求处理出现了问题。 + * @param 泛型参数,表示可以返回不同类型的业务数据,这里创建实例时暂未指定具体数据类型。 + * @return 返回一个表示失败状态的ServerResponse实例,包含默认的失败状态码和描述信息。 + */ + public static ServerResponse createByError() { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); + } + /** + * 创建一个表示失败状态的ServerResponse实例,使用ResponseEnum中定义的默认失败状态码,并传入自定义的提示消息进行实例化, + * 适用于已知具体错误原因,需要向客户端传递详细错误信息的场景,通过传入的msg参数告知客户端出现错误的具体情况。 + * @param 泛型参数,表示可以返回不同类型的业务数据,这里创建实例时暂未指定具体数据类型。 + * @param msg 自定义的提示消息,用于向客户端传达具体的错误原因等相关信息。 + * @return 返回一个表示失败状态的ServerResponse实例,包含默认的失败状态码和传入的提示消息。 + */ + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } -} + /** + * 创建一个表示失败状态的ServerResponse实例,传入自定义的状态码和提示消息进行实例化, + * 适用于需要根据不同的业务逻辑返回特定的错误状态码以及对应的详细错误信息的场景,通过传入的code和msg参数灵活地构建错误返回对象。 + * @param 泛型参数,表示可以返回不同类型的业务数据,这里创建实例时暂未指定具体数据类型。 + * @param code 自定义的状态码,用于标识具体的错误情况,根据业务逻辑可以设置不同的值。 + * @param msg 自定义的提示消息,用于向客户端传达具体的错误原因等相关信息。 + * @return 返回一个表示失败状态的ServerResponse实例,包含传入的状态码和提示消息。 + */ + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + return new ServerResponse<>(code, msg); + } +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/CookieUtil.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/CookieUtil.java index ffab240..b891260 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/CookieUtil.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/CookieUtil.java @@ -8,67 +8,109 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * cookie读写 + * @Author 作者信息 + * @Date 创建日期 + * @CONTACT 联系方式 + * @DESC 这个类(CookieUtil)主要用于处理与Cookie相关的操作,包括向客户端写入登录相关的Cookie、从客户端请求中读取登录的Cookie以及在注销时删除相应的Cookie等功能。 + * 它通过使用一些固定的域名、Cookie名称等配置信息,来统一管理项目中登录相关的Cookie操作,同时借助相关的设置来保障Cookie的安全性以及有效性等,并且使用了@Slf4j注解来记录操作过程中的相关日志信息,便于跟踪和排查问题。 */ @Slf4j public class CookieUtil { + + // 定义Cookie的域名,用于指定该Cookie在哪个域名下有效,这里固定设置为"oursnail.cn",意味着只有访问该域名下的页面时,对应的Cookie才会被携带和使用。 private final static String COOKIE_DOMAIN = "oursnail.cn"; - private final static String COOKIE_NAME = "snailmall_login_token"; + // 定义Cookie的名称,用于标识登录相关的Cookie,在整个项目中通过这个固定的名称来获取和操作对应的Cookie,这里设置为"snailmall_login_token"。 + private final static String COOKIE_NAME = "snailmall_login_token"; /** - * 登陆的时候写入cookie - * @param response - * @param token + * 此方法用于在用户登录的时候向客户端(浏览器)写入登录相关的Cookie。 + * 通过创建一个新的Cookie对象,设置其域名、路径、是否仅HTTP访问以及有效期等属性,然后将该Cookie添加到响应对象中,使得浏览器能够接收到并保存这个Cookie信息。 + * @param response HttpServletResponse对象,用于向客户端发送响应,通过它来添加要写入的Cookie信息。 + * @param token 要写入Cookie的具体值,通常是代表用户登录认证的令牌等相关信息,用于后续识别用户身份等操作。 */ - public static void writeLoginToken(HttpServletResponse response,String token){ - Cookie ck = new Cookie(COOKIE_NAME,token); + public static void writeLoginToken(HttpServletResponse response, String token) { + // 创建一个新的Cookie对象,名称为固定的COOKIE_NAME,值为传入的token参数 + Cookie ck = new Cookie(COOKIE_NAME, token); + + // 设置Cookie的域名,使其在指定的域名(COOKIE_DOMAIN)下有效,确保只有访问该域名的页面时,浏览器才会发送这个Cookie到服务器端。 ck.setDomain(COOKIE_DOMAIN); - ck.setPath("/");//设值在根目录 - ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击 - ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒,maxage不设置的话,cookie就不会写入硬盘,只会写在内存,只在当前页面有效 - log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); + + // 设置Cookie的路径为根目录("/"),意味着该Cookie在该域名下的所有页面路径都会被携带,方便在整个网站范围内都能使用这个Cookie进行相关操作。 + ck.setPath("/"); + + // 设置Cookie为HttpOnly属性为true,这样可以防止通过JavaScript等脚本语言来访问该Cookie,避免了可能的脚本攻击风险,提高了Cookie的安全性。 + ck.setHttpOnly(true); + + // 设置Cookie的最大存活时间,这里设置为一年(以秒为单位进行换算,60秒 * 60分钟 * 24小时 * 365天),表示该Cookie在客户端浏览器上保存的时长,超过这个时间后浏览器会自动删除该Cookie。 + // 如果设置为-1则表示永久有效,若不设置这个属性(maxAge不设置),Cookie就不会写入硬盘,只会保存在内存中,仅在当前页面会话有效,关闭页面后就会失效。 + ck.setMaxAge(60 * 60 * 24 * 365); + + // 记录写入的Cookie的名称和值的日志信息,方便在调试或者查看操作记录时了解具体情况。 + log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue()); + + // 将设置好的Cookie添加到响应对象中,这样浏览器在接收到响应时,就会保存这个Cookie信息。 response.addCookie(ck); } /** - * 读取登陆的cookie - * @param request - * @return + * 此方法用于从客户端发送的HTTP请求中读取登录相关的Cookie信息。 + * 它首先获取请求中的所有Cookie数组,然后遍历该数组,通过比较Cookie的名称是否与预定义的登录Cookie名称(COOKIE_NAME)相等,来找到对应的登录Cookie,并返回其值,如果没有找到则返回null。 + * @param request HttpServletRequest对象,用于获取客户端发送的请求信息,从中读取Cookie相关内容。 + * @return 返回找到的登录Cookie的值,如果不存在则返回null,该值通常是之前写入的代表用户登录认证的令牌等相关信息。 */ - public static String readLoginToken(HttpServletRequest request){ + public static String readLoginToken(HttpServletRequest request) { + // 获取请求中的所有Cookie数组,如果没有Cookie则返回null Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks){ - log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ - log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + + if (cks!= null) { + for (Cookie ck : cks) { + // 记录每个Cookie的名称和值的日志信息,方便查看请求中携带的Cookie情况 + log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + + // 通过比较Cookie的名称与预定义的登录Cookie名称(COOKIE_NAME)是否相等,来判断是否为我们要找的登录Cookie + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + // 如果找到对应的登录Cookie,记录其名称和值的日志信息,并返回其值 + log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); return ck.getValue(); } } } + return null; } /** - * 注销的时候进行删除 - * @param request - * @param response + * 此方法用于在用户注销操作时,从客户端删除对应的登录Cookie信息。 + * 它先获取请求中的所有Cookie数组,然后遍历查找名称与预定义登录Cookie名称(COOKIE_NAME)相同的Cookie,找到后通过设置其最大存活时间为0(表示立即删除),并将修改后的Cookie添加回响应对象中,从而实现删除该Cookie的目的。 + * @param request HttpServletRequest对象,用于获取客户端发送的请求信息,从中查找要删除的Cookie。 + * @param response HttpServletResponse对象,用于向客户端发送响应,通过它将修改后的Cookie(设置为删除状态)发送回客户端,使得客户端浏览器能够删除对应的Cookie。 */ - public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ + public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) { + // 获取请求中的所有Cookie数组,如果没有Cookie则返回null Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks) { - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ + + if (cks!= null) { + for (Cookie ck : cks) { + // 通过比较Cookie的名称与预定义的登录Cookie名称(COOKIE_NAME)是否相等,来判断是否为我们要删除的登录Cookie + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + // 设置要删除的Cookie的域名,确保与之前设置的域名一致,以便准确删除对应的Cookie ck.setDomain(COOKIE_DOMAIN); + + // 设置Cookie的路径为根目录("/"),与之前写入时的路径设置保持一致,确保删除的是对应的那个Cookie ck.setPath("/"); - ck.setMaxAge(0);//0表示消除此cookie - log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + + // 设置Cookie的最大存活时间为0,表示让浏览器立即删除这个Cookie + ck.setMaxAge(0); + + // 记录要删除的Cookie的名称和值的日志信息,方便查看操作情况 + log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + + // 将设置好要删除状态的Cookie添加到响应对象中,浏览器接收到这个响应后,就会删除对应的Cookie response.addCookie(ck); return; } } } } - -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/DateTimeUtil.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/DateTimeUtil.java index 7b8fed0..0e6d467 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/DateTimeUtil.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/DateTimeUtil.java @@ -10,59 +10,99 @@ import java.text.SimpleDateFormat; import java.util.Date; /** - * @DESC 时间转换的工具类 + * @DESC 这个类(DateTimeUtil)是一个时间转换的工具类,主要用于在字符串、日期对象(Date)以及时间戳之间进行相互转换操作,方便在不同的业务场景下对时间相关的数据进行处理和格式化展示等。 + * 它借助了Joda-Time库来实现更灵活便捷的日期时间操作,同时也使用了Java标准库中的相关类(如SimpleDateFormat等)来完成部分功能,并且定义了一些常用的时间格式相关的方法和常量。 */ public class DateTimeUtil { - //joda-time - //str->Date - //Date->str + // joda-time + // 以下定义了一个常量,表示时间格式化的标准格式,用于在没有指定特定格式时,按照此通用格式进行日期时间的字符串与Date对象之间的转换操作,格式为"yyyy-MM-dd HH:mm:ss",符合常见的日期时间展示格式要求。 + // str->Date + // Date->str public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; - - - public static Date strToDate(String dateTimeStr, String formatStr){ + /** + * 将指定格式的日期时间字符串转换为Date对象的方法。 + * 首先根据传入的格式字符串(formatStr)创建一个DateTimeFormatter对象,用于解析日期时间字符串,然后使用该格式化器解析传入的日期时间字符串(dateTimeStr)得到一个DateTime对象,最后将DateTime对象转换为Java标准的Date对象并返回。 + * @param dateTimeStr 要转换的日期时间字符串,其格式需要与传入的formatStr参数指定的格式相匹配。 + * @param formatStr 用于解析日期时间字符串的格式字符串,例如"yyyy-MM-dd HH:mm:ss"等,指定了日期时间各部分的排列和表示方式。 + * @return 返回解析后的Date对象,如果解析过程出现问题则可能抛出异常(由Joda-Time库内部处理相关异常情况)。 + */ + public static Date strToDate(String dateTimeStr, String formatStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); return dateTime.toDate(); } - public static String dateToStr(Date date,String formatStr){ - if(date == null){ + /** + * 将Date对象转换为指定格式的日期时间字符串的方法。 + * 如果传入的Date对象为null,则返回一个空字符串。否则,先根据传入的Date对象创建一个DateTime对象,然后按照指定的格式字符串(formatStr)将其转换为对应的日期时间字符串并返回。 + * @param date 要转换的Date对象,如果为null则返回空字符串。 + * @param formatStr 用于将Date对象转换为字符串时的格式字符串,决定了生成的日期时间字符串的格式表现形式。 + * @return 返回转换后的日期时间字符串,如果传入的Date对象为null则返回空字符串,否则按照指定格式返回对应的字符串表示。 + */ + public static String dateToStr(Date date, String formatStr) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); return dateTime.toString(formatStr); } - //固定好格式 - public static Date strToDate(String dateTimeStr){ + /** + * 将符合标准格式("yyyy-MM-dd HH:mm:ss")的日期时间字符串转换为Date对象的方法。 + * 内部先基于预定义的标准格式(STANDARD_FORMAT)创建一个DateTimeFormatter对象,然后使用该格式化器解析传入的日期时间字符串得到一个DateTime对象,最后将其转换为Date对象并返回。 + * 此方法适用于已知日期时间字符串符合标准格式的情况,无需额外传入格式字符串参数,方便快捷地进行转换操作。 + * @param dateTimeStr 要转换的日期时间字符串,其格式需要符合预定义的标准格式("yyyy-MM-dd HH:mm:ss")。 + * @return 返回解析后的Date对象,如果解析过程出现问题则可能抛出异常(由Joda-Time库内部处理相关异常情况)。 + */ + public static Date strToDate(String dateTimeStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); return dateTime.toDate(); } - public static String dateToStr(Date date){ - if(date == null){ + /** + * 将Date对象转换为符合标准格式("yyyy-MM-dd HH:mm:ss")的日期时间字符串的方法。 + * 如果传入的Date对象为null,则返回一个空字符串。否则,先根据传入的Date对象创建一个DateTime对象,然后按照标准格式(STANDARD_FORMAT)将其转换为对应的日期时间字符串并返回。 + * 此方法适用于需要将Date对象按照通用的标准格式转换为字符串展示的场景,方便统一日期时间的字符串表示形式。 + * @param date 要转换的Date对象,如果为null则返回空字符串。 + * @return 返回转换后的日期时间字符串,如果传入的Date对象为null则返回空字符串,否则按照标准格式返回对应的字符串表示。 + */ + public static String dateToStr(Date date) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); return dateTime.toString(STANDARD_FORMAT); } - //Date -> 时间戳 + /** + * 将Date对象转换为时间戳(从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数)的方法。 + * 如果传入的Date对象为null,则返回null。否则,先创建一个SimpleDateFormat对象,按照标准格式("yyyy-MM-dd HH:mm:ss")来格式化Date对象,然后将其解析为一个新的Date对象,最后获取该Date对象对应的时间戳(以毫秒为单位)并返回。 + * 此方法在需要将日期时间转换为时间戳用于存储、比较或者其他与时间戳相关的业务操作场景下使用。 + * @param date 要转换的Date对象,如果为null则返回null。 + * @return 返回对应的时间戳(以毫秒为单位),如果传入的Date对象为null则返回null,否则返回从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数。 + * @throws ParseException 如果在使用SimpleDateFormat解析日期时间字符串过程中出现格式不匹配等解析错误,则会抛出此异常,需要调用者进行相应的异常处理。 + */ public static Long dateToChuo(Date date) throws ParseException { - if(date == null){ + if (date == null) { return null; } - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.parse(String.valueOf(date)).getTime(); } + /** + * 主函数,用于简单的测试功能,在这个方法中,创建了一个SimpleDateFormat对象,按照标准格式解析一个给定的日期时间字符串为Date对象,然后输出该Date对象对应的时间戳(以毫秒为单位)。 + * 可以通过运行这个main方法来验证dateToChuo等相关时间转换方法的部分功能是否正确,不过这只是一个简单的示例测试,实际应用中可能需要更完善的单元测试等方式来全面测试工具类的功能。 + * @param args 命令行参数,这里在示例中未使用。 + * @throws ParseException 如果在使用SimpleDateFormat解析日期时间字符串过程中出现格式不匹配等解析错误,则会抛出此异常,需要在调用main方法时进行相应的异常处理。 + */ public static void main(String[] args) throws ParseException { - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); - String time="1970-01-06 11:45:55"; + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String time = "1970-01-06 11:45:55"; Date date = format.parse(time); - System.out.print("Format To times:"+date.getTime()); + System.out.print("Format To times:" + date.getTime()); } -} +} \ No newline at end of file diff --git a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/JsonUtil.java b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/JsonUtil.java index 135096d..ac68a35 100644 --- a/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/JsonUtil.java +++ b/snailmall-api-gateway/src/main/java/com/njupt/swg/utils/JsonUtil.java @@ -13,114 +13,130 @@ import java.io.IOException; import java.text.SimpleDateFormat; /** - * jackson的序列化和反序列化 + * @Author 作者信息(如果有) + * @Date 创建日期(如果有) + * @CONTACT 联系方式(如果有) + * @DESC 这个类(JsonUtil)主要用于处理Jackson库相关的序列化和反序列化操作,是对Jackson功能的一个简单封装,方便在项目中统一地将Java对象转换为JSON字符串(序列化)以及将JSON字符串转换为Java对象(反序列化)。 + * 它通过配置ObjectMapper对象的一些属性来定制序列化和反序列化的行为,例如控制哪些字段参与转换、时间格式的处理以及如何处理一些可能出现的转换错误等情况,并且使用了@Slf4j注解来记录在操作过程中出现的相关警告等日志信息。 */ @Slf4j public class JsonUtil { + + // 创建一个ObjectMapper对象,它是Jackson库中用于进行JSON序列化和反序列化的核心类,后续通过配置它的各种属性来实现特定的序列化和反序列化规则。 private static ObjectMapper objectMapper = new ObjectMapper(); + // 静态代码块,在类加载时执行,用于对ObjectMapper对象进行一系列的配置操作,以定制其序列化和反序列化的行为。 static { - //所有字段都列入进行转换 + // 设置序列化时包含的字段规则,这里设置为JsonSerialize.Inclusion.ALWAYS,表示所有字段都列入进行转换,即无论字段值是否为null,都会包含在序列化生成的JSON字符串中。 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); - //取消默认转换timestamp形式 - objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); - //忽略空bean转json的错误 - objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); - //统一时间的格式 + + // 取消默认将日期类型转换为时间戳(timestamp)形式的行为,这样在序列化日期对象时,会按照后续配置的日期格式进行转换,而不是转换为时间戳格式。 + objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); + + // 忽略在序列化空的Java Bean(即没有属性值的对象)转JSON字符串时可能出现的错误,使得即使对象为空,也能尝试进行序列化操作,避免抛出异常。 + objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false); + + // 统一设置时间的格式,通过传入一个SimpleDateFormat对象,并指定格式为DateTimeUtil类中定义的标准格式("yyyy-MM-dd HH:mm:ss"),使得在序列化和反序列化日期对象时,都按照这个标准格式进行处理。 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); - //忽略json存在属性,但是java对象不存在属性的错误 - objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); + + // 忽略在反序列化JSON字符串时,如果JSON中存在的属性但对应的Java对象不存在该属性的这种错误情况,使得即使JSON数据和Java对象结构不完全匹配,也能尽量进行反序列化操作,避免因属性不匹配而抛出异常。 + objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** - * 序列化方法,将对象转为字符串 - * @param obj - * @param - * @return + * 序列化方法,用于将Java对象转换为JSON字符串。 + * 如果传入的对象为null,则直接返回null。如果对象本身就是String类型,则直接返回该字符串(因为本身已经是符合要求的字符串形式了),否则使用ObjectMapper对象将对象转换为JSON字符串,若转换过程中出现IO异常,则记录警告日志并返回null。 + * @param obj 要进行序列化的Java对象,其类型可以是任意Java类,只要该类能被Jackson库正确序列化(即符合Jackson序列化的相关规则,比如有合适的Getter、Setter方法等)。 + * @param 泛型参数,表示传入对象的类型,这里主要用于方法定义的通用性,使得可以处理各种类型的对象序列化。 + * @return 返回序列化后的JSON字符串,如果传入对象为null或者序列化过程出现异常则返回null。 */ - public static String obj2String(T obj){ - if(obj == null){ + public static String obj2String(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); + return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** - * 序列化方法,同上,只是输出的格式是美化的,便于测试 - * @param obj - * @param - * @return + * 序列化方法,功能与obj2String方法类似,也是将Java对象转换为JSON字符串,不过这个方法输出的JSON字符串格式是美化后的,更便于查看和测试(例如会进行缩进、换行等格式化处理)。 + * 如果传入的对象为null,则直接返回null。如果对象本身就是String类型,则直接返回该字符串,否则使用ObjectMapper对象的美化打印功能将对象转换为格式化后的JSON字符串,若转换过程中出现IO异常,则记录警告日志并返回null。 + * @param obj 要进行序列化的Java对象,其类型可以是任意Java类,只要该类能被Jackson库正确序列化。 + * @param 泛型参数,表示传入对象的类型,用于方法定义的通用性。 + * @return 返回序列化后的、经过美化的JSON字符串,如果传入对象为null或者序列化过程出现异常则返回null。 */ - public static String obj2StringPretty(T obj){ - if(obj == null){ + public static String obj2StringPretty(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** - * 比较简单的反序列化的方法,将字符串转为单个对象 - * @param str - * @param clazz - * @param - * @return + * 比较简单的反序列化方法,用于将JSON字符串转换为单个Java对象。 + * 如果传入的JSON字符串为空或者要转换的目标Java类(clazz)为null,则直接返回null。如果目标类是String类型,且传入的字符串不为空,则直接将该字符串作为结果返回(因为本身就是符合要求的字符串形式了),否则使用ObjectMapper对象将JSON字符串转换为指定类型的Java对象,若转换过程中出现IO异常,则记录警告日志并返回null。 + * @param str 要进行反序列化的JSON字符串,需要符合Jackson库反序列化的格式要求,即与对应的Java对象结构能够匹配(根据配置可能允许一定的属性差异情况)。 + * @param clazz 要转换生成的目标Java对象的类型,通过传入的Class对象来指定具体的类型,以便ObjectMapper能正确地将JSON数据转换为对应的Java对象实例。 + * @param 泛型参数,表示目标Java对象的类型,用于方法定义的通用性,使得可以处理各种类型对象的反序列化。 + * @return 返回反序列化后的Java对象,如果传入的JSON字符串为空、目标类为null或者转换过程出现异常则返回null。 */ - public static T String2Obj(String str,Class clazz){ - if(StringUtils.isEmpty(str) || clazz == null){ + public static T String2Obj(String str, Class clazz) { + if (StringUtils.isEmpty(str) || clazz == null) { return null; } try { - return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz); + return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** - * 复杂对象的反序列化(通用) - * @param str - * @param typeReference - * @param - * @return + * 复杂对象的反序列化(通用)方法,用于处理更复杂类型的JSON字符串到Java对象的转换,通过TypeReference来指定复杂的类型信息。 + * 如果传入的JSON字符串为空或者TypeReference对象为null,则直接返回null。如果TypeReference表示的类型是String类型,且传入的字符串不为空,则直接将该字符串作为结果返回,否则使用ObjectMapper对象根据TypeReference指定的复杂类型信息将JSON字符串转换为对应的Java对象,若转换过程中出现IO异常,则记录警告日志并返回null。 + * @param str 要进行反序列化的JSON字符串,需要符合对应的复杂类型结构要求以及Jackson库反序列化的相关规则。 + * @param typeReference TypeReference对象,用于指定复杂的Java对象类型信息,例如可以是包含泛型的集合类型、嵌套的复杂对象类型等,使得ObjectMapper能准确地进行反序列化操作。 + * @param 泛型参数,表示要转换生成的复杂Java对象的类型,用于方法定义的通用性。 + * @return 返回反序列化后的复杂Java对象,如果传入的JSON字符串为空、TypeReference对象为null或者转换过程出现异常则返回null。 */ - public static T Str2Obj(String str, TypeReference typeReference){ - if(StringUtils.isEmpty(str) || typeReference == null){ + public static T Str2Obj(String str, TypeReference typeReference) { + if (StringUtils.isEmpty(str) || typeReference == null) { return null; } try { - return (T) (typeReference.getType().equals(String.class)?str:objectMapper.readValue(str,typeReference)); + return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference)); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** - * 第二种方式实现复杂对象的反序列化 - * @param str - * @param collectionClass - * @param elementClasses - * @param - * @return + * 第二种方式实现复杂对象的反序列化方法,通过传入集合类(collectionClass)以及元素类(elementClasses)的Class对象来构建JavaType对象,进而指定复杂的类型结构,然后使用ObjectMapper将JSON字符串转换为对应的复杂Java对象。 + * 如果转换过程中出现IO异常,则记录警告日志并返回null。 + * @param str 要进行反序列化的JSON字符串,需要符合根据传入的类型信息构建的复杂类型结构以及Jackson库反序列化的相关规则。 + * @param collectionClass 表示集合类型的Class对象,例如List.class、Set.class等,用于指定要转换生成的复杂对象中最外层的集合类型。 + * @param elementClasses 可变参数,表示集合中元素的类型的Class对象,按照顺序依次指定集合中元素的类型,用于构建复杂的嵌套类型结构,例如对于List,第一个参数是List.class,第二个参数是String.class。 + * @param 泛型参数,表示要转换生成的复杂Java对象的类型,用于方法定义的通用性。 + * @return 返回反序列化后的复杂Java对象,如果转换过程中出现IO异常则返回null。 */ - public static T Str2Obj(String str,Class collectionClass,Class... elementClasses){ - JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); + public static T Str2Obj(String str, Class collectionClass, Class... elementClasses) { + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); try { - return objectMapper.readValue(str,javaType); + return objectMapper.readValue(str, javaType); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java b/snailmall-cart-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java index 66c6499..66a1fa8 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java @@ -12,22 +12,29 @@ import redis.clients.jedis.JedisPool; * @Date 2019/1/1 15:03 * @CONTACT 317758022@qq.com * @DESC + * 这个类(CommonCacheUtil)是一个工具类,用于对Redis缓存进行常见的操作,例如向缓存中存储数据、获取缓存中的数据、设置带有过期时间的数据以及删除缓存中的数据等操作。 + * 它依赖于JedisPoolWrapper类来获取JedisPool连接池对象,进而获取Jedis客户端来与Redis服务器进行交互,并且通过使用@Slf4j注解来记录在操作Redis过程中出现的错误等相关日志信息,同时在遇到异常情况时会抛出SnailmallException异常,方便在业务层进行统一的异常处理。 + * 整体作为Spring的一个组件(通过@Component注解),可以方便地在其他需要操作缓存的地方通过依赖注入来使用它。11 */ @Component @Slf4j public class CommonCacheUtil { + // 自动注入JedisPoolWrapper对象,用于获取JedisPool连接池,以此来建立与Redis服务器的连接并获取Jedis客户端实例,从而进行后续的缓存操作。 @Autowired private JedisPoolWrapper jedisPoolWrapper; - /** - * 缓存永久key + * 此方法用于向Redis缓存中存储一个永久有效的键值对(即没有设置过期时间)。 + * 首先通过JedisPoolWrapper获取JedisPool连接池,如果连接池不为空,则从连接池中获取一个Jedis客户端资源,然后选择Redis的第0个数据库(通过Jedis.select(0)操作,通常可以根据实际需求选择不同的数据库编号),最后使用Jedis客户端的set方法将指定的键(key)和值(value)存储到Redis中。 + * 如果在操作过程中出现任何异常,会记录错误日志,并抛出SnailmallException异常,向外传递Redis操作出现错误的信息。 + * @param key 要存储到Redis中的键,通常是一个唯一标识缓存数据的字符串。 + * @param value 要存储到Redis中对应键的值,类型为字符串,可以是各种序列化后的对象数据、文本信息等,只要符合Redis存储要求即可。 */ public void cache(String key, String value) { try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis Jedis = pool.getResource()) { Jedis.select(0); Jedis.set(key, value); @@ -40,13 +47,17 @@ public class CommonCacheUtil { } /** - * 获取缓存key + * 此方法用于从Redis缓存中获取指定键(key)对应的缓存值。 + * 先尝试通过JedisPoolWrapper获取JedisPool连接池,若连接池不为空,则从中获取Jedis客户端资源,接着选择Redis的第0个数据库,然后使用Jedis客户端的get方法获取指定键对应的缓存值,并将其赋值给本地变量value,最后返回该值。 + * 如果在操作过程中出现异常,会记录错误日志,并抛出SnailmallException异常,向外传递Redis操作出错的信息。 + * @param key 要从Redis中获取缓存值对应的键,需确保该键在Redis中已存在(否则将返回null),其类型为字符串。 + * @return 返回从Redis中获取到的对应键的缓存值,如果获取失败或者键不存在则返回null,返回值类型为字符串,需要根据业务情况进行相应的反序列化等后续处理(如果存储的是序列化后的对象等情况)。 */ public String getCacheValue(String key) { String value = null; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis Jedis = pool.getResource()) { Jedis.select(0); value = Jedis.get(key); @@ -60,13 +71,19 @@ public class CommonCacheUtil { } /** - * 过期key + * 此方法用于向Redis缓存中存储一个带有过期时间的键值对,并且只有在键不存在时才进行存储(通过setnx操作实现)。 + * 首先获取JedisPool连接池,若不为空则获取Jedis客户端资源,选择第0个数据库后,先使用setnx方法尝试设置键值对(如果键已经存在则设置失败,返回0;如果键不存在则设置成功,返回1),然后立即使用expire方法为该键设置指定的过期时间(以秒为单位),最后返回setnx操作的结果(即表示是否成功设置键值对)。 + * 如果在操作过程中出现异常,会记录错误日志,并抛出SnailmallException异常,向外传递Redis操作出现错误的信息。 + * @param key 要存储到Redis中的键,类型为字符串,需确保符合业务逻辑下的唯一性等要求(因为setnx基于键的唯一性判断是否设置成功)。 + * @param value 要存储到Redis中对应键的值,类型为字符串,同样可以是各种序列化后的对象数据等符合Redis存储要求的内容。 + * @param expire 要设置的过期时间,单位为秒,表示该键值对在Redis中有效的时长,超过这个时间后Redis会自动删除对应的键值对。 + * @return 返回setnx操作的结果,1表示设置键值对成功(即键原本不存在),0表示设置失败(键已存在)。 */ public long cacheNxExpire(String key, String value, int expire) { long result = 0; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); result = jedis.setnx(key, value); @@ -82,11 +99,13 @@ public class CommonCacheUtil { } /** - * 删除缓存key + * 此方法用于从Redis缓存中删除指定的键(key)对应的键值对。 + * 先获取JedisPool连接池,若不为空则获取Jedis客户端资源,选择第0个数据库后,使用Jedis客户端的del方法尝试删除指定的键值对,若删除过程中出现异常,会记录错误日志,并抛出SnailmallException异常,向外传递Redis操作出现错误的信息。 + * @param key 要从Redis中删除的键,需确保该键在Redis中已存在(否则删除操作无实际效果),其类型为字符串。. */ public void delKey(String key) { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); try { @@ -98,7 +117,4 @@ public class CommonCacheUtil { } } } - - - -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java b/snailmall-cart-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java index addfe24..1036fe2 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java @@ -12,31 +12,58 @@ import javax.annotation.PostConstruct; * @Author swg. * @Date 2019/1/1 15:00 * @CONTACT 317758022@qq.com - * @DESC 只做了单个redis,但是课程中实现的redis客户端集群,要掌握一致性hash算法 + * @DESC 这个类(JedisPoolWrapper)主要用于创建和管理JedisPool(Jedis连接池)对象,通过从配置参数中获取相关的Redis连接配置信息来初始化连接池。 + * 虽然当前代码只实现了针对单个Redis的连接池配置,但注释中提到课程中涉及了Redis客户端集群以及要掌握一致性Hash算法,意味着后续可能有扩展到集群相关配置的需求。 + * 它是一个Spring的组件(通过@Component注解),可以被自动扫描并注入到其他需要使用JedisPool的地方,并且使用了@Slf4j注解来记录在初始化连接池过程中的相关日志信息,方便查看初始化是否成功以及出现问题时进行排查。 */ @Component @Slf4j public class JedisPoolWrapper { + + // 自动注入Parameters对象,该对象应该包含了Redis相关的配置参数,例如Redis服务器的主机地址、端口号以及连接池的一些配置参数(最大连接数、最大空闲连接数、最大等待时间等),用于后续初始化JedisPool连接池。 @Autowired private Parameters parameters; + // 用于存储创建好的JedisPool对象,初始化为null,在init方法中会根据配置参数进行实例化,后续通过getJedisPool方法向外提供这个连接池对象,供其他代码获取并使用来与Redis服务器建立连接。 private JedisPool jedisPool = null; + /** + * @PostConstruct注解标记的方法会在对象实例化且依赖注入完成后自动执行,用于进行一些初始化的操作。 + * 在这里,此方法(init)的主要功能是根据从Parameters对象中获取的Redis配置参数来创建并初始化JedisPool连接池对象,具体步骤包括创建JedisPoolConfig对象并设置相应的连接池配置参数,然后基于这个配置对象以及Redis的主机地址、端口号等信息创建JedisPool对象。 + * 如果初始化过程出现异常,会记录错误日志信息,方便后续排查问题。 + */ @PostConstruct - public void init(){ + public void init() { try { + // 创建一个JedisPoolConfig对象,用于配置JedisPool连接池的各种属性,比如连接池的最大连接数、最大空闲连接数、最大等待时间等,以控制连接池的行为和性能。 JedisPoolConfig config = new JedisPoolConfig(); + + // 从Parameters对象中获取Redis连接池的最大连接数配置参数,并设置到JedisPoolConfig对象中,用于限制连接池中总共可以创建的最大连接数量。 config.setMaxTotal(parameters.getRedisMaxTotal()); + + // 从Parameters对象中获取Redis连接池的最大空闲连接数配置参数,并设置到JedisPoolConfig对象中,用于限制连接池中处于空闲状态的最大连接数量。 config.setMaxIdle(parameters.getRedisMaxIdle()); + + // 从Parameters对象中获取获取Redis连接时的最大等待时间(单位为毫秒)配置参数,并设置到JedisPoolConfig对象中,用于指定当连接池中的连接都被占用时,获取连接最多等待的时间,超过这个时间则会抛出异常。 config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis()); - jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx"); + + // 使用配置好的JedisPoolConfig对象,以及从Parameters对象中获取的Redis服务器的主机地址(parameters.getRedisHost())、端口号(parameters.getRedisPort())等信息创建JedisPool对象, + // 这里的2000表示连接超时时间(单位为毫秒),"xxx"表示连接Redis服务器的密码(实际应用中应替换为真实的密码),通过这个构造方法实例化JedisPool连接池,完成初始化工作。 + jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx"); + + // 记录初始化Redis连接池成功的日志信息,便于在查看日志时确认连接池是否正常初始化完成。 log.info("【初始化redis连接池成功】"); - }catch (Exception e){ - log.error("【初始化redis连接池失败】",e); + } catch (Exception e) { + // 如果在初始化过程中出现异常,记录初始化Redis连接池失败的错误日志信息,并将异常对象e传递进去,方便查看详细的异常堆栈信息,用于排查初始化失败的原因。 + log.error("【初始化redis连接池失败】", e); } } + /** + * 这个方法用于对外提供创建好的JedisPool连接池对象,供其他代码获取并使用,以便通过获取的连接池来获取Jedis客户端实例,进而与Redis服务器进行交互操作,比如执行数据的存储、获取、删除等操作。 + * @return 返回创建好的JedisPool对象,如果初始化失败(即init方法执行出现异常导致jedisPool仍为null),则返回null,调用者需要进行相应的空值判断等处理。 + */ public JedisPool getJedisPool() { return jedisPool; } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/cache/Parameters.java b/snailmall-cart-service/src/main/java/com/njupt/swg/cache/Parameters.java index 9121848..4b13147 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/cache/Parameters.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/cache/Parameters.java @@ -9,25 +9,39 @@ import org.springframework.stereotype.Component; * @Date 2019/1/1 14:27 * @CONTACT 317758022@qq.com * @DESC + * 这个类(Parameters)是一个用于存储应用程序配置参数的实体类,通过使用Spring的`@Value`注解从配置文件(通常是`application.properties`或`application.yml`等)中读取相应的配置值,并将其注入到对应的属性中。 + * 它被标记为Spring的组件(通过`@Component`注解),可以方便地在其他需要使用这些配置参数的类中通过依赖注入的方式获取该对象,从而获取到具体的配置值。 + * 同时使用了Lombok的`@Data`注解,自动生成了属性的Getter、Setter、ToString、EqualsAndHashCode等方法,简化了代码编写,方便对这些配置参数进行操作和使用。 */ @Component @Data public class Parameters { + /*****redis config start*******/ + // 使用`@Value("${redis.host}")`注解从配置文件中读取`redis.host`属性对应的值,并将其注入到`redisHost`属性中,用于存储Redis服务器的主机地址。 @Value("${redis.host}") private String redisHost; + + // 同样通过`@Value("${redis.port}")`注解从配置文件获取`redis.port`属性的值,注入到`redisPort`属性,用于存储Redis服务器的端口号。 @Value("${redis.port}") private int redisPort; + + // 此处注解可能存在书写错误,按照语义推测,应该是`@Value("${redis.max-total}")`用于获取Redis连接池的最大连接数配置参数,注入到`redisMaxTotal`属性中。 @Value("${redis.max-idle}") private int redisMaxTotal; + + // 按照正确的语义,`@Value("${redis.max-idle}")`应该是用于获取Redis连接池的最大空闲连接数配置参数,注入到`redisMaxIdle`属性,这里的注解与语义上的属性对应可能弄反了,实际使用时需留意修正。 @Value("${redis.max-total}") private int redisMaxIdle; + + // 使用`@Value("${redis.max-wait-millis}")`注解从配置文件读取Redis连接时最大等待时间(单位为毫秒)的配置参数,并注入到`redisMaxWaitMillis`属性中,用于控制获取连接的等待时长限制。 @Value("${redis.max-wait-millis}") private int redisMaxWaitMillis; /*****redis config end*******/ /*****curator config start*******/ + // 通过`@Value("${zk.host}")`注解从配置文件获取`zk.host`属性对应的值,注入到`zkHost`属性中,用于存储Zookeeper服务器的主机地址,可能用于与Zookeeper相关的操作配置(比如分布式相关的协调等场景)。 @Value("${zk.host}") private String zkHost; /*****curator config end*******/ -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/clients/ProductClient.java b/snailmall-cart-service/src/main/java/com/njupt/swg/clients/ProductClient.java index 93c224d..ca95f65 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/clients/ProductClient.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/clients/ProductClient.java @@ -10,14 +10,28 @@ import org.springframework.web.bind.annotation.RequestParam; * @Date 2019/1/5 14:57 * @CONTACT 317758022@qq.com * @DESC + * 这个接口(ProductClient)是基于Spring Cloud OpenFeign框架定义的一个客户端接口,用于声明对名为"product-service"的服务进行远程调用的相关方法。 + * 通过使用@FeignClient注解指定了要调用的服务名称("product-service"),然后在接口中定义的各个方法上使用@RequestMapping注解来映射到对应服务中的具体端点路径,并且通过@RequestParam注解来指定请求参数,实现了以一种声明式的方式进行HTTP请求的发送以及响应的接收,方便在微服务架构中进行服务间的通信。 */ @FeignClient("product-service") public interface ProductClient { + /** + * 此方法声明了一个对"product-service"服务中"/product/detail.do"端点的远程调用,用于获取产品的详细信息。 + * 通过@RequestParam("productId")注解指定了一个名为"productId"的请求参数,其类型为Integer,意味着在实际调用时需要传入产品的ID作为参数,然后期望接收到一个ServerResponse类型的响应对象,该对象包含了服务端返回的产品详细信息以及对应的状态码、提示消息等内容。 + * @param productId 产品的唯一标识符,用于指定要获取详细信息的具体产品,其类型为Integer。 + * @return 返回一个ServerResponse对象,其中封装了从"product-service"服务获取到的产品详细信息以及相关的响应状态码、提示消息等内容。 + */ @RequestMapping("/product/detail.do") - ServerResponse getProductDetail(@RequestParam("productId") Integer productId); + ServerResponse getProductDetail(@RequestParam("productId") Integer productId) ; + /** + * 此方法声明了一个对"product-service"服务中"/product/queryProduct.do"端点的远程调用,用于查询产品信息。 + * 同样通过@RequestParam("productId")注解指定了名为"productId"的请求参数(类型为Integer),即需要传入产品ID作为参数进行查询操作,最终期望得到一个ServerResponse类型的响应对象,里面包含了查询到的产品相关信息以及对应的响应状态码、提示消息等情况。 + * @param productId 产品的唯一标识符,用于指定要查询的具体产品,其类型为Integer。 + * @return 返回一个ServerResponse对象,其中包含了从"product-service"服务查询到的产品相关信息以及相关的响应状态码、提示消息等内容。 + */ @RequestMapping("/product/queryProduct.do") ServerResponse queryProduct(@RequestParam("productId") Integer productId); -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/constants/Constants.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/constants/Constants.java index 5f7b081..c23eed4 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/constants/Constants.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/constants/Constants.java @@ -5,38 +5,58 @@ package com.njupt.swg.common.constants; * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com * @DESC + * 这个类(Constants)主要用于定义项目中的各种常量,通过将常量集中管理在一个类中,方便在整个项目中统一引用,提高代码的可维护性和可读性,避免了常量值的硬编码分散在各个地方,使得代码更易于理解和修改。 + * 它包含了自定义的状态码、购物车相关的常量、产品状态相关的常量以及与Redis中产品库存等相关的前缀字符串常量等不同类型的常量定义。 */ public class Constants { + /**自定义状态码 start**/ + // 表示请求成功的状态码,通常在服务端成功处理请求并返回正常结果时使用,符合HTTP状态码中200表示成功的语义,方便在项目中统一表示成功的响应情况。 public static final int RESP_STATUS_OK = 200; + // 表示未授权(用户未登录或者权限不足等情况)的状态码,对应HTTP状态码中的401,用于在需要进行权限校验的场景下,当用户没有相应权限访问资源时返回该状态码,告知客户端需要进行认证授权操作。 public static final int RESP_STATUS_NOAUTH = 401; + // 表示服务器内部错误的状态码,等同于HTTP状态码中的500,当服务端在处理请求过程中出现了未预期的错误,例如代码运行时异常等情况时,返回这个状态码给客户端,表示服务端出现了问题,需要进一步排查和修复。 public static final int RESP_STATUS_INTERNAL_ERROR = 500; + // 表示请求的语法错误或者参数等不符合要求的状态码,对应HTTP状态码中的400,常用于客户端发送的请求格式不正确、缺少必要参数等情况,提示客户端检查并修正请求内容。 public static final int RESP_STATUS_BADREQUEST = 400; /**自定义状态码 end**/ - public interface Cart{ - int CHECKED = 1;//即购物车选中状态 - int UN_CHECKED = 0;//购物车中未选中状态 + // 以下是一个内部接口(Cart),用于定义与购物车相关的常量,将购物车相关的常量集中在这个内部接口中进行管理,使得代码结构更清晰,方便对购物车相关逻辑中使用的常量进行统一维护。 + public interface Cart { + // 表示购物车中商品处于选中状态的常量,值为1,在处理购物车中商品的选中与否逻辑时,可以通过这个常量来判断和设置商品的选中状态。 + int CHECKED = 1; + + // 表示购物车中商品处于未选中状态的常量,值为0,与CHECKED常量相对应,用于区分商品在购物车中的不同选中情况。 + int UN_CHECKED = 0; + // 表示在购物车中对商品数量限制操作失败时的提示信息常量,其值为"LIMIT_NUM_FAIL",可用于在业务逻辑中当商品数量限制相关操作(比如添加商品数量超过限制等情况)失败时,返回给客户端相应的提示信息。 String LIMIT_NUM_FAIL = "LIMIT_NUM_FAIL"; + + // 表示在购物车中对商品数量限制操作成功时的提示信息常量,值为"LIMIT_NUM_SUCCESS",用于在商品数量限制相关操作成功后,向客户端反馈相应的成功提示消息。 String LIMIT_NUM_SUCCESS = "LIMIT_NUM_SUCCESS"; } + // 内部接口(Product)用于定义与产品状态相关的常量,将产品不同状态对应的常量集中在此处管理,便于在涉及产品状态判断、更新等业务逻辑中统一使用和维护这些常量。 /** 产品的状态 **/ - public interface Product{ + public interface Product { + // 表示产品处于上架、可销售状态的常量,值为1,在业务逻辑中可以通过这个常量来判断产品是否可供用户购买等情况。 int PRODUCT_ON = 1; + + // 表示产品处于下架状态的常量,值为2,用于标识产品暂时不可销售,例如库存不足、商品调整等原因导致下架时使用该常量来表示其状态。 int PRODUCT_OFF = 2; + + // 表示产品已被删除的状态常量,值为3,当产品从系统中彻底删除后,通过这个常量来体现其在业务层面的最终状态,方便在数据查询、过滤等操作中基于这个状态进行相关处理。 int PRODUCT_DELETED = 3; } /***redis product stock**/ + // 用于定义在Redis中存储产品库存相关数据时的键(key)的前缀字符串常量,方便在Redis操作中统一管理和区分不同产品的库存相关数据,后续实际的键可以在此前缀基础上添加具体的产品标识等信息来构成完整的键。 public static final String PRODUCT_TOKEN_STOCK_PREFIX = "product__stock_"; + // 用于定义在Redis中存储产品相关数据(除库存外可能还有其他产品相关属性等情况)时的键(key)的前缀字符串常量,同样起到统一管理和区分不同产品数据在Redis中的存储的作用,便于根据这个前缀构建完整的Redis键来进行数据的读写操作。 public static final String PRODUCT_TOKEN_PREFIX = "product__"; - - -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java index cef87ac..3871035 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java @@ -1,6 +1,5 @@ package com.njupt.swg.common.exception; - import com.njupt.swg.common.constants.Constants; import com.njupt.swg.common.resp.ServerResponse; import lombok.extern.slf4j.Slf4j; @@ -12,22 +11,35 @@ import org.springframework.web.bind.annotation.ResponseBody; * @Author swg. * @Date 2019/1/1 13:21 * @CONTACT 317758022@qq.com - * @DESC 全局异常处理 + * @DESC 这个类(ExceptionHandlerAdvice)用于实现全局异常处理,它借助了Spring框架提供的相关注解来拦截并处理在整个应用程序运行过程中出现的异常情况,将异常统一转化为合适的响应结果返回给客户端,提高了应用程序的稳定性和用户体验。 + * 通过使用@ControllerAdvice注解,表明这是一个全局的控制器增强类,能够对多个控制器中的异常进行统一处理。结合@ResponseBody注解,使得处理异常后返回的结果能够直接以JSON等格式写入到响应体中,方便客户端解析和处理。并且使用了@Slf4j注解来记录异常相关的详细信息,便于后续排查问题。 */ @ControllerAdvice @ResponseBody @Slf4j public class ExceptionHandlerAdvice { + + /** + * 此方法是一个异常处理方法,使用了@ExceptionHandler(Exception.class)注解来指定它能够处理的异常类型为所有的Exception及其子类(也就是几乎所有的运行时异常和非运行时异常情况)。 + * 当应用程序中出现未被其他更具体的异常处理器捕获的异常时,这个方法就会被调用。它首先会记录异常的详细信息(包括异常消息和堆栈信息等)到日志中,然后返回一个ServerResponse对象,该对象通过调用ServerResponse的静态方法createByErrorCodeMessage,使用预定义的表示服务器内部错误的状态码(Constants.RESP_STATUS_INTERNAL_ERROR)以及相应的提示消息("系统异常,请稍后再试")来构建一个统一的错误响应,告知客户端系统出现了异常情况,需要稍后再次尝试操作。 + * @param e 捕获到的Exception类型的异常对象,包含了异常发生时的详细信息,例如异常消息、堆栈轨迹等,通过对这个对象的处理可以获取到具体的异常情况用于记录日志等操作。 + * @return 返回一个ServerResponse对象,作为处理异常后的响应结果,其中包含了表示服务器内部错误的状态码以及相应的提示消息,用于告知客户端系统出现异常,让客户端进行相应的处理(如提示用户等)。 + */ @ExceptionHandler(Exception.class) - public ServerResponse handleException(Exception e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); + public ServerResponse handleException(Exception e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试"); } + /** + * 此方法同样是一个异常处理方法,使用@ExceptionHandler(SnailmallException.class)注解指定它专门用于处理SnailmallException类型的异常,这种异常通常是项目中自定义的业务相关异常。 + * 当抛出SnailmallException异常时,这个方法会被调用,它先将异常的详细信息记录到日志中,然后返回一个ServerResponse对象,通过调用ServerResponse的静态方法createByErrorCodeMessage,使用SnailmallException异常对象中携带的异常状态码(通过e.getExceptionStatus()获取)以及异常消息(e.getMessage())来构建一个符合业务逻辑的错误响应,向客户端准确反馈具体的业务异常情况以及对应的错误提示信息。 + * @param e 捕获到的SnailmallException类型的异常对象,包含了自定义业务异常发生时的具体状态码以及详细的异常消息等信息,用于构建合适的错误响应返回给客户端。 + * @return 返回一个ServerResponse对象,作为处理SnailmallException异常后的响应结果,其中包含了从异常对象中获取的异常状态码以及对应的异常消息,用于告知客户端具体的业务异常情况,让客户端知晓并进行相应的处理(如根据错误提示修正操作等)。 + */ @ExceptionHandler(SnailmallException.class) - public ServerResponse handleException(SnailmallException e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); + public ServerResponse handleException(SnailmallException e) { + log.error(e.getMessage(), e); + return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage()); } - -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java index 363f19d..c44f217 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java @@ -8,18 +8,32 @@ import lombok.Getter; * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC + * 这个类(SnailmallException)是自定义的异常类,继承自Java的RuntimeException,意味着它属于运行时异常,不需要在方法声明中显式地抛出或者捕获(当然也可以根据具体业务需求选择捕获处理),方便在业务逻辑中遇到特定的错误情况时抛出,以表示业务相关的异常状况。 + * 通过使用Lombok的@Getter注解,自动生成了获取exceptionStatus属性的Getter方法,该属性用于存储异常对应的状态码,方便在全局异常处理等地方获取并使用这个状态码来构建合适的错误响应信息返回给客户端等操作。 */ @Getter -public class SnailmallException extends RuntimeException{ +public class SnailmallException extends RuntimeException { + + // 用于存储该异常对应的状态码,默认初始化为ResponseEnum中定义的通用错误状态码(ERROR的状态码),可以在构造方法中根据具体业务需求重新赋值,以此来区分不同类型的业务异常情况,便于在统一处理异常时进行针对性的响应。 private int exceptionStatus = ResponseEnum.ERROR.getCode(); - public SnailmallException(String msg){ + /** + * 构造方法之一,接收一个字符串类型的参数msg,用于创建一个SnailmallException实例,在构造时调用父类(RuntimeException)的构造方法传入msg参数,设置异常的详细消息内容,同时使用默认的异常状态码(即ResponseEnum.ERROR.getCode())。 + * 适用于只需要简单传递异常消息,不需要指定特定状态码的业务异常情况,例如一般性的业务逻辑错误等场景。 + * @param msg 异常的详细消息内容,用于描述出现异常的具体原因等信息,会在日志记录、向客户端反馈错误信息等场景下使用。 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 构造方法之二,接收一个整数类型的参数code和一个字符串类型的参数msg,用于创建一个SnailmallException实例,在构造时调用父类(RuntimeException)的构造方法传入msg参数来设置异常的详细消息内容,同时将传入的code参数赋值给exceptionStatus属性,以此来指定该异常对应的特定状态码。 + * 适用于需要根据不同的业务逻辑错误情况,设置不同的状态码以及相应的异常消息,方便在全局异常处理等环节根据状态码和消息准确地反馈具体的异常信息给客户端或者进行相应的业务处理的场景,例如不同模块的业务验证失败等情况可以对应不同的状态码和消息。 + * @param code 异常对应的状态码,用于区分不同类型的业务异常,可根据业务需求自定义不同的值,通常会和项目中预定义的状态码体系(如ResponseEnum中定义的各种状态码)相关联。 + * @param msg 异常的详细消息内容,用于描述出现异常的具体原因等信息,会在日志记录、向客户端反馈错误信息等场景下使用。 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java index e4e59c7..187b794 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java @@ -6,20 +6,38 @@ import lombok.Getter; * @Author swg. * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com - * @DESC 基本的返回状态描述 + * @DESC 这个枚举类(ResponseEnum)用于定义项目中基本的返回状态描述,通过将不同的返回状态码及其对应的文字描述进行统一封装在枚举值里,使得在整个项目中可以方便、规范地使用这些预定义的返回状态,增强了代码的可读性和可维护性。 + * 并且使用了Lombok的@Getter注解,这样就能自动生成获取code和desc属性的方法,方便在其他地方获取枚举实例中定义的状态码和描述信息,用于构建相应的返回结果或者进行状态判断等操作。 */ @Getter public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + // 表示操作成功的枚举值,状态码为0,对应的描述信息是"SUCCESS",通常在业务逻辑处理成功,需要向客户端返回成功标识及相关提示时使用这个枚举值来代表成功状态。 + SUCCESS(0, "SUCCESS"), + + // 表示出现错误的通用枚举值,状态码为1,描述信息为"ERROR",用于在各种未详细区分的错误场景下,作为一个笼统的错误返回状态,告知客户端请求处理出现了问题,但没有具体指明是哪种错误类型。 + ERROR(1, "ERROR"), + + // 代表请求参数不合法的枚举值,状态码为2,描述信息是"ILLEGAL_ARGUMENTS",常用于客户端发送的请求参数不符合业务规则、格式不正确等情况时,通过返回这个枚举值对应的状态码和描述来提示客户端参数存在问题,需要进行修正后重新请求。 + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + + // 表示需要用户登录的枚举值,状态码为10,描述信息为"NEED_LOGIN",当客户端尝试访问需要登录认证才能访问的资源,但未提供有效登录凭证时,服务端可以返回这个枚举值对应的状态码和描述信息,提示客户端需要先进行登录操作,然后再发起请求。 + NEED_LOGIN(10, "NEED_LOGIN"); + + // 用于存储每个枚举值对应的状态码,不同的枚举值有不同的状态码,以此来区分不同的返回状态情况,方便在业务逻辑中根据状态码进行不同的处理逻辑分支判断。 private int code; + + // 用于存储每个枚举值对应的文字描述信息,直观地展示该返回状态所代表的具体含义,便于客户端或者其他开发人员理解返回结果具体表示的情况,辅助进行相应的后续操作(如提示用户、进行日志记录等)。 private String desc; - ResponseEnum(int code,String desc){ + /** + * 枚举的构造方法,用于初始化每个枚举值对应的状态码和描述信息。 + * 在定义每个枚举值(如SUCCESS、ERROR等)时,会调用这个构造方法并传入相应的状态码和描述信息参数,来完成枚举值的实例化,使得每个枚举值都有其特定的状态码和对应的描述内容。 + * @param code 状态码,用于唯一标识该种返回状态,在整个项目的业务逻辑中可以依据这个状态码来判断请求处理的结果情况,进而执行不同的后续操作,比如返回不同的响应内容给客户端等。 + * @param desc 描述信息,用于详细说明状态码所代表的含义,让使用者(客户端或者其他开发人员查看代码时)能够清楚地知晓返回这个状态具体意味着什么情况,方便进行相应的处理。 + */ + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java index 5212ea6..1c95d04 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java @@ -10,68 +10,158 @@ import java.io.Serializable; * @Author swg. * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com - * @DESC 作为本项目的通用的返回封装类 + * @DESC 这个类(ServerResponse)作为本项目的通用的返回封装类,用于对服务端返回给客户端的数据进行统一格式的封装,使得返回结果具有统一的结构,方便客户端解析和处理,同时也提高了整个项目返回数据的规范性和可读性。 + * 它实现了Serializable接口,这样该类的实例就能够被序列化,便于在网络传输、缓存存储等场景下进行数据的传递和持久化操作,确保数据可以正确地在不同的环境中流转。 + * 利用了Lombok的@Getter注解自动生成获取类中属性的方法,并且通过@JsonSerialize注解配置了JSON序列化的相关行为,排除值为null的字段不进行序列化,以减少不必要的数据传输,优化网络资源利用。 */ @Getter @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) public class ServerResponse implements Serializable { + + // 用于存储返回结果的状态码,通过不同的状态码来标识请求处理的结果情况,例如成功、失败以及不同类型的错误等,方便客户端根据状态码来判断后续的操作逻辑,一般会与项目中定义的状态码枚举(如ResponseEnum)相对应。 private int status; + + // 用于存储返回结果的提示消息,是对状态码含义的文字描述,更直观地向客户端传达请求处理的相关情况,比如操作成功的提示语或者出现错误时的具体原因说明等内容,有助于客户端更好地向用户展示相应的信息。 private String msg; + + // 用于存储具体的业务数据,其类型为泛型参数T,意味着可以根据不同的业务场景返回不同类型的数据,例如可以是用户信息对象、商品列表、订单详情等各种与业务相关的实体数据,灵活性较高,能够满足多样化的业务需求。 private T data; - public ServerResponse(){} + // 默认的无参构造方法,主要用于在一些特定情况下(如反序列化等)创建对象实例,虽然当前类中暂时没有复杂的初始化逻辑依赖于这个构造方法,但遵循Java Bean规范保留了它。 + public ServerResponse() {} - private ServerResponse(int status){ + /** + * 私有构造方法之一,接收一个状态码作为参数,用于创建一个ServerResponse实例,通常在已知状态码且不需要额外设置提示消息和业务数据时使用, + * 比如在一些内部逻辑判断中,根据固定的状态码来构造返回对象,后续可能会根据具体需求再进一步完善其他属性的值,该构造方法提供了一种灵活构建返回对象的方式。 + * @param status 传入的状态码,用于标识请求处理的结果情况,会赋值给当前实例的status属性。 + */ + private ServerResponse(int status) { this.status = status; } - private ServerResponse(int status,String msg){ + + /** + * 私有构造方法之二,接收一个状态码和一个提示消息作为参数,用于创建一个包含特定状态码和对应提示消息的ServerResponse实例, + * 在需要明确告知客户端某个状态以及对应的详细说明时使用,例如返回错误状态并附上具体的错误原因描述等情况,方便客户端准确知晓请求处理出现的问题所在。 + * @param status 传入的状态码,用于标识请求处理的结果情况,会赋值给当前实例的status属性。 + * @param msg 传入的提示消息,用于详细说明状态码所代表的含义,会赋值给当前实例的msg属性。 + */ + private ServerResponse(int status, String msg) { this.status = status; this.msg = msg; } - private ServerResponse(int status,T data){ + + /** + * 私有构造方法之三,接收一个状态码和具体的业务数据作为参数,用于创建一个包含特定状态码以及对应业务数据的ServerResponse实例, + * 常用于请求处理成功并且有具体数据需要返回给客户端的场景,比如查询操作成功后返回查询到的结果数据等情况,使得客户端能够获取到业务相关的数据进行后续展示或处理。 + * @param status 传入的状态码,用于标识请求处理的结果情况,会赋值给当前实例的status属性。 + * @param data 传入的具体业务数据,类型为泛型参数T,会赋值给当前实例的data属性,代表需要返回给客户端的业务相关内容。 + */ + private ServerResponse(int status, T data) { this.status = status; this.data = data; } - private ServerResponse(int status,String msg,T data){ + + /** + * 私有构造方法之四,接收一个状态码、一个提示消息和具体的业务数据作为参数,用于创建一个完整包含状态码、提示消息以及业务数据的ServerResponse实例, + * 这种情况可以更全面地向客户端传达请求处理的结果,既包含了处理状态、详细说明又有具体的数据内容,适用于多种复杂的业务返回场景,比如查询操作成功后返回查询到的数据以及一些相关的提示等情况。 + * @param status 传入的状态码,用于标识请求处理的结果情况,会赋值给当前实例的status属性。 + * @param msg 传入的提示消息,用于详细说明状态码所代表的含义,会赋值给当前实例的msg属性。 + * @param data 传入的具体业务数据,类型为泛型参数T,会赋值给当前实例的data属性,代表需要返回给客户端的业务相关内容。 + */ + private ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + /** + * 使用@JsonIgnore注解标记此方法,表示在进行JSON序列化时,该方法不会被包含在序列化结果中,主要用于在不需要向客户端暴露该方法的场景下(比如该方法只是内部用于判断逻辑)。 + * 这个方法用于判断当前ServerResponse实例所表示的返回结果是否为成功状态,通过比较实例中的状态码与ResponseEnum中定义的成功状态码(即SUCCESS的状态码)是否相等来进行判断,方便在业务逻辑中快速确定请求是否成功处理。 + * @return 返回一个布尔值,如果状态码与成功状态码相等则返回true,表示返回结果为成功状态;否则返回false,表示返回结果为失败或者其他非成功的状态。 + */ @JsonIgnore - public boolean isSuccess(){ + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } /** - * 成功的方法 + * 以下是一系列静态方法,用于方便地创建表示成功状态的ServerResponse实例,根据不同的业务需求提供了多种创建方式,增强了在返回成功结果时的灵活性,使得可以根据具体情况选择合适的方式构建返回对象。 + + * 创建一个表示成功状态的ServerResponse实例,使用ResponseEnum中定义的默认成功状态码和对应的描述信息进行实例化, + * 适用于简单告知客户端操作成功,不需要额外传递具体消息和业务数据的场景,例如一些简单的操作,只需要让客户端知道请求已顺利完成即可。 + * @param 泛型参数,表示可以返回不同类型的业务数据,在这里创建实例时暂未指定具体的数据类型,只是表明该方法可以用于创建各种类型数据对应的成功返回结果。 + * @return 返回一个表示成功状态的ServerResponse实例,包含默认的成功状态码和描述信息,且无具体业务数据。 */ - public static ServerResponse createBySuccess(){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); - } - public static ServerResponse createBySuccessMessage(String message){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); - } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); - } - public static ServerResponse createBySuccess(String message,T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); + public static ServerResponse createBySuccess() { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc()); } /** - * 失败的方法 + * 创建一个表示成功状态的ServerResponse实例,使用ResponseEnum中定义的默认成功状态码,并传入自定义的提示消息进行实例化, + * 适用于操作成功但需要向客户端传递一些额外的说明信息的场景,比如操作成功后的一些提示语、相关说明等,使得客户端可以根据这些消息进行更合适的后续处理或者展示给用户。 + * @param 泛型参数,表示可以返回不同类型的业务数据,在这里创建实例时暂未指定具体的数据类型,只是表明该方法可以用于创建各种类型数据对应的成功返回结果。 + * @param message 传入的自定义提示消息,用于向客户端传达更详细的成功相关信息,会赋值给创建的ServerResponse实例的msg属性。 + * @return 返回一个表示成功状态的ServerResponse实例,包含默认的成功状态码和传入的提示消息,且无具体业务数据。 */ - public static ServerResponse createByError(){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); + public static ServerResponse createBySuccessMessage(String message) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createByErrorMessage(String msg){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); + + /** + * 创建一个表示成功状态的ServerResponse实例,使用ResponseEnum中定义的默认成功状态码,并传入具体的业务数据进行实例化, + * 适用于操作成功且有具体业务数据需要返回给客户端的场景,比如查询操作成功后返回查询到的数据、获取用户信息成功后返回用户详细信息等情况,方便客户端获取到业务相关的数据进行后续处理。 + * @param 泛型参数,表示可以返回不同类型的业务数据,在这里创建实例时指定了具体的数据类型,由传入的参数data决定,会赋值给创建的ServerResponse实例的data属性。 + * @param data 传入的具体业务数据,类型为泛型参数T,代表需要返回给客户端的业务相关内容。 + * @return 返回一个表示成功状态的ServerResponse实例,包含默认的成功状态码和传入的具体业务数据,无额外的提示消息。 + */ + public static ServerResponse createBySuccess(T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data); } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); + + /** + * 创建一个表示成功状态的ServerResponse实例,使用ResponseEnum中定义的默认成功状态码,并传入自定义的提示消息和具体的业务数据进行实例化, + * 适用于操作成功且既有需要向客户端传递的额外说明信息,又有具体业务数据需要返回的复杂场景,比如查询操作成功后返回查询到的数据以及一些相关的提示等情况,使得客户端能够全面了解请求处理的结果并获取到业务相关的数据进行后续处理。 + * @param 泛型参数,表示可以返回不同类型的业务数据,在这里创建实例时指定了具体的数据类型,由传入的参数data决定,会赋值给创建的ServerResponse实例的data属性。 + * @param message 传入的自定义提示消息,用于向客户端传达更详细的成功相关信息,会赋值给创建的ServerResponse实例的msg属性。 + * @param data 传入的具体业务数据,类型为泛型参数T,代表需要返回给客户端的业务相关内容。 + * @return 返回一个表示成功状态的ServerResponse实例,包含默认的成功状态码、传入的提示消息和具体业务数据。 + */ + public static ServerResponse createBySuccess(String message, T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } + /** + * 以下是一系列静态方法,用于方便地创建表示失败状态的ServerResponse实例,同样根据不同的业务需求提供了多种创建方式,便于在不同的错误场景下返回合适的错误信息,使得客户端能够清楚地知晓请求出现的问题所在。 + + * 创建一个表示通用失败状态的ServerResponse实例,使用ResponseEnum中定义的默认失败状态码和对应的描述信息进行实例化, + * 适用于一些未明确细分的错误情况,作为一个笼统的错误返回给客户端,告知客户端请求处理出现了问题,但没有具体指明是哪种错误类型,让客户端知晓请求未成功处理。 + * @param 泛型参数,表示可以返回不同类型的业务数据,在这里创建实例时暂未指定具体的数据类型,只是表明该方法可以用于创建各种类型数据对应的失败返回结果。 + * @return 返回一个表示失败状态的ServerResponse实例,包含默认的失败状态码和描述信息,且无具体业务数据。 + */ + public static ServerResponse createByError() { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); + } + /** + * 创建一个表示失败状态的ServerResponse实例,使用ResponseEnum中定义的默认失败状态码,并传入自定义的提示消息进行实例化, + * 适用于已知具体错误原因,需要向客户端传递详细错误信息的场景,通过传入的msg参数告知客户端出现错误的具体情况,方便客户端根据错误信息进行相应的处理,比如提示用户修改输入内容等。 + * @param 泛型参数,表示可以返回不同类型的业务数据,在这里创建实例时暂未指定具体的数据类型,只是表明该方法可以用于创建各种类型数据对应的失败返回结果。 + * @param msg 传入的自定义提示消息,用于向客户端传达具体的错误原因等相关信息,会赋值给创建的ServerResponse实例的msg属性。 + * @return 返回一个表示失败状态的ServerResponse实例,包含默认的失败状态码和传入的提示消息,且无具体业务数据。 + */ + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } -} + /** + * 创建一个表示失败状态的ServerResponse实例,传入自定义的状态码和提示消息进行实例化, + * 适用于需要根据不同的业务逻辑返回特定的错误状态码以及对应的详细错误信息的场景,通过传入的code和msg参数灵活地构建错误返回对象,使得可以针对不同的错误情况进行精准的返回,方便客户端准确处理相应的问题。 + * @param 泛型参数,表示可以返回不同类型的业务数据,在这里创建实例时暂未指定具体的数据类型,只是表明该方法可以用于创建各种类型数据对应的失败返回结果。 + * @param code 传入的自定义状态码,用于标识具体的错误情况,根据业务逻辑可以设置不同的值,会赋值给创建的ServerResponse实例的status属性。 + * @param msg 传入的自定义提示消息,用于向客户端传达具体的错误原因等相关信息,会赋值给创建的ServerResponse实例的msg属性。 + * @return 返回一个表示失败状态的ServerResponse实例,包含传入的状态码和提示消息,且无具体业务数据。 + */ + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + return new ServerResponse<>(code, msg); + } +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/BigDecimalUtil.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/BigDecimalUtil.java index 1a4397b..9034aea 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/BigDecimalUtil.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/BigDecimalUtil.java @@ -6,37 +6,67 @@ import java.math.BigDecimal; * @Author swg. * @Date 2019/1/5 15:20 * @CONTACT 317758022@qq.com - * @DESC 将数值转换为字符串类型,然后传入BigDecimal中进行处理,防止精度丢失 + * @DESC 这个工具类(BigDecimalUtil)主要用于对数值进行高精度的数学运算操作,通过先将传入的double类型数值转换为字符串形式,再传入BigDecimal类中进行处理,以此来防止在进行数值运算(特别是涉及到小数运算)时可能出现的精度丢失问题。 + * 它提供了加、减、乘、除四种基本的数学运算方法,方便在需要高精度数值计算的业务场景中进行准确的数值处理,并且将构造方法私有化,避免了类被实例化,符合工具类的设计规范,强调其功能性的使用方式。 */ public class BigDecimalUtil { - private BigDecimalUtil(){ + + // 将构造方法私有化,这样外部就不能通过new关键字来创建这个类的实例,因为这个类作为工具类,只提供静态方法供使用,不需要实例化对象来调用方法,从而保证了工具类使用方式的规范性。 + private BigDecimalUtil() { } - public static BigDecimal add(double v1, double v2){ + /** + * 实现两个double类型数值相加的方法,用于高精度的加法运算,避免精度丢失。 + * 首先将传入的两个double类型数值v1和v2分别转换为字符串形式,然后使用这些字符串创建对应的BigDecimal对象b1和b2,最后调用BigDecimal的add方法对这两个BigDecimal对象进行加法运算,并返回运算结果。 + * @param v1 参与加法运算的第一个double类型数值,例如商品价格、数量等需要进行精确加法计算的数值。 + * @param v2 参与加法运算的第二个double类型数值,与v1共同参与加法运算,获取精确的相加结果。 + * @return 返回一个BigDecimal对象,表示v1和v2相加后的精确结果,可用于后续需要高精度数值结果的业务逻辑处理,比如计算总价、累计金额等场景。 + */ + public static BigDecimal add(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.add(b2); } - public static BigDecimal sub(double v1,double v2){ + /** + * 实现两个double类型数值相减的方法,用于高精度的减法运算,防止精度丢失。 + * 具体操作是先把传入的两个double类型数值v1和v2转换为字符串,以此创建对应的BigDecimal对象b1和b2,接着调用BigDecimal的subtract方法对这两个对象进行减法运算,返回相减后的结果。 + * @param v1 参与减法运算的被减数,为double类型的数值,比如库存数量的减少、金额的扣除等业务场景中作为被减数使用。 + * @param v2 参与减法运算的减数,同样是double类型数值,用于从v1中减去相应的值,获取精确的差值结果。 + * @return 返回一个BigDecimal对象,代表v1减去v2后的精确差值,可应用于如计算差价、余量等需要高精度减法结果的业务逻辑场景中。 + */ + public static BigDecimal sub(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.subtract(b2); } - - public static BigDecimal mul(double v1,double v2){ + /** + * 实现两个double类型数值相乘的方法,用于高精度的乘法运算,保障运算精度。 + * 操作流程为先将传入的两个double类型数值v1和v2分别转换为字符串后构建BigDecimal对象b1和b2,随后利用BigDecimal的multiply方法对它们进行乘法运算,并返回乘积结果。 + * @param v1 参与乘法运算的第一个因数,是double类型的数值,例如计算商品总价(单价乘以数量)等场景中作为其中一个因数参与运算。 + * @param v2 参与乘法运算的第二个因数,同样为double类型数值,与v1共同参与乘法运算,获取精确的乘积结果。 + * @return 返回一个BigDecimal对象,为v1和v2相乘后的精确结果,常用于如计算面积、总价等需要高精度乘法结果的业务逻辑处理中。 + */ + public static BigDecimal mul(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.multiply(b2); } - public static BigDecimal div(double v1,double v2){ + /** + * 实现两个double类型数值相除的方法,用于高精度的除法运算,在保证精度的同时处理了除不尽的情况,默认采用四舍五入的方式保留2位小数。 + * 先是把传入的两个double类型数值v1和v2转化为字符串来创建对应的BigDecimal对象b1和b2,接着调用BigDecimal的divide方法进行除法运算,传入参数指定了保留小数位数为2以及舍入模式为ROUND_HALF_UP(四舍五入),最后返回除法运算的结果。 + * @param v1 参与除法运算的被除数,即要被除的double类型数值,比如计算平均价格(总价除以数量)等场景中作为被除数使用。 + * @param v2 参与除法运算的除数,为double类型数值,且不能为0(若为0会抛出异常,调用者需确保除数非零),用于对v1进行除法运算,获取精确的商值(保留2位小数并四舍五入)。 + * @return 返回一个BigDecimal对象,代表v1除以v2后的精确结果(四舍五入保留2位小数),适用于如计算比率、平均数值等需要高精度且规范小数位数的除法运算结果的业务场景中。 + */ + public static BigDecimal div(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); - return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);//四舍五入,保留2位小数 + return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP); //四舍五入,保留2位小数 //除不尽的情况 } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java index 0233e8d..82957a1 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java @@ -8,67 +8,107 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * cookie读写 + * @Author 作者相关信息(若有) + * @Date 创建日期(若有) + * @CONTACT 联系方式(若有) + * @DESC 这个类(CookieUtil)是一个工具类,用于处理与Cookie相关的常见操作,主要围绕项目中登录相关的Cookie进行写入、读取以及注销时删除等操作,通过统一的方法封装,方便在整个项目中对登录Cookie进行规范管理,同时利用日志记录相关操作信息,便于后续的跟踪与排查问题。 */ @Slf4j public class CookieUtil { + + // 定义了Cookie的域名,限定了该Cookie在哪个域名下能够被识别和使用,这里设置为"oursnail.cn",意味着只有当访问该域名下的网页时,浏览器才会发送对应的Cookie到服务器端,以此来确保Cookie的作用范围符合项目要求。 private final static String COOKIE_DOMAIN = "oursnail.cn"; - private final static String COOKIE_NAME = "snailmall_login_token"; + // 定义了登录相关Cookie的名称,在整个项目中通过这个固定的名称来识别和操作对应的登录Cookie,这里将其命名为"snailmall_login_token",方便统一管理与登录认证相关的Cookie操作。 + private final static String COOKIE_NAME = "snailmall_login_token"; /** - * 登陆的时候写入cookie - * @param response - * @param token + * 此方法用于在用户登录成功时,向客户端(浏览器)写入登录相关的Cookie信息。 + * 通过创建一个新的Cookie对象,设置其各项属性(如域名、路径、是否可通过脚本访问以及有效期等),然后将该Cookie添加到响应对象中,使得浏览器能够接收到并保存这个Cookie,以便后续在访问相关页面时携带该Cookie用于身份验证等操作。 + * @param response HttpServletResponse类型的对象,它代表服务器对客户端的响应,通过这个对象可以向客户端发送各种响应信息,包括添加要写入的Cookie,确保浏览器能获取并保存相应的Cookie信息。 + * @param token 要写入Cookie的具体值,通常是代表用户登录认证的令牌等关键信息,后续服务器端可以通过读取该Cookie的值来验证用户身份,确认是否已经登录等操作。 */ - public static void writeLoginToken(HttpServletResponse response,String token){ - Cookie ck = new Cookie(COOKIE_NAME,token); + public static void writeLoginToken(HttpServletResponse response, String token) { + // 创建一个新的Cookie对象,其名称为预先定义的登录Cookie名称(COOKIE_NAME),值为传入的token参数,以此构建一个符合项目要求的登录Cookie实例。 + Cookie ck = new Cookie(COOKIE_NAME, token); + + // 设置Cookie的域名属性,使其与预先定义的域名(COOKIE_DOMAIN)一致,这样浏览器就能根据该域名来判断何时发送这个Cookie到服务器,确保只有访问指定域名的页面时才会携带该Cookie,保障了Cookie的作用范围准确性。 ck.setDomain(COOKIE_DOMAIN); - ck.setPath("/");//设值在根目录 - ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击 - ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒,maxage不设置的话,cookie就不会写入硬盘,只会写在内存,只在当前页面有效 - log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); + + // 设置Cookie的路径为根目录("/"),这意味着该Cookie在整个域名下的所有页面路径中都有效,无论用户在该域名下访问哪个具体页面,浏览器都会自动携带这个Cookie发送给服务器,方便在项目的各个页面中都能基于这个Cookie进行相关的登录验证等操作。 + ck.setPath("/"); + + // 将Cookie的HttpOnly属性设置为true,这样可以防止通过JavaScript等脚本语言在客户端对该Cookie进行访问和操作,有效地避免了脚本攻击带来的安全风险,提高了Cookie存储用户信息的安全性。 + ck.setHttpOnly(true); + + // 设置Cookie的最大存活时间,这里设置为一年(换算成秒,即60秒 * 60分钟 * 24小时 * 365天),表示该Cookie在客户端浏览器上能够保存的时长,超过这个时间后浏览器会自动删除该Cookie。如果设置为 -1 则表示永久有效,若不设置该属性(maxAge不设置),Cookie就只会保存在内存中,不会写入硬盘,且只在当前页面有效,关闭页面后就会失效。 + ck.setMaxAge(60 * 60 * 24 * 365); + + // 记录要写入的Cookie的名称和值的日志信息,方便在查看日志时了解具体的Cookie写入情况,对于调试或者跟踪Cookie相关操作具有重要作用,比如排查是否正确写入了期望的登录Cookie等问题。 + log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue()); + + // 将设置好的Cookie添加到响应对象中,浏览器在接收到服务器的响应时,就会根据响应中的这个设置来保存相应的Cookie信息,从而完成登录Cookie的写入操作。 response.addCookie(ck); } /** - * 读取登陆的cookie - * @param request - * @return + * 此方法用于从客户端发送的HTTP请求中读取登录相关的Cookie信息。 + * 它首先尝试获取请求中包含的所有Cookie数组,然后遍历这个数组,通过比较每个Cookie的名称是否与预先定义的登录Cookie名称(COOKIE_NAME)相等,来找到对应的登录Cookie,并返回其值,如果遍历完整个Cookie数组都没有找到匹配的登录Cookie,则返回null。 + * @param request HttpServletRequest类型的对象,它代表客户端发送给服务器的请求信息,通过这个对象可以获取请求中携带的各种数据,包括Cookie信息,以便服务器端进行相应的处理,例如验证用户登录状态等操作。 + * @return 返回找到的登录Cookie的值,如果没有找到对应的登录Cookie则返回null,该值通常是之前写入的代表用户登录认证的令牌等关键信息,后续可用于进一步的身份验证等业务逻辑处理。 */ - public static String readLoginToken(HttpServletRequest request){ + public static String readLoginToken(HttpServletRequest request) { + // 获取请求中的所有Cookie数组,如果请求中没有携带任何Cookie,则返回null,否则得到一个包含所有Cookie的数组,用于后续遍历查找登录Cookie的操作。 Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks){ - log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ - log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + + if (cks!= null) { + for (Cookie ck : cks) { + // 记录每个Cookie的名称和值的日志信息,有助于在查看日志时了解请求中具体携带了哪些Cookie,方便排查与Cookie相关的问题,比如确认是否正确接收到了期望的登录Cookie等情况。 + log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + + // 通过比较当前Cookie的名称与预先定义的登录Cookie名称(COOKIE_NAME)是否相等,来判断该Cookie是否为我们要找的登录Cookie,如果相等则表示找到了对应的登录Cookie。 + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + // 记录找到的登录Cookie的名称和值的详细日志信息,方便后续查看具体找到了哪个登录Cookie及其对应的值,然后返回该Cookie的值,用于后续的业务逻辑处理,比如验证用户身份等操作。 + log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); return ck.getValue(); } } } + return null; } /** - * 注销的时候进行删除 - * @param request - * @param response + * 此方法用于在用户执行注销操作时,从客户端删除对应的登录Cookie信息。 + * 它首先获取请求中包含的所有Cookie数组,然后遍历该数组,查找名称与预先定义的登录Cookie名称(COOKIE_NAME)相同的Cookie,找到后通过设置其最大存活时间为0(表示立即删除),并将修改后的Cookie添加回响应对象中,从而通知浏览器删除对应的Cookie,完成注销时删除登录Cookie的操作。 + * @param request HttpServletRequest类型的对象,用于获取客户端发送的请求信息,从中查找要删除的登录Cookie,以便后续进行相应的删除操作,确保能够准确地从客户端清除对应的登录Cookie。 + * @param response HttpServletResponse类型的对象,用于向客户端发送响应信息,通过将修改后的(设置为删除状态的)Cookie添加到响应中,告知浏览器需要删除相应的Cookie,实现从客户端删除登录Cookie的目的。 */ - public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ + public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) { + // 获取请求中的所有Cookie数组,如果请求中没有携带Cookie,则返回null,否则得到包含所有Cookie的数组,用于后续遍历查找要删除的登录Cookie的操作。 Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks) { - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ + + if (cks!= null) { + for (Cookie ck : cks) { + // 通过比较当前Cookie的名称与预先定义的登录Cookie名称(COOKIE_NAME)是否相等,来判断该Cookie是否为我们要删除的登录Cookie,如果相等则表示找到了对应的登录Cookie,接下来进行删除操作。 + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + // 设置要删除的Cookie的域名属性,使其与预先定义的域名(COOKIE_DOMAIN)一致,确保能够准确地删除对应的登录Cookie,避免误删其他同名但不同域名下的Cookie情况发生。 ck.setDomain(COOKIE_DOMAIN); + + // 设置Cookie的路径为根目录("/"),与之前写入该Cookie时设置的路径保持一致,这样才能准确地找到并删除对应的登录Cookie,确保删除操作的准确性。 ck.setPath("/"); - ck.setMaxAge(0);//0表示消除此cookie - log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + + // 设置Cookie的最大存活时间为0,表示让浏览器立即删除这个Cookie,从而实现从客户端清除该登录Cookie的目的,完成注销时的Cookie删除操作。 + ck.setMaxAge(0); + + // 记录要删除的Cookie的名称和值的日志信息,方便在查看日志时了解具体的Cookie删除情况,有助于排查与Cookie删除相关的问题,比如确认是否正确地向浏览器发送了删除登录Cookie的指令等情况。 + log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + + // 将设置好要删除状态的Cookie添加到响应对象中,浏览器在接收到服务器的这个响应后,就会根据设置删除对应的Cookie,从而完成从客户端删除登录Cookie的操作。 response.addCookie(ck); return; } } } } - -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java index 8655b2a..58a5c30 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java @@ -4,65 +4,99 @@ import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** - * @DESC 时间转换的工具类 + * @DESC 这个类(DateTimeUtil)是一个用于时间转换的工具类,它借助了Joda-Time库以及Java标准库中的日期时间相关类,提供了在字符串、Date对象以及时间戳之间相互转换的功能,方便在不同的业务场景下对时间数据进行处理,使得时间数据的格式能够符合项目的各种需求。 */ public class DateTimeUtil { - //joda-time - //str->Date - //Date->str + // 引入Joda-Time库来进行时间相关的操作,相比Java原生的日期时间处理方式,Joda-Time提供了更方便、灵活且不易出错的接口和功能,适用于各种复杂的时间处理场景。 + // str->Date 表示可以将表示时间的字符串转换为Date对象;Date->str表示能把Date对象转换为对应的表示时间的字符串,以下定义了一些相关的方法来实现这些转换功能。 + // 定义了一个表示标准时间格式的常量,格式为"yyyy-MM-dd HH:mm:ss",符合常见的日期时间展示格式,在没有特别指定其他格式的情况下,很多时间转换操作会默认使用这个格式,保证时间表示的规范性和统一性。 public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; - - - public static Date strToDate(String dateTimeStr, String formatStr){ + /** + * 将指定格式的日期时间字符串转换为Date对象的方法。 + * 其实现过程是先根据传入的格式字符串(formatStr)创建一个DateTimeFormatter对象,该对象用于解析符合特定格式的日期时间字符串。然后使用这个格式化器去解析传入的日期时间字符串(dateTimeStr),得到一个DateTime对象,最后将这个DateTime对象转换为Java标准的Date对象并返回,完成从字符串到Date对象的转换。 + * @param dateTimeStr 要转换的日期时间字符串,其格式需要与传入的formatStr参数指定的格式相匹配,这样才能被正确解析,例如传入的formatStr为"yyyy-MM-dd",那么dateTimeStr就应该是符合这种格式的日期字符串。 + * @param formatStr 用于解析日期时间字符串的格式字符串,比如"yyyy-MM-dd HH:mm:ss"、"yyyy-MM-dd"等,它规定了日期时间各部分的排列和表示方式,指导DateTimeFormatter如何解析传入的字符串。 + * @return 返回解析后的Date对象,如果在解析过程中出现问题(例如字符串格式不符合要求等),则可能会抛出异常(由Joda-Time库内部处理相关异常情况)。 + */ + public static Date strToDate(String dateTimeStr, String formatStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); return dateTime.toDate(); } - public static String dateToStr(Date date,String formatStr){ - if(date == null){ + /** + * 将Date对象转换为指定格式的日期时间字符串的方法。 + * 首先判断传入的Date对象是否为null,如果是null,则返回一个空字符串。若Date对象不为null,就根据传入的Date对象创建一个DateTime对象,然后按照指定的格式字符串(formatStr)将其转换为对应的日期时间字符串并返回,以此实现从Date对象到特定格式字符串的转换。 + * @param date 要转换的Date对象,如果为null,则直接返回空字符串,表示没有有效的日期时间数据可转换为字符串形式。 + * @param formatStr 用于将Date对象转换为字符串时的格式字符串,决定了生成的日期时间字符串的格式表现形式,例如传入"yyyy-MM-dd",则返回的字符串会按照这种年-月-日的简洁格式展示日期。 + * @return 返回转换后的日期时间字符串,如果传入的Date对象为null则返回空字符串,否则按照指定格式返回对应的字符串表示,用于后续的展示、存储或者传递等操作。 + */ + public static String dateToStr(Date date, String formatStr) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); return dateTime.toString(formatStr); } - //固定好格式 - public static Date strToDate(String dateTimeStr){ + /** + * 将符合标准格式("yyyy-MM-dd HH:mm:ss")的日期时间字符串转换为Date对象的方法。 + * 内部先基于预定义的标准格式(STANDARD_FORMAT)创建一个DateTimeFormatter对象,然后使用该格式化器解析传入的日期时间字符串得到一个DateTime对象,最后将其转换为Date对象并返回。此方法适用于已知日期时间字符串符合标准格式的情况,无需额外传入格式字符串参数,方便快捷地进行转换操作。 + * @param dateTimeStr 要转换的日期时间字符串,其格式需要符合预定义的标准格式("yyyy-MM-dd HH:mm:ss"),例如"2024-12-18 10:20:30"这样的字符串才能被正确解析转换。 + * @return 返回解析后的Date对象,如果解析过程出现问题则可能抛出异常(由Joda-Time库内部处理相关异常情况)。 + */ + public static Date strToDate(String dateTimeStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); return dateTime.toDate(); } - public static String dateToStr(Date date){ - if(date == null){ + /** + * 将Date对象转换为符合标准格式("yyyy-MM-dd HH:mm:ss")的日期时间字符串的方法。 + * 如果传入的Date对象为null,则返回一个空字符串。否则,先根据传入的Date对象创建一个DateTime对象,然后按照标准格式(STANDARD_FORMAT)将其转换为对应的日期时间字符串并返回。此方法适用于需要将Date对象按照通用的标准格式转换为字符串展示的场景,方便统一日期时间的字符串表示形式。 + * @param date 要转换的Date对象,如果为null则返回空字符串,例如在没有获取到有效日期数据的情况下,返回空字符串以表示无对应的数据展示。 + * @return 返回转换后的日期时间字符串,如果传入的Date对象为null则返回空字符串,否则按照标准格式返回对应的字符串表示,可用于在界面上统一展示日期时间等业务场景。 + */ + public static String dateToStr(Date date) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); return dateTime.toString(STANDARD_FORMAT); } - //Date -> 时间戳 + /** + * 将Date对象转换为时间戳(从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数)的方法。 + * 如果传入的Date对象为null,则返回null。否则,先创建一个SimpleDateFormat对象,按照标准格式("yyyy-MM-dd HH:mm:ss")来格式化Date对象,然后将其解析为一个新的Date对象,最后获取该Date对象对应的时间戳(以毫秒为单位)并返回。此方法在需要将日期时间转换为时间戳用于存储、比较或者其他与时间戳相关的业务操作场景下使用。 + * @param date 要转换的Date对象,如果为null则返回null,表示没有有效的日期数据可供转换为时间戳,例如在某些业务中,未获取到具体时间时不进行时间戳转换操作。 + * @return 返回对应的时间戳(以毫秒为单位),如果传入的Date对象为null则返回null,否则返回从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数,用于后续如数据库存储、时间顺序比较等业务逻辑处理。 + * @throws ParseException 如果在使用SimpleDateFormat解析日期时间字符串过程中出现格式不匹配等解析错误,则会抛出此异常,需要调用者进行相应的异常处理,例如在调用该方法的地方使用try-catch块捕获异常并进行合适的处理(如记录日志、返回默认值等)。 + */ public static Long dateToChuo(Date date) throws ParseException { - if(date == null){ + if (date == null) { return null; } - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.parse(String.valueOf(date)).getTime(); } + /** + * 主函数,用于简单的测试功能,在这个方法中,创建了一个SimpleDateFormat对象,按照标准格式解析一个给定的日期时间字符串为Date对象,然后输出该Date对象对应的时间戳(以毫秒为单位)。 + * 可以通过运行这个main方法来验证dateToChuo等相关时间转换方法的部分功能是否正确,不过这只是一个简单的示例测试,实际应用中可能需要更完善的单元测试等方式来全面测试工具类的功能。 + * @param args 命令行参数,这里在示例中未使用,通常可以用于传入一些外部参数来影响测试的行为或者输入不同的测试数据等情况,但在当前简单示例中没有使用到该功能。 + * @throws ParseException 如果在使用SimpleDateFormat解析日期时间字符串过程中出现格式不匹配等解析错误,则会抛出此异常,需要在调用main方法时进行相应的异常处理,例如使用try-catch块捕获异常以避免程序异常终止。 + */ public static void main(String[] args) throws ParseException { - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); - String time="1970-01-06 11:45:55"; + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String time = "1970-01-06 11:45:55"; Date date = format.parse(time); - System.out.print("Format To times:"+date.getTime()); + System.out.print("Format To times:" + date.getTime()); } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java index 12daca4..7857b54 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java @@ -8,119 +8,134 @@ import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.annotate.JsonSerialize; import org.codehaus.jackson.type.JavaType; import org.codehaus.jackson.type.TypeReference; - import java.io.IOException; import java.text.SimpleDateFormat; /** - * jackson的序列化和反序列化 + * @Author 作者相关信息(若有) + * @Date 创建日期(若有) + * @CONTACT 联系方式(若有) + * @DESC 这个类(JsonUtil)是一个工具类,主要用于处理Jackson库相关的序列化和反序列化操作。 + * 通过对Jackson的ObjectMapper进行一系列配置,使其能够按照项目需求,将Java对象转换为JSON字符串(序列化)以及把JSON字符串还原为Java对象(反序列化),并且在操作出现异常时,利用日志记录相关错误信息,方便排查问题。 */ @Slf4j public class JsonUtil { + + // 创建一个ObjectMapper对象,它是Jackson库中核心的对象,负责实际执行序列化和反序列化的具体操作,后续通过配置它的各种属性来定制序列化和反序列化的行为规则。 private static ObjectMapper objectMapper = new ObjectMapper(); + // 静态代码块,在类加载时执行,用于对ObjectMapper对象进行一系列的配置操作,使得它符合项目在序列化和反序列化方面的特定要求。 static { - //所有字段都列入进行转换 + // 设置序列化时包含的字段规则,这里指定为JsonSerialize.Inclusion.ALWAYS,表示在将Java对象序列化为JSON字符串时,无论对象的字段值是否为null,都会把所有字段都包含进转换后的JSON字符串中,确保完整地表示对象的结构和数据。 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); - //取消默认转换timestamp形式 - objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); - //忽略空bean转json的错误 - objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); - //统一时间的格式 + + // 取消默认将日期类型转换为时间戳(timestamp)形式的行为。通常情况下,Jackson默认会把日期对象转换为时间戳进行序列化,通过设置这个配置为false,就可以按照后续指定的日期格式进行序列化,更符合常见的日期展示需求。 + objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); + + // 忽略在序列化空的Java Bean(即没有属性值或者属性值都为null的对象)转JSON字符串时可能出现的错误。这样即使对象为空,也能尝试进行序列化操作,避免因为空对象而抛出异常,使得序列化过程更加健壮。 + objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false); + + // 统一设置时间的格式,通过传入一个SimpleDateFormat对象,并指定格式为DateTimeUtil类中定义的标准格式(通常是"yyyy-MM-dd HH:mm:ss"这种常见的日期时间格式),使得在序列化和反序列化涉及日期类型的对象时,都按照这个统一的格式进行处理,便于数据的一致性和可读性。 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); - //忽略json存在属性,但是java对象不存在属性的错误 - objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); + + // 忽略在反序列化JSON字符串时,如果JSON中存在的属性但对应的Java对象不存在该属性的这种错误情况。这样即使JSON数据和Java对象结构不完全匹配(例如JSON有多余的字段),也能尽量进行反序列化操作,避免因属性不匹配而直接抛出异常,增强了反序列化的兼容性。 + objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** - * 序列化方法,将对象转为字符串 - * @param obj - * @param - * @return + * 序列化方法,用于将Java对象转换为JSON字符串。 + * 如果传入的对象为null,则直接返回null。若对象本身就是String类型,则直接返回该字符串(因为本身已经符合JSON字符串的要求了),否则使用配置好的ObjectMapper对象将对象转换为JSON字符串,若在转换过程中出现IO异常,则记录警告日志,并返回null表示转换失败。 + * @param obj 要进行序列化的Java对象,其可以是任意符合Jackson序列化规则的Java类实例(例如有合适的Getter、Setter方法等),类型通过泛型参数表示,体现了方法的通用性,可处理各种类型的对象序列化需求。 + * @param 泛型参数,用于指定传入对象的类型,使得该方法可以适用于不同类型的Java对象序列化,增强了方法的通用性和灵活性。 + * @return 返回序列化后的JSON字符串,如果传入对象为null或者序列化过程出现异常则返回null,返回的字符串符合JSON格式规范,可用于网络传输、存储等需要JSON数据表示的场景。 */ - public static String obj2String(T obj){ - if(obj == null){ + public static String obj2String(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); + return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** - * 序列化方法,同上,只是输出的格式是美化的,便于测试 - * @param obj - * @param - * @return + * 序列化方法,功能与obj2String方法类似,同样是将Java对象转换为JSON字符串,但这个方法输出的JSON字符串格式是经过美化的,更便于查看和测试。 + * 例如会进行缩进、换行等格式化处理,使得JSON结构更加清晰直观。如果传入的对象为null,则直接返回null,若对象本身是String类型则直接返回该字符串,否则使用ObjectMapper的美化打印功能将对象转换为格式化后的JSON字符串,若转换出现IO异常,则记录警告日志并返回null。 + * @param obj 要进行序列化的Java对象,类型可以是任意符合Jackson序列化要求的Java类实例,通过泛型参数来体现方法对不同类型对象的通用性支持,可用于各种需要将对象转换为美观JSON格式字符串的场景,比如调试、展示示例数据等。 + * @param 泛型参数,用于指定传入对象的类型,使得方法能够处理各种类型对象的序列化并输出美化后的JSON字符串,方便在不同业务场景下根据具体需求进行使用。 + * @return 返回序列化后且经过美化的JSON字符串,如果传入对象为null或者序列化过程出现异常则返回null,返回的美化后的字符串更适合人工查看和分析JSON结构内容。 */ - public static String obj2StringPretty(T obj){ - if(obj == null){ + public static String obj2StringPretty(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** - * 比较简单的反序列化的方法,将字符串转为单个对象 - * @param str - * @param clazz - * @param - * @return + * 比较简单的反序列化方法,用于将JSON字符串转换为单个Java对象。 + * 如果传入的JSON字符串为空或者要转换的目标Java类(clazz)为null,则直接返回null。若目标类是String类型,且传入的字符串不为空,则直接将该字符串作为结果返回(因为本身就是符合要求的字符串形式了),否则使用配置好的ObjectMapper对象将JSON字符串转换为指定类型的Java对象,若转换过程中出现IO异常,则记录警告日志并返回null。 + * @param str 要进行反序列化的JSON字符串,需要符合Jackson反序列化的格式要求以及与目标Java类的结构匹配规则(根据配置可能允许一定的属性差异情况),该字符串通常来源于网络请求、文件读取等获取JSON数据的途径。 + * @param clazz 要转换生成的目标Java对象的类型,通过传入的Class对象来明确指定具体的类型,以便ObjectMapper能准确地将JSON数据转换为对应的Java对象实例,类型通过泛型参数关联,体现了方法对不同类型对象反序列化的通用性支持。 + * @param 泛型参数,用于指定目标Java对象的类型,使得该方法可以处理各种类型的JSON字符串到Java对象的反序列化需求,方便在不同业务场景下根据实际获取的JSON数据和期望的对象类型进行转换操作。 + * @return 返回反序列化后的Java对象,如果传入的JSON字符串为空、目标类为null或者转换过程出现异常则返回null,返回的Java对象可用于后续的业务逻辑处理,比如填充到业务模型中进行数据操作等。 */ - public static T String2Obj(String str,Class clazz){ - if(StringUtils.isEmpty(str) || clazz == null){ + public static T String2Obj(String str, Class clazz) { + if (StringUtils.isEmpty(str) || clazz == null) { return null; } try { - return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz); + return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** - * 复杂对象的反序列化(通用) - * @param str - * @param typeReference - * @param - * @return + * 复杂对象的反序列化(通用)方法,用于处理更复杂类型的JSON字符串到Java对象的转换,通过TypeReference来指定复杂的类型信息。 + * 这种方式适用于包含泛型、嵌套结构等复杂类型的对象反序列化场景。如果传入的JSON字符串为空或者TypeReference对象为null,则直接返回null。若TypeReference表示的类型是String类型,且传入的字符串不为空,则直接将该字符串作为结果返回,否则使用ObjectMapper对象根据TypeReference指定的复杂类型信息将JSON字符串转换为对应的Java对象,若转换过程中出现IO异常,则记录警告日志并返回null。 + * @param str 要进行反序列化的JSON字符串,需要符合对应的复杂类型结构要求以及Jackson反序列化的相关规则,通常来源于复杂数据结构的JSON表示,比如包含嵌套对象、泛型集合等的JSON数据。 + * @param typeReference TypeReference对象,用于详细指定复杂的Java对象类型信息,例如可以是List、Map等包含泛型的集合类型或者更复杂的嵌套对象类型等,使得ObjectMapper能准确地按照指定的复杂类型进行反序列化操作,泛型参数表示最终要转换生成的复杂Java对象的类型。 + * @param 泛型参数,用于指定要转换生成的复杂Java对象的类型,使得该方法可以处理各种复杂类型的JSON字符串到Java对象的反序列化需求,满足不同业务场景下对复杂数据结构的反序列化处理要求。 + * @return 返回反序列化后的复杂Java对象,如果传入的JSON字符串为空、TypeReference对象为null或者转换过程出现异常则返回null,返回的复杂Java对象可用于后续涉及复杂数据结构的业务逻辑处理,如处理嵌套的业务数据、泛型集合数据等操作。 */ - public static T Str2Obj(String str, TypeReference typeReference){ - if(StringUtils.isEmpty(str) || typeReference == null){ + public static T Str2Obj(String str, TypeReference typeReference) { + if (StringUtils.isEmpty(str) || typeReference == null) { return null; } try { - return (T) (typeReference.getType().equals(String.class)?str:objectMapper.readValue(str,typeReference)); + return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference)); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** - * 第二种方式实现复杂对象的反序列化 - * @param str - * @param collectionClass - * @param elementClasses - * @param - * @return + * 第二种方式实现复杂对象的反序列化方法,通过传入集合类(collectionClass)以及元素类(elementClasses)的Class对象来构建JavaType对象,进而指定复杂的类型结构,然后使用ObjectMapper将JSON字符串转换为对应的复杂Java对象。 + * 这种方式适用于明确知道集合类型以及集合中元素类型的复杂对象反序列化场景,例如要反序列化一个List类型的JSON数据,就可以传入List.class和User.class来构建相应的类型信息进行反序列化操作。若在转换过程中出现IO异常,则记录警告日志并返回null。 + * @param str 要进行反序列化的JSON字符串,需要符合根据传入的类型信息构建的复杂类型结构以及Jackson反序列化的相关规则,来源通常是与复杂数据结构对应的JSON表示,比如包含特定类型集合数据的JSON内容。 + * @param collectionClass 表示集合类型的Class对象,例如List.class、Set.class、Map.class等,用于指定要转换生成的复杂对象中最外层的集合类型,明确了整体的集合框架结构,类型通过泛型参数关联,体现了对不同集合类型的复杂对象反序列化的通用性支持。 + * @param elementClasses 可变参数,表示集合中元素的类型的Class对象,按照顺序依次指定集合中元素的类型,用于构建复杂的嵌套类型结构,比如对于List,第一个参数传入List.class,后面跟着传入User.class来完整描述复杂的类型信息,方便ObjectMapper准确地进行反序列化操作。 + * @param 泛型参数,用于指定要转换生成的复杂Java对象的类型,使得该方法可以处理各种基于集合和元素类型组合的复杂对象的反序列化需求,适用于不同业务场景下对特定复杂数据结构的反序列化处理,便于后续在业务逻辑中使用反序列化后的复杂对象进行相关操作。 + * @return 返回反序列化后的复杂Java对象,如果转换过程中出现IO异常则返回null,返回的复杂Java对象可用于后续涉及该复杂数据结构的业务逻辑处理,比如对集合元素进行遍历、修改等操作。 */ - public static T Str2Obj(String str,Class collectionClass,Class... elementClasses){ - JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); + public static T Str2Obj(String str, Class collectionClass, Class... elementClasses) { + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); try { - return objectMapper.readValue(str,javaType); + return objectMapper.readValue(str, javaType); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java index e6e5c8a..5583df0 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java @@ -3,9 +3,19 @@ package com.njupt.swg.common.utils; import java.security.MessageDigest; /** - * MD5加密工具类 + * @Author 作者相关信息(若有) + * @Date 创建日期(若有) + * @CONTACT 联系方式(若有) + * @DESC 这个类(MD5Util)是一个用于进行MD5加密的工具类,通过Java提供的MessageDigest类实现对输入字符串的MD5加密操作,并提供了方便的方法来获取按照UTF-8编码格式进行加密后的结果。同时内部包含了一些辅助方法用于字节数组与十六进制字符串之间的转换,以完成MD5加密过程中字节数据到最终十六进制表示形式的转换操作。 */ public class MD5Util { + + /** + * 此私有方法用于将字节数组转换为十六进制字符串表示形式。 + * 它遍历传入的字节数组,对每个字节调用byteToHexString方法将其转换为对应的十六进制字符串表示,然后将这些十六进制字符串依次拼接起来,最终返回整个字节数组对应的十六进制字符串结果,该方法主要在MD5加密过程中用于将加密后得到的字节数组转换为常见的十六进制格式的字符串,方便展示和使用加密后的结果。 + * @param b 要转换的字节数组,通常是经过MD5加密算法处理后得到的字节数据,例如MessageDigest的digest方法返回的字节数组结果,需要将其转换为十六进制字符串来作为最终的MD5加密结果展示形式。 + * @return 返回一个表示字节数组的十六进制字符串,即将字节数组中的每个字节都转换为对应的十六进制表示后拼接而成的字符串,例如加密后的字节数组[10, 20, 30]可能会转换为类似"0a141e"这样的十六进制字符串形式(具体转换结果根据字节实际值而定)。 + */ private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) @@ -14,6 +24,12 @@ public class MD5Util { return resultSb.toString(); } + /** + * 此私有方法用于将单个字节转换为十六进制字符串表示形式。 + * 首先将传入的字节转换为整数形式(如果字节值为负数,按照Java的规则会加上256进行转换为无符号整数表示),然后将这个整数分别除以16和取模16得到高4位和低4位对应的十六进制数字索引,再从预先定义的十六进制数字字符数组(hexDigits)中获取对应的十六进制字符,最后将这两个字符拼接起来,得到该字节对应的十六进制字符串表示,该方法是byteArrayToHexString方法中对每个字节进行转换的具体实现逻辑。 + * @param b 要转换的单个字节,其值范围是 -128到127(在Java中字节的表示范围),经过转换后会得到对应的十六进制字符串表示,例如字节值为10,会转换为十六进制字符串"0a"。 + * @return 返回一个长度为2的十六进制字符串,代表传入字节对应的十六进制表示形式,用于在byteArrayToHexString方法中拼接成完整的字节数组对应的十六进制字符串。 + */ private static String byteToHexString(byte b) { int n = b; if (n < 0) @@ -24,11 +40,11 @@ public class MD5Util { } /** - * 返回大写MD5 - * - * @param origin - * @param charsetname - * @return + * 此私有方法用于执行实际的MD5加密操作,并将加密结果转换为十六进制字符串返回,支持指定字符编码或者使用默认编码(当传入的字符编码为空字符串或null时)。 + * 首先创建一个MessageDigest实例,指定使用MD5算法进行加密,然后根据传入的字符编码情况,将原始字符串转换为字节数组并传入MessageDigest的digest方法进行加密,得到加密后的字节数组,最后调用byteArrayToHexString方法将字节数组转换为十六进制字符串表示形式,若过程中出现异常则返回null,并且将最终结果转换为大写形式返回,符合常见的MD5加密结果展示规范。 + * @param origin 要进行MD5加密的原始字符串,例如用户输入的密码、需要加密保存的数据等字符串内容,会经过MD5算法处理转换为加密后的十六进制字符串形式。 + * @param charsetname 用于指定将原始字符串转换为字节数组时的字符编码名称,例如"utf-8"、"gbk"等,若传入为null或者空字符串,则使用默认的平台字符编码将字符串转换为字节数组进行加密操作,可根据实际业务需求选择合适的编码格式,一般推荐使用统一的编码(如UTF-8)来确保兼容性和一致性。 + * @return 返回经过MD5加密并转换为大写的十六进制字符串结果,如果在加密过程中出现异常(例如不支持的字符编码、获取MessageDigest实例失败等情况)则返回null,该结果可用于存储加密后的密码、验证数据完整性等业务场景下作为加密后的标识使用。 */ private static String MD5Encode(String origin, String charsetname) { String resultString = null; @@ -44,16 +60,26 @@ public class MD5Util { return resultString.toUpperCase(); } + /** + * 此公共方法是对外提供的一个方便的接口,用于按照UTF-8编码格式对输入字符串进行MD5加密,并返回加密后的结果(十六进制大写字符串形式)。 + * 它内部直接调用MD5Encode方法,传入原始字符串和"utf-8"作为字符编码参数,实现对字符串使用UTF-8编码进行MD5加密操作,同时可以在这个方法内方便地添加“加盐”等额外的加密增强操作(虽然当前代码中只是简单调用MD5Encode方法,但预留了加盐的注释提示,便于后续根据安全需求进行扩展),适用于项目中统一使用UTF-8编码进行MD5加密的常见业务场景,比如对用户密码进行加密存储等情况。 + * @param origin 要进行MD5加密的原始字符串,会按照UTF-8编码格式进行加密处理,例如用户登录密码等需要加密保存的关键字符串信息。 + * @return 返回经过UTF-8编码下的MD5加密并转换为大写的十六进制字符串结果,若加密过程出现异常则返回null,该结果可用于后续的密码验证、数据安全存储等业务逻辑处理。 + */ public static String MD5EncodeUtf8(String origin) { //这里可以加盐 return MD5Encode(origin, "utf-8"); } + /** + * 主函数,用于简单的测试功能,在这个方法中调用MD5EncodeUtf8方法对字符串"123456"进行MD5加密,并将加密后的结果输出到控制台,方便在开发过程中快速验证MD5加密功能是否正常工作,不过这只是一个简单的示例测试,实际应用中可能需要更完善的单元测试等方式来全面测试工具类的功能。 + * @param args 命令行参数,这里在示例中未使用,通常可以用于传入一些外部参数来影响测试的行为或者输入不同的测试数据等情况,但在当前简单示例中没有使用到该功能。 + */ public static void main(String[] args) { System.out.println(MD5EncodeUtf8("123456")); } - + // 预先定义的十六进制数字字符数组,用于在将字节转换为十六进制字符串时,根据字节对应的十六进制数字索引获取相应的字符表示,其中包含了0到9以及a到f的十六进制数字字符,按照顺序排列,方便在byteToHexString等方法中进行十六进制转换操作。 private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/PropertiesUtil.java b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/PropertiesUtil.java index 79f8335..ece39ad 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/PropertiesUtil.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/common/utils/PropertiesUtil.java @@ -12,35 +12,58 @@ import java.util.Properties; * @Date 2018/1/10 14:56 * @DESC * @CONTACT 317758022@qq.com + * 这个类(PropertiesUtil)是一个工具类,主要用于读取配置文件(parameter.properties)中的属性值,通过静态代码块在类加载时加载配置文件,并提供了方便的方法来获取指定键对应的属性值,在获取过程中进行了一些必要的字符串处理(如去除空白字符等)以及对属性值不存在情况的处理,同时利用日志记录了配置文件读取过程中出现的异常情况,方便排查问题。 */ @Slf4j public class PropertiesUtil { + + // 用于存储从配置文件中读取的属性信息,是一个Properties对象,它以键值对的形式保存配置文件中的各项配置内容,后续通过键来获取对应的值,实现对配置信息的访问和使用。 private static Properties props; + // 静态代码块,在类加载时执行,主要用于加载配置文件(parameter.properties)到Properties对象中,以便后续可以获取其中的配置属性值。 static { + // 指定要加载的配置文件的名称,这里固定为"parameter.properties",意味着该工具类默认会从类路径下查找这个名称的配置文件进行加载操作,如果实际配置文件名称不同或者路径有变化,需要相应地修改此处。 String fileName = "parameter.properties"; + + // 创建一个新的Properties对象,用于后续存放从配置文件中读取的键值对信息,它提供了加载配置文件以及获取属性值等相关方法,是Java中处理配置文件属性的常用类。 props = new Properties(); + try { - props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8")); + // 通过类加载器获取指定配置文件(fileName)的输入流,然后使用InputStreamReader将其包装为字符流,并指定字符编码为"UTF-8",最后调用Properties对象的load方法将配置文件的内容加载到props对象中,完成配置文件的读取操作。 + // 如果在这个过程中出现IO异常(例如配置文件不存在、无法正确读取等情况),则会捕获该异常,并通过日志记录错误信息,方便后续查看和排查配置文件读取失败的原因。 + props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName), "UTF-8")); } catch (IOException e) { - log.error("配置文件读取异常",e); + log.error("配置文件读取异常", e); } } - public static String getProperty(String key){ + /** + * 此方法用于获取指定键对应的配置文件属性值。 + * 它首先从已加载的Properties对象(props)中获取指定键(去除前后空白字符后的键)对应的属性值,然后判断获取到的值是否为空(空白字符串或者null),如果为空则返回null,表示没有找到对应的配置属性值,若不为空则去除值的前后空白字符后返回,确保返回的属性值格式规范,便于后续使用。 + * @param key 要获取属性值的键,是配置文件中定义的属性名称,通过传入该键可以从配置文件中查找对应的属性值,通常用于获取如数据库连接地址、端口号、应用程序的各种参数等配置信息,调用者需要确保传入的键是配置文件中存在的有效键名,否则可能返回null。 + * @return 返回获取到的配置文件属性值,如果没有找到对应键的属性值或者属性值为空字符串则返回null,否则返回去除前后空白字符后的属性值字符串,可用于后续在业务逻辑中根据配置进行相应的操作,比如设置数据库连接参数等。 + */ + public static String getProperty(String key) { String value = props.getProperty(key.trim()); - if(StringUtils.isBlank(value)){ + if (StringUtils.isBlank(value)) { return null; } return value.trim(); } - public static String getProperty(String key,String defaultValue){ + /** + * 此方法用于获取指定键对应的配置文件属性值,与getProperty(String key)方法类似,但增加了默认值的功能。 + * 首先从已加载的Properties对象(props)中获取指定键(去除前后空白字符后的键)对应的属性值,若获取到的值为空(空白字符串或者null),则将传入的默认值(defaultValue)赋给value变量,最后去除value的前后空白字符后返回,这样即使配置文件中没有定义对应键的属性值,也能返回一个默认的配置值,方便在业务逻辑中使用配置信息时提供一个兜底的默认设置。 + * @param key 要获取属性值的键,是配置文件中定义的属性名称,通过传入该键尝试从配置文件中查找对应的属性值,若不存在则使用默认值,常用于一些有默认配置需求的场景,比如默认的日志级别、默认的缓存时间等配置参数获取。 + * @param defaultValue 当配置文件中不存在指定键对应的属性值时,要返回的默认值,由调用者根据业务需求传入相应的默认配置字符串,确保在没有配置文件定义的情况下,业务逻辑能基于默认值正常运行,例如传入"DEBUG"作为默认的日志级别等情况。 + * @return 返回获取到的配置文件属性值,如果配置文件中没有对应键的属性值则返回传入的默认值(去除前后空白字符后的默认值字符串),若配置文件中有定义则返回去除前后空白字符后的属性值字符串,用于后续在业务逻辑中依据配置进行相应的操作,比如设置系统参数等。 + */ + public static String getProperty(String key, String defaultValue) { String value = props.getProperty(key.trim()); - if(StringUtils.isBlank(value)){ + if (StringUtils.isBlank(value)) { value = defaultValue; } return value.trim(); } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/controller/BaseController.java b/snailmall-cart-service/src/main/java/com/njupt/swg/controller/BaseController.java index 9a009ed..61019f6 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/controller/BaseController.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/controller/BaseController.java @@ -10,7 +10,6 @@ import com.njupt.swg.entity.User; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; - import javax.servlet.http.HttpServletRequest; /** @@ -18,23 +17,40 @@ import javax.servlet.http.HttpServletRequest; * @Date 2019/1/5 16:19 * @CONTACT 317758022@qq.com * @DESC + * 这个类(BaseController)作为一个基础的控制器类,很可能被其他具体的业务控制器继承,用于提供一些通用的、与获取当前用户相关的基础功能。 + * 它通过依赖注入获取了CommonCacheUtil对象,用于从缓存中获取数据,并且借助CookieUtil和JsonUtil工具类,分别实现从请求中读取登录相关的Cookie以及将缓存中的JSON字符串反序列化为User对象等操作,以此来获取当前登录用户的信息,若在获取过程中出现不符合预期的情况(如未登录、缓存中无对应用户信息等),则会抛出相应的自定义异常。 */ @Slf4j public class BaseController { + // 通过Spring的依赖注入机制,自动注入CommonCacheUtil对象,这个对象通常用于和缓存进行交互,比如从缓存中获取存储的用户相关数据等操作,方便在后续获取当前用户信息时使用缓存来提高效率、减少数据库查询等操作。 @Autowired private CommonCacheUtil commonCacheUtil; - User getCurrentUser(HttpServletRequest httpServletRequest){ + /** + * 此方法用于从HTTP请求中获取当前登录用户的信息,是整个获取当前用户逻辑的核心方法。 + * 首先通过CookieUtil工具类从传入的HttpServletRequest对象中读取登录相关的Cookie值(登录令牌,通常用于标识用户登录状态),若读取到的登录令牌为空字符串或者null,意味着用户未登录,此时会抛出一个SnailmallException异常,提示用户未登录无法获取当前用户信息。 + * 接着,如果成功获取到登录令牌,就使用CommonCacheUtil对象根据这个令牌去缓存中获取对应的用户信息的JSON字符串表示形式,若获取到的用户信息JSON字符串为null,表示缓存中不存在该用户的相关信息,可能是登录状态异常等原因,此时会抛出一个带有特定状态码(ResponseEnum.NEED_LOGIN.getCode())和对应描述信息(ResponseEnum.NEED_LOGIN.getDesc())的SnailmallException异常,提示需要登录。 + * 最后,如果从缓存中成功获取到了用户信息的JSON字符串,就利用JsonUtil工具类将这个JSON字符串反序列化为User对象并返回,完成获取当前用户信息的操作,该User对象可用于后续的业务逻辑处理,比如根据用户权限进行不同的操作、展示用户相关数据等情况。 + * @param httpServletRequest 代表客户端发送过来的HTTP请求对象,包含了请求的各种信息,例如请求头、请求参数以及这里用到的Cookie信息等,通过这个对象可以获取到用于判断用户登录状态的登录令牌等关键信息,是整个获取当前用户操作的基础数据来源。 + * @return 返回一个User对象,代表当前登录的用户信息,这个对象包含了如用户名、密码、用户角色等各种与用户相关的属性信息,后续可以在继承了BaseController的具体业务控制器中使用该用户对象进行相应的业务逻辑处理。 + * @throws SnailmallException 如果在获取当前用户信息的过程中出现用户未登录或者缓存中不存在用户信息等不符合预期的情况,会抛出这个自定义异常,由更上层的异常处理机制(比如全局异常处理类)进行统一处理,向客户端返回相应的错误提示信息,告知用户出现的问题情况。 + */ + User getCurrentUser(HttpServletRequest httpServletRequest) { + // 从请求中读取登录令牌(Cookie中的值),用于后续判断用户是否登录以及从缓存中获取对应的用户信息,若返回为空则表示没有找到登录相关的Cookie,即用户未登录。 String loginToken = CookieUtil.readLoginToken(httpServletRequest); - if(StringUtils.isEmpty(loginToken)){ + if (StringUtils.isEmpty(loginToken)) { throw new SnailmallException("用户未登陆,无法获取当前用户信息"); } + + // 根据获取到的登录令牌,从缓存中获取对应的用户信息的JSON字符串表示形式,缓存中通常会预先存储用户登录成功后放入的相关用户信息,方便后续快速获取,若返回为null则表示缓存中不存在该用户信息,可能是登录状态失效等原因导致。 String userJsonStr = commonCacheUtil.getCacheValue(loginToken); - if(userJsonStr == null){ - throw new SnailmallException(ResponseEnum.NEED_LOGIN.getCode(),ResponseEnum.NEED_LOGIN.getDesc()); + if (userJsonStr == null) { + throw new SnailmallException(ResponseEnum.NEED_LOGIN.getCode(), ResponseEnum.NEED_LOGIN.getDesc()); } - User user = JsonUtil.Str2Obj(userJsonStr,User.class); + + // 利用JsonUtil工具类将从缓存中获取到的用户信息的JSON字符串反序列化为User对象,使得可以在后续业务逻辑中直接使用User对象的属性和方法进行相关操作,比如获取用户权限、展示用户详情等操作,完成获取当前用户信息的关键步骤。 + User user = JsonUtil.Str2Obj(userJsonStr, User.class); return user; } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/controller/CartController.java b/snailmall-cart-service/src/main/java/com/njupt/swg/controller/CartController.java index 259c168..aa8e659 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/controller/CartController.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/controller/CartController.java @@ -1,19 +1,19 @@ package com.njupt.swg.controller; -import com.njupt.swg.cache.CommonCacheUtil; -import com.njupt.swg.common.constants.Constants; -import com.njupt.swg.common.resp.ResponseEnum; -import com.njupt.swg.common.resp.ServerResponse; -import com.njupt.swg.common.utils.JsonUtil; -import com.njupt.swg.entity.User; -import com.njupt.swg.service.ICartService; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; -import java.util.Enumeration; + import com.njupt.swg.cache.CommonCacheUtil; + import com.njupt.swg.common.constants.Constants; + import com.njupt.swg.common.resp.ResponseEnum; + import com.njupt.swg.common.resp.ServerResponse; + import com.njupt.swg.common.utils.JsonUtil; + import com.njupt.swg.entity.User; + import com.njupt.swg.service.ICartService; + import org.apache.commons.lang3.StringUtils; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RestController; + + import javax.servlet.http.HttpServletRequest; + import java.util.Enumeration; /** * @Author swg. @@ -23,168 +23,277 @@ import java.util.Enumeration; */ @RestController @RequestMapping("/cart") -public class CartController extends BaseController{ +// CartController类继承自BaseController类,这意味着它可以继承BaseController中定义的通用方法和属性, +// 例如获取当前用户信息的相关逻辑等,主要负责处理购物车模块相关的各种业务请求,是购物车功能在后端的具体控制器实现。 +public class CartController extends BaseController { + + // 通过Spring框架的依赖注入功能,自动将实现了ICartService接口的实例注入到这个属性中。 + // ICartService接口应该定义了一系列用于处理购物车业务逻辑的方法,例如添加商品、更新商品数量等操作对应的方法, + // 在本类的各个请求处理方法中会调用该服务层接口的方法来完成具体的业务功能。 @Autowired private ICartService cartService; + + // 同样通过Spring的依赖注入机制,注入CommonCacheUtil实例。 + // CommonCacheUtil通常用于与缓存系统进行交互,比如从缓存中获取用户相关的数据等操作, + // 在某些购物车相关业务处理中(例如需要获取缓存中的用户信息来确定购物车归属等情况)会起到重要作用。 @Autowired private CommonCacheUtil commonCacheUtil; /** - * 1.购物车添加商品 - * 超过数量会返回这样的标识"limitQuantity" - * 失败的:LIMIT_NUM_FAIL 成功的:LIMIT_NUM_SUCCESS + * 1. 购物车添加商品方法 + * 此方法用于处理客户端发起的向购物车中添加商品的HTTP请求。 + * 在业务逻辑中,如果添加的商品数量超过了预先设定的限制数量,将会返回一个特定标识"limitQuantity"给客户端, + * 同时,定义了表示添加操作失败和成功的两个不同标识,分别为LIMIT_NUM_FAIL和LIMIT_NUM_SUCCESS,方便客户端依据返回结果进行相应展示或后续处理。 + * 该方法首先调用父类BaseController中的getCurrentUser方法来获取当前已经登录的用户信息, + * 接着将获取到的用户的唯一标识符(user.getId())、客户端传递过来的要添加商品的唯一标识符(productId)以及商品的添加数量(count)这三个参数传递给cartService的add方法, + * 最后返回由cartService处理完添加业务逻辑后生成的ServerResponse对象,该对象封装了此次添加操作的相关结果信息,包括操作状态码、提示消息以及可能的其他业务相关数据等,供客户端了解操作执行情况。 + * + * @param httpServletRequest 这个参数代表了客户端发送过来的HTTP请求对象,它包含了请求的诸多信息,比如请求头、请求参数等内容。 + * 在本方法中,一方面可以从中获取客户端传递过来的productId和count参数,另一方面通过调用父类方法利用其中携带的信息(如Cookie等)来确定当前登录用户是谁,是整个添加商品操作的数据来源和请求上下文基础。 + * @param productId 表示要添加到购物车中的商品的唯一标识符,其类型为整数类型(Integer),由客户端在发起请求时作为参数传入。 + * 购物车相关业务逻辑(例如在cartService的add方法中)会依据这个标识符去查找商品的详细信息、验证商品是否满足添加条件等,以此来决定是否能够成功将该商品添加到购物车中。 + * @param count 代表要添加到购物车的商品数量,同样是整数类型。客户端指定要添加的该商品的具体数量, + * 在服务端的业务逻辑里会根据系统设定的规则(比如库存数量限制、购物车对同一种商品数量上限等规则)来判断这个数量是否合法,进而确定添加操作能否成功执行。 + * @return 返回一个ServerResponse对象,该对象是服务端对此次添加商品操作的整体响应封装。 + * 如果添加操作成功,其中会包含表示成功的状态码(例如可能对应LIMIT_NUM_SUCCESS相关的状态码)、提示添加成功的消息以及可能的其他相关数据(比如添加后购物车最新的商品详情等信息); + * 若添加操作失败,会包含相应的失败状态码(如LIMIT_NUM_FAIL对应的状态码)、提示失败原因的消息等内容,客户端可以根据这个返回对象来判断添加商品操作是否成功,并相应地展示提示信息给用户或者进行其他后续处理。 */ @RequestMapping("add.do") - public ServerResponse add(HttpServletRequest httpServletRequest, Integer productId, Integer count){ + public ServerResponse add(HttpServletRequest httpServletRequest, Integer productId, Integer count) { User user = getCurrentUser(httpServletRequest); - return cartService.add(user.getId(),productId,count); + return cartService.add(user.getId(), productId, count); } /** - * 2.更新购物车某个产品数量 - * 超过数量会返回这样的标识"limitQuantity" - * 失败的:LIMIT_NUM_FAIL 成功的:LIMIT_NUM_SUCCESS + * 2. 更新购物车某个产品数量方法 + * 用于处理更新购物车中某个已有商品数量的HTTP请求。 + * 当更新后的商品数量超出了业务规则所允许的数量范围时,会返回特定标识"limitQuantity"给客户端, + * 并且同样设定了表示操作失败和成功的标识,分别是LIMIT_NUM_FAIL和LIMIT_NUM_SUCCESS,便于客户端根据返回情况做出合适的响应和处理。 + * 本方法先借助父类的方法获取当前登录用户的信息,随后将用户的唯一标识符(user.getId())、要更新数量的商品的唯一标识符(productId)以及新的商品数量(count)传递给cartService的update方法, + * 由cartService去执行具体的更新商品数量的业务逻辑,最后返回处理后的ServerResponse对象,告知客户端更新操作的执行结果。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,通过它能够获取请求中的各种关键信息,例如请求参数以及借助父类方法利用其中的相关信息来确定当前登录用户,是整个更新商品数量操作的请求基础,为后续操作提供了必要的数据支撑。 + * @param productId 要更新数量的商品在整个系统中的唯一标识符,是整数类型参数,由客户端在请求时传入。 + * cartService会依据这个productId在购物车中准确找到对应的商品记录,进而去验证新传入的商品数量(count)是否符合业务规则(如不能超过库存、购物车单品最大数量限制等),并执行相应的更新数据库等业务操作。 + * @param count 代表要更新为的商品数量,为整数类型,是客户端指定的该商品在购物车中将要更新到的新数量值。 + * 服务端的业务逻辑会根据系统既定的各种规则来检查这个数量是否合法合规,以此来判定更新操作能否顺利完成,并返回相应的操作结果给客户端。 + * @return 返回一个ServerResponse对象,该对象包含了此次更新购物车商品数量操作的结果信息。 + * 如果更新操作成功,里面会包含表示成功的状态码(类似LIMIT_NUM_SUCCESS对应的状态码)、提示更新成功的消息以及可能涉及的其他相关数据(例如更新后购物车中该商品的最新数量情况等信息); + * 要是更新操作失败,则会包含相应的失败状态码(如LIMIT_NUM_FAIL对应的状态码)、提示失败原因的消息等内容,客户端可以根据此返回对象来判断更新操作是否成功,并进行相应的展示提示信息或后续处理工作。 */ @RequestMapping("update.do") - public ServerResponse update(HttpServletRequest httpServletRequest,Integer productId,Integer count){ + public ServerResponse update(HttpServletRequest httpServletRequest, Integer productId, Integer count) { User user = getCurrentUser(httpServletRequest); - return cartService.update(user.getId(),productId,count); + return cartService.update(user.getId(), productId, count); } /** - * 3.移除购物车某个产品 + * 3. 移除购物车某个产品方法 + * 此方法用于处理从购物车中移除特定商品的HTTP请求。 + * 首先会调用父类的方法获取当前登录用户的信息,然后把获取到的用户的唯一标识符(user.getId())以及表示要移除商品的相关标识信息(productIds)传递给cartService的delete方法, + * 这里的productIds参数为字符串类型,其内容格式可能根据业务需求而定,比如可以是单个商品的唯一标识符,也可能是多个商品的唯一标识符按照某种特定的分隔规则拼接而成的字符串, + * cartService会依据具体的业务逻辑对这个productIds进行解析和处理,执行相应的从购物车中移除商品的操作,最后返回ServerResponse对象告知客户端移除操作的执行结果。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,它承载了请求的相关信息,在本方法中主要用于获取客户端传入的要移除商品的productIds参数,同时借助父类方法获取当前登录用户信息,是整个移除商品操作的请求依据和数据来源。 + * @param productIds 表示要从购物车中移除的商品的标识信息,其类型为字符串类型(String),具体内容格式根据业务设计而定, + * 可能是单个商品的ID(如"123"代表某个商品),也可能是多个商品ID按照一定分隔符拼接的形式(如"123,456,789"代表要移除多个商品),cartService会按照既定的业务逻辑对其进行解析和处理,完成从购物车中移除对应商品的操作,并返回相应的结果。 + * @return 返回一个ServerResponse对象,该对象封装了移除商品操作的结果信息,比如操作成功时会包含表示成功的状态码、提示移除成功的消息以及可能相关的数据(例如移除后购物车剩余商品的情况等信息); + * 若操作失败,则包含相应的失败状态码、提示失败原因的消息等内容,客户端可以根据这个返回对象判断移除操作是否成功,并据此进行相应的展示提示信息或者后续处理等工作。 */ @RequestMapping("delete_product.do") - public ServerResponse delete_product(HttpServletRequest httpServletRequest,String productIds){ + public ServerResponse delete_product(HttpServletRequest httpServletRequest, String productIds) { User user = getCurrentUser(httpServletRequest); - return cartService.delete(user.getId(),productIds); + return cartService.delete(user.getId(), productIds); } /** - * 4.购物车List列表 - * 价格的单位是元,保留小数后2位 + * 4. 购物车List列表方法 + * 用于处理客户端发起的获取购物车商品列表的HTTP请求。 + * 该方法先通过调用父类的方法获取当前登录用户的信息,然后将获取到的用户的唯一标识符(user.getId())传递给cartService的list方法, + * cartService的list方法会去查询数据库或者其他数据源,获取该用户购物车中所有商品的详细信息,并整理成列表形式返回,返回的结果会封装在ServerResponse对象中, + * 同时要求商品价格信息的单位是元,并且保留小数点后2位,这样可以保证返回给客户端的购物车商品列表数据格式规范、便于展示给用户查看购物车中的商品详情以及总价等信息。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,通过它可以借助父类方法获取当前登录用户信息,以此来确定具体是哪个用户的购物车商品列表需要获取,是整个获取购物车列表操作的请求基础,为后续的查询操作提供了关键的数据依据。 + * @return 返回一个ServerResponse对象,该对象包含了操作的状态码、提示消息以及最重要的购物车商品列表数据。 + * 其中购物车商品列表数据部分要求商品价格按照规定格式展示,即单位为元且保留2位小数,客户端接收到这个对象后,可以根据其中的数据将购物车中的商品详情、总价等信息展示给用户,方便用户直观地查看购物车内容。 */ @RequestMapping("list.do") - public ServerResponse list(HttpServletRequest httpServletRequest){ + public ServerResponse list(HttpServletRequest httpServletRequest) { User user = getCurrentUser(httpServletRequest); return cartService.list(user.getId()); } - /** * 5.购物车全选 */ + /** + * 5. 购物车全选方法 + * 此方法用于处理将购物车中所有商品全部设置为选中状态的HTTP请求。 + * 首先,它会调用父类BaseController中的`getCurrentUser`方法,从传入的`httpServletRequest`中获取当前登录用户的信息, + * 这是因为后续操作需要明确是哪个用户的购物车进行全选操作,确保操作的针对性和数据准确性。 + * 然后,将获取到的用户的唯一标识符(`user.getId()`)、表示选中状态的常量(`Constants.Cart.CHECKED`)以及`null`(在这里表示不需要指定特定商品,因为是对所有商品进行操作)作为参数传递给`cartService`的`selectOrUnSelect`方法, + * 由`cartService`去执行具体的将购物车所有商品设置为选中状态的业务逻辑,例如更新数据库中对应购物车商品记录的选中标识等操作,最后返回`ServerResponse`对象告知客户端全选操作的执行结果。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,它包含了请求的各种信息,通过调用父类方法利用其中的相关信息(如Cookie等)来确定当前登录用户,是整个购物车全选操作的请求基础,为后续的业务操作提供必要的数据依据。 + * @return 返回一个`ServerResponse`对象,该对象包含了购物车全选操作的结果信息,例如操作成功时会包含表示成功的状态码、提示全选操作成功的消息等内容,方便客户端根据返回结果判断操作是否成功,并进行相应的后续处理(比如更新界面上购物车商品的选中状态显示等)。 + */ @RequestMapping("select_all.do") - public ServerResponse select_all(HttpServletRequest httpServletRequest){ + public ServerResponse select_all(HttpServletRequest httpServletRequest) { User user = getCurrentUser(httpServletRequest); - return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED,null); + return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED, null); } /** - * 6.购物车全不选 + * 6. 购物车全不选方法 + * 用于处理将购物车中所有商品全部取消选中(设置为未选中状态)的HTTP请求。 + * 同样先调用父类的`getCurrentUser`方法从`httpServletRequest`中获取当前登录用户信息,以明确是针对哪个用户的购物车进行操作。 + * 接着把用户的唯一标识符(`user.getId()`)、表示未选中状态的常量(`Constants.Cart.UN_CHECKED`)以及`null`(因为是对所有商品操作,无需指定特定商品)传递给`cartService`的`selectOrUnSelect`方法, + * 由`cartService`执行将购物车所有商品设置为未选中状态的业务逻辑,比如更新数据库中购物车商品记录的选中标识字段等,最后返回`ServerResponse`对象告知客户端全不选操作的执行结果。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,通过它可以获取请求相关信息并借助父类方法确定当前登录用户,是整个购物车全不选操作的请求基础,为后续业务逻辑执行提供了关键的数据支撑。 + * @return 返回一个`ServerResponse`对象,包含了购物车全不选操作的结果信息,如操作成功的状态码、提示全不选操作成功的消息等内容,方便客户端根据返回结果判断操作是否成功,并据此进行相应的后续处理(例如更新界面上购物车商品的选中状态显示等)。 */ @RequestMapping("un_select_all.do") - public ServerResponse un_select_all(HttpServletRequest httpServletRequest){ + public ServerResponse un_select_all(HttpServletRequest httpServletRequest) { User user = getCurrentUser(httpServletRequest); - return cartService.selectOrUnSelect(user.getId(),Constants.Cart.UN_CHECKED,null); + return cartService.selectOrUnSelect(user.getId(), Constants.Cart.UN_CHECKED, null); } /** - * 7.购物车选中某个商品 + * 7. 购物车选中某个商品方法 + * 此方法用于处理将购物车中某个特定商品设置为选中状态的HTTP请求。 + * 先是通过调用父类的`getCurrentUser`方法从`httpServletRequest`获取当前登录用户信息,确保知晓是哪个用户的购物车中的商品要进行选中操作。 + * 然后将用户的唯一标识符(`user.getId()`)、表示选中状态的常量(`Constants.Cart.CHECKED`)以及客户端传入的要选中商品的唯一标识符(`productId`)传递给`cartService`的`selectOrUnSelect`方法, + * `cartService`会依据传入的商品标识符在对应的购物车记录中找到该商品,并执行将其设置为选中状态的业务逻辑,比如更新数据库中该商品记录的选中标识字段,最后返回`ServerResponse`对象告知客户端选中该商品操作的执行结果。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,它承载了请求的相关信息,一方面可借助父类方法获取当前登录用户信息,另一方面从中获取客户端传入的要选中商品的`productId`参数,是整个购物车选中某个商品操作的请求基础,提供了必要的数据来源。 + * @param productId 要在购物车中设置为选中状态的商品的唯一标识符,是整数类型参数,由客户端在请求时传入,用于明确具体是哪个商品需要被设置为选中状态,`cartService`会根据这个标识符准确找到购物车中对应的商品记录并进行相应的选中状态设置操作。 + * @return 返回一个`ServerResponse`对象,包含了购物车选中某个商品操作的结果信息,如操作成功的状态码、提示选中操作成功的消息等内容,方便客户端根据返回结果判断操作是否成功,并进行相应的后续处理(比如更新界面上购物车商品的选中状态显示等)。 */ @RequestMapping("select.do") - public ServerResponse select(HttpServletRequest httpServletRequest,Integer productId){ + public ServerResponse select(HttpServletRequest httpServletRequest, Integer productId) { User user = getCurrentUser(httpServletRequest); - return cartService.selectOrUnSelect(user.getId(),Constants.Cart.CHECKED,productId); + return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED, productId); } /** - * 8.购物车取消选中某个商品 + * 8. 购物车取消选中某个商品方法 + * 用于处理将购物车中某个特定商品取消选中(设置为未选中状态)的HTTP请求。 + * 首先调用父类的`getCurrentUser`方法从`httpServletRequest`中获取当前登录用户信息,明确操作对应的用户购物车。 + * 接着将用户的唯一标识符(`user.getId()`)、表示未选中状态的常量(`Constants.Cart.UN_CHECKED`)以及客户端传入的要取消选中商品的唯一标识符(`productId`)传递给`cartService`的`selectOrUnSelect`方法, + * `cartService`会依据`productId`找到购物车中对应的商品记录,并执行将其设置为未选中状态的业务逻辑,例如更新数据库中该商品记录的选中标识字段,最后返回`ServerResponse`对象告知客户端取消选中该商品操作的执行结果。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,通过它能获取当前登录用户信息以及客户端传入的要取消选中商品的`productId`参数,是整个购物车取消选中某个商品操作的请求基础,为后续业务逻辑执行提供了关键的数据依据。 + * @param productId 要在购物车中设置为未选中状态的商品的唯一标识符,整数类型参数,由客户端请求传入,用于准确指定需要取消选中的商品,`cartService`会根据这个标识符定位到对应的商品记录并进行相应的状态更新操作。 + * @return 返回一个`ServerResponse`对象,包含了购物车取消选中某个商品操作的结果信息,如操作成功的状态码、提示取消选中操作成功的消息等内容,方便客户端根据返回结果判断操作是否成功,并进行相应的后续处理(比如更新界面上购物车商品的选中状态显示等)。 */ @RequestMapping("un_select.do") - public ServerResponse un_select(HttpServletRequest httpServletRequest,Integer productId){ + public ServerResponse un_select(HttpServletRequest httpServletRequest, Integer productId) { User user = getCurrentUser(httpServletRequest); - return cartService.selectOrUnSelect(user.getId(),Constants.Cart.UN_CHECKED,productId); + return cartService.selectOrUnSelect(user.getId(), Constants.Cart.UN_CHECKED, productId); } - /** - * 9.查询在购物车里的产品数量 + * 9. 查询在购物车里的产品数量方法 + * 用于处理查询购物车中商品总数量的HTTP请求。 + * 先通过调用父类的`getCurrentUser`方法从`httpServletRequest`获取当前登录用户信息,因为需要明确是查询哪个用户购物车中的商品数量。 + * 然后将获取到的用户的唯一标识符(`user.getId()`)传递给`cartService`的`get_cart_product_count`方法, + * `cartService`的`get_cart_product_count`方法会去查询相关数据源(如数据库中对应购物车表记录等),统计出该用户购物车中商品的总数量,最后返回包含商品数量信息的`ServerResponse`对象给客户端,告知客户端购物车中商品的数量情况。 + * 这里`ServerResponse`中的泛型``表示返回的数据部分是一个整数类型,即购物车中商品的数量,同时`ServerResponse`对象还包含了操作的状态码、提示消息等信息,方便客户端根据返回结果判断操作是否成功以及获取具体的商品数量数值,用于展示给用户或者进行其他相关的业务逻辑处理。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,通过它可以借助父类方法获取当前登录用户信息,以此确定要查询哪个用户购物车中的商品数量,是整个查询购物车商品数量操作的请求基础,为后续的查询统计操作提供了必要的数据依据。 + * @return 返回一个`ServerResponse`对象,其中包含了操作的状态码、提示消息以及表示购物车中商品数量的整数类型数据,客户端可以根据这个返回对象判断操作是否成功,并获取购物车商品数量的具体数值进行展示或其他相关业务处理。 */ @RequestMapping("get_cart_product_count.do") - public ServerResponse get_cart_product_count(HttpServletRequest httpServletRequest){ + public ServerResponse get_cart_product_count(HttpServletRequest httpServletRequest) { User user = getCurrentUser(httpServletRequest); return cartService.get_cart_product_count(user.getId()); } - /** - * 提供的feign接口,根据userId获取购物车列表 + * 提供的feign接口,根据userId获取购物车列表方法 + * 此方法用于处理通过Feign客户端调用的获取购物车列表的HTTP请求。 + * 首先,初始化一个`User`对象为`null`,用于后续存储从缓存中反序列化得到的用户信息。 + * 然后,通过`httpServletRequest`获取请求头的名称枚举(`headerNames`),如果这个枚举不为`null`,说明存在请求头信息,接着就遍历这些请求头名称。 + * 当遍历到名称为(忽略大小写)"snailmall_login_token"的请求头时,获取其对应的值(`value`),如果这个值为空字符串(即`StringUtils.isBlank(value)`为`true`),说明用户未登录或者登录信息不完整,此时直接返回一个表示需要登录的`ServerResponse`对象给客户端,提示用户未登陆,无法获取当前用户信息。 + * 若获取到的请求头值不为空,则尝试从缓存中获取对应的用户信息,通过`commonCacheUtil`的`getCacheValue`方法,以请求头的值作为缓存键去获取对应的用户信息的JSON字符串(`userJsonStr`),如果获取到的JSON字符串为`null`,同样意味着可能用户登录状态异常或者缓存中不存在相应信息,也返回表示需要登录的`ServerResponse`对象。 + * 若成功获取到用户信息的JSON字符串,就使用`JsonUtil`工具类的`Str2Obj`方法将其反序列化为`User`对象,赋值给之前初始化的`user`变量。 + * 最后,如果`user`对象不为`null`,说明获取用户信息成功,就将用户的唯一标识符(`user.getId()`)传递给`cartService`的`list`方法获取购物车列表信息,并返回包含购物车列表数据的`ServerResponse`对象给客户端,告知客户端操作结果;若`user`对象为`null`,还是返回表示需要登录的`ServerResponse`对象告知客户端用户未登录情况。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,通过它获取请求头信息,从中查找登录相关的请求头来判断用户登录状态以及获取用户信息,是整个通过Feign接口获取购物车列表操作的请求基础,提供了关键的数据来源。 + * @return 返回一个`ServerResponse`对象,包含了操作的状态码、提示消息以及购物车列表数据等信息,若操作成功则客户端可以根据这个对象展示购物车中的商品详情等信息给用户,若操作失败(如用户未登录)则会返回相应的错误提示信息告知客户端需要先登录。 */ @RequestMapping("getCartList.do") - public ServerResponse getCartList(HttpServletRequest httpServletRequest){ + public ServerResponse getCartList(HttpServletRequest httpServletRequest) { User user = null; Enumeration headerNames = httpServletRequest.getHeaderNames(); - if (headerNames != null) { + if (headerNames!= null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); - if(name.equalsIgnoreCase("snailmall_login_token")){ + if (name.equalsIgnoreCase("snailmall_login_token")) { String value = httpServletRequest.getHeader(name); - if(StringUtils.isBlank(value)){ - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息"); + if (StringUtils.isBlank(value)) { + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息"); } String userJsonStr = commonCacheUtil.getCacheValue(value); - if(userJsonStr == null){ - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息"); + if (userJsonStr == null) { + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息"); } - user = JsonUtil.Str2Obj(userJsonStr,User.class); + user = JsonUtil.Str2Obj(userJsonStr, User.class); } } } - if (user == null){ - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息"); + if (user == null) { + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息"); } return cartService.list(user.getId()); } /** - * 提供的feign接口,清空购物车 + * 提供的feign接口,清空购物车方法 + * 此方法用于处理通过Feign客户端调用的清空购物车的HTTP请求。 + * 同样先初始化一个`User`对象为`null`,用于后续存储获取到的用户信息。 + * 接着获取请求头的名称枚举(`headerNames`),若不为`null`则遍历这些请求头名称,查找名为(忽略大小写)"snailmall_login_token"的请求头,获取其对应的值(`value`)。 + * 如果这个值为空字符串,说明用户未登录或登录信息有问题,直接返回表示需要登录的`ServerResponse`对象给客户端,提示用户未登陆,无法获取当前用户信息。 + * 若获取到的值不为空,就通过`commonCacheUtil`从缓存中获取对应的用户信息的JSON字符串(`userJsonStr`),若获取的JSON字符串为`null`,也返回表示需要登录的`ServerResponse`对象,因为这意味着用户登录状态异常或者缓存中无对应信息。 + * 当成功获取到用户信息的JSON字符串后,使用`JsonUtil`的`Str2Obj`方法将其反序列化为`User`对象,赋值给`user`变量。 + * 最后,如果`user`对象不为`null`,表明获取用户信息成功,就将用户的唯一标识符(`user.getId()`)传递给`cartService`的`removeCart`方法执行清空购物车的业务逻辑,然后返回`ServerResponse`对象告知客户端清空购物车操作的执行结果;若`user`对象为`null`,则返回表示需要登录的`ServerResponse`对象告知客户端用户未登录情况。 + * + * @param httpServletRequest 代表客户端发送的HTTP请求对象,通过它获取请求头信息来判断用户登录状态以及获取用户信息,是整个通过Feign接口清空购物车操作的请求基础,提供了关键的数据来源。 + * @return 返回一个`ServerResponse`对象,包含了操作的状态码、提示消息以及清空购物车操作的结果信息等,若操作成功客户端可以根据返回结果知晓购物车已清空等情况,若操作失败(如用户未登录)则会收到相应的错误提示信息告知需要先登录。 */ @RequestMapping("removeCart.do") - public ServerResponse removeCart(HttpServletRequest httpServletRequest){ + public ServerResponse removeCart(HttpServletRequest httpServletRequest) { User user = null; Enumeration headerNames = httpServletRequest.getHeaderNames(); - if (headerNames != null) { + if (headerNames!= null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); - if(name.equalsIgnoreCase("snailmall_login_token")){ + if (name.equalsIgnoreCase("snailmall_login_token")) { String value = httpServletRequest.getHeader(name); - if(StringUtils.isBlank(value)){ - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息"); + if (StringUtils.isBlank(value)) { + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息"); } String userJsonStr = commonCacheUtil.getCacheValue(value); - if(userJsonStr == null){ - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息"); + if (userJsonStr == null) { + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息"); } - user = JsonUtil.Str2Obj(userJsonStr,User.class); + user = JsonUtil.Str2Obj(userJsonStr, User.class); } } } - if (user == null){ - return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息"); + if (user == null) { + return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息"); } return cartService.removeCart(user.getId()); } - - - -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/dao/CartMapper.java b/snailmall-cart-service/src/main/java/com/njupt/swg/dao/CartMapper.java index fbe0ea0..20e415c 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/dao/CartMapper.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/dao/CartMapper.java @@ -3,34 +3,64 @@ package com.njupt.swg.dao; import com.njupt.swg.entity.Cart; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; - import java.util.List; +// @Mapper注解用于标识这个接口是MyBatis的Mapper接口,MyBatis框架会自动扫描并生成该接口的代理实现类, +// 从而可以与数据库进行交互,执行对应的SQL操作,这个接口主要定义了针对购物车(Cart)相关数据的数据库操作方法。 @Mapper public interface CartMapper { + + // 根据购物车记录的主键(通常是一个唯一标识该记录的整数型ID)删除对应的购物车记录,返回值为受影响的行数, + // 如果成功删除一条记录,返回值通常为1,如果没有匹配的记录可删除,则返回0。 int deleteByPrimaryKey(Integer id); + // 向数据库中插入一条完整的购物车记录(Cart对象),这个方法会将Cart对象中的所有属性对应的字段值插入到数据库表中, + // 返回值同样为受影响的行数,若插入成功一般返回1,表示成功插入了一条记录。 int insert(Cart record); + // 插入购物车记录,但允许只插入Cart对象中部分非空的属性对应的字段值到数据库表中, + // 相比insert方法更加灵活,当Cart对象某些属性为null时,只会插入那些非null属性对应的字段,返回值为受影响的行数。 int insertSelective(Cart record); + // 根据购物车记录的主键(id)从数据库中查询并返回对应的购物车记录(以Cart对象形式返回), + // 如果找不到对应的记录,则返回null。 Cart selectByPrimaryKey(Integer id); + // 根据购物车记录的主键(id)来有选择性地更新购物车记录,只会更新Cart对象中那些非null属性对应的数据库字段值, + // 返回值为受影响的行数,用于指示实际更新了几条记录,若没有任何字段值被更新(比如传入的Cart对象属性都是null或者和数据库中原有值相同),则返回0。 int updateByPrimaryKeySelective(Cart record); + // 根据购物车记录的主键(id)更新购物车记录,会将Cart对象中的所有属性对应的字段值更新到数据库表中对应的记录里, + // 不管属性值是否为null,返回值为受影响的行数,用来表示实际更新了几条记录。 int updateByPrimaryKey(Cart record); + // 根据用户的ID(userId)和商品的ID(productId)联合查询购物车中对应的记录, + // 通过@Param注解明确指定了参数的名称,方便在对应的SQL语句中使用这些参数进行准确的条件查询, + // 返回值是匹配条件的购物车记录(以Cart对象形式返回),若找不到符合条件的记录则返回null。 Cart selectByUserIdProductId(@Param("userId") Integer userId, @Param("productId") Integer productId); + // 根据用户的ID(userId)查询该用户购物车中的所有记录,返回一个包含多个Cart对象的List集合, + // 如果该用户没有购物车记录,则返回一个空的List集合。 List selectCartByUserId(Integer userId); + // 根据用户的ID(userId)查询该用户购物车中已选中商品的状态数量(具体含义可能根据业务逻辑而定,比如返回已选中商品的记录条数等), + // 返回一个整数类型的结果,例如返回已选中商品的数量或者代表选中状态的某种统计数值等。 int selectCartCheckedStatusByUserId(Integer userId); + // 根据用户的ID(userId)以及要删除商品的ID列表(productIdList)从购物车中删除对应的商品记录, + // 通过@Param注解分别指定了参数名称,便于在对应的SQL语句中准确使用这些参数进行批量删除操作, + // 返回值为受影响的行数,代表实际删除了几条记录。 int deleteByProductIds(@Param("userId") Integer userId, @Param("productIdList") List productIdList); + // 根据用户的ID(userId)、商品的选中状态(checked,可能是用整数表示选中或未选中,比如1表示选中,0表示未选中等,具体由业务定义)以及商品的ID(productId), + // 来设置商品在购物车中的选中或未选中状态,此方法通常用于更新购物车中某个商品的选中情况,无返回值(void类型), + // 对应的SQL操作会根据传入的参数对数据库表中的相应字段进行更新操作。 void selectOrUnSelectProduct(@Param("userId") Integer userId, @Param("checked") int checked, @Param("productId") Integer productId); + // 根据用户的ID(userId)查询该用户购物车中商品的总数量,返回一个整数类型的结果,用于统计该用户购物车中一共有多少件商品。 int selectCartProductCount(Integer userId); + // 根据用户的ID(userId)查询该用户购物车中所有已选中商品的记录,返回一个包含多个已选中Cart对象的List集合, + // 方便后续对已选中商品进行相关业务处理(比如计算总价等操作),若没有已选中商品,则返回一个空的List集合。 List selectCheckedCartByUserId(Integer userId); } \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/entity/Cart.java b/snailmall-cart-service/src/main/java/com/njupt/swg/entity/Cart.java index 7a323d4..82c3dec 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/entity/Cart.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/entity/Cart.java @@ -4,26 +4,44 @@ import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; - import java.util.Date; +// 使用Lombok的 @Data注解,会自动为这个类生成常用的方法,比如Getter、Setter方法, +// 以及toString、hashCode、equals等方法,简化了代码编写,让实体类的属性访问和操作更加便捷。 @Data + +// 使用 @AllArgsConstructor注解,Lombok会为这个类自动生成一个包含所有参数的构造函数, +// 方便在创建对象时一次性传入所有属性的值进行初始化。 @AllArgsConstructor + +// 使用 @NoArgsConstructor注解,Lombok会为这个类自动生成一个无参构造函数, +// 这在很多框架(如一些序列化、反序列化的场景或者通过反射创建对象的情况)中是必要的,确保类有默认的构造方式。 @NoArgsConstructor public class Cart { + + // 购物车记录的唯一标识符,通常在数据库中作为主键使用,用于区分不同的购物车记录,类型为整数类型。 private Integer id; + // 关联的用户的唯一标识符,用于表明这个购物车记录属于哪个用户,通过这个字段可以将购物车与具体的用户关联起来,方便查询某个用户的购物车相关信息,类型为整数类型。 private Integer userId; + // 购物车中商品的唯一标识符,用于确定购物车中存放的是哪个具体商品,方便在业务逻辑中根据商品ID查找商品详情、库存等信息,类型为整数类型。 private Integer productId; + // 商品在购物车中的数量,代表用户添加该商品到购物车的数量情况,例如用户添加了3件某商品到购物车,这个字段的值就会是3,类型为整数类型。 private Integer quantity; + // 用于表示商品在购物车中的选中状态,具体取值含义可能由业务逻辑定义,比如可以用1表示选中,0表示未选中等,方便在购物车相关操作(如全选、取消全选、单独选中某个商品等操作)中标记商品的状态,类型为整数类型。 private Integer checked; + // 使用 @JsonFormat注解来指定日期类型字段的序列化格式,这里将createTime字段在序列化为JSON字符串时, + // 按照指定的格式("yyyy-MM-dd HH:mm:ss.SSS",即年-月-日 时:分:秒.毫秒)进行格式化,方便在前后端数据交互或者数据存储时,日期格式能够统一且符合预期的展示需求。 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS") private Date createTime; + // 与createTime类似,也是使用 @JsonFormat注解来指定日期类型字段的序列化格式, + // updateTime字段在序列化为JSON字符串时,按照"yyyy-MM-dd HH:mm:ss.SSS"格式进行格式化, + // 通常用于记录购物车记录最后一次更新的时间,方便跟踪数据的变化情况以及进行一些基于时间的业务逻辑处理(如判断数据是否过期等情况)。 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS") private Date updateTime; } \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/entity/Product.java b/snailmall-cart-service/src/main/java/com/njupt/swg/entity/Product.java index 72c0708..d88d62b 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/entity/Product.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/entity/Product.java @@ -3,35 +3,70 @@ package com.njupt.swg.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; - import java.math.BigDecimal; import java.util.Date; +// 使用Lombok的 @Data注解,它会自动帮我们生成这个类的一系列常用方法,包括所有属性的Getter和Setter方法, +// 以及重写的toString方法、hashCode方法和equals方法等。这样可以减少大量样板代码的编写,让代码看起来更简洁, +// 同时也方便在其他地方对类的属性进行访问和操作。 @Data + +// @AllArgsConstructor注解指示Lombok为这个类生成一个包含所有参数的构造函数。 +// 这意味着我们可以通过传入所有属性的值来便捷地创建一个Product类的实例,例如: +// Product product = new Product(1, 2, "商品名称", "商品副标题", "主图路径", "子图路径列表", "商品详情", new BigDecimal("9.99"), 100, 1, new Date(), new Date()); @AllArgsConstructor + +// @NoArgsConstructor注解让Lombok为这个类生成一个无参构造函数。 +// 在很多情况下,比如通过反射创建对象、一些序列化和反序列化的场景中,无参构造函数是必不可少的, +// 确保类有默认的创建实例的方式。 @NoArgsConstructor public class Product { + + // 商品的唯一标识符,在整个系统中用于区分不同的商品,通常会作为数据库表中的主键字段,类型为整数类型, + // 其他业务逻辑或者数据库操作往往会基于这个ID来对特定的商品进行查询、更新、删除等操作。 private Integer id; + // 商品所属的分类的标识符,用于将商品归类到不同的类别下,方便进行分类查询、展示等操作,比如按照不同的品类展示商品列表,类型为整数类型, + // 通过这个字段可以关联到对应的商品分类信息,了解商品所属的大类别或者小类别等情况。 private Integer categoryId; + // 商品的名称,用于展示给用户,让用户直观地知道商品是什么,是一个字符串类型的属性,例如"华为P50手机"等, + // 在商品列表展示、详情页展示等场景中会用到这个字段来呈现商品的基本信息。 private String name; + // 商品的副标题,通常可以用来补充说明商品的一些特点、卖点或者优惠信息等内容,也是字符串类型, + // 比如"华为P50手机,超感知徕卡四摄,限时优惠",可以进一步吸引用户关注该商品。 private String subtitle; + // 商品的主图片的路径或者标识,一般用于在前端页面展示商品的主要图片,字符串类型, + // 这个路径可以是相对路径或者绝对路径,指向存储商品主图的位置(可能是服务器本地存储、云存储等),方便前端根据这个路径来加载并展示图片。 private String mainImage; + // 商品的子图片的路径或者标识列表,以字符串形式存储,通常用特定的分隔符(如逗号等)将多个子图片的路径分隔开, + // 用于展示商品的多角度、多细节图片,让用户更全面地了解商品外观、功能等方面的情况,方便前端加载并展示商品的多张图片。 private String subImages; + // 商品的详细描述信息,包含了商品的各种详细参数、功能介绍、使用说明等内容,是一个比较长的字符串类型, + // 在商品详情页中会完整地展示这些信息,帮助用户深入了解商品的具体情况,以便做出购买决策。 private String detail; + // 商品的价格,使用BigDecimal类型来精确表示金额,避免使用浮点数类型(如double、float)带来的精度丢失问题, + // 例如可以表示为new BigDecimal("9.99"),准确地存储商品的售价信息,在购物车计算总价、下单结算等涉及金额计算的业务场景中会频繁使用这个字段。 private BigDecimal price; + // 商品的库存数量,用于记录当前商品还有多少件可供销售,整数类型, + // 在添加商品到购物车、下单扣减库存等业务操作中,会根据这个字段的值来判断操作是否可行,比如库存不足时不能继续添加商品到购物车等情况。 private Integer stock; + // 商品的状态标识,具体含义由业务逻辑定义,可能用不同的整数值来表示不同的状态,比如1表示上架、0表示下架等, + // 用于控制商品是否在前端展示、是否可购买等情况,方便后台管理人员对商品的售卖状态进行管理和操作。 private Integer status; + // 商品的创建时间,记录商品信息首次被录入系统的时间点,使用Date类型来准确表示时间, + // 在数据统计、历史记录查询等业务场景中可能会用到这个字段,例如查看某个时间段内新上架的商品等情况。 private Date createTime; + // 商品的更新时间,每当商品的相关信息(如名称、价格、库存等属性发生变化时)被修改后,会更新这个时间字段, + // 同样是Date类型,可用于跟踪商品信息的变更情况,比如查看商品最近一次的价格调整时间等业务需求。 private Date updateTime; } \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/entity/User.java b/snailmall-cart-service/src/main/java/com/njupt/swg/entity/User.java index 4354b81..c66ebce 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/entity/User.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/entity/User.java @@ -4,7 +4,6 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; - import java.io.Serializable; import java.util.Date; @@ -12,32 +11,52 @@ import java.util.Date; * @Author swg. * @Date 2018/12/31 21:01 * @CONTACT 317758022@qq.com - * @DESC 用户实体类 + * @DESC 用户实体类,用于在整个项目中表示用户相关的信息,是对用户这一概念在代码层面的抽象, + * 它在不同的业务层(如持久层、服务层、控制层等)之间传递用户数据,方便进行与用户相关的各种业务操作,例如用户注册、登录、信息查询与修改等。 */ @Data +// 使用Lombok的 @Data注解,会自动为这个类生成常用的方法,包括所有属性的Getter和Setter方法,以及重写的toString、hashCode和equals方法等,减少了手动编写这些样板代码的工作量,使代码更加简洁,便于对类中属性进行操作和使用。 + @NoArgsConstructor +// 通过 @NoArgsConstructor注解,Lombok会为这个类生成一个无参构造函数。在很多场景下,比如通过反射创建对象、进行序列化和反序列化操作时,无参构造函数是必要的,确保类有默认的创建实例的方式。 + @AllArgsConstructor +// @AllArgsConstructor注解则指示Lombok生成一个包含所有参数的构造函数,方便在创建对象时一次性传入所有属性的值进行初始化,例如: +// User user = new User(1, "testUser", "123456", "test@example.com", "13812345678", "你的爱好是什么?", "看书", 1, new Date(), new Date()); + @ToString +// @ToString注解会让Lombok自动重写toString方法,使得在打印对象或者将对象转换为字符串表示形式时,能够直观地展示对象的各个属性值,方便调试和查看对象的状态。 + +// 实现Serializable接口,表示这个类的对象可以被序列化和反序列化,这在很多场景下是很重要的,比如将用户对象存储到文件、在网络中传输用户对象等情况,确保对象能够以字节流的形式进行持久化和传递,并且在反序列化后能正确还原对象状态。 public class User implements Serializable { + + // 用户的唯一标识符,通常作为数据库表中的主键字段来区分不同的用户,在整个系统中,其他与用户相关的业务操作(如查询、更新、删除用户信息等)大多会基于这个ID来进行,类型为整数类型。 private Integer id; + // 用户登录使用的用户名,是一个字符串类型的属性,要求在系统中具有唯一性(一般会有相应的业务逻辑和数据库约束来保证),用于用户在登录界面输入,以标识自己的身份,例如"admin"、"user123"等。 private String username; + // 用户登录的密码,同样为字符串类型,存储用户设置的密码信息,出于安全考虑,在实际应用中通常会对密码进行加密存储,而不是以明文形式保存,例如使用MD5、SHA等加密算法对密码进行处理后再存储到数据库中。 private String password; + // 用户的电子邮箱地址,字符串类型,可用于用户注册验证、找回密码等功能,例如"test@example.com",通过向该邮箱发送验证链接或者重置密码的相关信息来完成相应的操作,同时也方便系统与用户进行一些重要通知的邮件沟通。 private String email; + // 用户的手机号码,字符串类型,常用于短信验证码验证、手机号登录等功能场景,例如"13812345678",在如今的很多应用中,手机号已经成为一种常用且重要的用户身份验证和联系的方式。 private String phone; + // 密保问题,字符串类型,是用户在设置账号安全相关信息时所填写的用于找回密码等情况的验证问题,例如"你的爱好是什么?",当用户忘记密码时,可以通过回答正确的密保问题来重置密码,增加账号的安全性。 private String question; + // 密保问题的答案,与question属性相对应,用户填写的用于验证密保问题的正确答案,也是字符串类型,例如"看书",只有回答正确这个答案,才能进行后续的密码重置等安全相关操作。 private String answer; - //角色0-管理员,1-普通用户 + // 用户的角色标识,整数类型,通过不同的整数值来区分用户在系统中的不同角色,在这里定义了0表示管理员角色,1表示普通用户角色,不同角色在系统中通常具有不同的权限,例如管理员可能具有管理所有用户、系统配置等高级权限,而普通用户只能进行与自身相关的常规操作(如查看购物车、下单等)。 private Integer role; + // 用户账号的创建时间,使用Date类型准确记录用户首次在系统中注册账号的时间点,在数据统计(如统计每日新增用户数量等)、历史记录查询(查看某个时间段内注册的用户等)等业务场景中会用到这个字段。 private Date createTime; + // 用户账号信息的最后更新时间,每当用户修改了自己的用户名、密码、邮箱等相关信息后,会更新这个时间字段,同样为Date类型,方便跟踪用户信息的变更情况,例如查看用户最近一次修改密码的时间等。 private Date updateTime; - } \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/message/MessageReceiver.java b/snailmall-cart-service/src/main/java/com/njupt/swg/message/MessageReceiver.java index 60d751c..b45026d 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/message/MessageReceiver.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/message/MessageReceiver.java @@ -9,31 +9,51 @@ import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; - import java.util.List; /** * @Author swg. * @Date 2019/1/7 13:23 * @CONTACT 317758022@qq.com - * @DESC + * @DESC 这个类(MessageReceiver)是一个消息接收者组件,用于处理通过消息队列(RabbitMQ)接收到的消息, + * 在整个项目架构中,它承担着从消息队列获取消息并基于消息内容执行相应业务逻辑的职责,例如这里接收到消息后会调用购物车服务来清除对应用户的购物车。 */ @Component +// 使用 @Component注解将这个类标记为Spring的一个组件,这样Spring容器会自动扫描并管理这个类的实例,使其可以参与依赖注入等Spring相关的功能。 + @Slf4j +// 通过 @Slf4j注解,使用Lombok的功能自动为这个类添加一个名为log的日志记录器,方便在类中记录各种运行时的日志信息,有助于调试、监控和追踪代码的执行情况。 public class MessageReceiver { + + // 通过Spring的依赖注入机制,自动注入ICartService接口的实现类对象,用于调用购物车相关的业务逻辑方法, + // 在这里具体用于执行清除购物车的操作,依赖注入使得代码的耦合性更低,方便替换不同的购物车服务实现类,并且符合Spring的面向接口编程的理念。 @Autowired private ICartService cartService; + /** + * 这个方法是一个消息监听器方法,通过 @RabbitListener注解配置了它与消息队列的绑定关系,用于监听特定队列("cart-queue")中的消息, + * 当有消息到达该队列时,这个方法就会被触发执行,它会接收消息队列传递过来的消息,并基于消息内容进行相应的业务逻辑处理,在这里主要是清除对应用户的购物车操作。 + * + * @param message 表示从消息队列中接收到的消息内容,在这里消息内容预期是一个可以转换为整数类型的字符串(代表用户的ID), + * 它的具体格式和含义由发送消息的一端决定,本方法接收到消息后会对其进行解析并转化为相应的业务操作参数(userId),进而执行后续的清除购物车操作。 + */ @RabbitListener(bindings = @QueueBinding( value = @Queue("cart-queue"), exchange = @Exchange("cart-exchange") )) - public void proess(String message){ - log.info("接收到的消息为:{}",message); + public void proess(String message) { + // 使用日志记录器记录接收到的消息内容,方便在运行时查看接收到的消息具体是什么,有助于调试和排查问题,例如查看消息是否符合预期格式等情况。 + log.info("接收到的消息为:{}", message); + + // 将接收到的消息(预期是一个字符串形式的整数)转换为实际的整数类型(userId),这里假设发送过来的消息内容就是有效的用户ID字符串表示形式, + // 如果消息格式不符合要求,可能会抛出NumberFormatException异常,在实际应用中可能需要更完善的错误处理机制来应对这种情况。 Integer userId = Integer.parseInt(message); - log.info("【MQ解析数据,前者为userId,后者为productId:{}】",userId); - //清除购物车 + + // 再次使用日志记录器记录解析后的用户ID信息,便于在日志中清晰地看到处理的用户相关数据,方便后续查看消息处理的具体情况以及追踪问题,比如查看是否处理了正确的用户的购物车清除操作等。 + log.info("【MQ解析数据,前者为userId,后者为productId:{}】", userId); + + // 调用ICartService的removeCart方法,传入解析得到的用户ID(userId),执行清除对应用户购物车的业务操作, + // 这个操作可能涉及到数据库中购物车相关记录的删除等具体的持久层操作,具体由ICartService的实现类中对应的方法逻辑来决定。 cartService.removeCart(userId); } - -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/service/CartServiceImpl.java b/snailmall-cart-service/src/main/java/com/njupt/swg/service/CartServiceImpl.java index 8daad9b..827ac9a 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/service/CartServiceImpl.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/service/CartServiceImpl.java @@ -31,211 +31,452 @@ import java.util.List; */ @Service @Slf4j -public class CartServiceImpl implements ICartService{ + + +// CartServiceImpl类实现了ICartService接口,意味着它需要实现该接口中定义的所有方法, +// 这个类主要负责处理购物车相关的具体业务逻辑,例如添加商品到购物车、更新购物车中商品数量等操作, +// 通过调用其他组件(如CartMapper、ProductClient、CommonCacheUtil等)来协同完成业务功能,并将结果以ServerResponse的形式返回给上层调用者(如控制层)。 +public class CartServiceImpl implements ICartService { + + // 通过Spring的依赖注入机制,自动注入CartMapper接口的实现类对象。 + // CartMapper用于与数据库进行交互,执行如查询、插入、更新、删除购物车相关记录等数据库操作, + // 在这里的各个购物车业务方法中会频繁调用它来完成持久化层面的工作,实现业务逻辑与数据访问的分离。 @Autowired private CartMapper cartMapper; + + // 同样通过依赖注入注入ProductClient对象。 + // ProductClient通常是用于和外部的商品服务进行通信的客户端,比如在购物车业务中,需要获取商品的详细信息(是否下架、库存情况等)时, + // 就会通过这个客户端去调用商品服务提供的接口来获取相应数据,实现不同微服务之间的协作。 @Autowired private ProductClient productClient; + + // 注入CommonCacheUtil对象,用于与缓存系统进行交互操作。 + // 在处理购物车业务时,可能会频繁用到商品相关信息,通过缓存可以减少重复查询数据库的次数,提高系统性能。 + // 例如先从缓存中查找商品信息,如果缓存中不存在再去查询数据库,并将查询到的数据存入缓存供后续使用。 @Autowired private CommonCacheUtil commonCacheUtil; + /** + * 实现ICartService接口中定义的添加商品到购物车的方法。 + * + * @param userId 用户的唯一标识符,它是确定购物车归属的关键,若该值为null,表示用户未登录,不符合添加商品到购物车的前置条件, + * 此时会抛出SnailmallException异常,由上层(如控制层)进行捕获并处理,告知客户端用户未登录的情况。 + * @param productId 要添加到购物车的商品的唯一标识符,若为null,说明传入的参数不完整,无法明确要添加的具体商品, + * 会返回一个表示参数不正确的ServerResponse对象给客户端,提示用户修正参数。 + * @param count 要添加到购物车的商品数量,同样不能为null,若为null则也返回参数不正确的提示响应,代表了此次操作中对应商品添加的具体数量。 + * @return 返回一个ServerResponse对象,该对象封装了添加商品到购物车操作的结果信息, + * 如果操作成功,其内部会包含代表成功的数据(在这里是构建好的CartVo对象,包含了购物车相关详细信息,例如商品列表、总价等内容), + * 若操作失败,则包含相应的错误提示消息等内容,方便调用者(比如控制层)根据返回结果判断操作是否成功,并将相应信息返回给前端展示给用户或者进行其他后续处理。 + */ @Override public ServerResponse add(Integer userId, Integer productId, Integer count) { - //1.校验参数 - if(userId == null){ + // 1. 校验参数 + // 首先检查用户ID是否为null,如果为null,意味着用户没有登录,这不符合添加商品到购物车的业务要求,所以抛出SnailmallException异常, + // 异常信息为"用户未登陆",后续在合适的地方(比如控制层)可以捕获这个异常并返回相应提示给客户端,告知用户需要先登录才能进行添加操作。 + if (userId == null) { throw new SnailmallException("用户未登陆"); } - if(productId == null || count == null){ + // 接着检查商品ID和商品数量是否为null,如果这两个参数中有任何一个为null,说明传入的参数不符合要求,无法准确执行添加商品的操作, + // 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示参数不正确的响应对象给客户端,提示用户检查并修正传入的参数。 + if (productId == null || count == null) { return ServerResponse.createByErrorMessage("参数不正确"); } - //2.校验商品 - String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+productId); + + // 2. 校验商品 + // 尝试从缓存中获取商品信息,缓存的键是通过Constants.PRODUCT_TOKEN_PREFIX加上商品ID拼接而成的, + // 这样可以根据不同的商品ID来准确地在缓存中查找对应的商品缓存数据,提高获取商品信息的效率,避免频繁访问数据库。 + String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + productId); Product product = null; - if(productStr == null){ + // 如果从缓存中获取到的商品信息字符串为null,说明缓存中不存在该商品的相关信息,此时需要通过ProductClient去远程调用商品服务来查询商品详情。 + if (productStr == null) { + // 调用ProductClient的queryProduct方法,传入商品ID作为参数,向商品服务发起查询请求,获取包含商品信息的ServerResponse对象。 ServerResponse response = productClient.queryProduct(productId); + // 从ServerResponse对象中获取其包含的商品数据部分(可能是一个Object类型的对象,具体内容由商品服务返回的数据结构决定)。 Object object = response.getData(); + // 使用JsonUtil工具类的obj2String方法将获取到的商品数据对象转换为字符串形式,方便后续进行反序列化操作。 String objStr = JsonUtil.obj2String(object); - product = (Product) JsonUtil.Str2Obj(objStr,Product.class); - }else { - product = (Product) JsonUtil.Str2Obj(productStr,Product.class); + // 再通过JsonUtil的Str2Obj方法将字符串形式的商品信息反序列化为Product对象,以便后续在业务逻辑中使用商品的各个属性进行相关判断和操作。 + product = (Product) JsonUtil.Str2Obj(objStr, Product.class); + } else { + // 如果缓存中存在商品信息字符串,则直接使用JsonUtil的Str2Obj方法将其反序列化为Product对象,避免了再次远程查询商品服务的开销。 + product = (Product) JsonUtil.Str2Obj(productStr, Product.class); } - if(product == null){ + // 如果经过上述步骤获取到的商品对象为null,说明商品不存在,可能是商品ID有误或者商品已经被彻底删除等原因, + // 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示商品不存在的响应对象给客户端,告知用户无法添加不存在的商品到购物车。 + if (product == null) { return ServerResponse.createByErrorMessage("商品不存在"); } - if(!product.getStatus().equals(Constants.Product.PRODUCT_ON)){ + // 检查商品的状态是否为Constants.Product.PRODUCT_ON(这个常量应该是在业务中定义好的,表示商品处于正常可售卖状态的标识), + // 如果商品状态不等于这个正常可售卖状态标识,说明商品可能已经下架或者被删除了,不应该再添加到购物车中, + // 所以返回一个表示商品下架或者删除的错误提示响应对象给客户端。 + if (!product.getStatus().equals(Constants.Product.PRODUCT_ON)) { return ServerResponse.createByErrorMessage("商品下架或者删除"); } - //3.根据商品或者购物车,购物车存在则增加商品数量即可,不存在则创建新的购物车,一个用户对应一个购物车 - Cart cart = cartMapper.selectByUserIdProductId(userId,productId); - if (cart == null){ + + // 3. 根据商品或者购物车,购物车存在则增加商品数量即可,不存在则创建新的购物车,一个用户对应一个购物车 + // 通过CartMapper的selectByUserIdProductId方法,根据传入的用户ID和商品ID去数据库中查询购物车中是否已经存在该商品的记录, + // 如果不存在(即查询返回的Cart对象为null),则表示该用户的购物车中还没有添加过这个商品,需要创建一个新的购物车记录。 + Cart cart = cartMapper.selectByUserIdProductId(userId, productId); + if (cart == null) { + // 创建一个新的Cart对象,用于表示要插入到数据库中的购物车记录信息。 Cart cartItem = new Cart(); + // 设置新购物车记录的用户ID,关联到对应的用户,表示这个购物车是属于哪个用户的。 cartItem.setUserId(userId); + // 设置商品ID,明确购物车中存放的是哪个商品。 cartItem.setProductId(productId); + // 设置商品数量,即此次要添加到购物车的商品数量。 cartItem.setQuantity(count); + // 设置商品的选中状态,这里设置为Constants.Cart.CHECKED(应该是在业务中定义好的表示选中的常量),表示默认添加到购物车后商品是选中状态,具体选中含义可根据业务逻辑确定,比如是否参与总价计算等。 cartItem.setChecked(Constants.Cart.CHECKED); + // 通过CartMapper的insert方法将新创建的购物车记录插入到数据库中,该方法会返回受影响的行数,正常插入成功应该返回1, + // 如果返回值为0,表示插入操作失败,可能是数据库出现问题或者其他原因导致插入不成功,此时返回一个表示添加购物车失败的错误提示响应对象给客户端。 int resultCount = cartMapper.insert(cartItem); - if(resultCount == 0){ + if (resultCount == 0) { return ServerResponse.createByErrorMessage("添加购物车失败"); } - }else { - cart.setQuantity(cart.getQuantity()+count); + } else { + // 如果购物车中已经存在该商品的记录,说明之前已经添加过这个商品到购物车了,此时只需要更新商品的数量即可, + // 将原有的商品数量加上此次要添加的数量,更新Cart对象中的quantity属性。 + cart.setQuantity(cart.getQuantity() + count); + // 通过CartMapper的updateByPrimaryKeySelective方法,根据更新后的Cart对象去更新数据库中对应的购物车记录, + // 这个方法会根据Cart对象中不为null的属性来更新数据库相应字段,返回受影响的行数,若返回0则表示更新失败,同样返回添加购物车失败的提示响应给客户端。 int resultCount = cartMapper.updateByPrimaryKeySelective(cart); - if(resultCount == 0){ + if (resultCount == 0) { return ServerResponse.createByErrorMessage("添加购物车失败"); } } - //构建购物车信息,返回给前端,并且要检查库存 - CartVo cartVo = getCartVoLimit(userId,true); + + // 构建购物车信息,返回给前端,并且要检查库存 + // 调用getCartVoLimit方法来构建包含购物车详细信息的CartVo对象,这个方法内部会进一步处理购物车中商品的各种信息, + // 比如获取商品的详细信息(可能再次从缓存或远程查询商品服务获取更详细数据)、判断库存情况、计算购物车商品的总价等操作, + // 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端,告知用户添加商品到购物车操作成功,并将购物车的详细信息返回给前端展示给用户。 + CartVo cartVo = getCartVoLimit(userId, true); return ServerResponse.createBySuccess(cartVo); } + /** + * 实现ICartService接口中定义的更新购物车中某个商品数量的方法。 + * + * @param userId 用户的唯一标识符,用于确定是哪个用户的购物车中的商品数量需要更新,是操作的关键依据,若为null则不符合业务逻辑(但此处未做处理,可根据实际情况完善), + * 类型为整数类型,在整个购物车业务体系中关联到具体的用户账户。 + * @param productId 要更新数量的商品的唯一标识符,若为null,说明没有明确要更新数量的具体商品,无法进行更新操作, + * 会返回一个表示参数错误的ServerResponse对象给客户端,提示用户传入正确的商品ID参数,类型为整数类型,用于在购物车记录中准确找到对应的商品。 + * @param count 要更新为的商品数量,同样不能为null,若为null则返回参数错误的提示响应,代表了更新后该商品在购物车中应有的具体数量,类型为整数类型。 + * @return 返回一个ServerResponse对象,该对象封装了更新购物车商品数量操作的结果信息, + * 如果操作成功,内部包含代表成功的数据(即构建好的CartVo对象,包含了更新数量后购物车的详细信息,例如商品列表、总价等情况), + * 若操作失败,则包含相应的错误提示消息等内容,方便调用者(如控制层)根据返回结果判断操作是否成功,并进行相应的处理(如返回给前端提示信息等)。 + */ @Override public ServerResponse update(Integer userId, Integer productId, Integer count) { - if(productId == null || count == null){ + // 首先对传入的参数进行校验,如果商品ID或者商品数量为null,说明参数不符合要求,无法进行更新操作, + // 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示参数错误的响应对象给客户端,提示用户修正传入的参数。 + if (productId == null || count == null) { return ServerResponse.createByErrorMessage("参数错误"); } - Cart cart = cartMapper.selectByUserIdProductId(userId,productId); - if(cart == null){ + + // 通过CartMapper的selectByUserIdProductId方法,根据传入的用户ID和商品ID去数据库中查询购物车中是否存在对应的商品记录, + // 如果不存在(即查询返回的Cart对象为null),说明要更新数量的商品在购物车中不存在,无法进行更新操作, + // 此时返回一个表示购物车不存在的错误提示响应对象给客户端,告知用户无法进行更新。 + Cart cart = cartMapper.selectByUserIdProductId(userId, productId); + if (cart == null) { return ServerResponse.createByErrorMessage("购物车不存在"); } + + // 如果找到了对应的购物车商品记录,将Cart对象中的quantity属性设置为新传入的商品数量值,准备更新数据库中的对应记录。 cart.setQuantity(count); + // 通过CartMapper的updateByPrimaryKeySelective方法,根据更新后的Cart对象去更新数据库中对应的购物车记录, + // 该方法会根据Cart对象中不为null的属性来更新数据库相应字段,返回受影响的行数,若返回0则表示更新失败,返回一个表示更新购物车失败的错误提示响应给客户端。 int updateCount = cartMapper.updateByPrimaryKeySelective(cart); - if(updateCount == 0){ + if (updateCount == 0) { return ServerResponse.createByErrorMessage("更新购物车失败"); } - CartVo cartVo = this.getCartVoLimit(userId,true); + + // 调用getCartVoLimit方法构建包含更新后购物车详细信息的CartVo对象,这个方法会进一步处理购物车商品的相关信息(如获取商品详情、判断库存、计算总价等), + // 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端,告知用户更新购物车商品数量操作成功,并将购物车的详细信息返回给前端展示给用户。 + CartVo cartVo = this.getCartVoLimit(userId, true); return ServerResponse.createBySuccess(cartVo); } + /** + * 实现ICartService接口中定义的从购物车中删除指定商品的方法。 + * + * @param userId 用户的唯一标识符,用于确定是哪个用户的购物车进行商品删除操作,若为null不符合业务逻辑(此处未做额外处理,可按需完善), + * 通过它能精准定位到对应用户的购物车记录,类型为整数类型。 + * @param productIds 表示要删除的商品的标识信息,格式为以逗号分隔的字符串,例如"1,2,3"表示要删除ID为1、2、3的商品, + * 若为空字符串或者格式不符合要求(无法解析出有效的商品ID列表)则返回表示参数错误的响应,类型为字符串类型。 + * @return 返回一个ServerResponse对象,该对象封装了删除购物车商品操作的结果信息, + * 如果操作成功,内部包含代表成功的数据(即构建好的CartVo对象,包含了删除商品后购物车的详细信息,例如剩余商品列表、总价等情况), + * 若操作失败,则包含相应的错误提示消息等内容,方便调用者(如控制层)根据返回结果判断操作是否成功,并进行相应的处理(如返回给前端提示信息等)。 + */ @Override public ServerResponse delete(Integer userId, String productIds) { + // 使用Google Guava库的Splitter工具类,按照逗号(",")将传入的商品ID字符串(productIds)分割成一个字符串列表, + // 这样就能方便地获取到要删除的各个商品的ID,以便后续批量操作数据库中对应的购物车商品记录。 List productIdList = Splitter.on(",").splitToList(productIds); - if(CollectionUtils.isEmpty(productIdList)){ + // 通过Apache Commons Collections的CollectionUtils工具类检查分割后的商品ID列表是否为空, + // 如果为空,说明传入的参数不符合要求,无法准确执行删除操作,此时返回一个表示参数错误的ServerResponse对象给客户端,提示用户修正传入的参数。 + if (CollectionUtils.isEmpty(productIdList)) { return ServerResponse.createByErrorMessage("参数错误"); } - int rowCount = cartMapper.deleteByProductIds(userId,productIdList); - if(rowCount == 0){ + + // 调用CartMapper的deleteByProductIds方法,根据用户ID和要删除的商品ID列表从数据库中删除对应的购物车商品记录, + // 该方法会返回受影响的行数,即实际删除的记录条数,正常情况下如果成功删除了对应的商品记录,返回值应该大于0。 + int rowCount = cartMapper.deleteByProductIds(userId, productIdList); + // 如果返回值为0,表示没有实际删除任何记录,可能是因为要删除的商品已经不存在于购物车中了, + // 此时返回一个表示商品已经不存在于购物车中,请勿重复删除的ServerResponse对象给客户端,告知用户当前操作情况。 + if (rowCount == 0) { return ServerResponse.createByErrorMessage("此商品已经不存在于购物车中,请勿重复删除"); } - CartVo cartVo = this.getCartVoLimit(userId,false); + + // 调用getCartVoLimit方法构建包含删除商品后购物车详细信息的CartVo对象,这个方法会进一步处理购物车商品的相关信息(如获取剩余商品详情、计算总价等), + // 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端,告知用户删除购物车商品操作成功,并将购物车的详细信息返回给前端展示给用户。 + CartVo cartVo = this.getCartVoLimit(userId, false); return ServerResponse.createBySuccess(cartVo); } + /** + * 实现ICartService接口中定义的查询购物车列表信息的方法。 + * + * @param userId 用户的唯一标识符,用于确定是查询哪个用户的购物车列表,类型为整数类型,是定位具体购物车数据的关键依据。 + * @return 返回一个ServerResponse对象,该对象封装了查询购物车列表操作的结果信息, + * 如果操作成功,内部包含代表成功的数据(即构建好的CartVo对象,包含了购物车的详细商品列表、总价等信息), + * 方便调用者(如控制层)根据返回结果将购物车信息展示给客户端(如前端页面展示购物车内容等)。 + */ @Override public ServerResponse list(Integer userId) { - CartVo cartVo = this.getCartVoLimit(userId,false); + // 直接调用getCartVoLimit方法构建包含购物车详细信息的CartVo对象,这个方法内部会处理购物车商品的相关信息(如获取商品详情、判断库存、计算总价等), + // 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端,告知用户查询购物车列表操作成功,并将购物车的详细信息返回给前端展示给用户。 + CartVo cartVo = this.getCartVoLimit(userId, false); return ServerResponse.createBySuccess(cartVo); } + /** + * 实现ICartService接口中定义的设置购物车中商品的选中或未选中状态的方法。 + * + * @param userId 用户的唯一标识符,用于确定是哪个用户的购物车进行商品选中状态设置操作,类型为整数类型,明确操作对应的购物车归属。 + * @param checked 表示商品要设置的选中状态,可能是用整数表示(例如Constants.Cart.CHECKED表示选中,Constants.Cart.UN_CHECKED表示未选中,具体由业务定义),类型为整数类型,用于传递要设置的具体状态值。 + * @param productId 要设置选中状态的商品的唯一标识符,若为null则表示对购物车中所有商品进行操作(根据具体业务逻辑而定),用于在购物车中定位具体的商品,类型为整数类型。 + * @return 返回一个ServerResponse对象,该对象封装了设置商品选中状态操作的结果信息, + * 如果操作成功,内部包含代表成功的数据(即构建好的CartVo对象,包含了更新后的购物车相关信息,如商品选中状态改变后的购物车总价等情况), + * 方便调用者根据返回结果判断操作是否成功,并进行相应的处理(如返回给前端提示信息等)。 + */ @Override public ServerResponse selectOrUnSelect(Integer userId, int checked, Integer productId) { - cartMapper.selectOrUnSelectProduct(userId,checked,productId); - CartVo cartVo = this.getCartVoLimit(userId,false); + // 调用CartMapper的selectOrUnSelectProduct方法,根据用户ID、要设置的选中状态以及商品ID(如果不为null则针对特定商品,为null则针对所有商品,具体看业务逻辑实现)来更新数据库中购物车商品的选中状态记录, + // 此方法无返回值,它直接执行数据库更新操作,将对应的购物车商品记录的选中状态字段更新为传入的checked值。 + cartMapper.selectOrUnSelectProduct(userId, checked, productId); + + // 调用getCartVoLimit方法构建包含更新后购物车详细信息的CartVo对象,这个方法会进一步处理购物车商品的相关信息(如获取商品详情、判断库存、计算总价等), + // 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端,告知用户设置商品选中状态操作成功,并将购物车的详细信息返回给前端展示给用户。 + CartVo cartVo = this.getCartVoLimit(userId, false); return ServerResponse.createBySuccess(cartVo); } + /** + * 实现ICartService接口中定义的查询购物车中商品总数量的方法。 + * + * @param userId 用户的唯一标识符,用于确定是哪个用户的购物车进行商品数量统计,若为null,按照业务逻辑返回数量为0的成功响应,类型为整数类型,作为统计的范围限定依据。 + * @return 返回一个ServerResponse对象,该对象封装了查询购物车商品数量操作的结果信息, + * 如果操作成功,其泛型部分的整数表示购物车中商品的总数量,同时ServerResponse对象本身还包含操作的状态码、提示消息等内容, + * 方便调用者(如控制层)根据返回结果判断操作是否成功,并获取购物车商品数量的具体数值进行展示或其他相关业务处理。 + */ @Override public ServerResponse get_cart_product_count(Integer userId) { - if(userId == null){ + // 首先判断用户ID是否为null,如果为null,按照业务逻辑,直接返回一个表示操作成功且数据部分为0的ServerResponse对象, + // 意味着如果用户未登录或者传入的用户ID无效等情况,默认购物车商品数量为0,告知调用者当前情况。 + if (userId == null) { return ServerResponse.createBySuccess(0); } + + // 调用CartMapper的selectCartProductCount方法,传入用户ID作为参数,去查询数据库中对应用户购物车中商品的总数量, + // 然后返回一个表示操作成功且数据部分为查询到的商品数量的ServerResponse对象给客户端,方便调用者获取并使用这个数量信息。 return ServerResponse.createBySuccess(cartMapper.selectCartProductCount(userId)); } + /** + * 实现ICartService接口中定义的清空购物车的方法。 + * + * @param userId 用户的唯一标识符,用于确定是哪个用户的购物车进行清空操作,类型为整数类型,明确操作对应的购物车归属。 + * @return 返回一个ServerResponse对象,该对象封装了清空购物车操作的结果信息, + * 如果操作成功,包含表示成功的提示消息(如这里的"清除购物车成功"),方便调用者(如控制层)根据返回结果判断操作是否成功,并进行相应的处理(如返回给前端提示信息等)。 + */ @Override public ServerResponse removeCart(Integer userId) { + // 首先通过CartMapper的selectCartByUserId方法,根据用户ID去查询数据库中该用户购物车中的所有商品记录,返回一个包含多个Cart对象的列表,代表购物车中的所有商品信息。 List cartList = cartMapper.selectCartByUserId(userId); - for(Cart cart:cartList){ + + // 遍历查询到的购物车商品记录列表,对于每一条购物车记录,调用CartMapper的deleteByPrimaryKey方法,根据购物车记录的主键(ID)从数据库中删除对应的记录, + // 这样就实现了逐个删除购物车中的所有商品记录,达到清空购物车的目的。 + for (Cart cart : cartList) { cartMapper.deleteByPrimaryKey(cart.getId()); } + + // 返回一个表示操作成功且包含提示消息"清除购物车成功"的ServerResponse对象给客户端,告知用户购物车已成功清空,方便调用者(如控制层)将这个消息返回给前端展示给用户。 return ServerResponse.createBySuccessMessage("清除购物车成功"); } /** - * 比较通用的构建购物车的方法 - * @param userId - * @return + * 比较通用的构建购物车的方法,用于组装包含购物车详细信息的CartVo对象,会综合考虑商品信息、库存情况、选中状态等来构建完整的购物车视图数据。 + * + * @param userId 用户的唯一标识符,用于确定是哪个用户的购物车信息需要构建,类型为整数类型,是获取相关数据的关键依据。 + * @param isJudgeStock 一个布尔类型的参数,用于决定是否需要判断商品的库存情况,有些业务接口在构建购物车信息时不需要考虑库存, + * 通过这个参数可以灵活控制是否执行库存相关的判断和处理逻辑,true表示需要判断库存,false表示不需要判断库存。 + * @return 返回一个CartVo对象,该对象包含了购物车的详细信息,如购物车商品列表(CartProductVo列表)、购物车总价、全选状态以及图片主机地址等信息, + * 方便在其他业务方法中作为返回数据提供给上层调用者(如控制层),进而展示给客户端(前端页面)使用。 */ - private CartVo getCartVoLimit(Integer userId,boolean isJudgeStock) { + private CartVo getCartVoLimit(Integer userId, boolean isJudgeStock) { + // 创建一个新的CartVo对象,用于组装最终要返回的购物车详细信息,这个对象将包含购物车的各种关键信息,如商品列表、总价、全选状态等内容。 CartVo cartVo = new CartVo(); + + // 创建一个空的CartProductVo列表,用于存放购物车中各个商品的详细信息对象,后续会遍历购物车记录,将每个商品的详细信息封装成CartProductVo对象后添加到这个列表中。 List cartProductVoList = Lists.newArrayList(); + + // 通过CartMapper的selectCartByUserId方法,根据传入的用户ID去查询数据库中该用户购物车中的所有商品记录,返回一个包含多个Cart对象的列表,代表购物车中的所有商品信息。 List cartList = cartMapper.selectCartByUserId(userId); + + // 初始化购物车总价为0,使用BigDecimal类型来精确表示金额,避免浮点数运算的精度问题,后续会根据购物车中选中商品的价格和数量来累加计算总价。 BigDecimal cartTotalPrice = new BigDecimal("0"); - if(CollectionUtils.isNotEmpty(cartList)){ - //1.遍历购物车,一条购物车记录对应一个商品,这些购物车共同对应到一个用户userId - for(Cart cart:cartList){ + + // 判断查询到的购物车商品记录列表是否不为空,如果不为空,说明该用户购物车中有商品,需要进一步处理这些商品的详细信息,进行购物车信息的构建。 + if (CollectionUtils.isNotEmpty(cartList)) { + // 1. 遍历购物车,一条购物车记录对应一个商品,这些购物车共同对应到一个用户userId + // 开始遍历购物车商品记录列表,对于每一条购物车记录(代表一个商品在购物车中的信息),进行如下操作,构建对应的CartProductVo对象来封装商品详细信息,并添加到购物车商品列表中。 + for (Cart cart : cartList) { CartProductVo cartProductVo = new CartProductVo(); + // 设置CartProductVo对象的ID,一般可以使用购物车记录的主键ID作为其唯一标识,方便后续在一些业务逻辑中对特定商品信息进行定位和操作。 cartProductVo.setId(cart.getId()); + // 设置CartProductVo对象的用户ID,关联到对应的用户,确保商品信息所属的用户明确,与传入的参数userId对应。 cartProductVo.setUserId(cart.getUserId()); + // 设置CartProductVo对象的商品ID,明确这个商品信息对应的具体商品,与购物车记录中的商品ID保持一致。 cartProductVo.setProductId(cart.getProductId()); - //2.从redis中获取商品,获取不到则feign获取并且重置进redis中 - String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+cart.getProductId()); + + // 2. 从redis中获取商品,获取不到则feign获取并且重置进redis中 + // 尝试从缓存(这里假设使用Redis缓存,通过CommonCacheUtil工具类操作)中获取商品信息,缓存的键是通过Constants.PRODUCT_TOKEN_PREFIX加上购物车记录中商品的ID拼接而成的, + // 这样可以根据不同的商品ID准确地在缓存中查找对应的商品缓存数据,提高获取商品信息的效率,避免频繁访问数据库。 + String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + cart.getProductId()); Product product = null; - if(productStr == null){ + // 如果从缓存中获取到的商品信息字符串为null,说明缓存中不存在该商品的相关信息,此时需要通过ProductClient去远程调用商品服务来查询商品详情。 + if (productStr == null) { ServerResponse response = productClient.queryProduct(cart.getProductId()); Object object = response.getData(); String objStr = JsonUtil.obj2String(object); - product = (Product) JsonUtil.Str2Obj(objStr,Product.class); - }else { - product = (Product) JsonUtil.Str2Obj(productStr,Product.class); + product = (Product) JsonUtil.Str2Obj(objStr, Product.class); + } else { + // 如果缓存中存在商品信息字符串,则直接使用JsonUtil的Str2Obj方法将其反序列化为Product对象,避免了再次远程查询商品服务的开销。 + product = (Product) JsonUtil.Str2Obj(productStr, Product.class); } - if(product != null){ + // 如果成功获取到了商品对象(即product不为null),说明获取到了商品的详细信息,接下来可以设置CartProductVo对象中与商品相关的各种属性值。 + if (product!= null) { + // 设置CartProductVo对象的商品主图片路径,从获取到的商品对象中获取主图片路径属性值,方便前端根据这个路径展示商品的主图片。 cartProductVo.setProductMainImage(product.getMainImage()); + // 设置CartProductVo对象的商品名称,从商品对象中获取商品名称属性值,用于在前端展示商品的名称信息。 cartProductVo.setProductName(product.getName()); + // 设置CartProductVo对象的商品副标题,从商品对象中获取副标题属性值,可在前端展示商品的补充说明信息,如卖点、优惠等相关内容。 cartProductVo.setProductSubtitle(product.getSubtitle()); + // 设置CartProductVo对象的商品状态,从商品对象中获取商品状态属性值,用于前端展示商品是否可购买等状态信息(例如是否下架等情况)。 cartProductVo.setProductStatus(product.getStatus()); + // 设置CartProductVo对象的商品价格,从商品对象中获取商品价格属性值,用于后续计算商品总价等操作,注意这里价格使用BigDecimal类型保证精度。 cartProductVo.setProductPrice(product.getPrice()); + // 设置CartProductVo对象的商品库存,从商品对象中获取商品库存属性值,用于判断库存是否足够等相关业务逻辑处理。 cartProductVo.setProductStock(product.getStock()); - //3.判断这个商品的库存,有些接口不需要再去判断库存了,所以根据传进来的isJudgeStock这个boolean参数来决定是否判断库存 + + // 3. 判断这个商品的库存,有些接口不需要再去判断库存了,所以根据传进来的isJudgeStock这个boolean参数来决定是否判断库存 int buyLimitCount = 0; - if (isJudgeStock){ - if(product.getStock() > cart.getQuantity()){ - //4.库存是够的 + if (isJudgeStock) { + // 如果需要判断库存(即isJudgeStock为true),则比较商品的库存数量和购物车中该商品的数量,判断库存是否足够。 + if (product.getStock() > cart.getQuantity()) { + // 4. 库存是够的 + // 如果商品库存大于购物车中该商品的数量,说明库存充足,购买数量可以按照购物车中记录的数量来确定,将购买数量设置为购物车中的商品数量。 buyLimitCount = cart.getQuantity(); + // 设置CartProductVo对象的限制数量标识为Constants.Cart.LIMIT_NUM_SUCCESS(应该是业务中定义好的表示数量限制成功,即库存足够的 + // 在构建购物车信息的方法中,针对每个购物车商品记录,根据库存情况设置相关属性,并计算商品总价等信息,以下是继续添加注释后的详细内容。 + +// 如果库存足够,设置CartProductVo对象的限制数量标识为表示库存足够的常量值,意味着当前商品在购物车中的数量是可购买的数量,没有受到库存限制。 cartProductVo.setLimitQuantity(Constants.Cart.LIMIT_NUM_SUCCESS); - }else { - //5.库存不够了,则返回当前最大库存 + } else { + // 5. 库存不够了,则返回当前最大库存 + // 当商品库存小于或等于购物车中该商品的数量时,说明库存不足,此时将购买数量设置为商品当前的库存数量,即用户最多只能购买库存剩余的数量。 buyLimitCount = product.getStock(); + // 设置CartProductVo对象的限制数量标识为Constants.Cart.LIMIT_NUM_FAIL(应该是业务中定义好的表示数量限制失败,即库存不足的标识), + // 用于前端展示等场景告知用户该商品库存不足,可能无法按照购物车中原本设置的数量购买。 cartProductVo.setLimitQuantity(Constants.Cart.LIMIT_NUM_FAIL); + // 创建一个新的Cart对象,用于更新购物车中该商品的记录,因为库存不足,需要将购物车中记录的商品数量更新为实际可购买的库存数量。 Cart cartItem = new Cart(); + // 设置新Cart对象的ID为当前购物车商品记录的ID,确保更新的是正确的购物车商品记录。 cartItem.setId(cart.getId()); + // 设置新Cart对象的商品数量为前面计算得到的可购买的库存数量(buyLimitCount),准备更新数据库中的购物车记录。 cartItem.setQuantity(buyLimitCount); + // 通过CartMapper的updateByPrimaryKeySelective方法,根据更新后的Cart对象去更新数据库中对应的购物车记录, + // 这个方法会根据Cart对象中不为null的属性来更新数据库相应字段,以确保购物车中的商品数量与实际库存情况相符。 cartMapper.updateByPrimaryKeySelective(cartItem); } - }else { + } else { + // 如果不需要判断库存(即isJudgeStock为false),则直接将购买数量设置为购物车中原本记录的商品数量,不考虑库存是否足够的情况,可能适用于某些特定业务场景,比如仅查看购物车信息而不涉及购买操作时。 buyLimitCount = cart.getQuantity(); } - //6.购买的数量已经是确定的了,下面就可以直接计算价格了 + // 6. 购买的数量已经是确定的了,下面就可以直接计算价格了 + // 设置CartProductVo对象的商品数量为前面确定好的购买数量(buyLimitCount),这个数量将用于后续计算该商品在购物车中的总价。 cartProductVo.setQuantity(buyLimitCount); - cartProductVo.setProductTotalPrice(BigDecimalUtil.mul(product.getPrice().doubleValue(),buyLimitCount)); + // 使用BigDecimalUtil工具类的mul方法(应该是自定义的用于BigDecimal类型乘法运算的工具方法,确保精度), + // 传入商品价格的double值(通过调用product.getPrice().doubleValue()获取)和购买数量(buyLimitCount),计算得到该商品在购物车中的总价, + // 并设置到CartProductVo对象的ProductTotalPrice属性中,方便后续汇总购物车总价以及展示给用户查看商品的价格明细。 + cartProductVo.setProductTotalPrice(BigDecimalUtil.mul(product.getPrice().doubleValue(), buyLimitCount)); + // 设置CartProductVo对象的商品选中状态,从购物车记录中获取商品的选中状态(cart.getChecked())并赋值给CartProductVo对象, + // 用于前端展示商品是否被选中以及参与购物车总价计算等相关业务逻辑(例如只有选中的商品总价才会累加到购物车总价中)。 cartProductVo.setProductChecked(cart.getChecked()); } - //7.选中的,就加入到总价中 - if(cart.getChecked() == Constants.Cart.CHECKED){ - cartTotalPrice = BigDecimalUtil.add(cartTotalPrice.doubleValue(),cartProductVo.getProductTotalPrice().doubleValue()); + // 7. 选中的,就加入到总价中 + // 判断购物车记录中该商品的选中状态是否为Constants.Cart.CHECKED(表示已选中),如果是,则将该商品的总价累加到购物车总价(cartTotalPrice)中。 + // 通过BigDecimalUtil工具类的add方法(同样是自定义的用于BigDecimal类型加法运算的工具方法,保证精度), + // 传入当前购物车总价的double值(cartTotalPrice.doubleValue())和该商品的总价(cartProductVo.getProductTotalPrice().doubleValue())进行累加计算, + // 更新购物车总价,以得到所有选中商品的总价信息。 + if (cart.getChecked() == Constants.Cart.CHECKED) { + cartTotalPrice = BigDecimalUtil.add(cartTotalPrice.doubleValue(), cartProductVo.getProductTotalPrice().doubleValue()); } + // 将构建好的包含该商品详细信息的CartProductVo对象添加到购物车商品列表(cartProductVoList)中,完成一个商品信息的处理,后续继续遍历其他购物车商品记录进行同样的操作。 cartProductVoList.add(cartProductVo); } } + + // 设置CartVo对象的购物车总价属性(CartTotalPrice)为前面计算得到的购物车总价(cartTotalPrice),方便将购物车总价信息传递给上层调用者(如控制层)展示给用户查看整个购物车的商品总价情况。 cartVo.setCartTotalPrice(cartTotalPrice); + // 设置CartVo对象的购物车商品列表属性(CartProductVoList)为前面构建好的包含所有商品详细信息的列表(cartProductVoList), + // 这样在前端展示购物车信息时,可以遍历这个列表展示每个商品的各项信息(如名称、价格、数量、选中状态等)。 cartVo.setCartProductVoList(cartProductVoList); + // 调用getAllCheckedStatus方法(下面定义的用于判断购物车中商品是否全选的方法),传入用户ID作为参数,获取购物车中商品的全选状态(是否所有商品都被选中), + // 并设置到CartVo对象的AllChecked属性中,方便前端展示购物车全选按钮的状态以及进行全选、取消全选等相关业务逻辑操作。 cartVo.setAllChecked(this.getAllCheckedStatus(userId)); - cartVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.oursnail.cn/")); - log.info("购物车列表内容为:{}",cartVo); + // 通过PropertiesUtil工具类的getProperty方法(应该是用于读取配置文件中属性值的工具方法),尝试获取配置文件中名为"ftp.server.http.prefix"的属性值, + // 如果获取不到,则使用默认值"http://img.oursnail.cn/"作为图片主机地址,这个地址通常用于前端展示购物车中商品图片时拼接图片的具体路径, + // 例如商品主图片路径可能是相对路径,通过拼接这个图片主机地址可以得到完整的可访问的图片URL,方便前端正确展示商品图片。 + cartVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix", "http://img.oursnail.cn/")); + // 使用日志记录器记录购物车列表的详细内容,方便在开发、调试以及运行过程中查看购物车信息的构建情况,排查可能出现的问题,例如购物车总价计算错误、商品信息缺失等情况。 + log.info("购物车列表内容为:{}", cartVo); + // 最后返回构建好的包含完整购物车详细信息的CartVo对象,供上层调用者(如在其他业务方法中)使用,例如将其包装在ServerResponse对象中返回给控制层,进而展示给前端页面。 return cartVo; } /** - * 0-未勾选,1-已勾选,所以我就找有没有未勾选的商品,找到就说明没有全选 + * 0 - 未勾选,1 - 已勾选,所以我就找有没有未勾选的商品,找到就说明没有全选 + * 此方法用于判断给定用户的购物车中商品是否全部被选中,通过查询购物车中已选中商品的状态数量来判断。 + * 如果查询到的已选中商品的状态数量为0,意味着没有商品被选中,即购物车中的商品不是全选状态,返回false; + * 否则说明购物车中至少有一个商品被选中,返回true表示全选状态(这里根据业务逻辑中对于全选的定义来判断,具体业务可能有所不同,可按需调整)。 + * + * @param userId 用户的唯一标识符,用于确定是哪个用户的购物车进行全选状态判断,类型为整数类型,是定位具体购物车数据的关键依据。 + * @return 返回一个布尔值,表示购物车中商品是否处于全选状态,true表示全选,false表示未全选。 */ private Boolean getAllCheckedStatus(Integer userId) { - if(userId == null){ + // 首先判断用户ID是否为null,如果为null,按照业务逻辑,直接返回false,表示无法判断全选状态或者默认不是全选状态(可根据实际业务含义调整), + // 例如在未登录或者传入无效用户ID的情况下,不认为购物车是全选状态。 + if (userId == null) { return false; } + // 调用CartMapper的selectCartCheckedStatusByUserId方法,传入用户ID作为参数,去查询数据库中该用户购物车中已选中商品的状态数量, + // 这里返回值的具体含义可能根据业务逻辑而定,例如返回的是已选中商品的记录条数等,只要返回值不为0,就表示有商品被选中。 return cartMapper.selectCartCheckedStatusByUserId(userId) == 0; } -} +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/vo/CartProductVo.java b/snailmall-cart-service/src/main/java/com/njupt/swg/vo/CartProductVo.java index 0d7bd32..1689e91 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/vo/CartProductVo.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/vo/CartProductVo.java @@ -3,7 +3,6 @@ package com.njupt.swg.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; - import java.io.Serializable; import java.math.BigDecimal; @@ -11,24 +10,69 @@ import java.math.BigDecimal; * @Author swg. * @Date 2019/1/5 15:18 * @CONTACT 317758022@qq.com - * @DESC + * @DESC 这个类(CartProductVo)是一个值对象(Value Object,简称 VO),用于在不同层之间传递购物车中商品的相关详细信息, + * 它将购物车中单个商品的多个属性整合在一起,方便进行数据传输和展示等操作。例如,在从服务层向控制层传递购物车商品信息, + * 或者控制层将这些信息进一步返回给前端展示时,使用这个VO对象可以清晰、规范地携带和传递数据,并且遵循了面向对象的设计原则, + * 使代码的可读性和可维护性更好。同时实现了Serializable接口,意味着这个对象可以被序列化和反序列化,便于在网络传输、存储等场景中使用。 */ @NoArgsConstructor +// 通过 @NoArgsConstructor注解,Lombok会为这个类自动生成一个无参构造函数。在很多情况下,比如通过反射创建对象、进行序列化和反序列化操作时,无参构造函数是必要的,确保类有默认的创建实例的方式。 + @AllArgsConstructor +// @AllArgsConstructor注解则指示Lombok生成一个包含所有参数的构造函数,方便在创建对象时一次性传入所有属性的值进行初始化,例如: +// CartProductVo cartProductVo = new CartProductVo(1, 1001, 2001, 2, "华为手机", "高性能智能手机", "http://img/phone.jpg", new BigDecimal("3999"), 1, new BigDecimal("7998"), 10, 1, "库存充足"); + @Data +// 使用Lombok的 @Data注解,会自动为这个类生成常用的方法,包括所有属性的Getter和Setter方法,以及重写的toString、hashCode和equals方法等,减少了手动编写这些样板代码的工作量,使代码更加简洁,便于对类中属性进行操作和使用。 + public class CartProductVo implements Serializable { + + // 购物车商品记录的唯一标识符,通常与数据库中购物车表内对应商品记录的主键相关联,用于在系统中唯一标识这条购物车商品记录,方便进行查询、更新、删除等操作,类型为整数类型。 private Integer id; + + // 用户的唯一标识符,关联到具体是哪个用户的购物车中的商品,通过这个字段可以明确该商品所属的用户,与系统中用户模块的用户ID相对应,类型为整数类型。 private Integer userId; + + // 商品的唯一标识符,用于确定购物车中存放的是哪个具体商品,可通过这个ID去查询商品的详细信息(如商品详情、库存等),在整个商品管理和购物流程中用于区分不同的商品,类型为整数类型。 private Integer productId; - private Integer quantity;//购物车中此商品的数量 + + // 购物车中此商品的数量,代表用户添加该商品到购物车的数量情况,例如用户添加了3件某商品到购物车,这个字段的值就会是3,类型为整数类型, + // 在购物车相关业务逻辑中,这个数量会参与到总价计算、库存判断等操作中。 + private Integer quantity; + + // 商品的名称,用于展示给用户,让用户直观地知道商品是什么,是一个字符串类型的属性,例如"华为P50手机"等, + // 在购物车列表展示、商品详情页展示等场景中会用到这个字段来呈现商品的基本信息。 private String productName; + + // 商品的副标题,通常可以用来补充说明商品的一些特点、卖点或者优惠信息等内容,也是字符串类型, + // 比如"华为P50手机,超感知徕卡四摄,限时优惠",可以进一步吸引用户关注该商品,在前端展示购物车商品时丰富商品的展示信息。 private String productSubtitle; + + // 商品的主图片的路径或者标识,一般用于在前端页面展示商品的主要图片,字符串类型, + // 这个路径可以是相对路径或者绝对路径,指向存储商品主图的位置(可能是服务器本地存储、云存储等),方便前端根据这个路径来加载并展示图片,使购物车中的商品展示更加直观形象。 private String productMainImage; + + // 商品的价格,使用BigDecimal类型来精确表示金额,避免使用浮点数类型(如double、float)带来的精度丢失问题, + // 例如可以表示为new BigDecimal("9.99"),准确地存储商品的售价信息,在购物车计算总价、下单结算等涉及金额计算的业务场景中会频繁使用这个字段。 private BigDecimal productPrice; + + // 商品的状态标识,具体含义由业务逻辑定义,可能用不同的整数值来表示不同的状态,比如1表示上架、0表示下架等, + // 用于控制商品是否在前端展示、是否可购买等情况,方便后台管理人员对商品的售卖状态进行管理和操作,在购物车中展示商品时也需要根据这个状态来决定是否显示商品等操作。 private Integer productStatus; + + // 商品在购物车中的总价,通过商品的单价(productPrice)乘以购物车中该商品的数量(quantity)计算得出,同样使用BigDecimal类型保证金额计算的精度, + // 用于在购物车中展示每个商品的总价情况,方便用户清楚了解购买该商品所需的费用,以及参与整个购物车总价的汇总计算等业务操作。 private BigDecimal productTotalPrice; + + // 商品的库存数量,用于记录当前商品还有多少件可供销售,整数类型, + // 在购物车相关业务中,如添加商品到购物车、更新商品数量等操作时,会根据这个字段的值来判断操作是否可行,比如库存不足时不能继续添加商品到购物车或者需要调整购物车中商品数量等情况,同时在前端展示时也可以告知用户商品的库存剩余情况。 private Integer productStock; - private Integer productChecked;//此商品是否勾选 - private String limitQuantity;//限制数量的一个返回结果 -} + // 此商品是否勾选,用于表示商品在购物车中的选中状态,具体取值含义可能由业务逻辑定义,比如可以用1表示选中,0表示未选中等,方便在购物车相关操作(如全选、取消全选、单独选中某个商品等操作)中标记商品的状态,类型为整数类型, + // 在计算购物车总价时,通常只会将勾选状态的商品总价进行累加,并且前端展示购物车时也会根据这个状态来展示商品是否被选中的视觉效果(如打勾图标等)。 + private Integer productChecked; + + // 限制数量的一个返回结果,这个字段的具体含义可能根据业务逻辑来定,比如在判断商品库存与购物车中商品数量关系后,根据库存情况返回相应的提示信息, + // 像"库存充足"、"库存不足,仅剩X件"等类似表示数量限制情况的内容,以字符串形式存储,方便前端展示给用户,让用户了解商品数量相关的限制情况。 + private String limitQuantity; +} \ No newline at end of file diff --git a/snailmall-cart-service/src/main/java/com/njupt/swg/vo/CartVo.java b/snailmall-cart-service/src/main/java/com/njupt/swg/vo/CartVo.java index 0ea144d..3bf9649 100644 --- a/snailmall-cart-service/src/main/java/com/njupt/swg/vo/CartVo.java +++ b/snailmall-cart-service/src/main/java/com/njupt/swg/vo/CartVo.java @@ -1,7 +1,6 @@ package com.njupt.swg.vo; import lombok.Data; - import java.math.BigDecimal; import java.util.List; @@ -9,12 +8,29 @@ import java.util.List; * @Author swg. * @Date 2019/1/5 15:18 * @CONTACT 317758022@qq.com - * @DESC + * @DESC 这个类(CartVo)是一个值对象(Value Object,简称VO),用于在不同业务层之间传递购物车整体相关的信息, + * 它将购物车的几个关键属性整合在一起,方便进行数据传输、展示以及在不同模块间进行交互操作。例如,从服务层将购物车的综合信息传递给控制层, + * 再由控制层将这些信息返回给前端进行展示,使用这个VO对象可以使数据传递更加规范、清晰,符合面向对象的设计理念,同时也增强了代码的可读性和可维护性。 */ @Data +// 使用Lombok的 @Data注解,会自动为这个类生成常用的方法,包括所有属性的Getter和Setter方法,以及重写的toString方法等,这样可以减少手动编写这些样板代码的工作量,使代码更加简洁,便于对类中属性进行操作和使用。 + public class CartVo { + + // 购物车中商品信息列表,是一个包含CartProductVo对象的List集合,每个CartProductVo对象代表购物车中的一个商品的详细信息(如商品名称、价格、数量、选中状态等), + // 通过这个列表可以完整地展示购物车中所有商品的各项具体情况,方便前端遍历并展示购物车中的商品列表,以及在后端进行基于购物车商品列表的各种业务逻辑处理(如计算总价、判断全选状态等)。 private List cartProductVoList; + + // 购物车的总价,使用BigDecimal类型来精确表示金额,避免浮点数运算带来的精度丢失问题,这个总价是通过计算购物车中所有已选中商品的价格总和得到的, + // 它准确地反映了用户当前选择购买的商品的总费用情况,在购物车页面展示以及下单结算等业务场景中是一个非常关键的信息,方便用户清楚知晓购买这些商品需要支付的金额总数。 private BigDecimal cartTotalPrice; - private Boolean allChecked;//是否已经都勾选 + + // 是否已经都勾选,是一个布尔类型的属性,用于表示购物车中的所有商品是否都处于被选中的状态,其取值为true表示购物车中的商品全部都被勾选了, + // false则表示存在至少一个商品未被勾选,这个属性方便前端展示购物车全选按钮的状态(如全选按钮是否应该被勾选等视觉效果), + // 同时在后端进行一些批量操作(如批量删除选中商品、批量结算等)时也可以根据这个属性来判断是否符合操作条件。 + private Boolean allChecked; + + // 图片主机地址,是一个字符串类型的属性,通常用于存放服务器上存储商品图片的主机地址或者前缀路径,例如"http://img.oursnail.cn/", + // 在前端展示购物车中商品的图片时,需要将商品图片的相对路径与这个图片主机地址进行拼接,从而形成完整的可访问的图片URL,以正确地加载并展示商品的图片信息,使购物车中的商品展示更加直观形象。 private String imageHost; -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java b/snailmall-user-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java index 66c6499..68051d0 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/cache/CommonCacheUtil.java @@ -11,70 +11,111 @@ import redis.clients.jedis.JedisPool; * @Author swg. * @Date 2019/1/1 15:03 * @CONTACT 317758022@qq.com - * @DESC + * @DESC 这个类(CommonCacheUtil)是一个工具类,主要用于与Redis缓存进行交互操作, + * 提供了如向缓存中存储数据、从缓存中获取数据、设置带过期时间的缓存数据以及删除缓存数据等常用功能, + * 在整个项目中,方便其他模块通过调用这些方法来利用Redis缓存提升系统性能,减少对数据库等其他数据源的频繁访问,例如在购物车模块中缓存商品信息等场景会用到此类方法。 + * 同时,在方法执行出现异常时,会进行相应的日志记录并抛出业务相关的异常(SnailmallException),方便上层调用者进行统一的异常处理和业务逻辑调整。 */ @Component +// 使用 @Component注解将这个类标记为Spring的一个组件,使得Spring容器能够自动扫描并管理这个类的实例,这样在其他需要使用的地方就可以通过依赖注入的方式来获取该类的实例进行缓存操作。 + @Slf4j +// 通过 @Slf4j注解,利用Lombok的功能自动为这个类添加一个名为log的日志记录器,用于记录在与Redis缓存交互过程中出现的各种错误信息等日志内容,方便后续排查问题、监控系统运行状态等。 + public class CommonCacheUtil { + // 通过Spring的依赖注入机制,自动注入JedisPoolWrapper对象,JedisPoolWrapper应该是对JedisPool(Jedis连接池对象,用于管理Jedis客户端与Redis服务器的连接)进行包装的一个类, + // 在这里注入它的目的是为了获取JedisPool实例,进而通过JedisPool获取Jedis客户端对象来与Redis进行交互操作,实现缓存相关的功能,这种方式有助于更好地管理和配置Jedis连接资源。 @Autowired private JedisPoolWrapper jedisPoolWrapper; - /** - * 缓存永久key + * 缓存永久key的方法,用于将指定的键值对永久存储到Redis缓存中(这里的永久是指没有设置过期时间,不过在实际生产环境中可能需要根据业务需求和缓存策略来调整)。 + * + * @param key 要存储到缓存中的键,是一个字符串类型,用于唯一标识缓存中的一个数据项,例如可以是按照一定规则拼接的商品ID、用户ID等信息作为键,方便后续根据键来获取对应的值,类型为字符串类型。 + * @param value 要存储到缓存中的值,同样为字符串类型,它可以是序列化后的对象数据(如JSON字符串形式的商品信息等),根据业务需求存储各种需要缓存的数据内容,类型为字符串类型。 */ public void cache(String key, String value) { try { + // 通过JedisPoolWrapper获取JedisPool对象,它是管理Jedis客户端连接的连接池,如果获取到的连接池不为null,说明可以正常获取到与Redis服务器的连接资源,进而进行后续操作。 JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { + // 从JedisPool中获取一个Jedis客户端对象,Jedis客户端用于实际与Redis服务器进行交互,这里使用了Java 7的try-with-resources语句, + // 可以确保在使用完Jedis客户端后自动关闭资源,释放连接,避免资源泄露,提高资源利用效率和系统的稳定性。 try (Jedis Jedis = pool.getResource()) { + // 选择Redis的数据库编号,这里选择0号数据库(通常Redis可以配置多个数据库,默认从0开始编号),根据实际业务需求和项目配置,可能会选择不同的数据库来区分不同类型的数据缓存等情况,不过这里固定选择了0号数据库进行操作。 Jedis.select(0); + // 使用Jedis客户端的set方法,将指定的键(key)和值(value)存储到Redis缓存中,实现缓存数据的功能,如果键已经存在,则会覆盖原有的值,这是一种简单的设置缓存数据的操作方式。 Jedis.set(key, value); } } } catch (Exception e) { + // 如果在缓存数据过程中出现任何异常,使用日志记录器记录错误信息,方便后续查看具体的异常情况,排查是网络问题、Redis服务器问题还是其他原因导致的缓存失败,这里记录的日志级别为ERROR,表示出现了错误情况。 log.error("redis存值失败", e); + // 抛出SnailmallException异常,异常信息为"redis报错",在调用这个方法的上层模块(如业务逻辑层的相关类)可以捕获这个异常,并根据业务需求进行相应的处理,比如返回错误提示给客户端或者进行重试等操作。 throw new SnailmallException("redis报错"); } } /** - * 获取缓存key + * 获取缓存key对应的value值的方法,用于从Redis缓存中根据指定的键获取对应的值,如果键不存在则返回null。 + * + * @param key 要从缓存中获取值的键,是一个字符串类型,通过这个键去查找在Redis缓存中之前存储的数据,类型为字符串类型。 + * @return 返回从缓存中获取到的对应键的值,为字符串类型,如果缓存中不存在该键对应的内容,则返回null,方便调用者根据返回值判断缓存中是否存在期望的数据并进行后续业务处理。 */ public String getCacheValue(String key) { String value = null; try { + // 通过JedisPoolWrapper获取JedisPool对象,用于后续获取Jedis客户端连接,判断连接池是否可用,若不为null则可以继续进行获取缓存值的操作。 JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { + // 从JedisPool中获取一个Jedis客户端对象,并使用try-with-resources语句确保资源的正确释放,然后通过这个客户端与Redis服务器进行交互操作。 try (Jedis Jedis = pool.getResource()) { + // 选择Redis的0号数据库,与前面存储数据时选择的数据库保持一致,确保在同一个数据库中进行数据的读写操作,避免出现数据查找不一致等问题。 Jedis.select(0); + // 使用Jedis客户端的get方法,根据传入的键(key)从Redis缓存中获取对应的值,并将获取到的值赋给局部变量value,若键不存在则返回null,这是Redis获取缓存值的基本操作方式。 value = Jedis.get(key); } } } catch (Exception e) { + // 如果在获取缓存值的过程中出现异常,记录详细的错误日志,方便后续排查问题,确定是网络、服务器还是其他原因导致的获取失败,日志级别为ERROR,表示出现了错误情况。 log.error("redis获取指失败", e); + // 抛出SnailmallException异常,告知调用者Redis操作出现报错,上层模块可以捕获该异常并进行相应的业务处理,比如返回默认值或者提示客户端缓存获取失败等情况。 throw new SnailmallException("redis报错"); } return value; } /** - * 过期key + * 过期key的方法,用于向Redis缓存中存储一个带有过期时间的数据项,即设置一个键值对,同时指定这个键在一定时间(以秒为单位)后过期失效, + * 并且先通过setnx操作保证只有在键不存在时才进行设置(原子操作,避免并发情况下的重复设置问题),返回一个表示设置是否成功的结果(1表示成功,0表示失败,因为键已经存在等原因)。 + * + * @param key 要存储到缓存中的键,是一个字符串类型,用于唯一标识缓存中的一个数据项,例如可以是按照一定规则拼接的临时数据的标识等,类型为字符串类型。 + * @param value 要存储到缓存中的值,同样为字符串类型,存储具体的数据内容,比如临时的验证码信息等需要设置过期时间的数据,类型为字符串类型。 + * @param expire 表示键值对在Redis缓存中的过期时间,单位为秒,通过这个参数可以控制缓存数据的有效时长,根据业务需求合理设置过期时间,例如设置验证码的有效时间为60秒等情况,类型为整数类型。 + * @return 返回一个长整型数值,表示设置操作的结果,1表示设置成功,即键不存在时成功设置了键值对并设置了过期时间;0表示设置失败,通常是因为键已经存在,不符合setnx操作只有键不存在时才设置的条件,方便调用者根据返回结果判断操作是否成功并进行后续业务处理。 */ public long cacheNxExpire(String key, String value, int expire) { long result = 0; try { + // 获取JedisPool对象,用于获取Jedis客户端连接,若连接池不为null则可进行后续与Redis服务器的交互操作。 JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { + // 从JedisPool中获取一个Jedis客户端对象,并通过try-with-resources语句确保使用后自动关闭资源,然后与Redis服务器进行操作。 try (Jedis jedis = pool.getResource()) { + // 选择Redis的0号数据库,确保在正确的数据库中进行数据操作。 jedis.select(0); + // 使用Jedis客户端的setnx方法(SET if Not eXists的缩写,是一个原子操作),尝试将指定的键(key)和值(value)存储到Redis缓存中, + // 只有当键不存在时才会设置成功,返回1;如果键已经存在,则设置失败,返回0,将这个结果赋值给result变量,用于后续判断操作是否成功以及返回给调用者。 result = jedis.setnx(key, value); + // 如果前面的setnx操作成功(即result为1),则使用Jedis客户端的expire方法,为刚刚设置的键(key)设置过期时间,单位为秒,通过传入的expire参数指定具体的过期时长,确保缓存数据在指定时间后自动失效。 jedis.expire(key, expire); } } } catch (Exception e) { + // 如果在设置带过期时间的缓存数据过程中出现异常,记录详细的错误日志,便于后续排查是哪个环节出现问题导致操作失败,日志级别为ERROR,表示出现错误情况。 log.error("redis塞值和设置缓存时间失败", e); + // 抛出SnailmallException异常,告知调用者Redis操作出现报错,上层模块捕获该异常后可以根据业务逻辑进行相应处理,比如重新尝试设置或者提示客户端操作失败等情况。 throw new SnailmallException("redis报错"); } @@ -82,23 +123,25 @@ public class CommonCacheUtil { } /** - * 删除缓存key + * 删除缓存key的方法,用于从Redis缓存中删除指定的键及其对应的数据项,如果键不存在则不会执行任何实际删除操作,也不会抛出异常。 + * + * @param key 要从缓存中删除的键,是一个字符串类型,通过这个键来定位并删除在Redis缓存中对应的缓存数据,类型为字符串类型。 */ public void delKey(String key) { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); try { + // 使用Jedis客户端的del方法,尝试从Redis缓存中删除指定的键(key)对应的缓存数据,若键存在则会成功删除,若不存在则不做任何操作,不会抛出异常,这是Redis删除缓存数据的基本操作方式。 jedis.del(key); } catch (Exception e) { + // 如果在删除缓存数据过程中出现异常,记录详细的错误日志,方便后续排查是网络、Redis服务器还是其他原因导致的删除失败,日志级别为ERROR,表示出现错误情况。 log.error("从redis中删除失败", e); + // 抛出SnailmallException异常,告知调用者Redis操作出现报错,上层模块捕获该异常后可以根据业务逻辑进行相应处理,比如提示客户端缓存删除失败等情况。 throw new SnailmallException("redis报错"); } } } } - - - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java b/snailmall-user-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java index addfe24..a40e96e 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/cache/JedisPoolWrapper.java @@ -13,30 +13,64 @@ import javax.annotation.PostConstruct; * @Date 2019/1/1 15:00 * @CONTACT 317758022@qq.com * @DESC 只做了单个redis,但是课程中实现的redis客户端集群,要掌握一致性hash算法 + * 这个类(JedisPoolWrapper)主要用于封装JedisPool(Jedis连接池对象)的初始化和获取操作,通过依赖注入获取相关配置参数, + * 利用JedisPoolConfig来配置连接池的各项属性,然后创建JedisPool实例,方便在其他类(如与Redis缓存交互的工具类)中获取JedisPool对象,进而获取Jedis客户端与Redis进行交互, + * 同时在初始化过程中进行了日志记录,以便及时知晓连接池初始化的成功与否情况,对于后续排查问题、监控系统状态等有一定帮助。 */ @Component +// 使用 @Component注解将这个类标记为Spring的一个组件,使得Spring容器能够自动扫描并管理这个类的实例,这样其他需要使用JedisPool的地方就可以通过依赖注入等方式获取该类的实例进行操作。 + @Slf4j +// 通过 @Slf4j注解,利用Lombok的功能自动为这个类添加一个名为log的日志记录器,用于记录在初始化JedisPool过程中出现的各种情况(成功或失败)的日志信息,方便后续排查问题、监控系统运行状态等。 + public class JedisPoolWrapper { + + // 通过Spring的依赖注入机制,自动注入Parameters对象,Parameters类应该是用于存放Redis相关配置参数(如最大连接数、最大空闲连接数、最大等待时间等)的一个类, + // 在这里注入它的目的是获取这些配置参数来正确配置JedisPool连接池,使连接池的各项属性符合项目的实际需求和运行环境要求。 @Autowired private Parameters parameters; + // 定义一个JedisPool类型的私有变量,用于存放初始化后的JedisPool实例,初始值设置为null,在后续的init方法中会根据配置参数进行实例化操作, + // 通过这种方式可以在类的其他方法(如getJedisPool方法)中返回这个实例供外部使用,实现对JedisPool的封装和统一管理。 private JedisPool jedisPool = null; + /** + * @PostConstruct注解标记的方法会在对象依赖注入完成后自动被调用,在这里用于初始化JedisPool连接池, + * 通过从注入的Parameters对象中获取配置参数,利用JedisPoolConfig来设置连接池的各项属性,然后创建JedisPool实例,完成连接池的初始化工作, + * 如果初始化过程中出现异常,会通过日志记录错误信息,方便后续排查问题。 + */ @PostConstruct - public void init(){ + public void init() { try { + // 创建一个JedisPoolConfig对象,JedisPoolConfig用于配置JedisPool连接池的各种属性,例如连接池的最大连接数、最大空闲连接数、最大等待时间等,通过设置这些属性可以优化连接池的性能和资源利用情况。 JedisPoolConfig config = new JedisPoolConfig(); + // 设置JedisPool连接池的最大连接数,通过调用Parameters对象的getRedisMaxTotal方法获取配置的最大连接数参数值,并设置到JedisPoolConfig对象中, + // 这个参数决定了连接池中最多可以同时存在多少个Jedis客户端连接,需要根据实际的Redis服务器性能、业务并发量等因素合理设置,避免连接数过多导致服务器压力过大或过少影响业务处理效率。 config.setMaxTotal(parameters.getRedisMaxTotal()); + // 设置JedisPool连接池的最大空闲连接数,从Parameters对象中获取对应的配置参数值(通过getRedisMaxIdle方法)并设置到JedisPoolConfig对象中, + // 最大空闲连接数表示连接池中最多可以空闲(未被使用)的Jedis客户端连接数量,合理设置这个参数有助于提高连接资源的利用效率,避免资源浪费。 config.setMaxIdle(parameters.getRedisMaxIdle()); + // 设置JedisPool连接池的最大等待时间,单位为毫秒,通过Parameters对象的getRedisMaxWaitMillis方法获取配置参数值并设置到JedisPoolConfig对象中, + // 当连接池中的连接资源耗尽时,如果有新的获取连接请求,请求线程会在这个最大等待时间内等待空闲连接出现,如果超过这个时间仍无空闲连接,则可能抛出异常,根据业务对响应时间的要求等因素合理设置这个参数。 config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis()); - jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx"); + // 使用配置好的JedisPoolConfig对象、Redis服务器的主机地址(通过Parameters对象的getRedisHost方法获取)、端口号(通过getRedisPort方法获取)以及连接超时时间(这里设置为2000毫秒)和密码(这里示例中为"xxx",实际中应替换为真实密码)等信息, + // 创建一个JedisPool实例,这个实例就是用于管理Jedis客户端与Redis服务器连接的连接池,后续其他代码可以通过这个连接池获取Jedis客户端来与Redis进行交互操作。 + jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx"); + // 使用日志记录器记录一条信息,表示初始化redis连接池成功,方便在运行时查看系统启动过程中连接池初始化的情况,确认是否正常初始化,对于监控系统状态有一定帮助。 log.info("【初始化redis连接池成功】"); - }catch (Exception e){ - log.error("【初始化redis连接池失败】",e); + } catch (Exception e) { + // 如果在初始化JedisPool连接池过程中出现异常,使用日志记录器记录错误信息,日志级别为ERROR,表示出现了错误情况,同时将异常对象e传入,方便记录详细的异常堆栈信息,便于后续排查问题,确定是配置参数错误、网络问题还是其他原因导致的初始化失败。 + log.error("【初始化redis连接池失败】", e); } } + /** + * 这个方法用于对外提供获取JedisPool实例的接口,使得其他类(如与Redis缓存交互的工具类)可以获取到这个连接池对象,进而通过连接池获取Jedis客户端来与Redis进行交互操作, + * 返回的就是之前在init方法中初始化好的JedisPool实例,如果初始化失败(即jedisPool为null),则返回null,调用者需要根据返回值判断是否能正常使用连接池进行后续操作。 + * + * @return 返回JedisPool实例,用于获取Jedis客户端与Redis进行交互,可能为null(如果初始化失败的情况),类型为JedisPool。 + */ public JedisPool getJedisPool() { return jedisPool; } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/cache/Parameters.java b/snailmall-user-service/src/main/java/com/njupt/swg/cache/Parameters.java index 9121848..01aaa8a 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/cache/Parameters.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/cache/Parameters.java @@ -8,26 +8,49 @@ import org.springframework.stereotype.Component; * @Author swg. * @Date 2019/1/1 14:27 * @CONTACT 317758022@qq.com - * @DESC + * @DESC 这个类(Parameters)是一个用于存储项目中各种配置参数的实体类,通过Spring的 @Value注解从配置文件(通常是application.properties或application.yml等配置文件)中读取对应配置项的值, + * 并将这些值注入到类的相应属性中,方便在项目的其他地方使用这些配置参数进行相关操作,例如在初始化Redis连接池、与Zookeeper进行交互等场景中,都需要使用到这里定义的配置参数。 + * 使用Lombok的 @Data注解,会自动为这个类生成常用的方法,包括所有属性的Getter和Setter方法,以及重写的toString方法等,减少了手动编写这些样板代码的工作量,使代码更加简洁,便于对类中属性进行操作和使用。 */ @Component +// 使用 @Component注解将这个类标记为Spring的一个组件,使得Spring容器能够自动扫描并管理这个类的实例,并且能够识别类中的 @Value注解进行属性值的注入操作,确保可以正确获取配置文件中的参数值。 + @Data +// 通过 @Data注解,Lombok会自动生成各属性的Getter和Setter方法等,方便获取和设置类中的各个配置参数属性的值,例如可以通过getRedisHost方法获取Redis服务器的主机地址,通过setRedisHost方法设置新的主机地址等操作,提高代码的简洁性和可维护性。 + public class Parameters { + /*****redis config start*******/ + // 使用 @Value注解,从配置文件(例如application.properties或application.yml等,具体取决于项目配置)中读取名为"redis.host"的配置项的值,并将其注入到这个名为redisHost的字符串类型属性中, + // 这个属性用于存储Redis服务器的主机地址,例如"localhost"或者具体的IP地址等,在初始化与Redis的连接时会用到这个地址来定位Redis服务器,类型为字符串类型。 @Value("${redis.host}") private String redisHost; + + // 同样通过 @Value注解,从配置文件中读取"redis.port"配置项的值,并注入到这个整数类型的属性中,该属性表示Redis服务器的端口号,默认Redis端口号一般是6379, + // 但可以根据实际部署情况进行配置修改,在建立与Redis服务器的连接时需要指定正确的端口号,类型为整数类型。 @Value("${redis.port}") private int redisPort; + + // 此处可能存在属性命名上的混淆,从 @Value注解的配置项"${redis.max-idle}"来看,本意应该是获取Redis连接池的最大空闲连接数配置参数, + // 但属性名却定义为redisMaxTotal,可能是命名错误(按常规理解应该是redisMaxIdle),这个属性用于设置JedisPool(如果使用Jedis连接Redis的话)中允许的最大空闲连接数量,合理设置可优化连接资源利用,类型为整数类型。 @Value("${redis.max-idle}") private int redisMaxTotal; + + // 类似地,这里的 @Value注解从配置文件中读取"${redis.max-total}"配置项的值注入到该属性,按常理应该是表示Redis连接池的最大连接数(即连接池中最多能同时存在的Jedis客户端连接数量), + // 不过属性名是redisMaxIdle(可能是命名失误,正常应类似redisMaxConnTotal之类更符合语义的名称),在配置JedisPool时会依据这个参数来控制连接池规模,类型为整数类型。 @Value("${redis.max-total}") private int redisMaxIdle; + + // 通过 @Value注解读取配置文件中"${redis.max-wait-millis}"配置项的值,注入到这个整数类型属性中,该属性表示当从Redis连接池中获取连接时,如果连接池中没有空闲连接, + // 请求线程最多等待的时间(单位为毫秒),根据业务的并发情况和对响应时间的要求等因素合理设置这个参数,避免长时间等待或因超时而出现异常,类型为整数类型。 @Value("${redis.max-wait-millis}") private int redisMaxWaitMillis; /*****redis config end*******/ /*****curator config start*******/ + // 使用 @Value注解从配置文件中读取名为"zk.host"的配置项的值,并注入到这个字符串类型属性中,该属性用于存储Zookeeper服务器的主机地址, + // 在项目中如果涉及到使用Zookeeper进行服务注册与发现、分布式锁等相关功能时,会通过这个地址去连接Zookeeper服务器,类型为字符串类型。 @Value("${zk.host}") private String zkHost; /*****curator config end*******/ -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/constants/Constants.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/constants/Constants.java index a510c62..494f8bd 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/constants/Constants.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/constants/Constants.java @@ -4,43 +4,78 @@ package com.njupt.swg.common.constants; * @Author swg. * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com - * @DESC + * @DESC 这个类(Constants)主要用于定义项目中的各种常量,将一些在项目中经常使用且固定不变的值集中管理起来, + * 通过使用常量可以提高代码的可读性、可维护性以及避免在代码中出现魔法数字(直接使用字面常量而难以理解其含义的情况)和硬编码字符串等问题,方便不同模块的代码复用这些常量进行相关业务逻辑处理。 */ public class Constants { - /**自定义状态码 start**/ + + /** + * 自定义状态码 start + * 以下是定义的一组表示不同响应状态的整型常量,用于在整个项目的前后端交互或者模块间通信时,统一标识操作的结果状态, + * 使得代码中对不同状态的判断和处理更加清晰明了,避免使用随意的数字来表示状态导致代码可读性差的问题。 + */ + // 表示操作成功的状态码,通常在请求处理成功后返回给客户端,对应HTTP状态码中的200,表示请求已成功被服务器接收、理解、并接受,按照业务逻辑正常处理完成,类型为整数类型。 public static final int RESP_STATUS_OK = 200; + // 表示未授权的状态码,对应HTTP状态码中的401,意味着客户端尝试访问需要授权的资源,但未提供有效的认证信息(如未登录或者登录凭证过期等情况),服务器拒绝该请求,类型为整数类型。 public static final int RESP_STATUS_NOAUTH = 401; + // 表示服务器内部错误的状态码,等同于HTTP状态码中的500,说明在服务器端处理请求的过程中发生了意外的错误,可能是代码逻辑问题、数据库连接异常等原因导致无法正常完成请求处理,类型为整数类型。 public static final int RESP_STATUS_INTERNAL_ERROR = 500; + // 表示请求参数错误的状态码,类似HTTP状态码中的400,意味着客户端发送的请求中包含了不符合要求的参数(如格式错误、必填参数缺失等情况),服务器无法按照这样的参数进行正确的业务处理,类型为整数类型。 public static final int RESP_STATUS_BADREQUEST = 400; + /** + * 自定义状态码 end + */ - /**自定义状态码 end**/ - - /***redis user相关的key以这个打头**/ + /** + * *redis user相关的key以这个打头 + * 定义了一个字符串常量,用于作为Redis中存储与用户相关数据的键(key)的前缀,通过添加这个前缀可以方便地对用户相关的缓存数据进行区分和管理, + * 例如存储用户登录信息、用户权限信息等在Redis中的键都可以以这个前缀开头,再拼接具体的用户标识(如用户ID等)来形成完整的键,类型为字符串类型。 + */ public static final String TOKEN_PREFIX = "user_"; /** * 用户登陆redis的过期时间 + * 这里定义了一个内部接口(RedisCacheExtime),在接口中定义了一个整型常量(REDIS_SESSION_EXTIME),用于表示用户登录信息在Redis缓存中的过期时间, + * 以秒为单位,当前设置为60 * 60 * 10,即30分钟,通过这种方式将缓存过期时间这个常量进行了集中管理,方便在需要设置用户登录缓存过期时间的地方统一引用,同时如果后续需要调整过期时间,只需要修改此处的值即可,无需在多处代码中逐个修改,提高了可维护性。 */ - public interface RedisCacheExtime{ - int REDIS_SESSION_EXTIME = 60 * 60 * 10;//30分钟 + public interface RedisCacheExtime { + int REDIS_SESSION_EXTIME = 60 * 60 * 10; // 30分钟 } - /** 用户注册判断重复的参数类型 start **/ + /** + * 用户注册判断重复的参数类型 start + * 以下定义了两个字符串常量,用于在用户注册相关业务逻辑中,明确判断重复时所针对的参数类型,例如在检查用户名或者邮箱是否已经被其他用户注册使用时, + * 通过使用这些常量可以使代码更加清晰直观,避免直接使用字面字符串导致代码可读性差和难以维护的问题,方便进行统一的逻辑判断和处理。 + */ + // 表示用于判断用户注册时邮箱是否重复的参数类型标识,在进行邮箱重复性验证的代码逻辑中可以使用这个常量来明确操作的对象是邮箱,类型为字符串类型。 public static final String EMAIL = "email"; + // 表示用于判断用户注册时用户名是否重复的参数类型标识,在验证用户名是否已被使用的业务逻辑中,通过这个常量来清晰地表明所处理的参数是用户名,类型为字符串类型。 public static final String USERNAME = "username"; - /** 用户注册判断重复的参数类型 end **/ + /** + * 用户注册判断重复的参数类型 end + */ - /** 用户角色 **/ - public interface Role{ - int ROLE_CUSTOME = 0;//普通用户 - int ROLE_ADMIN = 1;//管理员用户 + /** + * 用户角色 + * 这里定义了一个内部接口(Role),在接口中定义了两个整型常量,用于表示项目中不同的用户角色,通过这种方式清晰地划分和标识了用户角色类型, + * 在涉及用户权限管理、不同角色操作权限判断等业务逻辑中,可以方便地使用这些常量进行条件判断和相应的业务处理,提高代码的可读性和可维护性。 + */ + public interface Role { + // 表示普通用户的角色标识,通常普通用户具有基本的使用系统功能的权限,但不具备管理、配置等高级权限,在权限判断等代码中可以通过这个常量来识别用户是否为普通用户,类型为整数类型。 + int ROLE_CUSTOME = 0; // 普通用户 + + // 表示管理员用户的角色标识,管理员用户一般拥有系统的高级权限,例如可以管理用户、配置系统参数等操作,在进行权限相关业务逻辑处理时,通过这个常量来区分是否为管理员角色,类型为整数类型。 + int ROLE_ADMIN = 1; // 管理员用户 } - /**用户注册分布式锁路径***/ + /** + * 用户注册分布式锁路径 + * 定义了一个字符串常量,用于指定在使用分布式锁来保证用户注册操作的原子性和一致性时所对应的锁路径, + * 在涉及分布式系统中多节点同时可能进行用户注册操作的场景下,通过这个路径来创建和管理分布式锁,确保同一时间只有一个节点能够进行用户注册相关的关键操作(如写入数据库等),避免数据冲突等问题,类型为字符串类型。 + */ public static final String USER_REGISTER_DISTRIBUTE_LOCK_PATH = "/user_reg"; - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java index cef87ac..ec7b028 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/ExceptionHandlerAdvice.java @@ -1,6 +1,5 @@ package com.njupt.swg.common.exception; - import com.njupt.swg.common.constants.Constants; import com.njupt.swg.common.resp.ServerResponse; import lombok.extern.slf4j.Slf4j; @@ -13,21 +12,49 @@ import org.springframework.web.bind.annotation.ResponseBody; * @Date 2019/1/1 13:21 * @CONTACT 317758022@qq.com * @DESC 全局异常处理 + * 这个类(ExceptionHandlerAdvice)是一个用于全局处理异常的类,它利用了Spring框架提供的 @ControllerAdvice 和 @ExceptionHandler 注解来实现统一捕获并处理项目中出现的异常情况, + * 通过将异常信息记录到日志中,并根据不同类型的异常返回相应的统一格式的响应给客户端,提高了系统的稳定性和用户体验,避免异常信息直接暴露给用户导致用户困惑以及保证系统在出现异常后能给出合理的提示让用户知晓情况并采取适当的操作。 */ @ControllerAdvice +// @ControllerAdvice注解表明这个类是一个全局的异常处理类,它可以对整个Spring MVC应用中多个Controller(控制器)里抛出的异常进行统一处理,使得异常处理逻辑更加集中,便于维护和管理,而不需要在每个Controller中单独编写异常处理代码。 + @ResponseBody +// @ResponseBody注解用于指示这个类中的方法返回的结果直接作为响应体写入到HTTP响应中,而不是像常规的Controller方法那样去寻找视图进行渲染等操作,这样可以方便地返回JSON格式或者其他格式的数据作为响应,符合现在大多数前后端分离项目中直接返回数据给前端的需求。 + @Slf4j +// 通过 @Slf4j注解,利用Lombok的功能自动为这个类添加一个名为log的日志记录器,用于记录在处理异常过程中出现的各种异常信息,方便后续排查问题、分析系统出现异常的原因等,确保对异常情况有详细的记录可供查看。 + public class ExceptionHandlerAdvice { + + /** + * 异常处理方法,用于处理所有继承自Exception类的异常(也就是捕获几乎所有的运行时异常和非运行时异常), + * 当项目中任何地方抛出Exception类型或者其子类的异常时,都会被这个方法捕获并进行相应的处理,将异常信息记录到日志中,然后返回一个统一格式的表示系统内部错误的响应给客户端。 + * + * @param e 捕获到的异常对象,通过这个对象可以获取异常的详细信息,例如异常消息、堆栈信息等,方便记录日志以及判断异常的具体情况,类型为Exception。 + * @return 返回一个ServerResponse对象,这个对象封装了统一格式的响应信息,包含了表示系统内部错误的状态码(通过Constants.RESP_STATUS_INTERNAL_ERROR常量获取)以及提示用户系统异常,请稍后再试的错误消息,方便前端根据返回的状态码和消息展示相应的提示给用户。 + */ @ExceptionHandler(Exception.class) - public ServerResponse handleException(Exception e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); + public ServerResponse handleException(Exception e) { + // 使用日志记录器记录异常信息,通过e.getMessage()获取异常的简要消息,同时传入异常对象e本身,这样日志中还会记录详细的异常堆栈信息,方便后续排查问题,确定是哪里出现了问题导致这个异常被抛出,日志级别为ERROR,表示出现了错误情况。 + log.error(e.getMessage(), e); + // 通过ServerResponse的createByErrorCodeMessage静态方法创建一个表示错误响应的ServerResponse对象,传入表示系统内部错误的状态码(Constants.RESP_STATUS_INTERNAL_ERROR)以及提示用户的错误消息("系统异常,请稍后再试"), + // 返回这个对象给前端,告知前端系统出现了内部错误,让用户稍后再尝试操作,实现了对异常情况的统一响应处理,使前端能以统一的方式展示错误提示给用户。 + return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试"); } + /** + * 异常处理方法,专门用于处理SnailmallException类型的异常,SnailmallException应该是项目中自定义的业务异常类, + * 当业务逻辑中抛出这种自定义异常时,这个方法会捕获它,记录异常信息到日志中,并根据自定义异常中携带的状态码和消息返回相应的统一格式的响应给客户端,使得业务异常能够按照业务需求进行特定的处理和反馈给用户。 + * + * @param e 捕获到的SnailmallException类型的异常对象,通过这个对象可以获取自定义异常中定义的状态码、异常消息等详细信息,方便进行针对性的响应处理,类型为SnailmallException。 + * @return 返回一个ServerResponse对象,这个对象的状态码和消息根据传入的SnailmallException对象中的信息来设置,通过调用ServerResponse的createByErrorCodeMessage方法,传入异常对象的状态码(e.getExceptionStatus())和异常消息(e.getMessage()), + * 返回给前端相应的提示信息,告知用户业务层面出现的具体问题情况,实现了对业务异常的统一处理和合理反馈。 + */ @ExceptionHandler(SnailmallException.class) - public ServerResponse handleException(SnailmallException e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); + public ServerResponse handleException(SnailmallException e) { + // 使用日志记录器记录SnailmallException类型异常的详细信息,包括异常消息和堆栈信息,方便后续查看业务异常出现的原因,进行业务逻辑的调整和优化等,日志级别为ERROR,表示出现了错误情况。 + log.error(e.getMessage(), e); + // 根据自定义异常对象中携带的状态码和消息创建一个ServerResponse对象,返回给前端,使得前端可以根据返回的状态码和消息展示相应的提示给用户,告知用户业务层面具体的异常情况,实现了业务异常的统一处理和反馈机制。 + return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage()); } - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java index 363f19d..ff86151 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/exception/SnailmallException.java @@ -7,19 +7,40 @@ import lombok.Getter; * @Author swg. * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com - * @DESC + * @DESC 这个类(SnailmallException)是项目中自定义的一个异常类,继承自Java的RuntimeException(运行时异常), + * 用于在业务逻辑中抛出特定的、符合项目业务场景的异常情况,通过自定义异常可以携带更多与业务相关的信息(比如特定的状态码等),方便进行统一的异常处理以及向客户端反馈更准确的错误提示信息, + * 区别于Java内置的通用异常,使得异常处理更加贴合项目实际业务需求,提高系统的可维护性和异常处理的灵活性。 */ @Getter -public class SnailmallException extends RuntimeException{ +// 使用Lombok的 @Getter注解,会自动为这个类的私有属性(exceptionStatus)生成对应的Getter方法,这样在其他地方就可以方便地获取这个属性的值,无需手动编写Getter方法,使代码更加简洁,便于操作类中的属性。 + +public class SnailmallException extends RuntimeException { + + // 定义一个私有整型属性,用于存储异常对应的状态码,初始值设置为ResponseEnum.ERROR.getCode(),这里ResponseEnum应该是一个枚举类,用于定义各种响应状态相关的枚举值(如错误码等), + // 通过这种方式可以默认给自定义异常赋予一个通用的错误状态码,后续在构造函数中可以根据具体情况进行修改,类型为整数类型。 private int exceptionStatus = ResponseEnum.ERROR.getCode(); - public SnailmallException(String msg){ + /** + * 构造函数,用于创建一个只传入异常消息的SnailmallException实例, + * 调用父类(RuntimeException)的构造函数,将传入的异常消息(msg)传递给父类,以便在抛出和处理异常时能够获取到这个具体的异常消息内容, + * 此时异常状态码会使用默认的ResponseEnum.ERROR.getCode()值,适用于一些通用的、不需要特定状态码的业务异常情况。 + * + * @param msg 异常消息,是一个字符串类型,用于描述出现异常的具体原因或情况,方便在日志记录、向客户端反馈等场景中展示给开发人员或用户知晓具体的异常内容,类型为字符串类型。 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 构造函数,用于创建一个同时传入异常状态码和异常消息的SnailmallException实例, + * 首先调用父类(RuntimeException)的构造函数,将传入的异常消息(msg)传递给父类,确保可以获取到异常消息内容, + * 然后将传入的状态码(code)赋值给当前类的exceptionStatus属性,用于设置这个自定义异常对应的特定状态码,方便根据不同的业务异常场景设置不同的状态码,以便在统一异常处理时进行更精准的判断和反馈。 + * + * @param code 异常状态码,是一个整数类型,通过这个状态码可以区分不同类型的业务异常情况,例如不同业务模块出现的错误可以对应不同的状态码,便于在全局异常处理中进行针对性的响应处理,类型为整数类型。 + * @param msg 异常消息,同样是一个字符串类型,用于描述具体的异常原因或情况,与上面的构造函数中msg参数作用相同,类型为字符串类型。 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java index e4e59c7..927cda3 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ResponseEnum.java @@ -7,19 +7,34 @@ import lombok.Getter; * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com * @DESC 基本的返回状态描述 + * 这个类(ResponseEnum)是一个枚举类型,用于定义项目中常见的基本返回状态相关的枚举值,通过枚举的方式将不同的返回状态进行集中管理, + * 每个枚举常量都包含了一个状态码(code)和对应的描述信息(desc),方便在整个项目的不同模块中统一使用这些状态来表示操作的结果情况,提高代码的可读性、可维护性以及避免出现魔法数字(直接使用字面常量而难以理解其含义的情况)和硬编码字符串等问题。 */ @Getter +// 使用Lombok的 @Getter注解,会自动为这个枚举类型中的私有属性(code和desc)生成对应的Getter方法,这样在其他地方就可以方便地获取这些属性的值,无需手动编写Getter方法,使代码更加简洁,便于操作枚举中的属性值。 + public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + // 定义一个名为SUCCESS的枚举常量,表示操作成功的返回状态,其对应的状态码为0,描述信息为"SUCCESS", + // 在项目中当某个操作(如数据库查询、业务逻辑处理等)成功完成后,可以使用这个枚举常量来表示成功的结果,方便统一返回格式以及在其他代码中进行结果判断等操作。 + SUCCESS(0, "SUCCESS"), + // 定义一个名为ERROR的枚举常量,状态码为1,描述信息为"ERROR",用于表示出现错误的返回状态,在业务逻辑中如果发生了异常或者不符合预期的情况,可以使用这个枚举常量来表示操作失败,向调用者传达出现错误的信息。 + ERROR(1, "ERROR"), + // 定义一个名为ILLEGAL_ARGUMENTS的枚举常量,状态码为2,描述信息为"ILLEGAL_ARGUMENTS",通常用于表示传入的参数不符合要求(如参数格式错误、缺失必要参数等情况)的返回状态, + // 在进行参数校验的代码逻辑中,如果发现参数不合法,就可以使用这个枚举常量来返回相应的错误提示信息,告知调用者参数存在问题。 + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + // 定义一个名为NEED_LOGIN的枚举常量,状态码为10,描述信息为"NEED_LOGIN",主要用于表示需要用户登录才能进行后续操作的返回状态, + // 例如在访问某些需要权限的接口时,如果用户未登录,就可以返回这个枚举常量对应的状态信息,提示用户先进行登录操作,再尝试访问相应的资源。 + + NEED_LOGIN(10, "NEED_LOGIN"); + // 定义一个私有整型属性,用于存储枚举常量对应的状态码,通过构造函数进行初始化赋值,在其他地方可以通过Getter方法获取这个状态码,用于在返回结果中传递具体的状态标识,类型为整数类型。 private int code; + // 定义一个私有字符串属性,用于存储枚举常量对应的描述信息,同样通过构造函数初始化,方便在返回结果中向调用者展示更直观的操作结果描述内容,类型为字符串类型。 private String desc; - ResponseEnum(int code,String desc){ + // 枚举类型的构造函数,用于初始化每个枚举常量的状态码(code)和描述信息(desc)属性,在定义枚举常量时会自动调用这个构造函数传入相应的值进行初始化,确保每个枚举常量都有对应的状态码和描述内容,便于后续使用。 + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java index fd098b8..6c5971b 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/resp/ServerResponse.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; - import java.io.Serializable; /** @@ -13,67 +12,102 @@ import java.io.Serializable; * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com * @DESC 作为本项目的通用的返回封装类 + * 这个类(ServerResponse)是项目中的一个通用返回封装类,用于将服务端的操作结果统一进行包装,方便在不同的业务接口返回数据时,按照统一的格式向客户端(如前端应用)传递信息, + * 它包含了状态码(status)、提示消息(msg)以及具体的数据内容(data)等属性,同时提供了一系列静态方法来方便地创建不同情况下(成功或失败等)的返回实例,增强了返回结果的规范性、可读性以及便于客户端进行统一的结果处理。 */ @Getter +// 使用Lombok的 @Getter注解,会自动为这个类中的私有属性(status、msg、data)生成对应的Getter方法,使得在其他地方(如调用接口的客户端代码)可以方便地获取这些属性的值,无需手动编写Getter方法,代码更加简洁,便于操作类中的属性。 + @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// 这个注解用于配置Jackson序列化的行为,这里指定了只序列化非空的属性,也就是说在将ServerResponse对象转换为JSON格式(例如返回给前端时),如果属性值为null,那么该属性就不会出现在最终的JSON数据中, +// 这样可以减少不必要的数据传输,使返回的JSON数据更加精简,符合实际业务中通常不需要返回大量空值属性的需求。 + public class ServerResponse implements Serializable { + // 定义一个私有整型属性,用于存储返回结果的状态码,通过不同的状态码可以表示操作是成功还是失败以及其他各种不同的业务状态,例如可以使用前面定义的ResponseEnum枚举中的状态码值,类型为整数类型。 private int status; + // 定义一个私有字符串属性,用于存储返回结果的提示消息,这个消息可以是操作成功的提示、失败的错误信息或者其他相关的说明内容,方便客户端根据消息展示给用户相应的提示信息,类型为字符串类型。 private String msg; + // 定义一个泛型属性,用于存储具体的业务数据,类型根据实际业务场景而定,可以是单个对象、列表、Map等各种数据结构,通过泛型可以使这个返回封装类更加灵活,适用于不同类型数据的返回,类型为泛型类型。 private T data; - public ServerResponse(){} + // 默认的无参构造函数,用于创建一个初始状态的ServerResponse对象,在某些情况下可能需要先创建对象再设置其各个属性的值,提供了一种默认的创建方式,不过在实际使用中更多是通过静态方法来创建具体状态的返回实例。 + public ServerResponse() { + } - private ServerResponse(int status){ + // 私有构造函数,用于创建一个只传入状态码的ServerResponse对象,主要在类内部的其他构造函数或者静态方法中调用,方便根据给定的状态码初始化对象,外部一般不直接使用这个构造函数,而是通过提供的静态方法来创建返回实例,参数为状态码(status),类型为整数类型。 + private ServerResponse(int status) { this.status = status; } - private ServerResponse(int status,String msg){ + + // 私有构造函数,用于创建一个传入状态码和提示消息的ServerResponse对象,方便在需要指定特定状态码和相应提示消息的情况下初始化对象,同样主要在类内部被调用,参数为状态码(status)和提示消息(msg),分别为整数类型和字符串类型。 + private ServerResponse(int status, String msg) { this.status = status; this.msg = msg; } - private ServerResponse(int status,T data){ + + // 私有构造函数,用于创建一个传入状态码和具体数据的ServerResponse对象,适用于操作成功并需要返回具体业务数据的场景,在类内部通过静态方法等方式调用这个构造函数来创建相应的返回实例,参数为状态码(status)和数据(data),类型分别为整数类型和泛型类型。 + private ServerResponse(int status, T data) { this.status = status; this.data = data; } - private ServerResponse(int status,String msg,T data){ + + // 私有构造函数,用于创建一个传入状态码、提示消息和具体数据的ServerResponse对象,是最完整的构造方式,可用于各种不同的业务返回情况,根据具体的状态、消息以及要返回的数据来初始化对象,参数分别为状态码(status)、提示消息(msg)和数据(data),对应整数类型、字符串类型和泛型类型。 + private ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + /** + * @JsonIgnore注解用于指示在JSON序列化(例如将对象转换为JSON格式返回给前端时)过程中忽略这个方法, + * 这里定义了一个用于判断返回结果是否为成功状态的方法,通过比较当前对象的状态码(status)和ResponseEnum.SUCCESS.getCode()(即表示成功的状态码)是否相等来确定, + * 返回一个布尔值,true表示操作成功,false表示操作失败或者处于其他非成功的业务状态,方便客户端代码(如前端JavaScript代码接收返回结果后)进行简单的判断来决定后续如何展示信息或者进行其他操作。 + */ @JsonIgnore - public boolean isSuccess(){ + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } /** * 成功的方法 + * 以下是一组静态方法,用于方便地创建表示操作成功的不同情况的ServerResponse对象实例,返回给客户端相应的成功提示信息以及可能的数据内容,遵循统一的返回格式规范,使得在业务接口返回成功结果时代码更加简洁、清晰,易于维护和使用。 */ - public static ServerResponse createBySuccess(){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); + // 创建一个表示操作成功且只包含默认成功提示消息(从ResponseEnum.SUCCESS.getDesc()获取)的ServerResponse对象,适用于不需要额外返回具体数据的简单成功场景,例如一些只需要告知客户端操作已成功完成的接口返回情况,返回类型为泛型的ServerResponse,其中根据实际调用情况确定具体类型。 + public static ServerResponse createBySuccess() { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc()); } - public static ServerResponse createBySuccessMessage(String message){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); + + // 创建一个表示操作成功并可以自定义提示消息的ServerResponse对象,传入的参数message就是要设置的具体提示消息内容,适用于需要给客户端传递一些额外的成功相关说明信息的情况,例如操作成功后提示用户某些注意事项等,返回类型为泛型的ServerResponse。 + public static ServerResponse createBySuccessMessage(String message) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); + + // 创建一个表示操作成功且包含具体业务数据的ServerResponse对象,传入的参数data就是要返回的具体业务数据,适用于接口查询操作成功后返回查询到的数据给客户端等场景,返回类型为泛型的ServerResponse根据实际返回的数据类型确定。 + public static ServerResponse createBySuccess(T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data); } - public static ServerResponse createBySuccess(String message,T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); + + // 创建一个表示操作成功、可以自定义提示消息并且包含具体业务数据的ServerResponse对象,传入的参数message是提示消息,data是具体业务数据,综合了前面几种成功情况的特点,适用于更复杂一些的成功返回场景,返回类型为泛型的ServerResponse。 + public static ServerResponse createBySuccess(String message, T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } /** * 失败的方法 + * 以下是一组静态方法,用于创建表示操作失败的不同情况的ServerResponse对象实例,按照统一的格式向客户端返回失败的状态码和相应的错误提示信息,方便客户端根据返回结果展示错误提示给用户,同样使得在业务接口返回失败结果时代码编写更加规范、便捷,易于统一管理和维护。 */ - public static ServerResponse createByError(){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); - } - public static ServerResponse createByErrorMessage(String msg){ - return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg); + // 创建一个表示操作失败且只包含默认失败提示消息(从ResponseEnum.ERROR.getDesc()获取)的ServerResponse对象,适用于一些通用的、不需要特别说明具体错误原因的失败场景,返回类型为泛型的ServerResponse。 + public static ServerResponse createByError() { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); - } - + // 创建一个表示操作失败并可以自定义错误提示消息的ServerResponse对象,传入的参数msg就是要设置的具体错误提示内容,适用于根据不同的业务错误情况返回相应的具体错误信息给客户端的场景,返回类型为泛型的ServerResponse。 + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } -} + // 创建一个表示操作失败且可以自定义状态码和错误提示消息的ServerResponse对象,传入的参数code是要设置的具体状态码,msg是错误提示消息,适用于一些需要返回特定状态码来表示不同类型失败情况(例如不同业务模块的不同错误类型对应不同状态码)的场景,返回类型为泛型的ServerResponse。 + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + return new ServerResponse<>(code, msg); + } +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java index 0233e8d..9947f47 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/CookieUtil.java @@ -2,73 +2,105 @@ package com.njupt.swg.common.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; - import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * cookie读写 + * 这个类(CookieUtil)主要提供了操作Cookie的工具方法,用于在Web应用中进行与用户登录相关的Cookie的写入、读取以及删除操作, + * 通过合理设置Cookie的各种属性(如域名、路径、有效期、是否可通过脚本访问等)来实现安全且符合业务需求的Cookie管理,方便在用户登录认证等相关业务场景中利用Cookie来存储和获取用户登录凭证等信息。 */ @Slf4j +// 通过 @Slf4j注解,利用Lombok的功能自动为这个类添加一个名为log的日志记录器,用于记录在Cookie操作过程中出现的各种相关信息,比如写入、读取、删除Cookie时的具体内容等,方便后续排查问题、监控Cookie的使用情况等。 + public class CookieUtil { + + // 定义一个静态的字符串常量,用于指定Cookie的域名,这里设置为"oursnail.cn",意味着这个Cookie在该域名下及其子域名下都是有效的(具体还取决于浏览器的同源策略等因素), + // 在实际应用中,要根据项目部署的域名情况进行准确设置,确保Cookie能在正确的范围内被识别和使用,类型为字符串类型。 private final static String COOKIE_DOMAIN = "oursnail.cn"; + // 定义一个静态的字符串常量,用于指定Cookie的名称,这里设置为"snailmall_login_token",通过这个特定的名称可以在多个Cookie中准确地识别出用于存储用户登录凭证等相关信息的Cookie,类型为字符串类型。 private final static String COOKIE_NAME = "snailmall_login_token"; - /** * 登陆的时候写入cookie - * @param response - * @param token + * 这个方法用于在用户登录成功等需要设置登录凭证的场景下,向客户端(浏览器)写入一个Cookie,将用户的登录相关信息(以token形式传入)存储到Cookie中,方便后续请求中识别用户身份。 + * + * @param response HttpServletResponse对象,用于向客户端发送响应,通过这个对象可以添加要返回给客户端的Cookie信息,类型为HttpServletResponse。 + * @param token 要写入Cookie的值,通常是代表用户登录凭证的一个字符串(比如经过加密等处理后的用户标识信息等),类型为字符串类型。 */ - public static void writeLoginToken(HttpServletResponse response,String token){ - Cookie ck = new Cookie(COOKIE_NAME,token); + public static void writeLoginToken(HttpServletResponse response, String token) { + // 创建一个新的Cookie对象,使用前面定义的COOKIE_NAME作为Cookie的名称,传入的token作为Cookie的值,这样就构造好了一个要发送给客户端的Cookie实例。 + Cookie ck = new Cookie(COOKIE_NAME, token); + // 设置Cookie的域名,通过调用setDomain方法并传入COOKIE_DOMAIN常量,使得这个Cookie在指定的域名("oursnail.cn"及其子域名)下有效,确保在相应的网站范围内可以被正确识别和使用。 ck.setDomain(COOKIE_DOMAIN); - ck.setPath("/");//设值在根目录 - ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击 - ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒,maxage不设置的话,cookie就不会写入硬盘,只会写在内存,只在当前页面有效 - log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); + // 设置Cookie的路径为根目录("/"),意味着这个Cookie在整个网站的所有页面(该域名下的所有路径)都可以被访问到,根据业务需求合理设置路径可以控制Cookie的有效访问范围,例如设置为特定的子目录则只在该子目录下的页面有效等情况。 + ck.setPath("/"); + // 设置Cookie的HttpOnly属性为true,这是一种安全机制,设置后浏览器将不允许通过JavaScript等脚本语言来访问这个Cookie,有效地避免了跨站脚本攻击(XSS攻击)中攻击者通过脚本获取用户登录凭证等敏感信息的风险,提高了系统的安全性。 + ck.setHttpOnly(true); + // 设置Cookie的最大生存时间(有效期)为一年,通过将秒数换算(60 * 60 * 24 * 365)后设置给setMaxAge方法,单位是秒,这里表示这个Cookie从写入客户端开始,在一年内都有效, + // 如果设置为 -1 则表示永久有效(不过实际中永久有效的情况要谨慎使用,考虑到安全性和用户可能主动注销等因素),若不设置这个属性(即默认值),Cookie就只会存在于浏览器内存中,只在当前页面有效,关闭页面后就会被清除,根据业务需求合理设置有效期很重要。 + ck.setMaxAge(60 * 60 * 24 * 365); + // 使用日志记录器记录要写入的Cookie的名称和值,方便在运行时查看具体写入的Cookie信息,例如排查是否写入了正确的登录凭证等情况,日志级别根据实际情况可能为INFO等合适的级别,用于记录操作相关的重要信息。 + log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue()); + // 通过HttpServletResponse对象的addCookie方法,将构造好并设置好属性的Cookie添加到响应中,发送给客户端(浏览器),使得浏览器能够接收到并存储这个Cookie,完成Cookie的写入操作。 response.addCookie(ck); } /** * 读取登陆的cookie - * @param request - * @return + * 该方法用于从客户端发送过来的请求中读取之前设置的用于存储用户登录凭证的Cookie,通过查找名称匹配的Cookie来获取其值,进而可以在服务端进行用户身份验证等相关操作。 + * + * @param request HttpServletRequest对象,用于获取客户端发送过来的请求信息,通过这个对象可以获取请求中携带的Cookie数组,类型为HttpServletRequest。 + * @return 返回从Cookie中读取到的存储用户登录凭证的值(如果存在的话),为字符串类型,如果没有找到名称匹配的Cookie,则返回null,方便调用者根据返回值判断是否获取到了有效的登录凭证信息。 */ - public static String readLoginToken(HttpServletRequest request){ + public static String readLoginToken(HttpServletRequest request) { + // 通过HttpServletRequest对象的getCookies方法获取客户端发送过来的所有Cookie,返回一个Cookie数组,如果请求中没有携带Cookie,则返回null,这里先判断是否获取到了Cookie数组。 Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks){ - log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ - log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + if (cks!= null) { + // 遍历获取到的Cookie数组,对每个Cookie进行检查,查找名称与定义的COOKIE_NAME("snailmall_login_token")匹配的Cookie。 + for (Cookie ck : cks) { + // 使用日志记录器记录每个Cookie的名称和值,方便在运行时查看请求中携带的Cookie具体情况,例如排查是否存在预期的登录凭证Cookie以及其值是否正确等情况,日志级别可根据实际需求设置为INFO等合适级别。 + log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 通过StringUtils的equals方法(这里导入了Apache Commons Lang3的StringUtils类,用于方便地进行字符串比较操作,避免空指针等问题)比较当前Cookie的名称和定义的COOKIE_NAME是否相等, + // 如果相等,说明找到了存储用户登录凭证的Cookie,返回其值,以便后续在服务端进行相应的用户身份验证等操作。 + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); return ck.getValue(); } } } + // 如果遍历完所有Cookie都没有找到名称匹配的Cookie,说明没有获取到有效的用户登录凭证信息,返回null给调用者。 return null; } /** * 注销的时候进行删除 - * @param request - * @param response + * 此方法用于在用户注销登录等需要清除登录凭证的场景下,从客户端删除之前设置的存储用户登录凭证的Cookie,通过设置Cookie的有效期为0等操作来实现让浏览器自动删除该Cookie的目的。 + * + * @param request HttpServletRequest对象,用于获取客户端当前发送的请求信息,通过这个对象可以获取请求中携带的Cookie数组,以便查找要删除的目标Cookie,类型为HttpServletRequest。 + * @param response HttpServletResponse对象,用于向客户端发送响应,通过这个对象可以添加修改后的Cookie信息(设置有效期为0等操作后的Cookie),让浏览器执行删除Cookie的操作,类型为HttpServletResponse。 */ - public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ + public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) { + // 通过HttpServletRequest对象的getCookies方法获取客户端发送过来的所有Cookie,返回一个Cookie数组,如果请求中没有携带Cookie,则返回null,先判断是否获取到了Cookie数组。 Cookie[] cks = request.getCookies(); - if(cks != null){ - for(Cookie ck:cks) { - if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ + if (cks!= null) { + // 遍历获取到的Cookie数组,查找名称与定义的COOKIE_NAME("snailmall_login_token")匹配的Cookie,找到后进行删除操作。 + for (Cookie ck : cks) { + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + // 设置要删除的Cookie的域名,与写入Cookie时设置的域名保持一致(通过COOKIE_DOMAIN常量指定),确保删除的是正确域名下对应的Cookie。 ck.setDomain(COOKIE_DOMAIN); + // 设置Cookie的路径为根目录("/"),同样要和写入时的路径设置一致,保证准确删除对应的Cookie,避免因路径不一致导致删除失败的情况。 ck.setPath("/"); - ck.setMaxAge(0);//0表示消除此cookie - log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + // 设置Cookie的最大生存时间(有效期)为0,表示这个Cookie立即失效,浏览器接收到这样设置后的Cookie后,会自动将其从本地存储中删除,从而实现了删除登录凭证Cookie的目的。 + ck.setMaxAge(0); + // 使用日志记录器记录要删除的Cookie的名称和值等信息,方便在运行时查看具体的删除操作情况,例如确认是否删除了正确的Cookie等,日志级别可根据实际需求设置为INFO等合适级别。 + log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 通过HttpServletResponse对象的addCookie方法,将修改了属性(主要是设置有效期为0)后的Cookie添加到响应中发送给客户端,让浏览器执行删除操作,完成Cookie的删除过程,之后遇到return语句直接结束方法执行。 response.addCookie(ck); return; } } } } - -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java index 8655b2a..4461a81 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/DateTimeUtil.java @@ -4,65 +4,115 @@ import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * @DESC 时间转换的工具类 + * 这个类(DateTimeUtil)主要提供了一系列用于日期和时间格式转换的工具方法,借助了Joda-Time库(一个功能强大的日期和时间处理库)来实现更方便、灵活的时间操作, + * 可以将字符串形式的日期时间转换为 `Date` 对象,也能将 `Date` 对象转换为指定格式的字符串,以及实现 `Date` 对象与时间戳之间的转换,方便在项目的不同业务场景中对时间相关的数据进行处理和展示。 */ public class DateTimeUtil { - //joda-time - - //str->Date - //Date->str - public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + // joda-time + // 以下是使用Joda-Time库进行时间处理相关的代码,Joda-Time库提供了比Java标准库中 `java.util.Date` 和 `SimpleDateFormat` 更易用、功能更丰富且不易出错的日期时间处理方式,在很多项目中被广泛用于时间相关的操作。 + // str->Date + // Date->str + // 定义一个静态的字符串常量,用于表示标准的日期时间格式,这里采用了 "yyyy-MM-dd HH:mm:ss" 的格式,即年-月-日 时:分:秒的形式, + // 在很多业务场景中,如数据库存储时间、接口传输时间数据等经常会使用这种通用的日期时间格式,方便统一数据格式以及进行不同系统间的交互和数据展示等操作,类型为字符串类型。 + public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; - public static Date strToDate(String dateTimeStr, String formatStr){ + /** + * 将指定格式的字符串形式的日期时间转换为 `Date` 对象的方法,通过使用Joda-Time库的相关类和方法来解析字符串并生成对应的 `Date` 对象。 + * + * @param dateTimeStr 要转换的字符串形式的日期时间,其格式需要与传入的 `formatStr` 参数指定的格式一致,例如 "2024-12-18 10:30:00",类型为字符串类型。 + * @param formatStr 指定的日期时间字符串的格式,用于告诉解析器按照何种格式来解析输入的字符串,比如 "yyyy-MM-dd HH:mm:ss" 等,类型为字符串类型。 + * @return 返回解析后的 `Date` 对象,如果字符串格式不符合要求或者解析过程出现问题,可能会抛出异常(由Joda-Time库内部处理),返回的 `Date` 对象可用于后续的日期时间相关操作,如比较、计算等,类型为 `Date`。 + */ + public static Date strToDate(String dateTimeStr, String formatStr) { + // 根据传入的日期时间格式字符串(`formatStr`)创建一个 `DateTimeFormatter` 对象,它用于定义如何解析输入的日期时间字符串,类似于 `SimpleDateFormat` 在Java标准库中的作用,但功能更强大且更易于使用,是Joda-Time库中进行日期时间格式化和解析的关键类之一。 DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr); + // 使用创建好的 `DateTimeFormatter` 对象的 `parseDateTime` 方法,将输入的日期时间字符串(`dateTimeStr`)解析为一个 `DateTime` 对象,`DateTime` 是Joda-Time库中表示日期时间的核心类,提供了丰富的日期时间操作方法。 DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); + // 通过 `DateTime` 对象的 `toDate` 方法,将 `DateTime` 对象转换为Java标准库中的 `Date` 对象,这样就完成了从指定格式字符串到 `Date` 对象的转换,方便在Java代码中后续使用标准的 `Date` 相关操作方法进行处理。 return dateTime.toDate(); } - public static String dateToStr(Date date,String formatStr){ - if(date == null){ + /** + * 将 `Date` 对象转换为指定格式的字符串形式的日期时间的方法,利用Joda-Time库将 `Date` 对象按照给定的格式进行格式化输出。 + * + * @param date 要转换的 `Date` 对象,代表一个具体的日期时间点,例如通过数据库查询获取的时间数据等,类型为 `Date`。 + * @param formatStr 指定的目标格式字符串,用于控制转换后的字符串的日期时间格式,例如 "yyyy-MM-dd" 可以只输出年-月-日部分,类型为字符串类型。 + * @return 返回格式化后的字符串形式的日期时间,如果传入的 `date` 参数为 `null`,则返回一个空字符串(通过 `StringUtils.EMPTY` 获取),方便调用者根据返回值进行后续处理,如展示给用户或者进行数据传输等,类型为字符串类型。 + */ + public static String dateToStr(Date date, String formatStr) { + if (date == null) { return StringUtils.EMPTY; } + // 使用传入的 `Date` 对象创建一个 `DateTime` 对象,以便利用Joda-Time库的功能进行格式化操作,`DateTime` 类提供了更灵活方便的格式化方法来将日期时间转换为指定格式的字符串。 DateTime dateTime = new DateTime(date); return dateTime.toString(formatStr); } - //固定好格式 - public static Date strToDate(String dateTimeStr){ + /** + * 将字符串形式的日期时间转换为 `Date` 对象的方法,此方法使用了类中定义的标准日期时间格式(`STANDARD_FORMAT`)来解析输入的字符串, + * 适用于当日期时间字符串格式固定为 "yyyy-MM-dd HH:mm:ss" 的情况,简化了调用时需要传入格式字符串的操作,方便在项目中大量使用统一格式的日期时间转换场景。 + * + * @param dateTimeStr 要转换的字符串形式的日期时间,其格式需要符合标准的 "yyyy-MM-dd HH:mm:ss" 格式,例如 "2024-12-18 10:30:00",类型为字符串类型。 + * @return 返回解析后的 `Date` 对象,如果字符串格式不符合要求或者解析过程出现问题,可能会抛出异常(由Joda-Time库内部处理),类型为 `Date`。 + */ + public static Date strToDate(String dateTimeStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); return dateTime.toDate(); } - public static String dateToStr(Date date){ - if(date == null){ + /** + * 将 `Date` 对象转换为标准格式("yyyy-MM-dd HH:mm:ss")的字符串形式的日期时间的方法,当需要将 `Date` 对象按照统一的标准格式展示或者传输时,可以使用这个方法, + * 如果传入的 `Date` 对象为 `null`,则返回一个空字符串,方便在不同业务场景中进行统一的时间数据处理和展示操作。 + * + * @param date 要转换的 `Date` 对象,代表一个具体的日期时间点,类型为 `Date`。 + * @return 返回格式化后的字符串形式的日期时间,格式为 "yyyy-MM-dd HH:mm:ss",如果 `date` 参数为 `null`,则返回 `StringUtils.EMPTY`,类型为字符串类型。 + */ + public static String dateToStr(Date date) { + if (date == null) { return StringUtils.EMPTY; } DateTime dateTime = new DateTime(date); return dateTime.toString(STANDARD_FORMAT); } - //Date -> 时间戳 + /** + * 将 `Date` 对象转换为时间戳(从1970年1月1日00:00:00 UTC到指定日期时间的毫秒数)的方法,这里使用了Java标准库中的 `SimpleDateFormat` 来进行格式化和解析操作, + * 先将 `Date` 对象格式化为字符串,再将字符串解析为时间戳,注意在使用过程中可能会抛出 `ParseException` 异常,需要调用者进行相应的异常处理,例如在进行数据库存储时间戳数据、与其他系统交互时间戳相关信息等场景中会用到这个方法。 + * + * @param date 要转换为时间戳的 `Date` 对象,代表一个具体的日期时间点,类型为 `Date`。 + * @return 返回对应的时间戳,为长整型数值,表示从1970年1月1日00:00:00 UTC到传入的 `Date` 对象所代表的日期时间的毫秒数,如果传入的 `date` 参数为 `null`,则返回 `null`,方便调用者根据返回值进行后续处理,类型为 `Long`。 + * @throws ParseException 可能会抛出的异常,表示在使用 `SimpleDateFormat` 解析字符串过程中出现格式不匹配等解析错误情况,调用者需要捕获并处理这个异常,避免程序因异常而终止运行。 + */ public static Long dateToChuo(Date date) throws ParseException { - if(date == null){ + if (date == null) { return null; } - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); + // 创建一个 `SimpleDateFormat` 对象,指定日期时间格式为标准的 "yyyy-MM-dd HH:mm:ss",用于将 `Date` 对象格式化为对应的字符串形式,以便后续解析为时间戳。 + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 使用 `SimpleDateFormat` 对象的 `parse` 方法,先将 `Date` 对象转换为字符串(通过 `String.valueOf(date)` 获取 `Date` 对象对应的字符串表示),再将这个字符串解析为对应的日期时间,然后通过 `getTime` 方法获取从1970年1月1日00:00:00 UTC到这个日期时间的毫秒数,即时间戳,完成转换操作。 return format.parse(String.valueOf(date)).getTime(); } + /** + * 主函数,用于进行简单的测试,在类中直接运行这个方法可以验证时间转换相关方法的功能是否正确,这里示例了将一个指定格式的日期时间字符串解析为 `Date` 对象,然后获取其对应的时间戳并输出, + * 不过在实际项目中,这个主函数通常只是用于开发过程中的临时测试,项目部署运行时并不会执行这里的代码,更多是依靠单元测试等方式来全面验证工具类中各个方法的正确性和稳定性。 + * + * @param args 命令行参数,这里在示例中未使用,类型为字符串数组。 + * @throws ParseException 可能会抛出的异常,因为在主函数中调用了 `dateToChuo` 方法,该方法可能会抛出 `ParseException`,需要在这里进行捕获处理,避免程序因异常而终止运行。 + */ public static void main(String[] args) throws ParseException { - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); - String time="1970-01-06 11:45:55"; + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String time = "1970-01-06 11:45:55"; Date date = format.parse(time); - System.out.print("Format To times:"+date.getTime()); + System.out.print("Format To times:" + date.getTime()); } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java index 12daca4..a63b070 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/JsonUtil.java @@ -8,119 +8,168 @@ import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.annotate.JsonSerialize; import org.codehaus.jackson.type.JavaType; import org.codehaus.jackson.type.TypeReference; - import java.io.IOException; import java.text.SimpleDateFormat; /** * jackson的序列化和反序列化 + * 该类主要提供了一系列基于Jackson库的工具方法,用于实现Java对象与JSON字符串之间的序列化(将对象转换为JSON字符串)和反序列化(将JSON字符串转换为对象)操作, + * 并且对Jackson库中的ObjectMapper进行了配置,以适应项目中在处理JSON数据时的特定需求,比如统一日期格式、忽略特定的序列化和反序列化错误等情况。 */ @Slf4j +// 使用 @Slf4j注解,通过Lombok库自动为该类生成一个名为log的日志记录器,方便在序列化和反序列化出现问题时记录相关信息,便于后续排查错误和监控数据处理情况。 public class JsonUtil { + + // 创建一个静态的ObjectMapper对象,ObjectMapper是Jackson库中核心的类,负责处理JSON数据的序列化和反序列化工作,在这里作为整个工具类中进行JSON操作的基础实例,在类加载时就进行初始化,后续所有的序列化和反序列化方法都会使用到它。 private static ObjectMapper objectMapper = new ObjectMapper(); static { - //所有字段都列入进行转换 + // 以下是在静态代码块中对ObjectMapper对象进行的一系列配置操作,这些配置会影响整个工具类中使用该ObjectMapper进行JSON处理的行为。 + + // 所有字段都列入进行转换 + // 设置序列化时包含所有字段,即不管字段的值是否为null,在将Java对象转换为JSON字符串时都会包含这些字段,通过JsonSerialize.Inclusion.ALWAYS配置来实现, + // 这样可以确保JSON数据完整地反映Java对象的结构,避免因某些字段为null而被默认忽略的情况(如果有业务需求要求完整展示对象结构的话)。 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); - //取消默认转换timestamp形式 - objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); - //忽略空bean转json的错误 - objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); - //统一时间的格式 + + // 取消默认转换timestamp形式 + // 配置序列化时不将日期类型的数据转换为时间戳形式(默认情况下Jackson可能会将Date类型转换为时间戳),而是按照后续设置的日期格式进行转换, + // 通过将SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS设置为false来实现,这样可以使得日期在JSON中的表示更符合常见的日期字符串格式,便于阅读和理解,也便于与其他系统进行数据交互时的日期格式统一。 + objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); + + // 忽略空bean转json的错误 + // 配置在将一个空的Java对象(没有任何属性或者属性值都为null的对象,通常称为空Bean)转换为JSON字符串时忽略可能出现的错误, + // 将SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS设置为false,避免因为空对象转换问题导致整个序列化过程失败,提高了程序的健壮性,特别是在某些业务场景下可能会出现临时的空对象需要转换为JSON的情况。 + objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false); + + // 统一时间的格式 + // 设置日期格式,通过创建一个SimpleDateFormat对象并指定日期格式为DateTimeUtil.STANDARD_FORMAT(假设DateTimeUtil类中定义了通用的日期时间格式,比如"yyyy-MM-dd HH:mm:ss"), + // 然后将这个SimpleDateFormat对象设置给ObjectMapper,使得在序列化和反序列化涉及日期类型数据时,都按照这个统一的格式进行处理,保证了日期数据在JSON中的格式一致性,方便在不同模块间传递和解析时间相关的数据。 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); - //忽略json存在属性,但是java对象不存在属性的错误 - objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); + + // 忽略json存在属性,但是java对象不存在属性的错误 + // 配置在反序列化(将JSON字符串转换为Java对象)时,忽略JSON数据中存在但Java对象中不存在对应属性的情况, + // 将DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES设置为false,避免因为JSON数据结构和Java对象结构不完全匹配而导致反序列化失败, + // 这在处理来自不同数据源的JSON数据或者JSON数据结构发生变化但Java对象结构尚未完全同步更新的场景中非常有用,提高了反序列化的兼容性和容错性。 + objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** * 序列化方法,将对象转为字符串 - * @param obj - * @param - * @return + * 该方法用于将传入的Java对象转换为JSON字符串。如果传入的对象为null,则直接返回null。 + * 在转换过程中,如果出现IOException异常(例如对象结构不符合JSON序列化要求等原因导致无法正确转换),会记录警告日志并返回null, + * 方便在不同业务场景下将对象数据转换为JSON格式以便进行传输、存储等操作。 + * + * @param obj 要进行序列化的Java对象,其类型可以是任意Java对象,例如自定义的实体类、集合类等,类型为泛型类型。 + * @param 泛型标识,用于表示传入对象的类型以及返回的JSON字符串对应的Java对象类型,在调用这个方法时,Java编译器会根据实际传入的对象类型来确定具体的类型,保证类型的一致性和安全性。 + * @return 返回转换后的JSON字符串,如果对象为null或者转换过程出现异常,则返回null,方便调用者根据返回值判断是否成功进行了序列化操作并进行后续处理,如将JSON字符串发送给前端或者存储到缓存等,类型为字符串类型。 */ - public static String obj2String(T obj){ - if(obj == null){ + public static String obj2String(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); + // 判断传入的对象是否本身就是字符串类型,如果是,则直接返回该字符串(不需要进行JSON序列化操作), + // 否则使用ObjectMapper的writeValueAsString方法将对象转换为JSON字符串并返回。 + return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + // 如果在序列化过程中出现IOException(例如对象中有无法序列化的属性、循环引用等问题),使用日志记录器记录警告信息, + // 包含简单的错误描述以及异常对象本身,方便后续排查问题,同时返回null表示序列化失败,日志级别为WARN,表示出现了可继续运行但可能需要关注的情况。 + log.warn("parse object to string error", e); return null; } } /** * 序列化方法,同上,只是输出的格式是美化的,便于测试 - * @param obj - * @param - * @return + * 这个方法与obj2String方法功能类似,也是将Java对象转换为JSON字符串,但不同的是它会输出美化后的JSON字符串(格式更清晰,有缩进等格式排版), + * 方便在开发测试阶段查看JSON数据结构,同样如果对象为null或者转换过程出现异常,会返回null。 + * + * @param obj 要进行序列化的Java对象,类型为泛型类型,其具体类型可以是任意Java对象,同obj2String方法中的参数含义。 + * @param 泛型标识,用于表示对象类型以及返回的JSON字符串对应的Java对象类型,具体作用与obj2String方法中的一致,保证类型一致性和安全性。 + * @return 返回美化后的JSON字符串,如果对象为null或者转换过程出现异常,则返回null,类型为字符串类型,可用于在测试环境中展示给开发人员查看详细的JSON数据结构等情况。 */ - public static String obj2StringPretty(T obj){ - if(obj == null){ + public static String obj2StringPretty(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + // 判断传入的对象是否为字符串类型,如果是则直接返回该字符串,否则使用ObjectMapper的writerWithDefaultPrettyPrinter方法获取一个能够生成美化格式JSON字符串的写入器, + // 再通过这个写入器的writeValueAsString方法将对象转换为美化后的JSON字符串并返回。 + return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (IOException e) { - log.warn("parse object to string error",e); + log.warn("parse object to string error", e); return null; } } /** * 比较简单的反序列化的方法,将字符串转为单个对象 - * @param str - * @param clazz - * @param - * @return + * 这个方法用于将JSON字符串反序列化为指定类型的Java对象,适用于简单的单个对象的反序列化情况,例如将一个表示用户信息的JSON字符串转换为对应的User类对象等。 + * 如果传入的字符串为空或者指定的Java类对象类型为null,则直接返回null。 + * 在反序列化过程中,如果出现IOException(比如JSON格式不符合要求、缺少必要属性等原因导致无法正确解析),会记录警告日志并返回null。 + * + * @param str 要进行反序列化的JSON字符串,其格式需要符合Jackson库能够解析的JSON格式要求,并且与要转换的Java对象结构尽量匹配(根据配置可能会忽略部分属性不匹配情况),类型为字符串类型。 + * @param clazz 要反序列化生成的Java对象的类型,通过传入具体的类对象(例如User.class)来指定目标对象类型,Jackson会根据这个类型信息将JSON字符串解析并构造出对应的Java对象,类型为Class,表示一个具体的Java类类型,其中是泛型标识,与方法返回的对象类型相关联,保证类型的一致性。 + * @param 泛型标识,用于表示返回的Java对象类型,与传入的clazz参数指定的类类型相对应,确保反序列化后得到的对象类型符合预期,方便在不同业务场景下将JSON数据转换为具体的Java对象进行后续业务处理,如存入数据库、在业务逻辑中使用等,类型为泛型类型。 + * @return 返回反序列化后的Java对象,如果传入的字符串为空、指定的类类型为null或者反序列化过程出现异常,则返回null,类型为,即与传入的clazz参数指定的类类型一致的Java对象类型,方便调用者根据返回值进行后续操作,如调用对象的方法、获取对象的属性等。 */ - public static T String2Obj(String str,Class clazz){ - if(StringUtils.isEmpty(str) || clazz == null){ + public static T String2Obj(String str, Class clazz) { + if (StringUtils.isEmpty(str) || clazz == null) { return null; } try { - return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz); + // 判断传入的clazz参数指定的类是否就是String类,如果是,表示要将JSON字符串直接转换为String类型的对象(其实就是返回原字符串本身), + // 否则使用ObjectMapper的readValue方法,根据传入的JSON字符串str和指定的类类型clazz将JSON数据解析为对应的Java对象并返回。 + return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** * 复杂对象的反序列化(通用) - * @param str - * @param typeReference - * @param - * @return + * 这个方法用于处理更复杂的反序列化情况,通过使用TypeReference来指定复杂的目标对象类型(例如包含泛型的集合类型、嵌套的自定义对象类型等), + * 可以更灵活地将JSON字符串转换为相应的复杂Java对象,同样如果传入的字符串为空或者指定的TypeReference为null,则返回null,反序列化出现异常时会记录警告日志并返回null。 + * + * @param str 要进行反序列化的JSON字符串,格式需要符合Jackson库的解析要求并且与要转换的复杂对象结构相适配(根据配置可忽略部分不匹配情况),类型为字符串类型。 + * @param typeReference 用于指定复杂对象类型的TypeReference对象,通过创建TypeReference的匿名子类或者使用已有的具体子类来准确描述要反序列化生成的复杂对象的类型结构, + * 例如new TypeReference>() {}可以表示要将JSON字符串转换为List类型的对象,类型为TypeReference。 + * @param 泛型标识,用于表示返回的复杂Java对象的类型,与传入的typeReference指定的类型一致,方便在不同业务场景下将JSON数据转换为符合业务需求的复杂对象进行后续处理,如在业务逻辑中遍历集合、操作嵌套对象等,类型为泛型类型。 + * @return 返回反序列化后的复杂Java对象,如果传入的字符串为空、指定的TypeReference为null或者反序列化过程出现异常,则返回null,类型为,即与typeReference指定的复杂对象类型一致的Java对象类型,方便调用者根据返回值进行后续操作,如对集合进行添加元素、调用嵌套对象的方法等。 */ - public static T Str2Obj(String str, TypeReference typeReference){ - if(StringUtils.isEmpty(str) || typeReference == null){ + public static T Str2Obj(String str, TypeReference typeReference) { + if (StringUtils.isEmpty(str) || typeReference == null) { return null; } try { - return (T) (typeReference.getType().equals(String.class)?str:objectMapper.readValue(str,typeReference)); + // 判断传入的typeReference指定的类型是否就是String类,如果是,表示要将JSON字符串直接作为String类型返回(其实就是原字符串本身), + // 否则使用ObjectMapper的readValue方法,根据传入的JSON字符串str和typeReference指定的复杂对象类型将JSON数据解析为对应的Java对象并返回。 + return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference)); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } /** * 第二种方式实现复杂对象的反序列化 - * @param str - * @param collectionClass - * @param elementClasses - * @param - * @return + * 这个方法也是用于处理复杂对象的反序列化情况,通过传入表示集合类型的类对象以及集合中元素类型的类对象等参数,利用ObjectMapper的类型工厂来构造出具体的JavaType, + * 进而将JSON字符串转换为对应的复杂对象(通常是各种泛型集合类型等情况),同样在传入参数不符合要求或者反序列化出现异常时会返回null。 + * + * @param str 要进行反序列化的JSON字符串,其格式要符合Jackson库的解析要求并且与要转换的复杂对象结构相适配,类型为字符串类型。 + * @param collectionClass 表示集合类型的类对象,例如List.class、Set.class等,用于指定要反序列化生成的复杂对象的外层集合类型,类型为Class,表示一个不确定具体元素类型的类类型,这里的?表示通配符,因为只关注集合类型本身,暂不确定其元素类型。 + * @param elementClasses 可变参数,表示集合中元素类型的类对象,例如对于List类型,这里就传入User.class,可以传入多个类对象来表示嵌套的多层集合结构中各层的元素类型,类型为Class数组,即每个元素都是一个类类型对象,方便描述复杂的对象类型结构,类型为Class...。 + * @param 泛型标识,用于表示返回的复杂Java对象的类型,与根据传入的collectionClass和elementClasses参数构造出的复杂对象类型一致,方便在不同业务场景下将JSON数据转换为符合业务需求的复杂对象进行后续处理,类型为泛型类型。 + * @return 返回反序列化后的复杂Java对象,如果传入的字符串为空、传入的类对象参数不符合要求或者反序列化过程出现异常,则返回null,类型为,即与根据参数构造出的复杂对象类型一致的Java对象类型,方便调用者根据返回值进行后续操作,如对集合进行遍历、操作集合元素等。 */ - public static T Str2Obj(String str,Class collectionClass,Class... elementClasses){ - JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); + public static T Str2Obj(String str, Class collectionClass, Class... elementClasses) { + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); try { - return objectMapper.readValue(str,javaType); + return objectMapper.readValue(str, javaType); } catch (IOException e) { - log.warn("parse string to obj error",e); + log.warn("parse string to obj error", e); return null; } } -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java index e6e5c8a..3d42a13 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/MD5Util.java @@ -4,56 +4,102 @@ import java.security.MessageDigest; /** * MD5加密工具类 + * 这个类(MD5Util)主要用于实现对输入字符串进行MD5加密的功能,提供了相应的方法来将给定的字符串按照MD5算法转换为加密后的十六进制字符串表示形式, + * 并且支持指定字符编码(如UTF-8)进行加密操作,同时还预留了加盐(一种增强加密安全性的手段,不过当前代码示例中未完整实现加盐功能)的位置,方便在项目中对敏感信息(如用户密码等)进行加密处理,保障数据的安全性。 */ public class MD5Util { + + /** + * 将字节数组转换为十六进制字符串的方法,通过遍历字节数组,逐个将字节转换为对应的十六进制表示形式,并拼接起来形成最终的十六进制字符串。 + * + * @param b 要转换的字节数组,通常是经过MD5加密算法处理后得到的字节数组,例如通过MessageDigest的digest方法获取到的结果,类型为字节数组类型(byte[])。 + * @return 返回转换后的十六进制字符串,这个字符串就是字节数组对应的十六进制表示形式,可用于后续作为加密后的最终结果展示或者进一步处理等,类型为字符串类型。 + */ private static String byteArrayToHexString(byte b[]) { + // 创建一个StringBuffer对象,用于高效地拼接字符串,避免频繁创建新的字符串对象,提高性能,后续将逐个字节转换后的十六进制字符依次添加到这个StringBuffer中。 StringBuffer resultSb = new StringBuffer(); + // 遍历传入的字节数组,对每个字节调用byteToHexString方法进行十六进制转换,并将转换后的结果添加到StringBuffer中。 for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); + // 将StringBuffer中的内容转换为字符串并返回,这个字符串就是字节数组对应的十六进制表示形式。 return resultSb.toString(); } + /** + * 将单个字节转换为十六进制字符串的方法,先对字节进行处理(如果是负数则转换为正数范围),然后将其拆分为高4位和低4位,分别对应十六进制的一个数位,通过查找预定义的十六进制字符数组获取对应的十六进制字符,并拼接起来形成十六进制表示形式。 + * + * @param b 要转换的单个字节,其值范围在 -128 到 127 之间,类型为字节类型(byte)。 + * @return 返回该字节对应的十六进制字符串表示形式,长度为2,例如将字节值为10转换为十六进制的 "0a",类型为字符串类型。 + */ private static String byteToHexString(byte b) { + // 将传入的字节转换为整数类型,方便后续进行位运算等操作,由于字节类型在Java中是有符号的,其范围是 -128 到 127,所以转换后的整数可能是负数。 int n = b; + // 如果转换后的整数是负数,通过加上256将其转换为正数范围(0 到 255),这是因为十六进制表示时是基于无符号数的范围进行处理的,确保后续计算能正确对应十六进制值。 if (n < 0) n += 256; + // 获取字节转换后的整数的高4位(相当于除以16取整),用于对应十六进制的高位数字,例如对于字节值为20(十六进制为 14),这里得到的就是1。 int d1 = n / 16; + // 获取字节转换后的整数的低4位(相当于除以16取余),用于对应十六进制的低位数字,对于上面的例子,这里得到的就是4。 int d2 = n % 16; + // 通过查找预定义的十六进制字符数组(hexDigits),获取对应高4位和低4位的十六进制字符,并将它们拼接起来返回,例如对于上面的例子,会返回 "14",也就是十六进制表示形式。 return hexDigits[d1] + hexDigits[d2]; } /** * 返回大写MD5 + * 这个私有方法是实现MD5加密的核心方法,它接收一个原始字符串和字符编码名称作为参数,先获取MD5算法实例,然后根据是否指定字符编码,对原始字符串进行字节编码转换后,使用MD5算法进行加密处理, + * 最后将加密后的字节数组转换为十六进制字符串并返回,返回的结果是大写形式的十六进制字符串表示的MD5加密值,在加密过程中如果出现异常(例如不支持的字符编码等情况),当前只是简单地返回 `null`(不过在实际应用中可能需要更完善的异常处理机制)。 * - * @param origin - * @param charsetname - * @return + * @param origin 要进行MD5加密的原始字符串,例如用户输入的密码等敏感信息,类型为字符串类型。 + * @param charsetname 指定的字符编码名称,用于将原始字符串转换为字节数组时使用的编码方式,如果为 `null` 或者空字符串,则使用平台默认编码,类型为字符串类型。 + * @return 返回经过MD5加密后的大写十六进制字符串表示形式,如果加密过程出现异常则返回 `null`,类型为字符串类型。 */ private static String MD5Encode(String origin, String charsetname) { + // 先初始化一个变量,用于存储最终的加密结果字符串,初始值设置为原始字符串本身,后续会根据加密操作进行更新。 String resultString = null; try { resultString = new String(origin); + // 获取MD5算法的MessageDigest实例,用于执行MD5加密操作,通过传入算法名称 "MD5" 调用MessageDigest的getInstance方法来获取,如果不支持指定的算法会抛出异常。 MessageDigest md = MessageDigest.getInstance("MD5"); + // 判断传入的字符编码名称是否为 `null` 或者空字符串,如果是,则使用默认编码方式将原始字符串转换为字节数组后进行MD5加密,否则使用指定的字符编码将原始字符串转换为字节数组再进行加密操作。 if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString.getBytes())); else resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname))); } catch (Exception exception) { + // 当前代码只是简单地捕获了可能出现的异常(例如获取MD5算法实例失败、字符编码不支持等异常情况),但没有做进一步的处理,在实际应用中应该根据具体业务需求完善异常处理逻辑,比如记录日志、返回特定的错误提示等。 } + // 将最终得到的加密结果字符串转换为大写形式并返回,MD5加密结果通常以十六进制字符串表示,大写形式方便统一格式展示以及后续的比较等操作(例如与存储在数据库中的加密后的密码进行比对验证等情况)。 return resultString.toUpperCase(); } + /** + * 使用UTF-8编码进行MD5加密的方法,它调用了MD5Encode方法,并指定字符编码为 "utf-8",实现对传入的原始字符串按照UTF-8编码进行MD5加密, + * 这里可以在合适的位置添加加盐操作(通过在原始字符串中添加特定的盐值后再进行加密,盐值通常是一个固定的字符串或者根据一定规则生成的字符串,用于增加加密的安全性,防止彩虹表攻击等),不过当前代码示例中没有完整实现加盐功能,只是简单地进行了MD5加密并返回结果。 + * + * @param origin 要进行MD5加密的原始字符串,类型为字符串类型,例如需要加密的用户密码等信息。 + * @return 返回经过UTF-8编码的MD5加密后的大写十六进制字符串表示形式,类型为字符串类型,可用于后续存储加密后的敏感信息、验证密码等业务场景。 + */ public static String MD5EncodeUtf8(String origin) { - //这里可以加盐 + // 这里可以加盐,例如可以在origin字符串前后添加特定的盐值字符串后再进行加密,不过当前只是简单调用MD5Encode方法进行常规的MD5加密,没有实际添加盐值操作。 return MD5Encode(origin, "utf-8"); } + /** + * 主函数,用于进行简单的测试,在类中直接运行这个方法可以验证MD5加密功能是否正确,这里示例了对字符串 "123456" 进行MD5加密,并将加密后的结果输出到控制台, + * 不过在实际项目中,这个主函数通常只是用于开发过程中的临时测试,项目部署运行时并不会执行这里的代码,更多是依靠单元测试等方式来全面验证工具类中各个方法的正确性和稳定性。 + * + * @param args 命令行参数,这里在示例中未使用,类型为字符串数组。 + */ public static void main(String[] args) { System.out.println(MD5EncodeUtf8("123456")); } - + /** + * 预定义的十六进制字符数组,用于在将字节转换为十六进制字符串时,根据字节的数值查找对应的十六进制字符,例如字节值为0对应字符 "0",字节值为10对应字符 "a" 等, + * 通过这个数组可以方便地实现字节到十六进制字符串的转换操作,是整个MD5加密过程中将加密后的字节数组转换为十六进制表示形式的关键辅助数据结构。 + */ private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; -} +} \ No newline at end of file diff --git a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/ZkClient.java b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/ZkClient.java index d44bcf2..ebf0e20 100644 --- a/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/ZkClient.java +++ b/snailmall-user-service/src/main/java/com/njupt/swg/common/utils/ZkClient.java @@ -13,22 +13,43 @@ import org.springframework.stereotype.Component; * @Date 2019/1/1 17:57 * @CONTACT 317758022@qq.com * @DESC zk的curator客户端 + * 这个类(ZkClient)主要用于创建和配置一个Apache Curator框架下的ZooKeeper客户端实例,通过依赖注入获取相关配置参数(从 `Parameters` 类中获取ZooKeeper服务器的连接信息等), + * 利用CuratorFrameworkFactory来构建客户端,并设置连接超时时间、重试策略等重要属性,最后启动客户端,以便在项目中与ZooKeeper进行交互,实现诸如配置管理、服务发现等基于ZooKeeper的功能。 */ @Component +// 使用Spring的@Component注解将这个类标记为一个组件,意味着Spring容器在扫描到这个类时会将其纳入管理范围,能够进行依赖注入等操作,方便在项目的其他地方使用这个类创建的ZooKeeper客户端实例。 public class ZkClient { + @Autowired + // 通过Spring的 @Autowired注解进行依赖注入,将 `Parameters` 类的实例注入到当前类中,`Parameters` 类应该是用于存储项目相关配置参数的,在这里主要是获取ZooKeeper服务器的连接字符串等配置信息,以便后续构建ZooKeeper客户端时使用。 private Parameters parameters; + /** + * @Bean注解用于将这个方法返回的对象作为一个Spring管理的Bean实例,在Spring容器启动时会调用这个方法创建并注册一个CuratorFramework类型的Bean, + * 这个Bean就是配置好的ZooKeeper客户端实例,其他需要使用ZooKeeper客户端的地方(比如在服务注册与发现的相关业务逻辑中)可以通过依赖注入获取这个Bean来与ZooKeeper进行交互操作。 + * + * @return 返回创建并配置好的CuratorFramework类型的ZooKeeper客户端实例,该实例已经设置好了连接字符串、连接超时时间以及重试策略等关键属性,并且已经启动,可以直接用于与ZooKeeper服务器进行通信,类型为CuratorFramework。 + */ @Bean - public CuratorFramework getZkClient(){ + public CuratorFramework getZkClient() { - CuratorFrameworkFactory.Builder builder= CuratorFrameworkFactory.builder() + // 使用CuratorFrameworkFactory的builder方法创建一个构建器对象,用于构建CuratorFramework类型的ZooKeeper客户端实例,通过链式调用的方式可以方便地设置各种客户端的配置属性。 + CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder() + // 通过调用connectString方法设置ZooKeeper服务器的连接字符串,这个连接字符串的值从注入的 `Parameters` 类实例中获取(通过调用 `parameters.getZkHost()` 方法), + // 连接字符串通常格式类似 "ip1:port1,ip2:port2,...",用于指定要连接的ZooKeeper服务器节点的地址和端口信息,确保客户端能够准确连接到ZooKeeper集群。 .connectString(parameters.getZkHost()) + // 设置连接超时时间为3000毫秒(3秒),通过调用connectionTimeoutMs方法进行设置,意味着客户端在尝试连接ZooKeeper服务器时,如果在3秒内无法建立连接,将会判定连接超时并可能触发相应的异常处理或者重试逻辑, + // 根据业务需求合理设置连接超时时间很重要,避免长时间等待连接导致程序响应缓慢等问题。 .connectionTimeoutMs(3000) + // 设置重试策略,这里使用了 `RetryNTimes` 重试策略,它表示客户端在连接ZooKeeper服务器出现失败时,会进行最多5次重试,每次重试间隔10毫秒,通过创建 `RetryNTimes` 实例并传入重试次数和重试间隔时间参数来配置, + // 合理的重试策略有助于提高客户端在面对网络波动等临时连接问题时的健壮性,确保能够成功连接到ZooKeeper服务器并进行后续操作。 .retryPolicy(new RetryNTimes(5, 10)); + + // 通过构建器对象的build方法构建出最终的CuratorFramework类型的ZooKeeper客户端实例,此时客户端实例已经根据前面设置的配置属性进行了相应的初始化,但还未启动。 CuratorFramework framework = builder.build(); + // 调用客户端实例的start方法启动ZooKeeper客户端,启动后客户端就可以与ZooKeeper服务器进行通信,例如发送请求获取节点数据、创建或删除节点等操作,完成整个客户端的创建和启动流程,最后返回这个启动好的客户端实例。 framework.start(); return framework; } -} +} \ No newline at end of file -- 2.34.1