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缓存的操作已成功完成, + // 调用者可以根据这个返回结果进行相应的后续处理(如记录日志、展示操作成功提示给用户等),保证业务流程的完整性以及对操作结果的合理反馈。 }