diff --git a/snailmall-category-service/pom.xml b/snailmall-category-service/pom.xml index 8a7f0fe..1e527b0 100644 --- a/snailmall-category-service/pom.xml +++ b/snailmall-category-service/pom.xml @@ -84,6 +84,11 @@ joda-time joda-time + + org.projectlombok + lombok + provided + 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..dcdc71f 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,46 @@ package com.njupt.swg.common.constants; /** + * 这个类名为Constants,主要用于集中定义项目中常用的自定义状态码常量。 + * 通过将这些状态码统一在此处定义,方便在整个项目的其他代码部分进行引用, + * 提高了代码的可维护性和可读性,避免了在不同地方重复使用硬编码的数字来表示状态码。 + * * @Author swg. * @Date 2019/1/1 13:19 * @CONTACT 317758022@qq.com * @DESC */ public class Constants { - /**自定义状态码 start**/ + /** + * 自定义状态码区域开始标记,以下定义的都是项目中会用到的各种自定义状态码常量。 + * 这样的注释可以清晰地划分代码逻辑区域,便于代码的阅读和维护。 + */ public static final int RESP_STATUS_OK = 200; + /** + * RESP_STATUS_OK表示请求成功的状态码,其值设定为200, + * 该值符合HTTP状态码中表示请求成功的通用约定,在项目中可用于返回成功响应时设置相应状态。 + */ public static final int RESP_STATUS_NOAUTH = 401; + /** + * RESP_STATUS_NOAUTH用于表示客户端请求未被授权的情况,例如缺少登录认证信息等。 + * 它对应HTTP状态码中表示未授权的含义,方便在处理需要认证授权的请求时使用该常量来设置相应的错误状态码。 + */ public static final int RESP_STATUS_INTERNAL_ERROR = 500; + /** + * RESP_STATUS_INTERNAL_ERROR代表服务器内部出现错误的状态码, + * 当服务端在处理请求过程中发生了意外情况,无法正常完成请求的处理时, + * 可以使用这个常量来向客户端返回表示服务器内部错误的状态信息,其值符合HTTP状态码中对应含义的约定。 + */ public static final int RESP_STATUS_BADREQUEST = 400; + /** + * RESP_STATUS_BADREQUEST意味着客户端发送的请求存在语法错误或者请求参数不符合要求等情况, + * 属于客户端请求错误的一种标识状态码,遵循HTTP状态码中对这类错误的通用定义,便于在验证请求合法性时使用该常量设置相应状态。 + */ - /**自定义状态码 end**/ - - -} + /** + * 自定义状态码区域结束标记,清晰界定了状态码常量定义的范围。 + */ +} \ No newline at end of file diff --git a/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ControllerAdvice.java b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ControllerAdvice.java new file mode 100644 index 0000000..72e27d3 --- /dev/null +++ b/snailmall-category-service/src/main/java/com/njupt/swg/common/exception/ControllerAdvice.java @@ -0,0 +1,4 @@ +package com.njupt.swg.common.exception; + +public @interface ControllerAdvice { +} 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..4ad4e6f 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,66 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** + * 这个类名为ExceptionHandlerAdvice,主要承担了全局异常处理的功能。 + * 它使用了Spring框架提供的相关注解来拦截并处理在整个Web应用程序中抛出的异常, + * 然后根据不同类型的异常返回相应合适的响应给客户端,提高应用程序的健壮性和用户体验。 + * * @Author swg. * @Date 2019/1/1 13:21 * @CONTACT 317758022@qq.com * @DESC 全局异常处理 */ @ControllerAdvice +// @ControllerAdvice注解表明这个类是一个全局的异常处理类,它可以拦截整个应用程序中符合条件的异常, +// 并进行统一的处理,使得异常处理逻辑可以集中管理,而不用在每个Controller中单独编写异常处理代码。 + @ResponseBody +// @ResponseBody注解表示该类中处理异常的方法返回的结果会直接作为响应体写入到HTTP响应中, +// 通常返回的数据格式可以是JSON等,方便客户端直接解析和处理响应内容。 + @Slf4j +// @Slf4j是Lombok库提供的注解,用于自动生成一个名为log的SLF4J日志记录器,方便在类中记录日志信息, +// 在这里主要用于记录异常相关的信息,便于后续排查问题。 public class ExceptionHandlerAdvice { + + /** + * 这个方法是用于处理所有类型为Exception(即最通用的异常类型,所有异常都继承自它)的异常处理方法。 + * 当应用程序中任何地方抛出了Exception或者其子类异常,且没有被更具体的异常处理方法拦截时, + * 就会进入到这个方法进行处理。 + * + * @param e 捕获到的Exception类型的异常对象,包含了异常相关的详细信息,比如异常消息、堆栈信息等。 + * @return 返回一个ServerResponse对象,该对象用于封装返回给客户端的响应信息, + * 这里返回的响应会设置状态码为表示服务器内部错误的状态码(Constants.RESP_STATUS_INTERNAL_ERROR), + * 并附带相应的错误提示消息告知客户端系统出现异常,让用户稍后再试。 + */ @ExceptionHandler(Exception.class) - public ServerResponse handleException(Exception e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); + public ServerResponse handleException(Exception e) { + log.error(e.getMessage(), e); + // 使用log记录异常的详细信息,包括异常消息以及完整的堆栈信息,方便后续排查问题, + // 调用error方法记录错误级别日志,因为这里处理的是异常情况,属于应用程序中的错误场景。 + + return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试"); + // 通过ServerResponse的静态方法createByErrorCodeMessage创建一个包含错误状态码和错误消息的响应对象, + // 此处使用了在Constants类中定义的表示服务器内部错误的状态码常量, + // 向客户端返回一个统一格式的错误响应,告知客户端系统出现问题,建议稍后再尝试操作。 } + /** + * 这个方法是专门用于处理SnailmallException类型异常的异常处理方法。 + * 当应用程序中抛出了SnailmallException异常时,就会进入这个方法进行针对性的处理。 + * + * @param e 捕获到的SnailmallException类型的异常对象,它包含了该特定异常相关的详细信息, + * 比如可能自定义的异常状态码以及异常消息等内容。 + * @return 返回一个ServerResponse对象,该对象的状态码会设置为SnailmallException对象中携带的异常状态码(e.getExceptionStatus()), + * 响应消息则使用异常对象中自带的消息(e.getMessage()),这样可以根据具体的SnailmallException情况返回更精准的响应给客户端。 + */ @ExceptionHandler(SnailmallException.class) - public ServerResponse handleException(SnailmallException e){ - log.error(e.getMessage(),e); - return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); - } + public ServerResponse handleException(SnailmallException e) { + log.error(e.getMessage(), e); + // 同样使用log记录该特定异常的详细信息,方便后续分析出现该异常的原因以及进行问题排查。 -} + return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage()); + // 根据SnailmallException异常对象自身携带的状态码和消息来构建并返回相应的ServerResponse对象, + // 向客户端返回符合该特定异常情况的响应内容,告知客户端具体的错误信息。 + } +} \ 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..4ba5ccb 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,51 @@ import com.njupt.swg.common.resp.ResponseEnum; import lombok.Getter; /** + * 这个类名为 `SnailmallException`,它继承自 `RuntimeException`,属于自定义的运行时异常类。 + * 主要用于在特定业务场景下抛出有针对性的异常情况,方便在整个应用程序中根据不同的业务错误进行统一处理, + * 并且通过 `@Getter` 注解可以方便地获取异常对象中的相关属性值(在这里主要是异常状态码 `exceptionStatus`)。 + * * @Author swg. * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC */ @Getter -public class SnailmallException extends RuntimeException{ +// @Getter 是Lombok库提供的注解,它会自动为类中的私有成员变量生成对应的 `get` 方法, +// 在这里就是为 `exceptionStatus` 生成 `getExceptionStatus` 方法,方便外部获取该变量的值, +// 而不用手动编写 `get` 方法,简化了代码结构。 +public class SnailmallException extends RuntimeException { private int exceptionStatus = ResponseEnum.ERROR.getCode(); + /** + * 定义了一个私有整型变量 `exceptionStatus`,用于存储该异常对应的状态码, + * 默认初始化为 `ResponseEnum.ERROR.getCode()` 的值,这意味着如果没有通过构造函数传入特定的状态码, + * 则该异常默认使用这个从 `ResponseEnum` 中获取的错误状态码值,这样可以确保异常总是带有一个合理的状态码标识。 + */ - public SnailmallException(String msg){ + /** + * 这个是 `SnailmallException` 类的一个构造函数,它接收一个字符串类型的参数 `msg`。 + * 主要用于创建一个只带有错误消息的 `SnailmallException` 实例,在构造函数内部, + * 通过调用父类(`RuntimeException`)的构造函数将错误消息传递上去, + * 这样在抛出和处理该异常时就能获取到对应的错误消息信息,同时异常状态码会使用默认值(前面定义的 `ResponseEnum.ERROR.getCode()`)。 + * + * @param msg 表示异常相关的错误消息,用于描述出现异常的具体原因等内容,方便后续在日志或者返回给客户端的响应中体现具体错误情况。 + */ + public SnailmallException(String msg) { super(msg); } - public SnailmallException(int code,String msg){ + /** + * 这是 `SnailmallException` 类的另一个构造函数,它接收两个参数:整型的 `code` 和字符串类型的 `msg`。 + * 这个构造函数更加灵活,不仅可以设置异常的错误消息,还能指定具体的异常状态码, + * 适用于在不同业务场景下根据实际情况准确设置异常对应的状态码和错误消息的需求。 + * 在构造函数内部,先调用父类(`RuntimeException`)的构造函数传递错误消息 `msg`, + * 然后将传入的 `code` 值赋给 `exceptionStatus` 变量,更新该异常实例的状态码为传入的指定值。 + * + * @param code 表示要设置的异常状态码,通过传入不同的整数值可以对应不同的业务错误情况,使其更具针对性。 + * @param msg 表示异常相关的错误消息,与前面的构造函数中的 `msg` 作用相同,用于描述具体的异常原因。 + */ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} +} \ No newline at end of file diff --git a/snailmall-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..2fc7453 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,6 +3,11 @@ package com.njupt.swg.common.resp; import lombok.Getter; /** + * 这个类名为 `ResponseEnum`,是一个枚举类型(`enum`),用于定义一些基本的返回状态描述。 + * 在整个应用程序中,它可以作为一种标准化的方式来表示不同的响应结果状态, + * 使得各个模块在返回结果时能够使用统一的、具有明确含义的状态标识,提高代码的可读性和可维护性。 + * 同时,借助 `@Getter` 注解(由Lombok库提供),可以自动为枚举中的成员变量生成对应的获取方法(`get` 方法),方便外部获取这些变量的值。 + * * @Author swg. * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com @@ -10,16 +15,49 @@ import lombok.Getter; */ @Getter public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + // 以下是枚举的各个常量实例,每个实例都代表一种特定的响应状态,并且通过构造函数传入相应的状态码(`code`)和描述信息(`desc`)。 + + SUCCESS(0, "SUCCESS"), + /** + * `SUCCESS` 枚举常量,表示操作成功的状态。 + * 其对应的状态码为 `0`,状态描述为 `"SUCCESS"`,在程序中当某个操作顺利完成并需要返回成功的响应时, + * 可以使用这个枚举常量来代表相应的状态,方便统一处理和识别成功情况。 + */ + + ERROR(1, "ERROR"), + /** + * `ERROR` 枚举常量,用于表示出现一般性错误的状态。 + * 状态码设定为 `1`,描述为 `"ERROR"`,当遇到一些未明确细分的错误情况时, + * 可以返回这个枚举常量来告知调用方操作出现了问题,后续可以根据具体需求进一步细化错误处理逻辑。 + */ + + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + /** + * `ILLEGAL_ARGUMENTS` 枚举常量,专门用来表示接收到的参数不合法的状态。 + * 状态码是 `2`,描述为 `"ILLEGAL_ARGUMENTS"`,例如在接口接收到不符合要求的请求参数时, + * 可以使用这个枚举常量返回相应的错误状态,让调用方知晓参数方面存在问题。 + */ + + NEED_LOGIN(10, "NEED_LOGIN"); + /** + * `NEED_LOGIN` 枚举常量,代表需要用户登录的状态。 + * 状态码为 `10`,描述为 `"NEED_LOGIN"`,常用于在访问某些需要认证登录的资源或接口时, + * 如果用户未登录,就可以返回这个枚举常量来提示调用方需要先进行登录操作才能继续。 + */ private int code; private String desc; - ResponseEnum(int code,String desc){ + /** + * 这是 `ResponseEnum` 枚举类型的构造函数,用于初始化每个枚举常量的状态码(`code`)和描述信息(`desc`)。 + * 当定义枚举常量时,通过传入相应的参数值,就能为每个枚举实例设置其特有的状态码和对应的描述内容, + * 使得每个枚举常量都能准确地表示一种特定的响应状态情况。 + * + * @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..9574448 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 @@ -8,71 +8,125 @@ 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`)生成对应的 `get` 方法, +// 方便外部获取这些变量的值,而无需手动编写 `get` 方法,简化了代码结构。 + @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// 这个注解用于配置Jackson序列化的行为,这里设置为 `NON_NULL`,表示在将对象序列化为JSON等格式时, +// 只会包含非空的属性,避免返回包含大量空值的冗余数据给客户端,优化响应数据的结构。 public class ServerResponse implements Serializable { + // 实现 `Serializable` 接口,使得该类的对象可以被序列化和反序列化,便于在网络传输、持久化存储等场景中使用。 + private int status; + // 用于存储响应的状态码,通过不同的状态码值可以表示请求处理的不同结果情况,例如成功、失败、需要登录等状态, + // 通常会参考项目中定义的状态码枚举(如 `ResponseEnum`)来设置合理的值。 + private String msg; + // 用于存放响应的提示消息,该消息可以是对响应状态的简单描述,比如成功时的提示语或者出现错误时的具体错误原因等, + // 方便客户端直观地了解请求处理的情况。 + private T data; + // 泛型成员变量,用于承载具体的响应数据内容,根据不同的业务场景可以是各种类型的数据,例如实体对象、列表、Map等, + // 如果没有具体的数据需要返回(比如仅返回状态码和提示消息表示操作结果时),这个变量可以为 `null`。 - public ServerResponse(){} + public ServerResponse() { + } + // 默认的无参构造函数,方便在一些情况下创建 `ServerResponse` 对象,不过使用时可能需要后续再设置具体的属性值。 - 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){ + // 接收状态码和提示消息两个参数的构造函数,适用于创建带有特定状态码和对应描述消息的响应对象, + // 常用于返回操作结果并告知客户端相关情况,但不需要返回具体业务数据的场景。 + + public ServerResponse(int status, T data) { this.status = status; this.data = data; } - public ServerResponse(int status,String msg,T data){ + // 接收状态码和业务数据的构造函数,用于在操作成功且有具体数据需要返回给客户端时创建响应对象, + // 此时可以将正确的状态码以及获取到的业务数据传入,构造出符合要求的返回结果。 + + public ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + // 完整的构造函数,接收状态码、提示消息以及业务数据三个参数,能够满足各种情况下创建 `ServerResponse` 对象的需求, + // 可以根据具体的业务处理结果灵活设置各个参数的值来构造合适的返回响应。 @JsonIgnore - public boolean isSuccess(){ + // 这个注解用于告诉Jackson在序列化对象时忽略该方法对应的属性,在这里使得 `isSuccess` 方法不会作为一个属性出现在序列化结果中。 + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } + // 定义了一个判断响应是否成功的方法,通过比较当前响应对象的状态码和 `ResponseEnum` 枚举中定义的成功状态码(`SUCCESS` 对应的代码)是否相等, + // 来确定此次响应是否代表操作成功,方便外部调用者快速判断请求处理的结果是否符合预期。 /** * 成功的方法 + * 以下是一组静态方法,用于方便地创建表示成功状态的 `ServerResponse` 对象,提供了不同参数组合的创建方式, + * 以满足各种成功场景下返回响应的需求。 */ - 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); + // 这个静态方法创建一个表示成功状态的默认响应对象,使用 `ResponseEnum` 中定义的成功状态码和对应的描述信息, + // 适用于只需要简单返回成功标识而无需额外提示消息和业务数据的场景。 + + public static ServerResponse createBySuccessMessage(String message) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message); } - public static ServerResponse createBySuccess(T data){ - return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); + // 创建一个成功状态的响应对象,允许传入自定义的提示消息,方便在成功情况下根据具体业务需求返回更有针对性的提示内容给客户端, + // 但此时不包含具体的业务数据。 + + public static ServerResponse createBySuccess(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` 对象,便于统一处理各种错误返回情况。 */ - 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); - } - + // 创建一个表示一般性错误状态的响应对象,使用 `ResponseEnum` 中定义的错误状态码和对应的描述信息, + // 适用于在出现未细分的错误情况时返回给客户端,告知操作出现问题。 + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } + // 用于创建带有自定义错误提示消息的错误状态响应对象,传入的 `msg` 参数可以是具体的错误原因等内容, + // 使得在不同的业务错误场景下能够返回更明确的错误信息给客户端,帮助客户端了解出现问题的具体情况。 -} + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + 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..39f14d8 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,99 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - /** + * 这个类名为 `CategoryController`,是一个Spring框架中的 `RestController`,用于处理与品类(category)相关的HTTP请求, + * 它属于后端管理系统的一部分,主要提供了一些操作品类信息的接口,由于涉及后端管理操作,所以这些接口通常需要管理员权限来访问。 + * 当前代码中还遗留了一些待完善或后续优化的事项,通过 `TODO` 注释进行了标注,例如鉴权相关的逻辑后续计划移植到网关中统一处理, + * 并且目前暂时只开放了 `GET` 请求等情况。 + * * @Author swg. * @Date 2019/1/2 12:57 * @CONTACT 317758022@qq.com * @DESC 品类接口,这属于后端管理系统人员可以操作的,所以需要管理员权限 */ -//TODO 这里首先实现业务 关于这里重复的鉴权,后面将会移植到网关中统一去做 -//TODO 先开放GET请求 +// TODO 这里首先实现业务 关于这里重复的鉴权,后面将会移植到网关中统一去做 +// 此处表明当前代码专注于先实现业务功能,而对于鉴权部分,后续会将分散在这里重复进行的鉴权操作提取出来,放到网关层面进行统一的处理, +// 这样可以提高鉴权逻辑的复用性和可维护性,避免在每个接口中都重复编写相似的鉴权代码。 + +// TODO 先开放GET请求 +// 说明当前阶段暂时只允许 `GET` 类型的HTTP请求访问这些接口,后续可能会根据业务需求进一步开放其他类型的请求(如 `POST`、`PUT`、`DELETE` 等), +// 这可能涉及到对接口安全性、功能完整性等方面的综合考虑。 + @RestController +// `@RestController` 注解是Spring框架提供的,它结合了 `@Controller` 和 `@ResponseBody` 的功能, +// 意味着这个类中的所有方法返回值都会直接作为响应体(通常是JSON等格式)返回给客户端,而不需要额外配置视图解析等, +// 适用于构建纯粹的RESTful风格的Web服务接口。 + @RequestMapping("/manage/category/") +// `@RequestMapping` 注解用于给这个控制器类中的所有接口方法配置一个基础的请求路径, +// 即所有该控制器下的接口请求路径都将以 `/manage/category/` 开头,这样便于对相关接口进行统一的路径管理和分类。 public class CategoryController { + @Autowired private ICategoryService categoryService; + // 通过Spring的依赖注入(Dependency Injection)机制,使用 `@Autowired` 注解自动注入 `ICategoryService` 类型的实例, + // `ICategoryService` 应该是一个服务层接口,它定义了一系列与品类操作相关的业务逻辑方法, + // 在这里注入后,控制器类中的方法就可以调用服务层提供的这些方法来完成具体的业务处理。 /** * 获取品类子节点(平级) + * 这个方法用于处理获取品类子节点(平级关系)的HTTP请求,它接收一个可选的 `categoryId` 参数, + * 如果客户端没有传入该参数,则默认值为 `0`。通过调用注入的 `categoryService` 的 `getCategory` 方法来获取相应的品类子节点信息, + * 最后将服务层返回的 `ServerResponse` 对象直接返回给客户端,该 `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; } /** * 增加节点 + * 用于处理添加品类节点的HTTP请求,它接收一个品类名称 `categoryName` 参数以及一个可选的 `parentId` 参数(默认值为 `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; } /** * 修改品类名称 + * 该方法用于处理修改品类名称的HTTP请求,接收要修改的品类名称 `categoryName` 和对应的品类 `categoryId` 两个参数, + * 直接调用 `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); } /** * 递归获取自身和所有的子节点 + * 处理递归获取品类自身及其所有子节点信息的HTTP请求,接收一个可选的 `categoryId` 参数(默认值为 `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); } /** * 这是为了给其他服务调用而新增的接口 + * 表示这个接口是特意为了满足其他服务调用需求而添加的,具体功能是根据传入的 `categoryId` 参数, + * 通过调用 `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..16383dc 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,96 @@ 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 的映射机制,这些方法会与对应的 SQL 语句进行关联,从而实现与数据库的交互, + * 操作对应的 `Category` 表数据(这里假设数据库中有 `Category` 表来存储品类相关信息)。 + * + * @Mapper 注解标识这个接口是 MyBatis 的 Mapper 接口,在 Spring 与 MyBatis 整合的项目中, + * 框架会自动扫描带有该注解的接口,并为其创建代理实现类,使得可以在其他层(如服务层)方便地调用这些数据库操作方法。 + */ @Mapper public interface CategoryMapper { + + /** + * 根据主键删除对应的记录。 + * 该方法用于从数据库中删除 `Category` 表中主键(通常对应 `id` 字段)为指定值的记录, + * 传入的参数 `Integer id` 就是要删除记录的主键值,方法返回一个整型值,表示受影响的行数, + * 可以通过返回值判断是否成功删除了对应的数据记录(例如返回值为 1 表示成功删除了 1 条记录等)。 + * + * @param id 要删除记录的主键值,即 `Category` 表中对应记录的唯一标识符。 + * @return 受影响的行数,用于判断删除操作的执行情况。 + */ int deleteByPrimaryKey(Integer id); + /** + * 插入一条新的 `Category` 记录。 + * 此方法用于向 `Category` 表中插入一条完整的 `Category` 记录,传入的参数 `Category record` + * 就是要插入的实体对象,该对象包含了各个字段的值(如品类名称、其他相关属性等),方法返回一个整型值, + * 同样表示受影响的行数,正常情况下返回值为 1 表示成功插入了 1 条记录。 + * + * @param record 要插入的 `Category` 实体对象,包含了待插入记录的所有字段信息。 + * @return 受影响的行数,用于判断插入操作的执行情况。 + */ int insert(Category record); + /** + * 选择性地插入一条 `Category` 记录。 + * 与 `insert` 方法不同的是,这个方法会根据传入的 `Category` 实体对象中不为空的字段值来进行插入操作, + * 也就是说只会将有值的字段插入到数据库表中,对于那些值为 `null` 的字段则不会插入, + * 这样更灵活地适应了不同插入场景下可能不需要插入所有字段的情况,返回值同样表示受影响的行数。 + * + * @param record 要插入的 `Category` 实体对象,包含了部分或全部待插入记录的字段信息。 + * @return 受影响的行数,用于判断插入操作的执行情况。 + */ int insertSelective(Category record); + /** + * 根据主键查询对应的 `Category` 记录。 + * 该方法通过传入的主键值(`Integer id`)从 `Category` 表中查找并返回对应的 `Category` 记录, + * 如果找到匹配的记录,则返回对应的 `Category` 实体对象,若没找到则返回 `null`, + * 用于在需要获取特定主键对应的品类详细信息的场景下进行查询操作。 + * + * @param id 要查询记录的主键值,即 `Category` 表中对应记录的唯一标识符。 + * @return 匹配主键的 `Category` 实体对象,如果不存在则返回 `null`。 + */ Category selectByPrimaryKey(Integer id); + /** + * 根据主键选择性地更新 `Category` 记录。 + * 类似于 `insertSelective` 方法的逻辑,这个方法会根据传入的 `Category` 实体对象中不为空的字段值, + * 对数据库中主键对应的 `Category` 记录进行更新操作,只会更新有值的那些字段, + * 更灵活地适应了部分字段需要更新的业务场景,返回值表示受影响的行数,用于判断更新操作是否成功执行。 + * + * @param record 包含要更新字段值的 `Category` 实体对象,其主键值用于确定要更新的数据库记录。 + * @return 受影响的行数,用于判断更新操作的执行情况。 + */ int updateByPrimaryKeySelective(Category record); + /** + * 根据主键更新 `Category` 记录。 + * 此方法会使用传入的 `Category` 实体对象的所有字段值来更新数据库中主键对应的 `Category` 记录, + * 不管字段值是否为 `null`,都会覆盖原记录中的对应字段值,返回值表示受影响的行数, + * 用于判断更新操作是否成功执行。 + * + * @param record 包含更新后所有字段值的 `Category` 实体对象,其主键值用于确定要更新的数据库记录。 + * @return 受影响的行数,用于判断更新操作的执行情况。 + */ int updateByPrimaryKey(Category record); + /** + * 根据父品类的 `id` 查询其所有子品类记录。 + * 该方法用于从 `Category` 表中查找指定父品类 `id`(`Integer categoryId`)对应的所有子品类记录, + * 返回一个 `List` 类型的结果集,其中包含了所有符合条件的子品类的 `Category` 实体对象列表, + * 方便在业务中获取某个品类下的所有子品类信息,例如构建品类层级结构等场景。 + * + * @param categoryId 父品类的 `id` 值,用于在 `Category` 表中筛选出属于该父品类的子品类记录。 + * @return 包含指定父品类所有子品类的 `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..b4bb613 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 @@ -6,21 +6,49 @@ import lombok.NoArgsConstructor; import java.util.Date; +/** + * 这个类名为 `Category`,它代表了项目中的一个实体类,通常用于映射数据库中的 `Category`(品类)表结构, + * 或者在业务逻辑处理过程中作为承载品类相关数据的对象。实体类用于封装与某个具体业务领域相关的数据以及其对应的操作, + * 在这里主要包含了品类相关的各种属性信息,比如品类的编号、父品类编号、名称、状态、排序顺序以及创建时间和更新时间等。 + * + * @Data 注解是由Lombok库提供的,它会自动为类生成一系列常用的方法,包括所有属性的 `getter`、`setter` 方法, + * `toString` 方法、`equals` 方法和 `hashCode` 方法等,极大地简化了代码编写,避免手动去编写这些重复的、样板式的代码。 + * + * @AllArgsConstructor 注解同样来自Lombok库,它会为类生成一个包含所有参数的构造函数, + * 方便在创建 `Category` 对象时可以一次性传入所有属性的值来进行初始化。 + * + * @NoArgsConstructor 注解也是Lombok库提供的,它会为类生成一个无参构造函数, + * 在某些情况下(比如使用框架进行对象实例化等场景)可能需要类具有无参构造函数,该注解就满足了这一需求。 + */ @Data @AllArgsConstructor @NoArgsConstructor public class Category { private Integer id; + // 用于存储品类的唯一标识符,在数据库中通常对应主键字段,通过这个 `id` 可以唯一确定一个品类记录, + // 比如在进行数据库查询、更新、删除等操作时,往往需要依据这个 `id` 来定位具体的品类数据。 private Integer parentId; + // 表示该品类所属的父品类的 `id`,用于构建品类之间的层级关系,通过这个字段可以知道某个品类隶属于哪个上级品类, + // 有助于实现诸如查询某个品类的子品类、获取品类树结构等相关业务功能。 private String name; + // 存储品类的名称,是用于展示和区分不同品类的重要属性,比如在前端界面上呈现给用户的品类名称, + // 或者在业务逻辑中通过名称来进行品类的查找、筛选等操作时会用到这个属性。 private Boolean status; + // 用于表示品类的当前状态,比如可以设定 `true` 表示该品类处于启用、可用状态,`false` 表示禁用、不可用状态等, + // 在业务中可以根据这个状态属性来决定是否展示该品类、是否允许对其进行相关操作等情况。 private Integer sortOrder; + // 用于确定品类在展示或者排序时的顺序,例如在一个列表中按照一定规则排列品类的先后顺序时, + // 就会依据这个 `sortOrder` 属性的值来进行排序,数值小的品类可能排在前面等,方便对品类进行有序的展示和管理。 private Date createTime; + // 记录品类的创建时间,它可以用于跟踪品类数据的创建历史,比如查看某个品类是什么时候首次添加到系统中的, + // 在一些数据审计、数据分析等业务场景中可能会用到这个时间属性。 private Date updateTime; + // 用于记录品类数据最后一次更新的时间,通过对比 `createTime` 和 `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..905cdc6 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 @@ -9,35 +9,73 @@ 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 注解用于生成一个无参构造函数,在一些场景下(例如使用某些框架进行对象实例化时)可能需要类具有无参构造函数, +// 这个注解就满足了这一需求,方便对象的创建操作。 + @AllArgsConstructor +// @AllArgsConstructor 注解会为类生成一个包含所有参数的构造函数,当需要一次性传入所有属性的值来创建 `User` 对象时, +// 就可以使用这个构造函数,提高了对象初始化的便利性。 + @ToString +// @ToString 注解由Lombok库提供,它会自动重写类的 `toString` 方法,使得在打印对象或者将对象转换为字符串表示形式时, +// 能够更直观地展示对象包含的各个属性及其对应的值,方便调试和查看对象的状态信息。 + public class User implements Serializable { + // 实现 `Serializable` 接口意味着该类的对象可以被序列化和反序列化,这在很多场景下非常重要, + // 比如将用户对象存储到文件中、通过网络传输用户对象等操作时,都需要对象具备可序列化的特性。 + private Integer id; + // 用于存储用户的唯一标识符,通常对应数据库表中的主键字段,通过这个 `id` 可以在系统中唯一确定一个用户, + // 在进行用户相关的数据库查询、更新、删除等操作时,往往需要依据这个 `id` 来精准定位具体的用户数据。 private String username; + // 代表用户的用户名,是用户在登录系统或者进行其他交互操作时使用的标识名称,具有唯一性(一般情况下), + // 在业务逻辑中常用于用户认证、权限验证以及在界面上展示用户相关信息等场景。 private String password; + // 存储用户的登录密码,是保障用户账号安全的关键属性,在用户登录验证环节会将用户输入的密码与该属性存储的值进行比对, + // 以确定用户身份的合法性,密码通常会进行加密存储,以提高安全性。 private String email; + // 用于存放用户的电子邮箱地址,可用于多种用途,比如找回密码、接收系统通知、验证用户身份等操作, + // 方便系统与用户进行信息沟通和交互,并且在一些业务逻辑中可能会对邮箱格式的合法性进行验证。 private String phone; + // 存储用户的手机号码,同样可用于诸如短信验证码验证、找回密码、接收重要通知等功能, + // 在当今的移动互联网应用场景下,手机号往往也是用户重要的联系方式和身份验证依据之一。 private String question; + // 用于存储用户设置的密保问题,在用户忘记密码等情况下,可以通过回答正确的密保问题来重置密码, + // 这是一种常见的用户账号安全保障措施,增加了账号找回密码等操作的安全性和可靠性。 private String answer; + // 对应于密保问题的答案,与 `question` 属性配合使用,用于验证用户在找回密码等操作时提供的答案是否正确, + // 只有答案匹配才能进行后续的密码重置等相关操作。 - //角色0-管理员,1-普通用户 + // 角色0-管理员,1-普通用户 private Integer role; + // 这个属性用于定义用户在系统中的角色,通过整数值来区分不同角色类型,这里规定 `0` 表示管理员角色,`1` 表示普通用户角色, + // 根据用户的角色不同,系统会赋予其不同的权限,例如管理员可能具有更多的系统管理、数据操作等权限,而普通用户则只能进行一些常规的操作。 private Date createTime; + // 记录用户账号的创建时间,它可以用于跟踪用户在系统中的注册历史,了解用户何时加入系统, + // 在一些数据统计、用户行为分析以及审计等业务场景中可能会用到这个时间属性。 private Date updateTime; - + // 用于记录用户账号信息最后一次更新的时间,通过对比 `createTime` 和 `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..79575c0 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,199 @@ import java.util.List; import java.util.Set; /** + * 这个类名为 `CategoryServiceImpl`,它实现了 `ICategoryService` 接口,是品类相关业务逻辑的具体实现类。 + * 主要负责处理与品类操作相关的各种业务功能,例如获取品类信息、添加品类、更新品类名称、递归获取品类及其子节点信息以及获取品类详细信息等, + * 通过调用 `CategoryMapper` 接口提供的数据库操作方法与数据库进行交互,并根据业务规则进行相应的逻辑处理,最终返回合适的响应给调用方。 + * 同时,它使用了Spring框架的 `@Service` 注解将自身注册为一个服务层组件,方便在其他地方进行依赖注入使用,并且使用 `@Slf4j` 注解来引入日志记录功能,便于记录业务处理过程中的相关信息。 + * * @Author swg. * @Date 2019/1/2 12:54 * @CONTACT 317758022@qq.com * @DESC */ @Service +// `@Service` 注解是Spring框架提供的,用于将当前类标记为服务层组件,表明这个类包含了业务逻辑处理的相关代码, +// 会被Spring容器自动扫描并管理,使得其他类可以通过依赖注入的方式使用这个类的实例,方便业务逻辑的分层和复用。 + @Slf4j -public class CategoryServiceImpl implements ICategoryService{ +// `@Slf4j` 是Lombok库提供的注解,用于自动生成一个名为 `log` 的SLF4J日志记录器,在类中可以方便地使用这个日志记录器记录各种级别的日志信息, +// 例如记录业务操作的关键步骤、异常情况等,有助于后续的问题排查和系统监控。 +public class CategoryServiceImpl implements ICategoryService { + @Autowired private CategoryMapper categoryMapper; - + // 通过Spring的依赖注入(Dependency Injection)机制,使用 `@Autowired` 注解自动注入 `CategoryMapper` 类型的实例, + // `CategoryMapper` 是数据访问层接口,它定义了一系列操作品类数据的数据库方法,在这里注入后,服务层的业务方法就可以调用这些方法来与数据库进行交互, + // 实现对品类数据的增删改查等操作。 @Override public ServerResponse getCategory(Integer categoryId) { - //1.校验参数 - if(categoryId == null){ + // 1.校验参数 + if (categoryId == null) { throw new SnailmallException("未找到该品类"); } - //2.根据父亲id获取这个父亲下一级所有子ID + // 首先对传入的品类 `id` 参数进行合法性校验,如果 `id` 为 `null`,说明参数不合法,此时抛出 `SnailmallException` 异常, + // 告知调用方没有找到对应的品类,遵循了先进行参数校验再进行业务操作的良好编程习惯,避免后续因参数问题导致的潜在错误。 + + // 2.根据父亲id获取这个父亲下一级所有子ID List categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); - if(CollectionUtils.isEmpty(categoryList)){ + // 调用 `CategoryMapper` 接口的 `selectCategoryChildrenByParentId` 方法,根据传入的 `categoryId`(作为父品类 `id`), + // 从数据库中查询获取该父品类下一级的所有子品类记录,返回的结果是一个 `List` 类型的列表,包含了符合条件的子品类对象集合。 + + if (CollectionUtils.isEmpty(categoryList)) { log.info("该节点下没有任何子节点"); } + // 使用Apache Commons Collections工具类的 `CollectionUtils.isEmpty` 方法判断获取到的子品类列表是否为空, + // 如果为空,则通过日志记录器 `log` 记录一条信息日志,表示当前节点下没有任何子节点,方便后续查看业务执行情况进行调试和监控。 + return ServerResponse.createBySuccess(categoryList); + // 最后,使用 `ServerResponse` 类的静态方法 `createBySuccess` 创建一个表示成功的响应对象,并将获取到的子品类列表作为数据传入, + // 返回给调用方,告知操作成功并携带相应的子品类数据,如果列表为空则表示没有子品类数据可返回,但操作本身是成功执行的。 } @Override public ServerResponse addCategory(String categoryName, int parentId) { - //1.校验参数 - if(StringUtils.isBlank(categoryName)){ + // 1.校验参数 + if (StringUtils.isBlank(categoryName)) { throw new SnailmallException("品类名字不能为空"); } - //2.创建类目 + // 对传入的品类名称参数进行校验,使用Apache Commons Lang3工具类的 `StringUtils.isBlank` 方法判断名称是否为空(包括 `null` 或者空字符串情况), + // 如果为空则抛出 `SnailmallException` 异常,提示品类名字不能为空,确保添加品类时必须有合法的名称。 + + // 2.创建类目 Category category = new Category(); category.setName(categoryName); category.setParentId(parentId); category.setStatus(true); + // 创建一个 `Category` 实体对象,将传入的品类名称和父品类 `id` 设置到该对象的对应属性上,并默认将品类的状态设置为 `true`(表示启用、可用状态等), + // 准备将这个对象插入到数据库中,完成添加品类的操作。 int resultCount = categoryMapper.insert(category); - if(resultCount > 0){ + // 调用 `CategoryMapper` 接口的 `insert` 方法,将创建好的 `Category` 实体对象插入到数据库中,该方法会返回受影响的行数, + // 用于判断插入操作是否成功执行,正常情况下如果成功插入一条记录,返回值应该为 `1`。 + + if (resultCount > 0) { return ServerResponse.createBySuccessMessage("添加品类成功"); } return ServerResponse.createByErrorMessage("添加品类失败"); + // 根据插入操作返回的受影响行数判断,如果大于 `0`,说明插入成功,使用 `ServerResponse` 类的静态方法 `createBySuccessMessage` 创建一个表示成功的响应对象, + // 并传入提示消息 "添加品类成功" 返回给调用方;如果插入失败(返回值不大于 `0`),则使用 `createByErrorMessage` 方法创建一个表示失败的响应对象, + // 传入 "添加品类失败" 消息返回给调用方,告知调用方添加品类的操作结果情况。 } @Override public ServerResponse updateCategoryName(String categoryName, Integer categoryId) { - //1.校验参数 - if(StringUtils.isBlank(categoryName)){ + // 1.校验参数 + if (StringUtils.isBlank(categoryName)) { throw new SnailmallException("品类名字不能为空"); } - //2.根据id获取品类 + // 同样先对传入的品类名称参数进行校验,确保名称不为空,若为空则抛出 `SnailmallException` 异常,提示品类名字不能为空, + // 保证只有合法的名称才能进行后续的更新操作。 + + // 2.根据id获取品类 Category tmpCat = categoryMapper.selectByPrimaryKey(categoryId); - if(tmpCat == null){ + // 调用 `CategoryMapper` 接口的 `selectByPrimaryKey` 方法,根据传入的品类 `id` 从数据库中查询获取对应的 `Category` 实体对象, + // 如果能找到则返回该对象,若找不到(即 `tmpCat` 为 `null`)则说明要更新的品类不存在。 + + if (tmpCat == null) { throw new SnailmallException("品类不存在"); } - //3.更新品类名称 + // 如果根据 `id` 查询不到对应的品类对象,则抛出 `SnailmallException` 异常,告知调用方品类不存在,无法进行名称更新操作。 + + // 3.更新品类名称 Category category = new Category(); category.setId(categoryId); category.setName(categoryName); + // 创建一个新的 `Category` 实体对象,将传入的品类 `id` 和要更新的品类名称设置到该对象的对应属性上, + // 准备用这个对象来更新数据库中对应品类的名称信息,这里只设置了 `id` 和 `name` 属性,其他属性可能保持原有值不变(根据数据库操作逻辑)。 + int resultCount = categoryMapper.updateByPrimaryKeySelective(category); - if(resultCount > 0){ + // 调用 `CategoryMapper` 接口的 `updateByPrimaryKeySelective` 方法,根据创建的 `Category` 实体对象中设置的非空属性值(这里主要是 `name` 属性), + // 对数据库中主键为对应 `id` 的品类记录进行选择性更新操作,即只更新设置了值的属性,该方法返回受影响的行数用于判断更新操作是否成功执行。 + + if (resultCount > 0) { return ServerResponse.createBySuccessMessage("更新品类名称成功"); } return ServerResponse.createByErrorMessage("更新品类名称失败"); + // 根据更新操作返回的受影响行数判断,如果大于 `0`,说明更新成功,使用 `ServerResponse` 类的静态方法 `createBySuccessMessage` 创建一个表示成功的响应对象, + // 传入提示消息 "更新品类名称成功" 返回给调用方;如果更新失败(返回值不大于 `0`),则使用 `createByErrorMessage` 方法创建一个表示失败的响应对象, + // 传入 "更新品类名称失败" 消息返回给调用方,告知调用方更新品类名称的操作结果情况。 } @Override public ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId) { - //1、创建一个空Set用来存放不重复的品类对象--去重 + // 1、创建一个空Set用来存放不重复的品类对象--去重 Set categorySet = Sets.newHashSet(); - //2、递归获取所有的子节点(儿子、孙子、等等),包括自己也添加进去 - findChildCategory(categorySet,categoryId); - //3、将递归获取到的品类id取出来放进list中 + // 使用Google Guava库的 `Sets.newHashSet` 方法创建一个空的 `Set` 集合,用于存放品类对象,这里的 `Set` 集合特性可以保证元素的唯一性, + // 目的是在后续递归获取品类及其子节点信息的过程中避免重复添加相同的品类对象,起到去重的作用。 + + // 2、递归获取所有的子节点(儿子、孙子、等等),包括自己也添加进去 + findChildCategory(categorySet, categoryId); + // 调用私有方法 `findChildCategory`,传入创建好的 `categorySet` 和要查询的品类 `id`,开始递归地获取指定品类及其所有层级的子品类对象, + // 将获取到的品类对象添加到 `categorySet` 集合中,实现深度遍历品类树结构的功能。 + + // 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()); } } + // 创建一个 `List` 类型的列表,用于存放从 `categorySet` 集合中提取出来的品类 `id` 值, + // 如果传入的 `categoryId` 不为 `null`,则遍历 `categorySet` 中的每个品类对象,将其 `id` 属性值添加到 `categoryIdList` 列表中, + // 这样最终得到的列表就是包含了指定品类及其所有子品类的 `id` 集合,方便后续业务使用或者返回给调用方等操作。 + return ServerResponse.createBySuccess(categoryIdList); + // 使用 `ServerResponse` 类的静态方法 `createBySuccess` 创建一个表示成功的响应对象,并将包含品类 `id` 列表的 `categoryIdList` 作为数据传入, + // 返回给调用方,告知操作成功并携带相应的品类 `id` 数据,方便调用方根据这些 `id` 进一步获取详细的品类信息等操作。 } - private Set findChildCategory(Set categorySet,Integer categoryId){ - //4、如果自己不为空的话,首先把自己添加进去;如果自己为空,这个递归分支就结束,所以也是一个停止条件 + private Set findChildCategory(Set categorySet, Integer categoryId) { + // 4、如果自己不为空的话,首先把自己添加进去;如果自己为空,这个递归分支就结束,所以也是一个停止条件 Category category = categoryMapper.selectByPrimaryKey(categoryId); - if(category != null){ + if (category!= null) { categorySet.add(category); } - //5、根据父亲id获取下一级所有品类(即先获取儿子们) + // 首先根据传入的品类 `id` 通过 `CategoryMapper` 接口的 `selectByPrimaryKey` 方法从数据库中查询对应的 `Category` 实体对象, + // 如果查询到的对象不为 `null`,说明该品类存在,则将其添加到传入的 `categorySet` 集合中,这是递归过程中的一个基础操作, + // 同时如果查询结果为空,说明已经遍历到品类树的叶子节点或者不存在该 `id` 对应的品类,此时这个递归分支就结束,符合递归的停止条件设定。 + + // 5、根据父亲id获取下一级所有品类(即先获取儿子们) List categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); - //6、根据每一个儿子再获取儿子的儿子们,递归下去 - for(Category categoryItem:categoryList){ - findChildCategory(categorySet,categoryItem.getId()); + // 调用 `CategoryMapper` 接口的 `selectCategoryChildrenByParentId` 方法,根据当前品类的 `id`(作为父品类 `id`), + // 从数据库中查询获取该父品类下一级的所有子品类记录,返回的结果是一个 `List` 类型的列表,包含了符合条件的子品类对象集合, + // 这些就是当前品类的“儿子”品类。 + + // 6、根据每一个儿子再获取儿子的儿子们,递归下去 + for (Category categoryItem : categoryList) { + findChildCategory(categorySet, categoryItem.getId()); } + // 遍历获取到的“儿子”品类列表,对于每个“儿子”品类,再次调用 `findChildCategory` 方法,传入当前的 `categorySet` 和“儿子”品类的 `id`, + // 继续递归地获取“儿子”品类的子品类(即“孙子”品类等),如此循环下去,实现深度优先遍历整个品类树结构,将所有层级的品类对象添加到 `categorySet` 集合中。 + return categorySet; } - @Override public ServerResponse getCategoryDetail(Integer categoryId) { - if(categoryId == null){ + if (categoryId == null) { return ServerResponse.createByErrorMessage("参数不能为空"); } + // 先对传入的品类 `id` 参数进行合法性校验,如果 `id` 为 `null`,说明参数不合法,此时使用 `ServerResponse` 类的静态方法 `createByErrorMessage` 创建一个表示失败的响应对象, + // 传入提示消息 "参数不能为空" 返回给调用方,告知调用方参数不符合要求。 + Category category = categoryMapper.selectByPrimaryKey(categoryId); - if(category == null){ + // 调用 `CategoryMapper` 接口的 `selectByPrimaryKey` 方法,根据传入的品类 `id` 从数据库中查询获取对应的 `Category` 实体对象, + // 如果能找到则返回该对象,若找不到则说明要获取详细信息的品类不存在。 + + if (category == null) { return ServerResponse.createByErrorMessage("品类不存在"); } + // 如果根据 `id` 查询不到对应的品类对象,则使用 `ServerResponse` 类的静态方法 `createByErrorMessage` 创建一个表示失败的响应对象, + // 传入提示消息 "品类不存在" 返回给调用方,告知调用方要获取详细信息的品类不存在;如果查询到了品类对象,则继续下一步操作。 + return ServerResponse.createBySuccess(category); + // 使用 `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..3ec9aef 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,61 @@ import com.njupt.swg.entity.Category; */ public interface ICategoryService { - /** 根据类目id获取其下面所有的一级子类目 **/ + /** + * 根据类目id获取其下面所有的一级子类目 + * 该方法用于获取指定品类 `id`(`categoryId`)下的所有一级子品类信息,返回一个 `ServerResponse` 对象, + * 这个对象中包含了请求结果的状态码、提示消息以及可能的数据内容(在这里就是对应的一级子品类集合等信息), + * 通过这个方法,调用者可以方便地获取某个品类下直接的子品类情况,比如在构建品类层级结构展示或者进行相关业务判断时会用到。 + * + * @param categoryId 要查询其一级子品类的品类 `id`,通过这个 `id` 来定位到对应的品类记录,进而查找它的子品类信息。 + * @return 返回 `ServerResponse` 对象,其中封装了操作结果及对应一级子品类数据等相关信息,以便调用者知晓请求的执行情况并获取所需数据。 + */ ServerResponse getCategory(Integer categoryId); - /** 新建一个商品类目 **/ + /** + * 新建一个商品类目 + * 用于创建一个新的商品品类,接收要创建的品类名称(`categoryName`)和父品类 `id`(`parentId`)两个参数, + * 通过该方法执行添加品类的业务逻辑,最后返回一个 `ServerResponse` 对象来告知调用者添加操作的结果情况, + * 例如添加成功与否、若失败可能的错误提示消息等信息,方便在前端界面或者其他调用处根据返回结果进行相应的提示和后续处理。 + * + * @param categoryName 要创建的商品品类的名称,用于唯一标识该品类(在一定范围内),不能为空,通常会有相应的命名规范和长度限制等要求。 + * @param parentId 新建品类所属的父品类的 `id`,用于确定该品类在整个品类层级结构中的位置,若为 `0` 等特定值可能表示该品类为顶级品类等情况。 + * @return 返回 `ServerResponse` 对象,其中包含了添加品类操作的结果状态以及可能的相关提示消息等内容。 + */ ServerResponse addCategory(String categoryName, int parentId); - /** 更新品类名称 **/ + /** + * 更新品类名称 + * 此方法用于更新指定品类的名称,接收要更新的品类名称(`categoryName`)和对应的品类 `id`(`categoryId`)两个参数, + * 执行更新品类名称的业务逻辑后,返回一个 `ServerResponse` 对象,这里的泛型 `` 可能意味着返回结果中包含了与字符串相关的一些额外信息(比如更新后的名称等情况), + * 调用者可以通过这个返回对象获取更新操作的结果情况以及相关的数据内容,便于后续根据结果进行相应的展示或者其他处理。 + * + * @param categoryName 要更新成的新的品类名称,需符合相应的命名规范等要求,不能为空,用于替换原有的品类名称。 + * @param categoryId 要更新名称的品类的 `id`,通过这个 `id` 来定位到数据库中对应的品类记录进行名称更新操作。 + * @return 返回 `ServerResponse` 对象,包含更新操作结果以及可能的相关字符串类型的数据信息,告知调用者名称更新情况。 + */ ServerResponse updateCategoryName(String categoryName, Integer categoryId); - /** 递归查询出所有品类 **/ + /** + * 递归查询出所有品类 + * 用于递归地查询出指定品类及其所有层级的子品类信息,接收一个品类 `id`(`categoryId`)作为起始查询点, + * 通过该方法可以深度遍历品类树结构,获取从指定品类开始的所有子孙品类情况,返回一个 `ServerResponse` 对象, + * 其包含了请求结果的状态码、提示消息以及获取到的所有品类相关数据(例如品类 `id` 集合等形式),方便调用者获取完整的品类层级结构信息。 + * + * @param categoryId 起始查询的品类 `id`,可以是任意一个品类的 `id` 值,以它为起点进行递归查询,获取其下所有子品类信息。 + * @return 返回 `ServerResponse` 对象,封装了操作结果及递归查询得到的所有品类相关数据等内容,供调用者使用。 + */ ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId); - /** 被其他服务调用的接口 **/ + /** + * 被其他服务调用的接口 + * 表明这个接口方法主要是提供给其他服务进行调用的,其功能是根据传入的品类 `id`(`categoryId`)获取品类的详细信息, + * 返回一个 `ServerResponse` 对象,该对象中包含了请求结果的状态码、提示消息以及对应的品类详细数据内容, + * 方便其他服务获取品类的具体情况,比如在进行跨服务的数据交互、业务协作等场景下会用到这个接口来获取品类相关的详细信息。 + * + * @param categoryId 要获取详细信息的品类 `id`,通过这个 `id` 来查找并获取对应品类的详细数据,如品类的各种属性值等。 + * @return 返回 `ServerResponse` 对象,其中包含了品类详细信息以及操作结果状态等相关内容,供其他服务使用。 + */ ServerResponse getCategoryDetail(Integer categoryId); -} +} \ No newline at end of file diff --git a/snailmall-config-server/pom.xml b/snailmall-config-server/pom.xml index a91edfe..ec1fae1 100644 --- a/snailmall-config-server/pom.xml +++ b/snailmall-config-server/pom.xml @@ -42,6 +42,10 @@ org.springframework.cloud spring-cloud-config-monitor + + org.springframework.cloud + spring-cloud-commons + diff --git a/snailmall-config-server/src/main/java/com/njupt/swg/EnableConfigServer.java b/snailmall-config-server/src/main/java/com/njupt/swg/EnableConfigServer.java new file mode 100644 index 0000000..4302cc4 --- /dev/null +++ b/snailmall-config-server/src/main/java/com/njupt/swg/EnableConfigServer.java @@ -0,0 +1,4 @@ +package com.njupt.swg; + +public @interface EnableConfigServer { +} 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..3315bbd 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,38 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.config.server.EnableConfigServer; +/** + * 这个类名为 `SnailmallConfigServerApplication`,它是整个Spring Boot应用程序的启动类, + * 主要负责启动基于Spring Boot框架构建的应用,并进行一系列相关的配置和初始化工作,使得应用能够正常运行并对外提供相应的服务功能。 + * 该类通过几个关键的注解来声明应用的特性和启用相应的功能模块,是整个应用的入口点所在。 + */ @SpringBootApplication +// `@SpringBootApplication` 是一个组合注解,它整合了多个Spring Boot相关的注解,例如 `@Configuration`、`@EnableAutoConfiguration` 和 `@ComponentScan` 等。 +// `@Configuration` 表示这个类可以作为一个配置类,用于定义Spring容器中的各种Bean以及配置信息; +// `@EnableAutoConfiguration` 会根据项目中添加的依赖自动配置Spring应用上下文,比如自动配置数据源、Web相关组件等,使得开发者可以快速搭建应用而无需手动进行大量繁琐的配置; +// `@ComponentScan` 则用于指定Spring要扫描的包路径,以便自动发现并注册项目中的各种组件(如 `@Controller`、`@Service`、`@Repository` 等标注的类)到Spring容器中,方便进行依赖注入等操作。 +// 总的来说,`@SpringBootApplication` 注解让这个类具备了启动Spring Boot应用并自动配置相关组件的核心功能。 + @EnableDiscoveryClient +// `@EnableDiscoveryClient` 注解用于启用服务发现客户端功能,在微服务架构中,往往会有服务注册与发现中心(例如Eureka、Consul等), +// 各个微服务通过这个注解可以将自己注册到服务发现中心,并能从其中发现其他的服务实例,方便不同微服务之间进行相互调用和协作, +// 使得服务之间的通信更加灵活和可管理,基于服务名称就能进行服务查找和调用,而不用硬编码具体的服务地址等信息。 + @EnableConfigServer +// `@EnableConfigServer` 注解用于启用Spring Cloud配置服务器功能,它允许应用作为一个配置中心,集中管理各个微服务的配置文件, +// 其他微服务可以从这个配置服务器获取它们所需的配置信息,例如数据库连接配置、各种业务相关的参数配置等,实现了配置的集中化管理, +// 方便在多个微服务环境中统一维护和更新配置,提高配置管理的效率和灵活性。 + public class SnailmallConfigServerApplication { + /** + * `main` 方法是Java应用程序的入口点,在这里是整个Spring Boot应用启动的起点。 + * 通过调用 `SpringApplication.run` 方法,传入当前应用的主类(`SnailmallConfigServerApplication.class`)以及命令行参数(`args`), + * Spring Boot框架会启动应用,进行一系列的初始化操作,包括加载配置、创建Spring容器、扫描并注册组件、启动嵌入式服务器(如Tomcat等,如果是Web应用的话)等, + * 最终使得应用能够正常运行并对外提供相应的服务功能。 + */ public static void main(String[] args) { SpringApplication.run(SnailmallConfigServerApplication.class, args); } -} - +} \ No newline at end of file diff --git a/snailmall-product-service/pom.xml b/snailmall-product-service/pom.xml index e3c6cba..cf49683 100644 --- a/snailmall-product-service/pom.xml +++ b/snailmall-product-service/pom.xml @@ -1,5 +1,5 @@ - 4.0.0 @@ -122,6 +122,11 @@ commons-net commons-net + + org.projectlombok + lombok + provided + 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..ad1b626 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,26 +8,44 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** + * 这个类名为 `CommonCacheUtil`,它是一个工具类,主要用于与 Redis 缓存进行交互操作, + * 提供了缓存数据的存储、获取、设置过期时间以及删除缓存等常用功能,方便在项目中对需要缓存的数据进行管理, + * 通过依赖注入 `JedisPoolWrapper` 来获取 `JedisPool`,进而操作 Redis 缓存实例,同时利用了日志记录功能来记录操作过程中出现的异常情况, + * 并且在遇到异常时会抛出 `SnailmallException` 异常,便于统一的异常处理。 + * * @Author swg. * @Date 2019/1/1 15:03 * @CONTACT 317758022@qq.com * @DESC */ @Component +// `@Component` 注解是Spring框架提供的,用于将当前类标记为一个普通的Spring组件,意味着这个类会被Spring容器扫描并管理, +// 可以通过依赖注入的方式在其他类中使用该类的实例,在这里表明 `CommonCacheUtil` 是一个可被复用的组件,在整个项目的缓存相关操作中发挥作用。 + @Slf4j +// `@Slf4j` 是Lombok库提供的注解,用于自动生成一个名为 `log` 的SLF4J日志记录器,在类中可以方便地使用这个日志记录器记录各种级别的日志信息, +// 例如记录与 Redis 缓存交互过程中的关键步骤、出现的异常情况等,有助于后续的问题排查和系统监控。 public class CommonCacheUtil { @Autowired private JedisPoolWrapper jedisPoolWrapper; - + // 通过Spring的依赖注入(Dependency Injection)机制,使用 `@Autowired` 注解自动注入 `JedisPoolWrapper` 类型的实例, + // `JedisPoolWrapper` 应该是一个对 `JedisPool`(Jedis连接池,用于管理与Redis的连接)进行包装的类,通过它可以获取到 `JedisPool` 实例, + // 进而获取 `Jedis`(Redis客户端操作对象)来与 Redis 缓存进行各种操作,实现了对 Redis 连接资源的有效管理和复用。 /** * 缓存永久key + * 该方法用于将给定的键值对以永久存储的方式存入 Redis 缓存中,即将指定的 `key` 和对应的 `value` 保存到 Redis 里, + * 这里默认没有设置过期时间,意味着数据会一直存储在缓存中,直到手动删除或者 Redis 服务器出现相关存储策略调整等情况。 + * + * @param key 要存入缓存的键,在 Redis 中通过这个键来唯一标识对应的缓存值,是后续获取、操作该缓存数据的依据,通常具有唯一性。 + * @param value 要存入缓存的值,它可以是各种类型的数据转换后的字符串表示形式(因为 Redis 中数据基本以字符串形式存储), + * 例如序列化后的对象、简单的文本数据等,与传入的 `key` 对应,共同构成缓存中的一条数据记录。 */ public void cache(String key, String value) { try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis Jedis = pool.getResource()) { Jedis.select(0); Jedis.set(key, value); @@ -37,16 +55,26 @@ public class CommonCacheUtil { log.error("redis存值失败", e); throw new SnailmallException("redis报错"); } + // 首先尝试通过注入的 `jedisPoolWrapper` 获取 `JedisPool` 实例,如果获取到的 `JedisPool` 不为 `null`, + // 则从连接池中获取一个 `Jedis` 资源(这是与 Redis 进行交互的客户端对象),调用 `Jedis` 的 `select` 方法选择 Redis 的第 `0` 个数据库(默认数据库,Redis可以有多个数据库), + // 然后使用 `set` 方法将传入的 `key` 和 `value` 存入到 Redis 中。如果在这个过程中出现任何异常, + // 会通过日志记录器 `log` 记录错误信息(使用 `error` 级别日志记录,因为这属于操作失败的异常情况), + // 并抛出 `SnailmallException` 异常,告知调用方 Redis 操作出现报错,方便统一的异常处理以及后续排查问题。 } /** * 获取缓存key + * 用于从 Redis 缓存中根据指定的 `key` 获取对应的缓存值,如果缓存中存在该 `key`,则返回对应的 `value`, + * 如果不存在,则返回 `null`,使得调用者可以通过这个方法查询之前缓存的数据是否还存在以及获取其具体的值内容。 + * + * @param key 要获取缓存值的键,通过这个键在 Redis 中查找对应的缓存数据,必须是之前已经存入到 Redis 中的有效键,否则将返回 `null`。 + * @return 返回从 Redis 缓存中获取到的对应 `key` 的值,如果缓存中不存在该 `key`,则返回 `null`,方便调用者根据返回值进行后续处理。 */ public String getCacheValue(String key) { String value = null; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis Jedis = pool.getResource()) { Jedis.select(0); value = Jedis.get(key); @@ -57,16 +85,29 @@ public class CommonCacheUtil { throw new SnailmallException("redis报错"); } return value; + // 同样先尝试获取 `JedisPool` 实例,若不为 `null`,则获取 `Jedis` 资源并选择第 `0` 个 Redis 数据库, + // 接着使用 `Jedis` 的 `get` 方法根据传入的 `key` 去获取对应的缓存值,将获取到的值赋给局部变量 `value`, + // 如果在操作过程中出现异常,记录错误日志并抛出 `SnailmallException` 异常,最后将 `value`(可能为 `null` 或者获取到的实际缓存值)返回给调用者, + // 以便调用者知晓是否获取到了期望的缓存数据以及进行后续的业务处理。 } /** * 过期key + * 这个方法实现了向 Redis 缓存中存入一个带有过期时间的键值对的功能,首先会尝试使用 `setnx` 命令(设置键值对,只有键不存在时才设置成功)来设置缓存数据, + * 如果设置成功(即键不存在,返回值为 `1`),则接着使用 `expire` 命令为这个新设置的键设置过期时间,以达到缓存数据在一定时间后自动过期删除的效果, + * 最后返回 `setnx` 操作的结果(用于判断是否成功设置了键值对),方便调用者根据返回值了解操作情况。 + * + * @param key 要存入缓存的键,具有唯一性,用于在 Redis 中标识对应的缓存数据,并且后续通过这个键来判断数据是否过期等情况。 + * @param value 要存入缓存的值,与 `key` 对应,构成缓存中的一条数据记录,同样可以是各种类型数据转换后的字符串形式。 + * @param expire 要设置的过期时间,单位通常是秒,表示缓存数据在存入 Redis 后经过多少秒后会自动过期被删除,需要根据具体业务需求合理设置该值。 + * @return 返回 `setnx` 操作的结果,若返回值为 `1`,表示成功设置了键值对(因为键之前不存在),若返回 `0`,表示键已经存在,设置失败, + * 调用者可以根据这个返回值判断本次操作的情况并进行后续相应的处理。 */ public long cacheNxExpire(String key, String value, int expire) { long result = 0; try { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); result = jedis.setnx(key, value); @@ -79,14 +120,22 @@ public class CommonCacheUtil { } return result; + // 先获取 `JedisPool` 实例,若不为 `null`,获取 `Jedis` 资源并选择第 `0` 个数据库,然后使用 `jedis` 的 `setnx` 方法尝试设置键值对, + // 将返回结果(表示是否设置成功)赋给 `result` 变量,接着使用 `expire` 方法为刚设置的键设置指定的过期时间(`expire` 参数指定的秒数), + // 如果在整个过程中出现异常,记录错误日志并抛出 `SnailmallException` 异常,最后将 `result`(`setnx` 操作的结果)返回给调用者, + // 方便调用者知晓是否成功存入了带有过期时间的缓存数据以及进行后续处理。 } /** * 删除缓存key + * 用于从 Redis 缓存中删除指定的 `key` 及其对应的数据记录,使得对应的缓存数据不再存在于 Redis 中, + * 常用于当缓存数据需要更新或者不再需要时,手动删除缓存以保证数据的准确性和一致性等情况。 + * + * @param key 要删除的缓存键,必须是已经存在于 Redis 缓存中的有效键,否则删除操作将不会有实际效果(不会报错,但也不会删除任何数据)。 */ public void delKey(String key) { JedisPool pool = jedisPoolWrapper.getJedisPool(); - if (pool != null) { + if (pool!= null) { try (Jedis jedis = pool.getResource()) { jedis.select(0); try { @@ -97,8 +146,8 @@ public class CommonCacheUtil { } } } + // 先获取 `JedisPool` 实例,若不为 `null`,获取 `Jedis` 资源并选择第 `0` 个数据库,然后使用 `jedis` 的 `del` 方法尝试删除指定的 `key` 及其对应的数据, + // 如果在删除过程中出现异常,通过日志记录器记录错误信息,并抛出 `SnailmallException` 异常,告知调用方 Redis 删除操作出现报错, + // 方便统一的异常处理以及后续排查删除操作失败的原因等情况。 } - - - -} +} \ 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..8e7b2b6 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 @@ -9,34 +9,71 @@ import redis.clients.jedis.JedisPoolConfig; import javax.annotation.PostConstruct; /** + * 这个类名为 `JedisPoolWrapper`,它的主要作用是对 `JedisPool`(Jedis连接池,用于管理与 Redis 的连接)进行包装和初始化配置, + * 通过依赖注入获取相关配置参数,按照设定的规则创建 `JedisPool` 实例,以便在其他地方可以方便地获取到这个连接池对象,进而操作 Redis 缓存, + * 同时利用日志记录功能来记录初始化连接池过程中的成功或失败情况,虽然当前代码只实现了针对单个 Redis 的操作,但注释中提到课程中涉及 Redis 客户端集群以及一致性哈希算法相关内容,意味着后续可能有功能扩展的方向。 + * * @Author swg. * @Date 2019/1/1 15:00 * @CONTACT 317758022@qq.com * @DESC 只做了单个redis,但是课程中实现的redis客户端集群,要掌握一致性hash算法 */ @Component +// `@Component` 注解是Spring框架提供的,用于将当前类标记为一个普通的Spring组件,会被Spring容器扫描并管理, +// 意味着这个类的实例可以通过依赖注入的方式在其他类中使用,表明 `JedisPoolWrapper` 在整个项目的 Redis 连接管理方面起着重要作用,是可复用的一个组件。 + @Slf4j +// `@Slf4j` 是Lombok库提供的注解,用于自动生成一个名为 `log` 的SLF4J日志记录器,在类中可以方便地使用这个日志记录器记录各种级别的日志信息, +// 比如在这里用于记录 `JedisPool` 初始化过程中是成功还是出现异常等情况,便于后续的问题排查和系统监控。 public class JedisPoolWrapper { + @Autowired private Parameters parameters; + // 通过Spring的依赖注入(Dependency Injection)机制,使用 `@Autowired` 注解自动注入 `Parameters` 类型的实例, + // `Parameters` 应该是一个用于存储各种参数配置的类,在这里主要是期望从中获取与 Redis 连接相关的配置参数,例如最大连接数、最大空闲连接数、连接等待时间等信息, + // 为后续创建 `JedisPool` 实例提供必要的参数依据。 private JedisPool jedisPool = null; + // 定义一个私有变量 `jedisPool`,用于存储创建好的 `JedisPool` 实例,初始值设为 `null`,在 `init` 方法中会根据配置参数进行实例化操作, + // 后续其他类可以通过 `getJedisPool` 方法获取这个实例来与 Redis 进行连接和交互操作。 @PostConstruct - public void init(){ + public void init() { try { JedisPoolConfig config = new JedisPoolConfig(); + // 创建一个 `JedisPoolConfig` 对象,它用于配置 `JedisPool` 的各种属性,例如连接池的最大连接数、最大空闲连接数、获取连接的最大等待时间等, + // 这些属性的合理设置对于 Redis 连接的性能和资源管理非常重要。 + config.setMaxTotal(parameters.getRedisMaxTotal()); + // 通过注入的 `parameters` 对象获取 Redis 连接池的最大连接数配置参数,并设置到 `JedisPoolConfig` 实例中, + // `setMaxTotal` 方法用于指定连接池中允许存在的最大连接数量,避免创建过多连接导致资源浪费或者系统负担过重等问题。 + config.setMaxIdle(parameters.getRedisMaxIdle()); + // 获取 Redis 连接池的最大空闲连接数配置参数,并设置到 `JedisPoolConfig` 实例中, + // `setMaxIdle` 方法用于设定连接池中最大的空闲连接数量,即当连接池中空闲连接数超过这个值时,多余的空闲连接会被释放,有助于合理利用资源。 + config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis()); - jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx"); + // 获取 Redis 连接池的获取连接最大等待时间配置参数(单位为毫秒),并设置到 `JedisPoolConfig` 实例中, + // `setMaxWaitMillis` 方法定义了在获取连接时,如果连接池中没有可用连接,最长等待的时间,超过这个时间将会抛出异常, + // 可以避免长时间等待获取连接导致业务阻塞的情况发生。 + + jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx"); + // 使用配置好的 `JedisPoolConfig` 实例以及从 `parameters` 中获取的 Redis 主机地址(`parameters.getRedisHost()`)、端口号(`parameters.getRedisPort()`), + // 还有其他一些必要参数(这里超时时间设为 `2000` 毫秒,密码设为 `"xxx"`,实际中应根据真实配置填写)来创建 `JedisPool` 实例, + // 这个实例将负责管理与 Redis 的连接,后续可以从这个连接池中获取 `Jedis`(Redis客户端操作对象)来与 Redis 进行数据交互操作。 + log.info("【初始化redis连接池成功】"); - }catch (Exception e){ - log.error("【初始化redis连接池失败】",e); + // 如果 `JedisPool` 实例创建成功,通过日志记录器 `log` 记录一条信息日志,表示 Redis 连接池初始化成功,方便后续查看系统启动过程中相关组件的初始化情况。 + } catch (Exception e) { + log.error("【初始化redis连接池失败】", e); + // 如果在创建 `JedisPool` 实例的过程中出现任何异常,通过日志记录器 `log` 记录一条错误级别日志,详细记录异常信息(通过传入 `e` 参数), + // 方便后续排查初始化失败的原因,及时发现和解决与 Redis 连接池相关的问题。 } } public JedisPool getJedisPool() { return jedisPool; } -} + // 定义了一个公共的方法 `getJedisPool`,用于对外提供获取 `JedisPool` 实例的接口, + // 其他类可以调用这个方法来获取已经初始化好(或者初始化失败为 `null` 的情况)的 `JedisPool` 实例,进而利用这个实例从连接池中获取 `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..8f02a7c 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,57 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** + * 这个类名为 `Parameters`,它是一个用于存储项目中各种参数配置的实体类,在当前场景下主要聚焦于 Redis 相关的配置参数, + * 通过 Spring 的 `@Value` 注解从配置文件(例如 `application.properties` 或 `application.yml` 等)中读取对应配置项的值并注入到类的成员变量中, + * 同时借助 `@Component` 注解将自身注册为 Spring 容器中的一个组件,方便在其他需要使用这些参数的地方通过依赖注入获取该类实例来获取相应的参数值, + * 并且使用了 `@Data` 注解(由Lombok库提供)自动生成了常用的方法(如各成员变量的 `getter`、`setter` 方法等),简化了代码编写,提高了代码的可读性和可维护性。 + * * @Author swg. * @Date 2019/1/1 14:27 * @CONTACT 317758022@qq.com * @DESC */ @Component +// `@Component` 注解是Spring框架提供的,用于将当前类标记为一个普通的Spring组件,意味着这个类会被Spring容器扫描并管理, +// 使得其他类可以通过依赖注入的方式使用该类的实例,在这里表明 `Parameters` 类作为一个可复用的组件,为整个项目提供参数配置相关的服务, +// 特别是在涉及 Redis 连接配置等方面发挥作用。 + @Data +// `@Data` 注解由Lombok库提供,它会自动为类中的所有非静态、非 `final` 属性生成对应的 `getter` 和 `setter` 方法, +// 同时还会生成 `toString`、`equals` 和 `hashCode` 方法,避免了手动编写这些重复的、样板式的代码,让代码结构更加简洁清晰, +// 方便在其他地方获取和设置该类中定义的各个参数属性的值。 + public class Parameters { /*****redis config start*******/ @Value("${redis.host}") private String redisHost; + // `@Value` 注解用于将配置文件(通常是 `application.properties` 或 `application.yml` 等格式)中的属性值注入到对应的成员变量中。 + // 这里 `${redis.host}` 表示从配置文件中查找名为 `redis.host` 的配置项,将其值赋给 `redisHost` 变量, + // 该变量用于存储 Redis 服务器的主机地址,比如 `"localhost"` 或者具体的IP地址等,是连接 Redis 服务器必不可少的配置信息。 + @Value("${redis.port}") private int redisPort; + // 同样通过 `@Value` 注解从配置文件中获取名为 `redis.port` 的配置项的值,并赋给 `redisPort` 变量, + // 这个变量用于存储 Redis 服务器的端口号,常见的 Redis 默认端口是 `6379`,但也可以根据实际部署情况进行配置修改, + // 与 `redisHost` 一起确定了 Redis 服务器的网络地址,以便后续建立连接。 + @Value("${redis.max-idle}") private int redisMaxTotal; + // 此处存在一个可能的配置项命名混淆问题(按照语义推测可能应该是 `redis.max-total` 获取最大连接总数,不过以代码实际为准), + // 通过 `@Value` 注解从配置文件读取 `redis.max-idle` 配置项的值注入到 `redisMaxTotal` 变量,该变量用于定义 Redis 连接池相关的一个参数, + // 可能是用于控制连接池中某种数量相关的上限值(具体取决于项目对该参数的实际使用方式和含义设定)。 + @Value("${redis.max-total}") private int redisMaxIdle; + // 类似地,从配置文件中获取 `redis.max-total` 配置项的值赋给 `redisMaxIdle` 变量,同样是用于 Redis 连接池相关的参数配置, + // 按照常规理解,它可能是用于设定连接池中最大的空闲连接数量,不过具体含义还是要结合项目整体对该参数的使用逻辑来确定, + // 合理设置这些参数有助于优化 Redis 连接资源的管理和利用效率。 + @Value("${redis.max-wait-millis}") private int redisMaxWaitMillis; + // 通过 `@Value` 注解获取配置文件中 `redis.max-wait-millis` 配置项的值,并赋值给 `redisMaxWaitMillis` 变量, + // 这个变量用于设定在获取 Redis 连接时,如果连接池中没有可用连接,最长等待的时间(单位为毫秒), + // 超过这个时间将会抛出异常,以此避免长时间等待获取连接导致业务阻塞的情况发生,对系统的性能和稳定性有重要影响。 + /*****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..a84ca29 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,33 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** + * 这个接口名为 `CategoryClient`,它是基于Spring Cloud OpenFeign框架定义的一个客户端接口,用于与名为 `category-service` 的微服务进行交互通信。 + * 其主要作用是通过声明式的方式定义了对 `category-service` 中特定接口的远程调用方法,使得在当前服务中可以方便地调用其他微服务提供的功能, + * 就好像调用本地方法一样简洁,隐藏了底层的HTTP请求等复杂细节,提高了微服务之间协作的便利性和代码的可读性。 + * * @Author swg. * @Date 2019/1/3 16:56 * @CONTACT 317758022@qq.com * @DESC */ @FeignClient("category-service") +// `@FeignClient` 注解用于标识这是一个Feign客户端接口,括号中的 `"category-service"` 是要调用的目标微服务在服务注册与发现中心注册的名称, +// 通过这个名称,Feign会在运行时根据服务发现机制找到对应的微服务实例地址,并发起HTTP请求与之通信,实现跨微服务的调用功能。 + public interface CategoryClient { + @RequestMapping("/manage/category/get_category_detail.do") ServerResponse getCategoryDetail(@RequestParam("categoryId") Integer categoryId); + // 这个方法定义了对 `category-service` 微服务中 `/manage/category/get_category_detail.do` 接口的远程调用逻辑。 + // 它接收一个 `Integer` 类型的 `categoryId` 参数,通过 `@RequestParam` 注解将其绑定到请求参数中,名称为 `"categoryId"`, + // 表示向目标微服务请求获取指定 `categoryId` 的品类详细信息,返回的是一个 `ServerResponse` 类型的对象,该对象中包含了请求结果的状态码、提示消息以及对应的品类详细数据等信息, + // 调用者可以根据这个返回对象知晓远程调用的执行情况以及获取所需的数据内容。 @RequestMapping("/manage/category/get_deep_category.do") ServerResponse getDeepCategory(@RequestParam(value = "categoryId") Integer categoryId); -} + // 此方法定义了对 `category-service` 微服务中 `/manage/category/get_deep_category.do` 接口的远程调用逻辑。 + // 同样接收一个 `Integer` 类型的 `categoryId` 参数(通过 `@RequestParam` 注解绑定到请求参数中,默认参数名称就是 `"categoryId"`), + // 用于向目标微服务请求递归获取指定 `categoryId` 的品类及其所有子品类信息,返回的 `ServerResponse` 对象包含了请求结果状态码、提示消息以及相关的品类数据(如品类 `id` 列表等形式), + // 方便调用者获取完整的品类层级结构相关信息,以进行后续的业务处理等操作。 + +} \ 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..04c97d0 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,87 @@ 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` 常量定义了表示操作成功的状态码,值为 `200`,通常在请求处理成功,无任何错误且能够正常返回期望的数据时使用该状态码, + // 符合HTTP协议中常用的成功状态码规范,方便前端或者其他调用方根据这个状态码判断请求是否顺利完成并获取相应的数据。 public static final int RESP_STATUS_NOAUTH = 401; + // `RESP_STATUS_NOAUTH` 常量表示未授权的状态码,值为 `401`,当客户端发起的请求需要身份验证但未提供有效的认证信息, + // 或者认证信息过期、无效等情况时,服务端会返回这个状态码给客户端,告知其需要重新进行身份认证才能继续操作, + // 遵循了HTTP协议中关于未授权情况的状态码约定,有助于在涉及权限验证的业务场景中统一处理相关逻辑。 public static final int RESP_STATUS_INTERNAL_ERROR = 500; + // `RESP_STATUS_INTERNAL_ERROR` 常量定义了表示服务器内部错误的状态码,值为 `500`,当服务端在处理请求过程中发生了未预期的错误, + // 例如代码运行时出现异常、数据库操作失败等内部问题时,会返回这个状态码给客户端,提示客户端请求处理出现了服务器端的问题, + // 这也是符合HTTP协议中对于服务器内部错误情况的标准状态码设定,方便客户端知晓请求失败的大致原因类别。 public static final int RESP_STATUS_BADREQUEST = 400; + // `RESP_STATUS_BADREQUEST` 常量表示请求参数错误的状态码,值为 `400`,当客户端发送的请求中参数不符合要求, + // 比如缺少必要参数、参数格式错误、参数值超出范围等情况时,服务端会返回这个状态码给客户端,告知其请求的参数存在问题, + // 同样遵循了HTTP协议中关于请求参数错误情况的状态码规范,便于统一处理请求参数校验相关的业务逻辑。 - /**自定义状态码 end**/ - + /** + * 自定义状态码 end + */ - /** 产品的状态 **/ - public interface Product{ + /** + * 产品的状态 + * 以下通过内部接口(Java中的接口可以包含常量定义等)的形式定义了与产品状态相关的一组常量,用于表示产品在不同情况下的状态值, + * 在涉及产品业务逻辑的模块中,可以通过这些常量来清晰地标识产品所处的状态,提高代码的可读性和状态判断的准确性。 + */ + public interface Product { int PRODUCT_ON = 1; + // `PRODUCT_ON` 常量表示产品处于开启、可用、上线等正常状态,值为 `1`,在业务逻辑中可以通过判断产品的状态是否等于这个值, + // 来确定产品是否可以正常展示、销售或者参与其他业务操作,方便统一管理产品的可用状态逻辑。 + int PRODUCT_OFF = 2; + // `PRODUCT_OFF` 常量代表产品处于关闭、下架等不可用状态,值为 `2`,当产品需要暂停销售、暂时隐藏等情况下,可以将其状态设置为这个值, + // 使得在相关业务处理中能够依据这个状态值进行相应的处理,例如不展示该产品给用户等操作。 + int PRODUCT_DELETED = 3; + // `PRODUCT_DELETED` 常量用于表示产品已经被删除的状态,值为 `3`,当产品从系统中彻底删除后,可以用这个状态值来标记, + // 在涉及产品数据查询、展示等业务逻辑中,遇到这个状态值的产品记录可以进行相应的特殊处理(比如不返回给前端等),便于对产品的生命周期状态进行准确管理。 } - public interface ProductListOrderBy{ - Set PRICE_ASC_DESC = Sets.newHashSet("price_desc","price_asc"); + public interface ProductListOrderBy { + Set PRICE_ASC_DESC = Sets.newHashSet("price_desc", "price_asc"); + // `PRICE_ASC_DESC` 是一个 `Set` 类型的常量集合,通过Google Guava库的 `Sets.newHashSet` 方法创建,包含了两个字符串元素 `"price_desc"` 和 `"price_asc"`, + // 它用于表示产品列表按照价格进行排序的两种可能顺序,即价格降序(`price_desc`)和价格升序(`price_asc`), + // 在涉及产品列表展示并且需要按照价格排序的业务场景中,可以通过判断传入的排序参数是否在这个集合中来确定是否是合法的价格排序方式, + // 同时也方便统一管理产品列表排序相关的逻辑,确保只接受预定义的有效排序规则。 } - - /***redis product**/ + /*** + * redis product + * 以下定义了与Redis中存储产品相关信息的一组常量,用于在使用Redis作为缓存或者存储产品相关临时数据等场景下, + * 作为统一的键前缀或者其他配置参数,方便在不同的Redis操作代码中进行引用,确保相关的键名等配置具有一致性和规范性。 + */ public static final String PRODUCT_TOKEN_PREFIX = "product__"; + // `PRODUCT_TOKEN_PREFIX` 常量定义了在Redis中存储产品相关令牌(Token,可能用于标识产品相关的一些临时权限、验证信息等情况)时的键前缀, + // 后续在Redis中存储具体的产品令牌相关数据时,键名通常会以这个前缀开头,再加上具体的产品标识等信息,有助于对Redis中的产品相关键进行分类和管理, + // 方便快速定位和区分不同用途的Redis数据。 + public static final int PRODUCT_EXPIRE_TIME = 60 * 60 * 24 * 300; + // `PRODUCT_EXPIRE_TIME` 常量定义了产品相关数据在Redis中存储的过期时间,单位为秒,这里通过计算得出是一个较长的时间(大约为300天), + // 具体的过期时间设置需要根据业务需求来确定,例如产品的一些临时缓存信息多久更新一次等情况,通过统一设置这个常量, + // 可以在多处使用Redis存储产品数据的地方方便地应用相同的过期时间配置,保证数据的时效性和缓存的有效性。 public static final String PRODUCT_TOKEN_STOCK_PREFIX = "product__stock_"; - - -} + // `PRODUCT_TOKEN_STOCK_PREFIX` 常量定义了在Redis中存储产品库存相关令牌(Token,可能与产品库存验证、操作权限等相关)时的键前缀, + // 与 `PRODUCT_TOKEN_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..4502d81 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,51 @@ 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` 或者 `@RestController` 的控制器类中的方法进行增强处理, +// 在这里主要是用于全局异常处理,意味着可以捕获这些控制器方法在执行过程中抛出的异常,无论异常发生在哪个具体的控制器方法里,都能在这里进行统一的处理。 + @ResponseBody +// `@ResponseBody` 注解结合 `@ControllerAdvice` 使用,表示这个类中处理异常的方法返回的结果会直接作为响应体(通常是JSON等格式)返回给客户端, +// 而不需要经过视图解析等传统的MVC流程,符合现在大多数基于RESTful风格的Web服务开发中对异常响应处理的需求,能够直接将处理后的异常信息以合适的格式返回给调用方。 + @Slf4j +// `@Slf4j` 是Lombok库提供的注解,用于自动生成一个名为 `log` 的SLF4J日志记录器,在类中可以方便地使用这个日志记录器记录各种级别的日志信息, +// 在这里主要用于记录捕获到的异常信息,方便后续查看和分析系统出现异常的原因以及具体情况,有助于问题排查和系统监控。 + public class ExceptionHandlerAdvice { + @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); + // 首先使用日志记录器 `log` 的 `error` 方法记录异常的详细信息,包括异常消息(`e.getMessage()`)以及整个异常对象(`e`), + // 这样在查看日志文件时可以获取到完整的异常堆栈等情况,便于准确分析异常出现的原因和定位问题所在。 + + return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试"); + // 通过调用 `ServerResponse` 类的静态方法 `createByErrorCodeMessage` 创建一个表示错误的响应对象, + // 使用项目中定义的常量 `Constants.RESP_STATUS_INTERNAL_ERROR`(通常表示服务器内部错误的状态码,值为 `500`)作为错误状态码, + // 并传入提示消息 "系统异常,请稍后再试",将这个包含错误状态码和提示消息的响应对象返回给客户端,告知客户端系统出现了内部错误, + // 让用户知晓请求没有成功处理,需要稍后再次尝试,实现了对所有未被特定处理的普通异常(即 `Exception` 类型,它是所有异常的基类)的统一处理方式。 } @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); + // 同样先使用日志记录器记录 `SnailmallException` 类型异常的详细信息,方便后续排查该特定类型异常出现的原因以及具体情况。 -} + return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage()); + // 对于 `SnailmallException` 这种项目中自定义的特定异常类型,通过调用 `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..d8f46ce 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,38 @@ import com.njupt.swg.common.resp.ResponseEnum; import lombok.Getter; /** + * 这个类名为 `SnailmallException`,它是项目中自定义的一个异常类,继承自 `RuntimeException`,意味着它属于运行时异常, + * 在程序运行过程中如果出现符合其定义的错误情况时可以抛出该异常,方便对特定业务逻辑中的异常情况进行统一管理和处理, + * 同时通过 `@Getter` 注解自动生成了属性的 `getter` 方法,便于获取异常相关的状态码信息,并且提供了不同的构造函数来满足不同场景下创建异常对象的需求。 + * * @Author swg. * @Date 2019/1/1 13:18 * @CONTACT 317758022@qq.com * @DESC */ @Getter -public class SnailmallException extends RuntimeException{ +// `@Getter` 注解由Lombok库提供,它会自动为类中的所有非静态、非 `final` 属性生成对应的 `getter` 方法, +// 在这里就是为 `exceptionStatus` 属性生成 `get` 方法,使得在其他地方可以方便地获取该属性的值, +// 避免了手动编写 `getExceptionStatus` 这样的样板代码,让代码结构更加简洁清晰。 + +public class SnailmallException extends RuntimeException { private int exceptionStatus = ResponseEnum.ERROR.getCode(); + // 定义了一个私有整型变量 `exceptionStatus`,用于存储该异常对应的状态码,初始值设置为 `ResponseEnum.ERROR.getCode()`, + // 这里推测 `ResponseEnum` 是一个枚举类,用于定义各种响应状态相关的枚举值(比如错误码等),`ERROR` 应该是表示错误的一个枚举项, + // 通过获取其 `getCode` 方法的值来设定默认的异常状态码,意味着如果没有显式指定状态码,异常对象默认使用这个预定义的错误状态码值。 - public SnailmallException(String msg){ + public SnailmallException(String msg) { super(msg); } + // 这是一个构造函数,接收一个字符串类型的参数 `msg`,用于创建 `SnailmallException` 异常对象。 + // 它调用了父类(`RuntimeException`)的构造函数,并将传入的 `msg` 作为异常消息传递给父类, + // 这种方式适用于只需要传递异常消息,而使用默认异常状态码的情况,方便在业务逻辑中快速抛出该异常并携带相应的提示信息。 - public SnailmallException(int code,String msg){ + public SnailmallException(int code, String msg) { super(msg); exceptionStatus = code; } - -} + // 另一个构造函数,接收一个整型参数 `code` 和一个字符串参数 `msg`,同样先调用父类的构造函数将 `msg` 作为异常消息传递过去, + // 然后将传入的 `code` 参数赋值给 `exceptionStatus` 属性,用于在需要指定特定的异常状态码以及异常消息的场景下创建异常对象, + // 使得可以根据不同的业务错误情况,灵活地设置合适的异常状态码和对应的提示消息,方便在全局异常处理等环节根据状态码进行不同的响应处理。 +} \ 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..1074e47 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,48 @@ package com.njupt.swg.common.resp; import lombok.Getter; /** + * 这个枚举类名为 `ResponseEnum`,它主要用于定义项目中一些基本的返回状态描述,通过枚举的形式将不同的状态码及其对应的描述信息进行统一管理, + * 方便在整个项目中对各种操作结果的状态进行标准化表示,提高代码的可读性和可维护性,避免了在多处硬编码状态码和描述信息, + * 并且借助 `@Getter` 注解自动生成属性的 `getter` 方法,便于获取枚举项中定义的状态码和描述内容。 + * * @Author swg. * @Date 2018/12/31 20:15 * @CONTACT 317758022@qq.com * @DESC 基本的返回状态描述 */ @Getter +// `@Getter` 注解由Lombok库提供,它会自动为枚举类中的所有非静态、非 `final` 属性生成对应的 `getter` 方法, +// 在这里就是为 `code` 和 `desc` 属性生成 `get` 方法,使得在其他地方可以方便地获取每个枚举项对应的状态码和描述信息, +// 无需手动编写获取这些属性值的方法,使代码结构更加简洁清晰。 + public enum ResponseEnum { - SUCCESS(0,"SUCCESS"), - ERROR(1,"ERROR"), - ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), - NEED_LOGIN(10,"NEED_LOGIN"); + SUCCESS(0, "SUCCESS"), + // 定义了一个名为 `SUCCESS` 的枚举项,用于表示操作成功的状态。它的状态码为 `0`,对应的描述信息是 `"SUCCESS"`, + // 在项目中当某个操作顺利完成且符合预期时,可以使用这个枚举项来表示成功的返回状态,方便统一处理和判断操作是否成功。 + + ERROR(1, "ERROR"), + // `ERROR` 枚举项代表操作出现错误的状态,状态码设定为 `1`,描述为 `"ERROR"`, + // 当业务逻辑执行过程中发生了不符合预期的错误情况时,可以返回该枚举项对应的状态来告知调用方操作出现了问题, + // 后续可以根据这个状态码和描述信息进行相应的错误处理,比如在前端展示错误提示等操作。 + + ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"), + // 该枚举项 `ILLEGAL_ARGUMENTS` 用于表示传入的参数不合法的情况,状态码为 `2`,描述为 `"ILLEGAL_ARGUMENTS"`, + // 例如在接口接收到的请求参数不符合要求(如缺少必要参数、参数格式错误等)时,可以返回这个枚举项来表明参数存在问题, + // 便于统一进行参数校验相关的异常处理以及给调用方提供准确的错误提示信息。 + + NEED_LOGIN(10, "NEED_LOGIN"); + // `NEED_LOGIN` 枚举项表示需要用户登录的状态,状态码为 `10`,描述为 `"NEED_LOGIN"`, + // 当某个操作需要用户先进行身份认证登录才能继续执行,而用户未登录时,就可以返回这个枚举项对应的状态给客户端, + // 提示用户需要先登录系统,从而实现权限验证相关的业务逻辑处理和提示功能。 private int code; private String desc; - ResponseEnum(int code,String desc){ + ResponseEnum(int code, String desc) { this.code = code; this.desc = desc; } -} + // 这是枚举类的构造函数,用于初始化每个枚举项的状态码(`code`)和描述信息(`desc`)属性。 + // 每个枚举项在定义时(如 `SUCCESS(0, "SUCCESS")`)都会调用这个构造函数,传入对应的状态码和描述字符串, + // 来完成自身属性的初始化,确保每个枚举项都有正确的状态码和相应的描述内容,方便后续通过 `getter` 方法获取使用。 +} \ 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..7e4d26c 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 @@ -8,71 +8,133 @@ import lombok.NoArgsConstructor; import java.io.Serializable; /** + * 这个类名为 `ServerResponse`,它是本项目的通用返回封装类,用于对各种操作的返回结果进行统一的包装和规范表示, + * 包含了状态码、提示消息以及具体的数据内容等信息,方便在不同的业务逻辑处理后,以一种标准的格式返回给调用方(如前端界面或者其他服务调用者等), + * 同时借助了一些注解来控制JSON序列化的行为以及自动生成 `getter` 方法等,提高了代码的可读性和可维护性。 + * * @Author swg. * @Date 2018/12/31 20:11 * @CONTACT 317758022@qq.com * @DESC 作为本项目的通用的返回封装类 */ @Getter +// `@Getter` 注解由Lombok库提供,它会自动为类中的所有非静态、非 `final` 属性生成对应的 `getter` 方法, +// 在这里就是为 `status`、`msg` 和 `data` 属性生成 `get` 方法,使得在其他地方可以方便地获取这些属性的值, +// 避免了手动编写获取属性值的样板代码,让代码结构更加简洁清晰。 + @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +// `@JsonSerialize` 注解是由Jackson库提供的,用于控制对象在进行JSON序列化时的行为。 +// 这里 `include = JsonSerialize.Inclusion.NON_NULL` 的配置表示在序列化过程中,只会包含那些非 `null` 值的属性, +// 即如果某个属性的值为 `null`,在将 `ServerResponse` 对象转换为JSON格式时,该属性将不会出现在最终的JSON数据中, +// 这样可以减少不必要的数据传输,使得返回的JSON数据更加精简、高效,符合实际应用场景中对数据传输的优化需求。 + public class ServerResponse implements Serializable { private int status; + // 用于存储操作结果的状态码,通过不同的状态码值来表示操作是成功还是出现了某种类型的错误等情况, + // 其取值通常会参考项目中定义的相关枚举(如 `ResponseEnum`)来进行标准化设置,方便统一判断和处理不同的返回状态。 + private String msg; + // 存放与操作结果相关的提示消息,用于向调用方传达更详细的信息,比如操作成功时可以是简单的成功提示, + // 出现错误时则可以是具体的错误原因描述等,增强了返回结果的可读性和对调用方的友好性。 + private T data; + // 这是一个泛型属性,用于承载具体的业务数据内容,根据不同的业务操作,其类型可以是各种各样的,比如可以是一个实体对象、一个对象列表、一个简单的数据值等, + // 通过泛型的设计使得这个返回封装类能够灵活地适应不同业务场景下的数据返回需求。 - public ServerResponse(){} + public ServerResponse() { + } + // 默认构造函数,虽然这里没有具体的初始化逻辑,但在某些情况下(比如通过反射创建对象等场景), + // 拥有一个默认的无参构造函数是必要的,它可以满足一些框架或者特定代码逻辑对对象创建的要求。 - public ServerResponse(int status){ + public ServerResponse(int status) { this.status = status; } - public ServerResponse(int status,String msg){ + // 一个构造函数,接收一个 `int` 类型的状态码参数,用于初始化 `ServerResponse` 对象的 `status` 属性, + // 适用于只需要设置状态码,而提示消息和具体数据可以后续再进行设置或者根据默认情况处理的场景,方便在不同业务逻辑中根据具体情况灵活创建对象。 + + public ServerResponse(int status, String msg) { this.status = status; this.msg = msg; } - public ServerResponse(int status,T data){ + // 该构造函数接收状态码和提示消息两个参数,分别用于初始化 `status` 和 `msg` 属性, + // 当需要明确指定操作结果的状态码以及对应的提示消息时,可以使用这个构造函数来创建 `ServerResponse` 对象, + // 常用于返回特定的提示信息给调用方,告知其操作的结果情况。 + + public ServerResponse(int status, T data) { this.status = status; this.data = data; } - public ServerResponse(int status,String msg,T data){ + // 接收状态码和数据两个参数的构造函数,用于初始化 `status` 和 `data` 属性, + // 适用于操作成功且需要返回具体业务数据的场景,通过这个构造函数可以将操作成功的状态码和相应的数据一起封装到 `ServerResponse` 对象中返回给调用方。 + + public ServerResponse(int status, String msg, T data) { this.status = status; this.msg = msg; this.data = data; } + // 全参数构造函数,接收状态码、提示消息和数据三个参数,用于完整地初始化 `ServerResponse` 对象的所有属性, + // 在需要明确指定所有返回信息的情况下使用,比如在某些复杂业务逻辑中,根据不同的条件要返回特定的状态码、详细的提示消息以及具体的数据内容时,就可以调用这个构造函数来创建对象。 @JsonIgnore - public boolean isSuccess(){ + public boolean isSuccess() { return this.status == ResponseEnum.SUCCESS.getCode(); } + // 定义了一个使用 `@JsonIgnore` 注解标注的方法,`@JsonIgnore` 表示在JSON序列化时忽略这个方法(即不会将这个方法的返回值作为JSON数据的一部分进行序列化)。 + // 这个方法用于判断当前 `ServerResponse` 对象表示的操作结果是否成功,通过比较对象的 `status` 属性值与 `ResponseEnum.SUCCESS` 枚举项对应的状态码(通常表示成功的状态码)是否相等来确定, + // 方便在调用方接收到返回对象后,快速判断操作是否成功,进而进行相应的后续处理,比如根据成功与否决定是否提取数据进行展示等操作。 /** * 成功的方法 + * 以下是一组静态方法,用于方便快捷地创建表示操作成功的 `ServerResponse` 对象,通过不同的重载形式, + * 可以根据具体的业务需求选择合适的方式来创建并返回成功状态的响应对象,提高了代码的简洁性和创建成功响应的便利性。 */ - 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` 对象,使用 `ResponseEnum.SUCCESS` 枚举项对应的状态码和描述信息来初始化对象的 `status` 和 `msg` 属性, + // 适用于简单的成功情况,不需要返回具体业务数据时,直接返回一个标准的成功提示响应给调用方。 + + 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` 对象,使用 `ResponseEnum.SUCCESS` 枚举项对应的状态码作为 `status` 属性值, + // 并将传入的自定义消息 `message` 作为 `msg` 属性值,用于在操作成功且需要返回特定的自定义提示消息给调用方的场景下,方便地创建响应对象。 + + 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` 对象,将 `ResponseEnum.SUCCESS` 枚举项对应的状态码设置为 `status` 属性值, + // 并把传入的业务数据 `data` 赋给 `data` 属性,适用于操作成功且需要返回具体业务数据给调用方的场景,方便快速封装并返回成功响应。 + + public static ServerResponse createBySuccess(String message, T data) { + return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data); } + // 创建一个表示操作成功的 `ServerResponse` 对象,使用 `ResponseEnum.SUCCESS` 枚举项对应的状态码作为 `status` 属性值, + // 传入的自定义消息 `message` 作为 `msg` 属性值,同时将业务数据 `data` 赋给 `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() { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc()); } - public static ServerResponse createByErrorCodeMessage(int code,String msg){ - return new ServerResponse<>(code,msg); - } - + // 创建一个表示操作失败的 `ServerResponse` 对象,使用 `ResponseEnum.ERROR` 枚举项对应的状态码和描述信息来初始化对象的 `status` 和 `msg` 属性, + // 适用于通用的、没有特定错误消息的失败情况,返回一个标准的表示失败的提示响应给调用方。 + public static ServerResponse createByErrorMessage(String msg) { + return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg); + } + // 创建一个表示操作失败的 `ServerResponse` 对象,将 `ResponseEnum.ERROR` 枚举项对应的状态码作为 `status` 属性值, + // 并把传入的自定义错误消息 `msg` 作为 `msg` 属性值,用于在操作失败且需要返回特定的自定义错误消息给调用方的场景下,方便地创建响应对象。 -} + public static ServerResponse createByErrorCodeMessage(int code, String msg) { + return new ServerResponse<>(code, msg); + } + // 创建一个表示操作失败的 `ServerResponse` 对象,使用传入的自定义状态码 `code` 作为 `status` 属性值, + // 传入的自定义错误消息 `msg` 作为 `msg` 属性值,适用于需要返回特定的错误码和对应错误消息的失败场景,比如根据不同业务逻辑中的特定错误情况, + // 灵活地创建并返回相应的失败响应对象给调用方,方便调用方根据具体的错误码和消息进行相应的处理。 +} \ 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..1a923c6 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,73 +2,143 @@ package com.njupt.swg.common.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; - import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * cookie读写 + * 这个类名为 `CookieUtil`,它是一个工具类,主要用于处理与 `Cookie` 相关的操作,包括在用户登录时写入 `Cookie`、读取登录相关的 `Cookie` 值以及在注销时删除对应的 `Cookie`, + * 通过设置合理的 `Cookie` 属性来保障其安全性和有效性,同时利用日志记录功能来记录 `Cookie` 操作过程中的关键信息,方便后续排查问题以及进行相关的监控,在涉及用户登录态管理等场景下发挥重要作用。 + * + * @Slf4j 注解用于自动生成一个名为 `log` 的 `SLF4J` 日志记录器,使得在类中可以方便地使用这个日志记录器记录各种级别的日志信息,比如记录 `Cookie` 的读写、删除等操作情况。 */ @Slf4j public class CookieUtil { + private final static String COOKIE_DOMAIN = "oursnail.cn"; - private final static String COOKIE_NAME = "snailmall_login_token"; + // 定义了一个私有静态常量 `COOKIE_DOMAIN`,用于指定 `Cookie` 的作用域域名,即这个 `Cookie` 在哪个域名下是有效的, + // 在这里设置为 `"oursnail.cn"`,意味着该 `Cookie` 只会在这个域名及其子域名下可以被发送和接收,限制了 `Cookie` 的使用范围,提高安全性和数据的针对性。 + private final static String COOKIE_NAME = "snailmall_login_token"; + // 定义了一个私有静态常量 `COOKIE_NAME`,它表示 `Cookie` 的名称,在这里特定用于存储与登录相关的令牌(`token`)信息, + // 通过这个固定的名称,可以在后续的 `Cookie` 操作中准确地识别出需要处理的登录相关 `Cookie`,便于统一管理登录态。 /** * 登陆的时候写入cookie - * @param response - * @param token + * 这个方法用于在用户登录成功时,向客户端(浏览器)写入一个包含登录令牌(`token`)信息的 `Cookie`, + * 通过设置 `Cookie` 的各项属性来确保其能正确地存储在客户端并且在后续请求中按照期望的方式被发送回服务器,以维持用户的登录状态。 + * + * @param response `HttpServletResponse` 对象,用于向客户端发送响应,在这里主要是通过它来添加要写入客户端的 `Cookie`, + * 服务器通过操作这个对象来设置响应头中的 `Cookie` 相关信息,使得客户端能够接收并保存 `Cookie`。 + * @param token 要写入 `Cookie` 的登录令牌(`token`)内容,通常是经过加密、编码等处理后的字符串, + * 它代表了用户的登录身份验证信息,后续客户端每次发送请求时携带这个 `Cookie`,服务器就能识别用户是否已经登录等情况。 */ - public static void writeLoginToken(HttpServletResponse response,String token){ - Cookie ck = new Cookie(COOKIE_NAME,token); + public static void writeLoginToken(HttpServletResponse response, String token) { + Cookie ck = new Cookie(COOKIE_NAME, token); + // 创建一个新的 `Cookie` 对象,使用预定义的 `COOKIE_NAME` 作为 `Cookie` 的名称,传入的 `token` 作为 `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` 只会在指定的域名下生效, + // 避免在不相关的域名中被错误地发送和接收,增强了 `Cookie` 使用的安全性和针对性。 + + ck.setPath("/"); + // 设置 `Cookie` 的路径为根目录(`/`),表示这个 `Cookie` 在整个域名下的所有路径(页面)中都有效, + // 如果设置了具体的子路径,那么 `Cookie` 就只在该子路径及其子路径下的页面请求中才会被发送,这里设为根目录使得其通用性更强, + // 只要是在该域名下访问页面,都会携带这个 `Cookie`,方便服务器在不同页面间通过这个 `Cookie` 来识别用户登录状态。 + + ck.setHttpOnly(true); + // 将 `Cookie` 设置为 `HttpOnly` 模式,这意味着通过客户端脚本(如JavaScript)是无法访问这个 `Cookie` 的, + // 主要是为了防止跨站脚本攻击(XSS攻击),恶意脚本无法获取到这个包含敏感信息(登录令牌)的 `Cookie`,从而提高了系统的安全性。 + + ck.setMaxAge(60 * 60 * 24 * 365); + // 设置 `Cookie` 的最大存活时间,这里设置为一年(单位是秒,通过计算得出大约一年的秒数),表示这个 `Cookie` 在客户端保存的时长, + // 超过这个时间后,客户端会自动删除这个 `Cookie`,如果设置为 `-1` 则表示永久有效,若不设置 `maxAge` 属性, + // `Cookie` 就不会写入硬盘,只会临时存放在内存中,并且只在当前页面有效,下次访问其他页面就不会再携带了,所以这里根据业务需求设置了较长的有效期来维持登录状态。 + + log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue()); + // 使用日志记录器 `log` 记录一条信息日志,记录当前写入的 `Cookie` 的名称和值,方便后续查看 `Cookie` 写入操作的具体情况, + // 比如排查是否写入了正确的 `Cookie` 信息、是否按照预期设置了属性等问题,有助于系统的监控和维护。 + response.addCookie(ck); + // 通过 `HttpServletResponse` 对象的 `addCookie` 方法,将设置好属性的 `Cookie` 添加到响应中,使得客户端能够接收到这个 `Cookie` 并保存起来, + // 从而完成在用户登录时向客户端写入登录相关 `Cookie` 的操作。 } /** * 读取登陆的cookie - * @param request - * @return + * 此方法用于从客户端发送过来的 `Cookie` 集合中读取名称为 `COOKIE_NAME`(即与登录相关的)的 `Cookie` 值, + * 如果找到了对应的 `Cookie`,则返回其值(也就是登录令牌 `token`),若没找到则返回 `null`,方便后续根据获取到的令牌来验证用户登录状态等操作。 + * + * @param request `HttpServletRequest` 对象,它包含了客户端发送给服务器的所有请求信息, + * 在这里主要是通过它来获取客户端发送过来的 `Cookie` 数组,以便从中查找我们需要的登录相关 `Cookie`。 + * @return 返回从 `Cookie` 中读取到的登录令牌(`token`)值,如果没找到对应的 `Cookie`,则返回 `null`, + * 调用者可以根据返回值判断是否获取到了有效的登录令牌,进而进行后续的登录态验证等相关业务处理。 */ - 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` 的传递情况。 + + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { + log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); return ck.getValue(); } + // 通过 `StringUtils` 的 `equals` 方法(来自 `Apache Commons Lang3` 库,用于避免 `null` 值导致的空指针异常等情况,更安全地比较字符串), + // 比较当前 `Cookie` 的名称是否与预定义的 `COOKIE_NAME`(登录相关 `Cookie` 的名称)相等,如果相等,则说明找到了我们要的登录相关 `Cookie`, + // 此时记录下这个 `Cookie` 的名称和值,并返回其值(也就是登录令牌 `token`)给调用者,用于后续的登录态验证等操作。 } } return null; + // 如果遍历完所有的 `Cookie` 都没有找到名称为 `COOKIE_NAME` 的 `Cookie`,则返回 `null`,表示没有获取到有效的登录令牌, + // 调用者可以根据这个返回值知晓当前请求中不存在对应的登录 `Cookie`,进而进行相应的业务处理,比如提示用户重新登录等操作。 } /** * 注销的时候进行删除 - * @param request - * @param response + * 该方法用于在用户执行注销操作时,从客户端删除之前写入的与登录相关的 `Cookie`, + * 通过设置 `Cookie` 的相关属性(主要是将最大存活时间设置为 `0`),使得客户端接收到这个更新后的 `Cookie` 后会自动将其删除,从而清除用户的登录状态。 + * + * @param request `HttpServletRequest` 对象,用于获取客户端发送过来的 `Cookie` 数组,从中查找需要删除的登录相关 `Cookie`, + * 以便后续对其进行属性修改并重新发送给客户端,实现删除的效果。 + * @param response `HttpServletResponse` 对象,通过它来将修改属性后的 `Cookie`(最大存活时间设置为 `0`)添加到响应中, + * 发送给客户端,让客户端按照新的 `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)){ + // 首先获取客户端发送过来的所有 `Cookie`,为后续查找和处理登录相关 `Cookie` 做准备,与前面读取 `Cookie` 的操作类似,需要对可能为 `null` 的情况进行判断。 + + if (cks!= null) { + for (Cookie ck : cks) { + if (StringUtils.equals(ck.getName(), COOKIE_NAME)) { ck.setDomain(COOKIE_DOMAIN); + // 重新设置 `Cookie` 的作用域域名,确保与之前写入 `Cookie` 时设置的域名一致,使得客户端能准确识别这个 `Cookie`, + // 虽然在一般情况下如果域名没有变化可以不重新设置,但这里为了保证操作的严谨性和准确性,还是进行了设置操作。 + ck.setPath("/"); - ck.setMaxAge(0);//0表示消除此cookie - log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); + // 同样重新设置 `Cookie` 的路径为根目录,保证其在整个域名下的所有路径中都能按照期望被处理, + // 与写入 `Cookie` 时的路径设置保持一致,确保删除操作的有效性和通用性。 + + ck.setMaxAge(0); + // 将 `Cookie` 的最大存活时间设置为 `0`,这是关键的一步,当客户端接收到这个 `Cookie` 并且发现 `maxAge` 为 `0` 时, + // 就会自动从本地删除这个 `Cookie`,从而实现了在注销时清除登录相关 `Cookie` 的目的,达到清除用户登录状态的效果。 + + log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue()); + // 使用日志记录器记录当前要删除的 `Cookie` 的名称和值,方便后续查看注销操作时具体删除了哪个 `Cookie`, + // 有助于排查注销功能是否正常执行、是否删除了正确的 `Cookie` 等问题,便于系统的监控和维护。 + response.addCookie(ck); + // 通过 `HttpServletResponse` 对象的 `addCookie` 方法,将修改好属性(主要是设置 `maxAge` 为 `0`)的 `Cookie` 添加到响应中发送给客户端, + // 让客户端根据新的 `Cookie` 属性执行删除操作,完成注销时删除登录相关 `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..4adb304 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,163 @@ 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; /** + * 这个类名为 `DateTimeUtil`,它是一个时间转换相关的工具类,主要用于在字符串与 `Date` 类型之间进行相互转换,以及将 `Date` 类型转换为时间戳, + * 通过使用 `Joda-Time` 库来实现更方便、灵活的日期和时间处理操作,同时提供了不同格式要求下的转换方法,有助于在项目中统一处理时间相关的数据格式转换需求, + * 并且在代码中还包含了一个简单的 `main` 方法用于测试某个时间转换功能,方便验证代码的正确性。 + * * @DESC 时间转换的工具类 */ public class DateTimeUtil { - //joda-time - - //str->Date - //Date->str - public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + // joda-time + // 这里的注释表明代码中使用了 `Joda-Time` 这个日期和时间处理的第三方库,它提供了比Java标准库中 `Date`、`Calendar` 等相关类更易用、功能更丰富的时间操作方法, + // 方便在项目中进行各种复杂的日期和时间格式转换、计算等操作。 + // str->Date + // Date->str + public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + // 定义了一个公共静态常量 `STANDARD_FORMAT`,用于表示一种标准的日期时间格式字符串,即年 - 月 - 日 时:分:秒的格式, + // 在没有指定其他格式的情况下,类中的一些方法会默认使用这个格式来进行字符串与 `Date` 类型之间的转换,方便统一时间格式标准,避免混乱。 - public static Date strToDate(String dateTimeStr, String formatStr){ + /** + * 将指定格式的字符串转换为 `Date` 类型 + * 这个方法用于把一个符合特定格式的日期时间字符串转换为Java中的 `Date` 类型对象,通过 `Joda-Time` 库的相关类和方法来实现转换操作, + * 使得可以在后续的业务逻辑中以 `Date` 类型来处理日期时间数据,例如进行日期比较、计算时间间隔等操作。 + * + * @param dateTimeStr 要转换的日期时间字符串,其格式需要与传入的 `formatStr` 参数指定的格式一致,否则会导致解析失败, + * 例如传入 `"2024-12-17 10:30:00"` 这样符合对应格式要求的字符串才能正确转换。 + * @param formatStr 用于指定 `dateTimeStr` 字符串的日期时间格式,比如 `"yyyy-MM-dd HH:mm:ss"`、`"yyyy-MM-dd"` 等不同的格式模式, + * 需根据实际传入的字符串格式进行准确设置,这样 `Joda-Time` 库才能按照正确的规则解析字符串为 `Date` 类型。 + * @return 返回解析后的 `Date` 类型对象,如果字符串格式不符合要求或者解析出现问题,可能会抛出相应的异常(由 `Joda-Time` 库内部处理机制决定), + * 调用者可以根据业务需求对可能出现的异常进行捕获和处理,得到转换后的 `Date` 对象后就能在Java中以标准的日期时间对象形式使用该数据了。 + */ + public static Date strToDate(String dateTimeStr, String formatStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr); + // 通过 `Joda-Time` 库的 `DateTimeFormat` 类的 `forPattern` 方法,根据传入的 `formatStr` 参数创建一个 `DateTimeFormatter` 对象, + // 这个对象用于定义如何解析给定格式的日期时间字符串,它知道按照什么样的模式(比如年、月、日等各部分的位置和格式要求)去解析输入的字符串。 + DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); + // 使用创建好的 `DateTimeFormatter` 对象的 `parseDateTime` 方法,对传入的 `dateTimeStr` 字符串进行解析,将其转换为 `Joda-Time` 库中的 `DateTime` 类型对象, + // `DateTime` 类型在 `Joda-Time` 库中是一个功能强大、便于操作的日期时间表示形式,它提供了很多方便的日期时间计算、比较等方法。 + return dateTime.toDate(); + // 最后通过 `DateTime` 对象的 `toDate` 方法,将 `Joda-Time` 库中的 `DateTime` 类型转换为Java标准库中的 `Date` 类型对象, + // 这样就完成了从特定格式字符串到 `Date` 类型的转换,返回的 `Date` 对象可以在Java代码的其他地方按照标准的日期时间对象进行使用,比如存入数据库、进行日期比较等操作。 } - public static String dateToStr(Date date,String formatStr){ - if(date == null){ + /** + * 将 `Date` 类型转换为指定格式的字符串 + * 此方法用于把一个 `Date` 类型的日期时间对象转换为指定格式的字符串表示形式,方便在界面展示、数据传输等场景下以字符串形式呈现日期时间信息, + * 并且当传入的 `Date` 对象为 `null` 时,会返回一个空字符串,避免出现 `null` 值导致的异常等情况,提高了方法的健壮性。 + * + * @param date 要转换的 `Date` 类型的日期时间对象,如果为 `null`,则按照逻辑会返回一个空字符串, + * @param formatStr 用于指定转换后字符串的日期时间格式,同样可以是诸如 `"yyyy-MM-dd HH:mm:ss"`、`"yyyy-MM-dd"` 等不同的格式模式, + * 根据实际业务需求设置想要的字符串格式,使得转换后的字符串符合展示或者传输等要求。 + * @return 返回转换后的日期时间字符串,如果传入的 `Date` 对象为 `null`,则返回 `StringUtils.EMPTY`(即空字符串), + * 否则返回按照指定格式转换后的日期时间字符串,调用者可以直接使用这个字符串进行后续的操作,比如展示给用户、发送到其他系统等。 + */ + public static String dateToStr(Date date, String formatStr) { + if (date == null) { return StringUtils.EMPTY; } + // 首先判断传入的 `Date` 对象是否为 `null`,如果是 `null`,则直接返回一个空字符串,避免后续操作出现空指针异常等问题, + // 这样调用者在使用返回的字符串时无需额外担心 `null` 值的情况,可以更方便地进行处理,比如直接用于拼接其他字符串等操作。 + DateTime dateTime = new DateTime(date); + // 如果传入的 `Date` 对象不为 `null`,则使用 `Joda-Time` 库的 `DateTime` 类的构造函数,将Java标准库中的 `Date` 类型对象转换为 `Joda-Time` 库中的 `DateTime` 类型对象, + // 以便后续利用 `Joda-Time` 库提供的更丰富的日期时间格式化功能进行操作。 + return dateTime.toString(formatStr); + // 通过 `DateTime` 对象的 `toString` 方法,按照传入的 `formatStr` 指定的格式,将 `DateTime` 类型对象转换为对应的字符串表示形式, + // 最终返回这个格式化后的日期时间字符串,使得调用者可以得到符合期望格式的字符串用于各种业务场景下的展示、传输等操作。 } - //固定好格式 - public static Date strToDate(String dateTimeStr){ + /** + * 将符合标准格式的字符串转换为 `Date` 类型(使用默认标准格式) + * 这个方法是 `strToDate(String dateTimeStr, String formatStr)` 方法的一个重载形式,它默认使用类中定义的 `STANDARD_FORMAT`(即 `"yyyy-MM-dd HH:mm:ss"`)格式, + * 对传入的日期时间字符串进行解析转换为 `Date` 类型对象,方便在大多数情况下按照统一的标准格式进行字符串到 `Date` 类型的转换,无需每次都传入格式字符串参数。 + * + * @param dateTimeStr 要转换的日期时间字符串,其格式需要符合默认的 `STANDARD_FORMAT`(`"yyyy-MM-dd HH:mm:ss"`)要求, + * 例如 `"2024-12-17 14:20:00"` 这样的字符串才能被正确解析转换为 `Date` 类型对象。 + * @return 返回解析后的 `Date` 类型对象,如果字符串格式不符合要求或者解析出现问题,可能会抛出相应的异常(由 `Joda-Time` 库内部处理机制决定), + * 调用者可以根据业务需求对可能出现的异常进行捕获和处理,得到转换后的 `Date` 对象后就能在Java中以标准的日期时间对象形式使用该数据了。 + */ + public static Date strToDate(String dateTimeStr) { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT); + // 创建一个 `DateTimeFormatter` 对象,使用类中预定义的 `STANDARD_FORMAT` 作为日期时间格式模式, + // 这样这个 `DateTimeFormatter` 就知道按照 `"yyyy-MM-dd HH:mm:ss"` 的格式规则去解析输入的字符串了。 + DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); + // 利用创建好的 `DateTimeFormatter` 对象的 `parseDateTime` 方法,对传入的 `dateTimeStr` 字符串进行解析,将其转换为 `Joda-Time` 库中的 `DateTime` 类型对象。 + return dateTime.toDate(); + // 最后将 `DateTime` 类型对象转换为Java标准库中的 `Date` 类型对象并返回,完成按照默认标准格式将字符串转换为 `Date` 类型的操作。 } - public static String dateToStr(Date date){ - if(date == null){ + /** + * 将 `Date` 类型转换为标准格式的字符串(使用默认标准格式) + * 这是 `dateToStr(Date date, String formatStr)` 方法的一个重载形式,它默认使用类中定义的 `STANDARD_FORMAT`(`"yyyy-MM-dd HH:mm:ss"`)格式, + * 把传入的 `Date` 类型对象转换为对应的字符串表示形式,方便在大多数情况下按照统一的标准格式输出日期时间字符串,无需每次都指定格式字符串参数。 + * + * @param date 要转换的 `Date` 类型的日期时间对象,如果为 `null`,则按照逻辑会返回一个空字符串, + * @return 返回按照默认 `STANDARD_FORMAT` 格式转换后的日期时间字符串,如果传入的 `Date` 对象为 `null`,则返回 `StringUtils.EMPTY`(即空字符串), + * 否则返回格式化后的日期时间字符串,调用者可以直接使用这个字符串进行后续的操作,比如展示给用户、发送到其他系统等。 + */ + public static String dateToStr(Date date) { + if (date == null) { return StringUtils.EMPTY; } + // 首先判断传入的 `Date` 类型对象是否为 `null`,如果是 `null`,则直接返回一个空字符串,避免后续操作出现空指针异常等问题。 + DateTime dateTime = new DateTime(date); + // 如果传入的 `Date` 对象不为 `null`,则将其转换为 `Joda-Time` 库中的 `DateTime` 类型对象,以便利用 `Joda-Time` 库的格式化功能进行操作。 + return dateTime.toString(STANDARD_FORMAT); + // 通过 `DateTime` 对象的 `toString` 方法,按照类中预定义的 `STANDARD_FORMAT`(`"yyyy-MM-dd HH:mm:ss"`)格式,将 `DateTime` 类型对象转换为对应的字符串表示形式并返回, + // 使得调用者可以得到符合默认标准格式的字符串用于各种业务场景下的展示、传输等操作。 } - //Date -> 时间戳 + /** + * 将 `Date` 类型转换为时间戳 + * 此方法用于把一个 `Date` 类型的日期时间对象转换为时间戳(从1970年1月1日0时0分0秒到指定日期时间的毫秒数), + * 在一些需要以时间戳形式记录、比较或者传递日期时间信息的场景下会用到这个转换操作,比如与某些系统进行时间相关数据交互时,对方要求以时间戳形式提供数据。 + * + * @param date 要转换的 `Date` 类型的日期时间对象,如果为 `null`,则返回 `null`,避免出现空指针异常等问题, + * @return 返回对应的时间戳数值(类型为 `Long`),表示从1970年1月1日0时0分0秒到传入的 `Date` 对象所代表的日期时间的毫秒数, + * 如果传入的 `Date` 对象为 `null`,则返回 `null`,调用者可以根据返回值判断是否成功获取到时间戳以及进行后续相应的业务处理, + * 注意这个方法内部可能会抛出 `ParseException` 异常(由 `SimpleDateFormat` 的解析机制决定),调用者需要根据业务需求进行异常捕获和处理。 + * @throws ParseException 这个方法声明了可能抛出 `ParseException` 异常,因为在内部使用 `SimpleDateFormat` 进行日期时间字符串解析时,如果格式不匹配等情况可能会导致解析失败并抛出此异常, + * 调用者在使用这个方法时需要考虑对该异常进行合适的处理,比如捕获并记录错误日志、给用户提示等操作。 + */ public static Long dateToChuo(Date date) throws ParseException { - if(date == null){ + if (date == null) { return null; } - SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); + // 首先判断传入的 `Date` 类型对象是否为 `null`,如果是 `null`,则直接返回 `null`,避免后续操作出现空指针异常以及对无效数据进行不必要的处理。 + + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 创建一个 `SimpleDateFormat` 对象,使用 `"yyyy-MM-dd HH:mm:ss"` 格式(与前面定义的 `STANDARD_FORMAT` 一致), + // 这个对象用于将 `Date` 类型对象转换为对应的日期时间字符串,以便后续获取其时间戳数值。 + return format.parse(String.valueOf(date)).getTime(); + // 先通过 `format` 对象的 `parse` 方法,将传入的 `Date` 类型对象转换为对应的日期时间字符串(这里先通过 `String.valueOf` 将 `Date` 对象转换为字符串形式), + // 然后调用 `parse` 方法解析这个字符串并返回一个 `Date` 类型对象(这里其实就是原传入的 `Date` 对象,只是经过了一次格式化和解析的过程), + // 最后通过 `getTime` 方法获取这个 `Date` 类型对象对应的时间戳数值(即从1970年1月1日0时0分0秒到该日期时间的毫秒数)并返回,完成 `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()); + // 这是一个简单的 `main` 方法,用于对代码中的时间转换功能进行测试验证,在这里主要测试了将一个指定格式的日期时间字符串转换为 `Date` 类型, + // 然后获取其时间戳并输出的功能,通过创建 `SimpleDateFormat` 对象并指定格式,使用 `parse` 方法解析传入的字符串为 `Date` 类型对象, + // 最后通过 `getTime` 方法获取时间戳并输出,方便开发人员在本地快速验证 `dateToChuo` 等相关时间转换方法的逻辑是否正确, + // 虽然这只是一个简单的测试示例,但可以在一定程度上帮助检查代码的功能是否符合预期,在实际开发中可以根据更多的测试用例来全面验证工具类的功能完整性和正确性。 } -} +} \ 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..c39cefd 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 @@ -3,7 +3,6 @@ package com.njupt.swg.common.utils; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.net.ftp.FTPClient; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -12,80 +11,182 @@ import java.util.List; /** * @Author 【swg】. * @Date 2018/1/11 14:32 - * @DESC + * @DESC 该类用于实现与FTP服务器进行交互的相关功能,主要聚焦在将本地文件上传至FTP服务器,提供了相应的工具方法,便于在项目中进行文件传输操作。 * @CONTACT 317758022@qq.com */ @Data +// 通过Lombok的@Data注解,自动为类中的非静态、非final属性(如ip、port、user、pwd、ftpClient)生成对应的getter、setter方法, +// 以及toString、equals和hashCode方法,减少了手动编写这些常规方法的代码量,使代码更加简洁,方便对类属性进行访问和操作。 + @Slf4j +// 使用Lombok的@Slf4j注解,自动生成名为log的SLF4J日志记录器,用于在类中记录不同级别(如info、error等)的日志信息, +// 方便后续对FTP文件上传过程中的关键步骤、异常情况等进行记录和追踪,便于调试与问题排查。 + public class FtpUtil { + + // 以下三个静态变量用于存储从配置文件中读取的FTP服务器相关配置信息,通过PropertiesUtil工具类(推测是自定义用于读取配置属性的类)获取, + // 这种方式便于统一管理FTP服务器的配置,若配置发生变化,只需在配置文件中修改对应属性值即可,无需改动代码逻辑。 private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip"); private static String ftpUser = PropertiesUtil.getProperty("ftp.user"); private static String ftpPass = PropertiesUtil.getProperty("ftp.pass"); - public FtpUtil(String ip,int port,String user,String pwd){ + /** + * 构造函数,用于创建FtpUtil类的实例,并初始化FTP服务器连接相关的属性,包括IP地址、端口号、用户名和密码。 + * 通过传入不同的参数值,可以连接到不同配置的FTP服务器,增加了灵活性,满足不同场景下连接不同FTP服务器的需求。 + * + * @param ip FTP服务器的IP地址,指定要连接的服务器网络位置。 + * @param port FTP服务器的端口号,通常FTP默认端口为21,但也可根据实际服务器配置情况传入不同端口值。 + * @param user FTP服务器的用户名,用于进行身份验证,需与服务器端配置的合法用户名一致。 + * @param pwd FTP服务器的密码,对应用户名的密码,用于登录验证。 + */ + public FtpUtil(String ip, int port, String user, String pwd) { this.ip = ip; this.port = port; this.user = user; this.pwd = pwd; } + + /** + * 静态方法,作为对外提供的主要文件上传入口,使用默认配置的FTP服务器信息(从配置文件中读取的IP、用户名、密码以及默认端口21), + * 创建FtpUtil实例后调用实例的另一个uploadFile方法来执行实际的文件上传操作,并返回上传结果,方便外部代码直接调用进行文件上传。 + * + * @param fileList 要上传的文件列表,是一个File类型的列表,其中每个File对象代表本地磁盘上的一个文件,这些文件将被上传到FTP服务器。 + * @return 返回一个布尔值,表示文件上传操作是否成功,true表示所有文件均成功上传至FTP服务器,false则表示上传过程中出现异常或部分文件未成功上传。 + * @throws IOException 因为在文件上传过程中涉及到文件读取、网络连接等可能出现I/O异常的操作,所以该方法声明抛出IOException异常, + * 调用此方法的代码需要对该异常进行适当处理,例如捕获并记录错误日志、提示用户上传失败等。 + */ public static boolean uploadFile(List fileList) throws IOException { - FtpUtil ftpUtil = new FtpUtil(ftpIp,21,ftpUser,ftpPass); + FtpUtil ftpUtil = new FtpUtil(ftpIp, 21, ftpUser, ftpPass); + // 根据从配置文件获取的默认FTP服务器配置信息创建FtpUtil实例,准备使用该实例进行后续的文件上传操作。 + log.info("开始连接ftp服务器"); - boolean result = ftpUtil.uploadFile("img",fileList); - log.info("开始连接ftp服务器,结束上传,上传结果:{}",result); + // 记录日志信息,提示即将开始连接FTP服务器,便于在查看日志时了解文件上传操作的流程和进度。 + + boolean result = ftpUtil.uploadFile("img", fileList); + // 调用实例的uploadFile方法(另一个重载版本),传入默认的远程路径"img"和文件列表,执行实际的文件上传操作,并获取上传结果。 + + log.info("开始连接ftp服务器,结束上传,上传结果:{}", result); + // 再次记录日志,包含开始连接FTP服务器以及最终的上传结果信息,方便后续查看整个上传过程是否成功以及排查可能出现的问题。 + return result; } - - private boolean uploadFile(String remotePath,List fileList) throws IOException { + /** + * 私有方法,用于执行将文件列表上传到指定FTP服务器远程路径下的实际操作,是文件上传功能的核心实现逻辑所在。 + * 该方法先尝试连接FTP服务器,若连接成功,则设置FTP客户端的相关参数,遍历文件列表逐个进行文件上传,最后关闭相关资源并返回上传结果。 + * + * @param remotePath 要上传文件到FTP服务器的远程路径,指定了在FTP服务器上文件将被存储的目录位置,如"img"表示将文件上传至名为img的目录下, + * 需确保该目录在FTP服务器上已存在且具备写入权限,否则文件上传会失败。 + * @param fileList 要上传的文件列表,与uploadFile(List fileList)方法中的参数含义相同,包含了本地待上传的文件对象集合。 + * @return 返回一个布尔值,表示文件上传操作是否成功,true表示所有文件都成功上传到了指定的远程路径下,false表示上传过程出现异常或部分文件未上传成功。 + * @throws IOException 由于在文件上传过程涉及文件读取、FTP客户端操作等可能出现I/O相关异常的情况,所以声明抛出IOException异常, + * 调用此方法的地方需要对该异常进行合适处理,例如捕获并记录异常详细信息、进行相应的错误提示等操作。 + */ + private boolean uploadFile(String remotePath, List fileList) throws IOException { boolean uploaded = true; + // 初始化上传结果变量uploaded为true,默认假设文件上传操作能够成功完成,后续若出现异常情况,将其修改为false来表示上传失败。 FileInputStream fis = null; - //连接FTP服务器 + // 初始化文件输入流对象为null,该输入流将用于读取本地文件内容,以便后续通过FTP客户端上传到服务器,在后续操作中会根据实际文件进行初始化。 + log.info("【开始连接文件服务器】"); - if(connectServer(this.ip,this.port,this.user,this.pwd)){ + // 记录日志信息,表明即将开始连接FTP服务器这一关键操作,方便后续通过日志查看文件上传流程和排查问题。 + + if (connectServer(this.ip, this.port, this.user, this.pwd)) { + // 调用connectServer方法尝试连接FTP服务器,并传入当前实例的IP、端口、用户名和密码信息进行连接和登录验证, + // 如果连接和登录成功(即该方法返回true),则进入后续的文件上传相关操作流程。 + try { ftpClient.changeWorkingDirectory(remotePath); + // 通过FTP客户端对象(ftpClient)的changeWorkingDirectory方法,将FTP客户端在服务器端的当前工作目录切换到指定的远程路径下, + // 确保后续上传的文件能够存储到正确的目标目录中,注意该目录需在FTP服务器上实际存在且具有相应权限,否则此操作会失败。 + ftpClient.setBufferSize(1024); + // 设置FTP客户端的缓冲区大小为1024字节,缓冲区用于临时存储文件数据,在文件传输过程中合理设置缓冲区大小可以提高传输效率, + // 这里设置为1024字节是一种常见的设置方式,实际应用中可根据文件大小、网络状况等因素进行适当调整。 + ftpClient.setControlEncoding("UTF-8"); + // 设置FTP客户端的控制编码为"UTF-8",这样在与FTP服务器进行命令交互、传输文件名等文本信息时,能够保证字符编码的一致性, + // 避免出现中文或其他特殊字符乱码的问题,提高文件上传操作的兼容性和稳定性。 + ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); + // 将FTP客户端上传文件的类型设置为二进制文件类型(FTPClient.BINARY_FILE_TYPE),因为大多数实际应用中的文件(如图像、文档等)都是以二进制形式存储和传输的, + // 这样设置能确保文件在上传过程中的完整性和正确性,若上传的是纯文本文件,根据情况也可设置为文本文件类型(FTPClient.ASCII_FILE_TYPE)。 + ftpClient.enterLocalPassiveMode(); - for(File fileItem : fileList){ + // 将FTP客户端设置为被动模式(enterLocalPassiveMode),在很多网络环境下,特别是客户端处于防火墙或网络地址转换(NAT)之后时, + // 被动模式可以使FTP客户端更好地与服务器建立数据连接,提高文件上传的成功率,避免因网络配置问题导致的连接失败情况。 + + for (File fileItem : fileList) { fis = new FileInputStream(fileItem); - ftpClient.storeFile(fileItem.getName(),fis); + // 遍历要上传的文件列表,对于每个文件,创建一个新的FileInputStream对象,用于从本地文件系统读取该文件的内容, + // 通过这个输入流,FTP客户端可以获取文件的二进制数据并上传到FTP服务器上,每次循环都会针对不同的文件重新初始化输入流。 + + ftpClient.storeFile(fileItem.getName(), fis); + // 使用FTP客户端的storeFile方法,将通过输入流读取到的当前文件内容上传到FTP服务器上,以文件的原始名称(fileItem.getName())作为在服务器端保存的文件名, + // 若服务器上已存在同名文件,具体的覆盖行为通常取决于服务器的配置情况,通过循环依次完成文件列表中所有文件的上传操作。 + } } catch (IOException e) { - log.error("上传文件异常",e); + log.error("上传文件异常", e); uploaded = false; e.printStackTrace(); + // 如果在文件上传的过程中(包括设置FTP客户端参数、读取文件或者执行上传操作等环节)出现IOException异常, + // 首先使用日志记录器记录一条错误级别日志,记录异常相关信息(通过传入e参数),表示文件上传出现问题, + // 然后将uploaded变量设置为false,表示文件上传失败,同时通过e.printStackTrace()打印出异常的详细堆栈信息,方便更深入地排查问题原因。 } finally { - fis.close(); + if (fis!= null) { + fis.close(); + } ftpClient.disconnect(); + // 在finally块中,无论文件上传过程是否出现异常,都需要进行资源清理操作。 + // 首先判断文件输入流是否已被初始化(不为null),如果是,则关闭该输入流,避免资源泄露; + // 然后通过FTP客户端对象的disconnect方法断开与FTP服务器的连接,释放网络相关资源,确保文件上传操作结束后系统资源被正确释放。 } } return uploaded; } - - - private boolean connectServer(String ip,int port,String user,String pwd){ + /** + * 私有方法,用于尝试连接到指定的FTP服务器并进行用户登录验证,返回连接和登录是否成功的结果, + * 在连接或登录过程中若出现异常情况,会通过日志记录器记录相应的错误信息,方便后续排查连接FTP服务器时出现问题的原因。 + * + * @param ip FTP服务器的IP地址,明确要连接的服务器网络位置,需确保该IP地址可访问且网络连接正常。 + * @param port FTP服务器的端口号,一般FTP服务器默认端口是21,但可根据实际配置传入不同端口值,与IP地址共同确定服务器的网络服务端点。 + * @param user FTP服务器的用户名,用于进行身份验证,必须是服务器端已配置且具备相应权限的合法用户名,否则登录操作会失败。 + * @param pwd FTP服务器的密码,与用户名相对应的密码,用于验证用户身份,需保证密码的准确性。 + * @return 返回一个布尔值,表示是否成功连接到FTP服务器并登录成功,true表示连接和登录均顺利完成,可继续进行后续文件上传等操作; + * false表示在连接或登录过程中出现问题,无法开展后续操作,调用者可根据此返回值判断是否能够进行文件上传,并采取相应的处理措施, + * 如提示用户连接失败、检查服务器配置等操作。 + */ + private boolean connectServer(String ip, int port, String user, String pwd) { boolean isSuccess = false; ftpClient = new FTPClient(); + // 创建一个FTPClient对象,它是Apache Commons Net库提供的用于与FTP服务器进行交互的客户端类, + // 通过该对象可以执行诸如连接服务器、登录、上传下载文件等一系列FTP相关操作,此处先创建好对象,为后续的连接和登录操作做准备。 + try { ftpClient.connect(ip); - isSuccess = ftpClient.login(user,pwd); + // 使用FTPClient对象的connect方法尝试连接到指定IP地址的FTP服务器,若服务器地址可访问且网络连接正常等条件满足, + // 将会建立起与FTP服务器的网络连接,但这只是第一步,还需要进行用户登录验证才能真正开始后续操作。 + + isSuccess = ftpClient.login(user, pwd); + // 通过FTPClient对象的login方法,使用传入的用户名和密码进行用户登录验证,若用户名和密码正确且用户在FTP服务器上具备相应权限, + // 登录操作将会成功,此时将isSuccess变量设置为true,表示连接和登录都成功了;否则登录失败,isSuccess保持为false,意味着无法进行后续操作。 } catch (IOException e) { - log.error("连接FTP服务器异常",e); + log.error("连接FTP服务器异常", e); + // 如果在连接服务器或者登录验证过程中出现IOException异常(例如网络不通、服务器拒绝连接、用户名或密码错误等原因导致), + // 使用日志记录器记录一条错误级别日志,记录异常相关信息(通过传入e参数),方便后续查看和分析连接FTP服务器出现问题的原因。 } return isSuccess; } - - + // 以下是类的私有属性,分别用于存储FTP服务器连接相关的信息以及FTP客户端对象。 + // 通过@Data注解自动生成了对应的getter和setter方法,方便在类内部及外部(通过公共方法间接访问)对这些属性进行操作和获取其值。 private String ip; private int port; private String user; private String pwd; private FTPClient ftpClient; -} +} \ No newline at end of file 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..b3472ba 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 @@ -1,126 +1,187 @@ package com.njupt.swg.common.utils; +import com.njupt.swg.entity.User; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.codehaus.jackson.map.DeserializationConfig; import org.codehaus.jackson.map.ObjectMapper; 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库的相关功能,通过对 `ObjectMapper` 进行特定配置,提供了多种方便的方法来实现Java对象与JSON字符串之间的相互转换, + * 并且在转换过程中进行了适当的错误处理和日志记录,以应对不同的业务场景需求,增强了代码在处理JSON数据时的健壮性和易用性。 + * + * @Slf4j 注解用于自动生成一个名为 `log` 的 `SLF4J` 日志记录器,方便在类中记录操作过程中出现的警告、错误等日志信息, + * 便于后续排查序列化和反序列化过程中可能出现的问题,同时也有助于在开发调试阶段观察数据转换情况。 */ @Slf4j public class JsonUtil { + + // 创建一个静态的 `ObjectMapper` 对象,它是Jackson库中核心的用于进行JSON序列化和反序列化操作的类。 + // 在这里将其声明为静态的,使得整个类中的所有静态方法都可以共享使用这个对象,避免多次创建带来的开销,同时保证配置的一致性。 private static ObjectMapper objectMapper = new ObjectMapper(); static { - //所有字段都列入进行转换 + // 静态代码块,在类加载时执行,用于对 `ObjectMapper` 进行一系列的配置操作,以定制JSON序列化和反序列化的行为。 + + // 所有字段都列入进行转换 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); - //取消默认转换timestamp形式 - objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); - //忽略空bean转json的错误 - objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); - //统一时间的格式 + // 通过设置序列化包含策略为 `ALWAYS`,表示在将Java对象序列化为JSON字符串时,无论对象的字段值是否为 `null`,都会包含该字段在JSON结果中。 + // 这样能确保完整的对象结构信息都被转换到JSON中,但可能会使生成的JSON数据稍显冗余,不过在某些场景下(如需要完整保留对象结构信息用于后续解析等)是很有用的。 + + // 取消默认转换timestamp形式 + objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); + // 配置 `ObjectMapper` 在序列化日期类型的字段时,不使用默认的将日期转换为时间戳的方式。 + // 通常情况下,如果不配置此项,Jackson会把 `java.util.Date` 等日期类型的对象转换为时间戳数值(一个表示从特定起始时间到该日期时间的毫秒数的长整型值), + // 这里关闭该默认行为后,会按照后续设置的日期格式来序列化日期字段,使生成的JSON中的日期格式更符合常规阅读和业务需求。 + + // 忽略空bean转json的错误 + objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false); + // 当要序列化的Java对象(通常被称为 `bean`)没有任何属性(即所谓的“空 `bean`”)时,默认情况下Jackson可能会抛出异常导致序列化失败。 + // 通过将此项配置为 `false`,让 `ObjectMapper` 在遇到这种情况时忽略该错误,直接返回一个空的JSON对象(例如 `{}`),提高了序列化操作的容错性, + // 避免因对象结构特殊而使整个序列化流程中断,尤其在处理一些可能存在空对象情况的业务逻辑中很有帮助。 + + // 统一时间的格式 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); - //忽略json存在属性,但是java对象不存在属性的错误 - objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); + // 调用 `setDateFormat` 方法设置日期格式,这里使用了 `DateTimeUtil` 类中定义的标准日期时间格式(可以推测是类似 `yyyy-MM-dd HH:mm:ss` 的格式)。 + // 这样做的目的是确保在序列化和反序列化过程中,日期类型的字段都能按照统一的、明确的格式进行处理,保证了JSON数据里日期时间表示的一致性, + // 便于不同系统或模块之间进行数据交互和解析时,对日期时间的准确理解和处理。 + + // 忽略json存在属性,但是java对象不存在属性的错误 + objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); + // 在进行反序列化操作(即将JSON字符串转换为Java对象)时,如果JSON字符串中包含了Java对象对应类中不存在的属性,默认情况下Jackson会抛出异常,导致反序列化失败。 + // 通过将此配置项设置为 `false`,让 `ObjectMapper` 忽略这种JSON数据和Java对象属性不匹配的情况,只解析JSON字符串中与Java对象类中已定义属性对应的部分, + // 提高了反序列化操作对不同来源JSON数据的兼容性,比如在接收外部接口传来的数据时,即使JSON数据中存在一些额外的、当前Java对象类不需要的属性,也能正常进行解析, + // 避免因数据结构不完全一致而无法处理的问题。 } /** * 序列化方法,将对象转为字符串 - * @param obj - * @param - * @return + * 该方法用于将任意的Java对象转换为JSON格式的字符串。如果传入的对象为 `null`,则直接返回 `null`。 + * 在转换过程中若出现 `IOException`(例如对象的结构不符合JSON序列化要求等原因导致),会记录一条警告日志,并返回 `null`, + * 正常情况下则根据已配置好的 `ObjectMapper` 的规则将对象正确转换为JSON字符串并返回。 + * + * @param obj 要进行序列化的Java对象,其类型可以是任意的Java类实例,包括自定义的实体类、各种集合类、基本数据类型的包装类等。 + * `ObjectMapper` 会依据其配置以及对象的实际结构将其转换为对应的JSON表示形式。 + * @param 泛型标识,代表传入对象的类型,通过使用泛型使得这个方法能够适用于各种不同类型对象的序列化操作,具有很强的通用性。 + * @return 返回转换后的JSON字符串,如果对象为 `null` 或者在转换过程中出现异常,则返回 `null`。 + * 调用者可以根据返回值判断序列化是否成功,并据此进行后续操作,比如将该字符串通过网络发送出去、保存到文件中等。 */ - public static String obj2String(T obj){ - if(obj == null){ + public static String obj2String(T obj) { + if (obj == null) { return null; } try { - return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); + // 首先判断传入的对象是否本身就是 `String` 类型,如果是,则直接返回该字符串,因为它已经是符合要求的字符串形式了,无需再进行序列化操作。 + // 如果不是 `String` 类型,则调用 `ObjectMapper` 的 `writeValueAsString` 方法,按照之前配置好的各种序列化规则(如包含所有字段、日期格式设置等), + // 将对象转换为JSON格式的字符串并返回。若在转换过程中出现I/O相关的异常,会由下面的 `catch` 块进行处理。 + 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; + // 如果在序列化过程中出现了 `IOException` 异常,使用自动生成的日志记录器 `log` 记录一条警告级别日志,记录异常相关信息(通过传入 `e` 参数), + // 表示对象转换为字符串时出现了问题,然后返回 `null`,告知调用者序列化操作失败,方便调用者根据返回值进行相应的错误处理,比如提示用户数据转换失败等操作。 } } /** * 序列化方法,同上,只是输出的格式是美化的,便于测试 - * @param obj - * @param - * @return + * 此方法与 `obj2String` 方法功能类似,也是用于将Java对象转换为JSON格式的字符串,但它生成的JSON字符串会进行格式美化, + * 例如进行缩进、换行等处理,使得生成的JSON数据结构更清晰直观,便于在测试过程中查看和验证对象转换后的JSON格式是否正确, + * 同样在对象为 `null` 或转换出现异常时返回 `null`。 + * + * @param obj 要进行序列化的Java对象,与 `obj2String` 方法中的对象参数含义一致,类型可以是任意的Java类实例,会按照配置规则转换为美化格式的JSON字符串。 + * @param 泛型标识,用于表示传入对象的类型,使方法能够适用于各种类型对象的序列化操作,保持通用性。 + * @return 返回格式美化后的JSON字符串,如果对象为 `null` 或者在转换过程中出现异常,则返回 `null`。 + * 调用者可以利用返回的美化后的字符串进行调试、查看数据结构等操作,比如在测试环境中验证对象转换后的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); + // 首先判断传入的对象是否为 `String` 类型,如果是,则直接返回该字符串,无需进行额外的序列化操作。 + // 如果不是 `String` 类型,则通过 `ObjectMapper` 获取一个带有默认美化打印机(`writerWithDefaultPrettyPrinter`)的写入器, + // 再利用这个写入器的 `writeValueAsString` 方法将对象转换为JSON字符串,这样生成的JSON字符串会按照美化规则进行格式化处理,更易于阅读和查看结构, + // 若在转换过程中出现 `IOException` 异常,会由下面的 `catch` 块进行捕获处理。 + 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; + // 如果在转换过程中出现 `IOException` 异常,使用日志记录器记录一条警告级别日志,记录异常相关信息, + // 然后返回 `null`,告知调用者转换失败,方便调用者根据返回值进行相应的错误处理,例如检查对象结构是否符合序列化要求等操作。 } } /** * 比较简单的反序列化的方法,将字符串转为单个对象 - * @param str - * @param clazz - * @param - * @return + * 该方法用于将JSON格式的字符串转换为指定类型的Java对象。当传入的JSON字符串为空(包括 `null` 或者空字符串情况,通过 `StringUtils` 判断) + * 或者指定的Java类类型为 `null` 时,直接返回 `null`。若在转换过程中出现 `IOException`(比如JSON字符串格式不符合要求、与指定的Java类结构不匹配等原因导致), + * 则记录一条警告日志并返回 `null`,正常情况下按照已配置好的 `ObjectMapper` 的反序列化规则将字符串解析为对应的Java对象并返回。 + * + * @param str 要进行反序列化的JSON字符串,其格式需要符合之前 `ObjectMapper` 配置的序列化规则以及对应Java类的结构要求, + * 例如包含正确的属性名、属性值类型与Java类中定义的属性类型匹配等,否则可能会导致反序列化失败。 + * @param clazz 用于指定要转换生成的Java对象的类型,需要传入具体的Java类类型(例如 `User` 类、`Integer` 类等), + * 通过这个参数告知 `ObjectMapper` 如何将JSON字符串解析为对应的对象,要求该类的结构要与JSON字符串中的数据结构能够在配置的容错规则下相匹配。 + * @param 泛型标识,代表要转换生成的Java对象的类型,与传入的 `clazz` 参数类型相对应,通过使用泛型使得该方法能够灵活处理不同类型对象的反序列化需求,通用性较强。 + * @return 返回解析后的Java对象,如果传入的字符串为空、指定的类类型为 `null` 或者在转换过程中出现异常,则返回 `null`。 + * 调用者可以根据返回值判断反序列化是否成功,并对得到的对象进行后续的业务处理,比如将对象存储到数据库、在业务逻辑中使用等操作。 */ - public static T String2Obj(String str,Class clazz){ - if(StringUtils.isEmpty(str) || clazz == null){ + public static T String2Obj(String str, Class clazz) { + if (StringUtils.isEmpty(str) || clazz == null) { return null; } try { - return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz); + // 首先通过 `StringUtils` 工具类(来自 `Apache Commons Lang3` 库)判断传入的JSON字符串是否为空(包含 `null` 和空字符串两种情况), + // 同时检查传入的Java类类型 `clazz` 是否为 `null`,如果有任一条件满足,则直接返回 `null`,表示无法进行反序列化操作。 + // 接着判断指定的类类型是否就是 `String` 类,如果是,则直接将传入的字符串强制转换为对应类型(`T` 类型,在这里就是 `String` 类)并返回, + // 因为如果期望的类型本身就是字符串,就不需要进行实际的解析操作了。 + // 如果不是 `String` 类,则调用 `ObjectMapper` 的 `readValue` 方法,按照传入的类类型 `clazz` 将JSON字符串解析为对应的Java对象并返回, + // 该方法内部会依据配置好的反序列化规则(如忽略未知属性等)进行解析操作,若在解析过程中出现 `IOException` 异常,则由下面的 `catch` 块进行捕获处理。 + 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; + // 如果在反序列化过程中出现 `IOException` 异常,使用日志记录器记录一条警告级别日志,记录异常相关信息, + // 然后返回 `null`,告知调用者反序列化失败,方便调用者根据返回值进行相应的错误处理,例如检查JSON字符串格式是否正确、类结构是否匹配等操作。 } } /** * 复杂对象的反序列化(通用) - * @param str - * @param typeReference - * @param - * @return + * 此方法用于处理更复杂的反序列化场景,比如将JSON字符串转换为包含泛型信息的复杂类型对象(例如 `List`、`Map>` 等)。 + * 通过传入 `TypeReference` 参数来指定复杂的类型信息,当传入的JSON字符串为空或者 `TypeReference` 为 `null` 时返回 `null`, + * 若在转换过程中出现 `IOException` 异常,则记录警告日志并返回 `null`,正常情况下按照配置好的 `ObjectMapper` 将字符串解析为对应的复杂类型对象并返回。 + * + * @param str 要进行反序列化的JSON字符串,其格式需要符合之前 `ObjectMapper` 配置的序列化规则以及对应复杂类型对象的结构要求, + * 对于复杂类型的情况,JSON字符串中的数据结构需要与通过 `TypeReference` 指定的类型结构相匹配,否则可能会导致反序列化失败。 + * @param typeReference 用于指定要转换生成的复杂类型对象的详细类型信息,通常需要通过创建 `TypeReference` 的子类实例(一般使用匿名内部类的方式)来准确表示复杂类型, + * 例如 `new TypeReference>() {}` 这样的形式就明确告诉 `ObjectMapper` 要将JSON字符串解析为 `List` 类型的对象。 + * @param 泛型标识,代表要转换生成的复杂类型对象的最终类型,与 `typeReference` 指定的类型相对应,通过泛型使得该方法能够处理各种不同复杂类型的反序列化需求,通用性良好。 + * @return 返回解析后的复杂类型的Java对象,如果传入的字符串为空、`typeReference` 为 `null` 或者在转换过程中出现异常,则返回 `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)); - } catch (IOException e) { - log.warn("parse string to obj error",e); + public static T Str2Obj(String str, Class typeReference) { + if (StringUtils.isEmpty(str) || typeReference == null) { return null; } - } - - /** - * 第二种方式实现复杂对象的反序列化 - * @param str - * @param collectionClass - * @param elementClasses - * @param - * @return - */ - public static T Str2Obj(String str,Class collectionClass,Class... elementClasses){ - JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); try { - return objectMapper.readValue(str,javaType); + // 首先判断传入的JSON字符串是否为空以及 `TypeReference` 是否为 `null`,如果满足其中一个条件,则直接返回 `null`,表示无法进行反序列化操作。 + // 接着判断 `TypeReference` 所表示的类型是否就是 `String` 类(通过 `getType` 方法获取类型并进行比较),如果是,则直接将传入的字符串作为结果返回, + // 因为如果期望的类型本身就是字符串,就无需进行实际的解析操作了。 + // 如果不是 `String` 类,则调用 `ObjectMapper` 的 `readValue` 方法,按照 `TypeReference` 指定的复杂类型信息将JSON字符串解析为对应的Java对象, + // 并将解析后的对象强制转换为泛型指定的 `T` 类型后返回,若在解析过程中出现 `IOException` 异常,则由下面的 `catch` 块进行捕获处理。 + 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; + // 如果在反序列化过程中出现 ` } } -} +} \ 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..3e520e9 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,109 @@ package com.njupt.swg.common.utils; import java.security.MessageDigest; /** - * MD5加密工具类 + * 这个类名为 `MD5Util`,是一个用于进行MD5加密操作的工具类。 + * 它提供了将输入的字符串按照MD5算法进行加密并转换为十六进制字符串表示的功能,同时支持指定字符编码以及在加密过程中可添加“盐值”(虽然当前代码中只是预留了加盐的位置但未实际实现加盐逻辑)的操作, + * 在需要对敏感信息(如用户密码等)进行加密处理以保障数据安全性的场景下可以使用这个工具类。 */ public class MD5Util { + + /** + * 将字节数组转换为十六进制字符串的方法 + * 该私有方法用于把给定的字节数组中的每个字节依次转换为对应的十六进制字符表示,并拼接成一个完整的十六进制字符串, + * 主要是在MD5加密后的字节结果转换为可展示、可存储的十六进制字符串形式时起到辅助作用。 + * + * @param b 要转换的字节数组,通常是经过MD5加密算法处理后得到的字节数组结果,每个字节都将被转换为十六进制表示形式。 + * @return 返回转换后的十六进制字符串,其内容是字节数组中每个字节对应的十六进制字符按顺序拼接而成,用于后续以更直观的字符串形式展示加密后的结果。 + */ private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); + // 创建一个 `StringBuffer` 对象,用于高效地拼接字符,因为在循环中需要逐个将字节转换后的十六进制字符添加进来, + // `StringBuffer` 相较于普通的字符串拼接操作(使用 `+` 运算符)在性能上更优,特别是在大量字符拼接的场景下。 + for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); + // 遍历传入的字节数组,对于每个字节,调用 `byteToHexString` 方法将其转换为十六进制字符表示形式, + // 然后将转换后的字符添加到 `resultSb` 这个 `StringBuffer` 对象中,通过循环完成整个字节数组的转换和拼接操作。 return resultSb.toString(); + // 最后将 `StringBuffer` 对象转换为普通的字符串并返回,得到由字节数组转换而来的十六进制字符串结果,可用于展示或者后续的处理,比如作为加密后的最终结果返回给调用者。 } + /** + * 将单个字节转换为十六进制字符串的方法 + * 此私有方法用于把一个字节转换为对应的两位十六进制字符表示形式,它先处理字节为负数的情况(将其转换为无符号整数范围), + * 然后分别计算出十六进制表示中的高位和低位数字对应的字符,最终拼接成两位十六进制字符串,是 `byteArrayToHexString` 方法中转换单个字节的具体实现逻辑。 + * + * @param b 要转换的字节,是一个字节类型的数据,其值将被转换为十六进制的两位字符表示,例如将字节值 `0x1A` 转换为十六进制字符串 `"1a"`。 + * @return 返回转换后的两位十六进制字符串,用于在 `byteArrayToHexString` 方法中拼接成完整的十六进制字符串,展示字节对应的十六进制表示形式。 + */ private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; + // 如果传入的字节值是负数(在Java中字节类型是有符号的,范围是 `-128` 到 `127`),将其转换为无符号整数范围(`0` 到 `255`), + // 这样便于后续按照常规的十六进制转换规则进行操作,确保转换结果的正确性,因为十六进制表示通常是基于无符号整数的概念来进行转换的。 + int d1 = n / 16; int d2 = n % 16; + // 通过整数除法和取余运算,分别获取该字节值对应的十六进制表示中的高位数字和低位数字,例如对于字节值 `26`(十六进制为 `0x1A`), + // `d1` 的值为 `1`(对应十六进制的高位数字),`d2` 的值为 `10`(对应十六进制的低位数字,在十六进制中用 `a` 表示)。 + return hexDigits[d1] + hexDigits[d2]; + // 从预定义的十六进制字符数组 `hexDigits` 中获取对应索引位置(`d1` 和 `d2`)的字符,并将它们拼接起来,形成两位十六进制字符串, + // 例如对于上述的 `d1 = 1` 和 `d2 = 10`,就会从 `hexDigits` 数组中获取到 `"1"` 和 `"a"` 并拼接为 `"1a"`,作为该字节的十六进制表示返回。 } /** - * 返回大写MD5 + * 执行MD5加密并返回大写十六进制字符串结果的私有方法 + * 这个私有方法是MD5加密的核心实现逻辑所在,它先获取 `MessageDigest` 实例用于执行MD5算法,然后根据传入的字符编码情况, + * 对输入的原始字符串进行字节编码转换并通过 `MessageDigest` 进行MD5摘要计算,最后将计算得到的字节数组结果转换为十六进制字符串并返回(结果转换为大写形式), + * 如果在过程中出现异常则直接返回 `null`,不过当前代码中只是简单地捕获异常而没有做进一步的处理,实际应用中可能需要更完善的异常处理机制。 * - * @param origin - * @param charsetname - * @return + * @param origin 要进行MD5加密的原始字符串,例如可以是用户输入的密码、需要加密存储的敏感信息等字符串内容, + * 其内容将按照MD5算法进行加密处理,转换为对应的十六进制字符串表示形式。 + * @param charsetname 用于指定原始字符串的字符编码名称,如 `"utf-8"`、`"gbk"` 等,如果传入 `null` 或者空字符串,则使用默认的字符编码(通常是平台默认编码)来转换字符串为字节数组进行加密, + * 正确指定字符编码很重要,确保加密过程中对字符串的字节表示是符合预期的,避免出现乱码等问题导致加密结果不正确。 + * @return 返回经过MD5加密后并转换为大写的十六进制字符串结果,如果在加密过程中出现异常(比如不支持的字符编码、`MessageDigest` 获取实例失败等原因),则返回 `null`, + * 调用者可以根据返回值判断加密是否成功,并对结果进行后续的处理,比如将加密后的字符串存储到数据库中用于验证用户密码等操作。 */ private static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); + // 先将传入的原始字符串赋值给 `resultString`,这里其实可以考虑是否有必要重新创建一个新的字符串对象, + // 不过当前这样做可能是为了后续在处理过程中不直接修改传入的原始字符串参数,保持其原始性,具体可以根据实际需求进一步优化。 + MessageDigest md = MessageDigest.getInstance("MD5"); + // 通过 `MessageDigest` 类的静态方法 `getInstance` 获取一个实现了MD5算法的 `MessageDigest` 实例, + // 这个实例将用于对字符串进行MD5摘要计算,也就是执行实际的加密操作,`"MD5"` 参数指定了要使用的加密算法名称,确保获取到的是MD5相关的实现类实例。 + if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString.getBytes())); else resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname))); + // 根据传入的字符编码名称情况进行不同的处理,如果字符编码名称为 `null` 或者空字符串,表示使用默认的字符编码来将字符串转换为字节数组, + // 然后通过 `md.digest` 方法对字节数组进行MD5摘要计算,得到加密后的字节数组结果,再调用 `byteArrayToHexString` 方法将其转换为十六进制字符串,赋值给 `resultString`; + // 如果指定了有效的字符编码名称,则按照指定的编码将字符串转换为字节数组后再进行MD5摘要计算和十六进制字符串转换操作,同样将结果赋值给 `resultString`。 } catch (Exception exception) { + // 当前代码只是简单地捕获了可能出现的异常,并没有做进一步的处理,比如记录日志、抛出更具体的业务相关异常等, + // 在实际应用中可以根据具体需求完善异常处理逻辑,以便更好地排查加密过程中出现问题的原因以及向调用者反馈更准确的错误信息。 } return resultString.toUpperCase(); + // 最后将得到的十六进制字符串结果转换为大写形式返回,这样做通常是为了统一加密结果的格式表示,便于后续比较、存储等操作, + // 例如将小写的十六进制字符串 `"abcdef"` 转换为 `"ABCDEF"` 后返回给调用者作为MD5加密后的最终结果。 } + /** + * 使用UTF-8编码进行MD5加密并返回大写十六进制字符串结果的公共方法 + * 这是一个对外提供的公共方法,它调用了 `MD5Encode` 私有方法,传入原始字符串和 `"utf-8"` 字符编码名称, + * 用于在大多数情况下方便地使用UTF-8编码对字符串进行MD5加密操作,并且这里可以作为添加“盐值”的扩展点(虽然当前代码中只是预留了位置但未实际实现加盐逻辑), + * 方便在需要增强加密安全性(通过添加盐值使得相同的原始字符串加密结果不同)的场景下进行功能扩展,返回加密后的大写十六进制字符串结果给调用者。 + * + * @param origin 要进行MD5加密的原始字符串,与 `MD5Encode` 方法中的参数含义相同,是需要进行加密处理的字符串内容,通常是敏感信息等。 + * @return 返回经过MD5加密(使用UTF-8编码)并转换为大写的十六进制字符串结果,调用者可以直接使用这个结果进行后续的操作, + * 比如在用户注册登录场景中,将加密后的密码存储到数据库,后续登录时再次对用户输入的密码进行同样方式的加密并与存储的加密结果进行比对验证用户身份等操作。 + */ public static String MD5EncodeUtf8(String origin) { //这里可以加盐 return MD5Encode(origin, "utf-8"); @@ -51,9 +113,13 @@ public class MD5Util { public static void main(String[] args) { System.out.println(MD5EncodeUtf8("123456")); + // 这是一个简单的 `main` 方法,用于对 `MD5EncodeUtf8` 方法进行测试,在这里传入字符串 `"123456"` 作为原始字符串进行MD5加密操作, + // 并将加密后的结果输出到控制台,方便开发人员在本地快速验证MD5加密功能是否正常工作,实际应用中可以根据更多的测试用例来全面测试加密逻辑的正确性和稳定性。 } - private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; -} + // 定义一个私有静态的字符串数组,用于存储十六进制中 `0` 到 `15` 对应的字符表示, + // 在 `byteToHexString` 方法中通过字节值转换后的十六进制数字作为索引,从这个数组中获取对应的字符来拼接成十六进制字符串, + // 是实现字节到十六进制字符串转换过程中的一个基础数据结构,方便了十六进制表示的转换操作。 +} \ 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..59b0420 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,7 +2,6 @@ package com.njupt.swg.common.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; - import java.io.IOException; import java.io.InputStreamReader; import java.util.Properties; @@ -10,37 +9,93 @@ import java.util.Properties; /** * @Author 【swg】. * @Date 2018/1/10 14:56 - * @DESC + * @DESC 该类是一个用于读取配置文件属性的工具类,旨在方便地从指定的配置文件(默认为 `parameter.properties`)中获取属性值, + * 提供了根据键获取对应值以及在值不存在时获取默认值的方法,同时具备基本的异常处理机制,通过日志记录配置文件读取过程中出现的问题, + * 在项目中用于统一管理和获取配置信息,避免硬编码配置数据,增强了配置的灵活性和可维护性。 * @CONTACT 317758022@qq.com */ @Slf4j +// 使用Lombok的 `@Slf4j` 注解,自动生成名为 `log` 的 `SLF4J` 日志记录器,方便在类中记录不同级别(如 `error` 等)的日志信息, +// 此处主要用于记录配置文件读取异常的情况,便于后续排查问题。 + public class PropertiesUtil { + private static Properties props; + // 定义一个静态的 `Properties` 对象,`Properties` 类常用于读取和操作配置文件中的键值对信息, + // 将其声明为静态的,以便在整个类的不同方法中共享使用,存储从配置文件中加载的所有属性数据。 static { String fileName = "parameter.properties"; props = new Properties(); try { - props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8")); + // 以下代码尝试从类加载器获取指定配置文件的输入流,并使用 `InputStreamReader` 以 `UTF-8` 编码将其加载到 `Properties` 对象中。 + + props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName), "UTF-8")); + // 通过 `PropertiesUtil` 类的类加载器(`ClassLoader`)获取配置文件对应的资源输入流(`getResourceAsStream` 方法), + // 这里传入配置文件的名称 `fileName`(默认为 `"parameter.properties"`)来定位文件,然后使用 `InputStreamReader` 对输入流进行包装, + // 指定编码为 `UTF-8`,确保正确解析配置文件中的字符内容(特别是包含中文等非ASCII字符时),最后调用 `Properties` 对象的 `load` 方法将配置文件内容加载进来, + // 使得 `props` 对象中存储了配置文件里的所有键值对信息,方便后续根据键来获取对应的值。 } catch (IOException e) { - log.error("配置文件读取异常",e); + log.error("配置文件读取异常", e); + // 如果在加载配置文件过程中出现 `IOException`(例如文件不存在、无法访问、编码错误等原因导致), + // 使用自动生成的日志记录器 `log` 记录一条错误级别日志,记录异常相关信息(通过传入 `e` 参数),表示配置文件读取出现了问题, + // 但当前代码只是记录了日志,并没有采取进一步的补救措施(如尝试其他默认配置等),实际应用中可根据需求完善异常处理逻辑。 } } - public static String getProperty(String key){ + /** + * 根据指定的键获取对应配置文件中的属性值,如果值为空(包括空白字符串)则返回 `null`。 + * 此方法用于从之前加载的配置文件(通过静态代码块加载到 `props` 对象中)中获取指定键对应的属性值, + * 会先对获取到的值进行空白判断,若为空则返回 `null`,否则返回去除首尾空白字符后的属性值,方便在获取配置信息时进行后续处理。 + * + * @param key 要获取属性值对应的键,是一个字符串类型,需确保该键在配置文件中已定义,否则将返回 `null`, + * 调用者可以传入诸如 `"ftp.server.ip"`、`"database.username"` 等与配置文件中定义的键一致的字符串来获取相应的值。 + * @return 返回从配置文件中获取到的对应键的属性值,如果值为空(空白字符串也算空)则返回 `null`, + * 调用者可以根据返回值判断是否成功获取到有效的配置信息,并进行后续的业务操作,比如使用获取到的IP地址连接服务器等。 + */ + public static String getProperty(String key) { String value = props.getProperty(key.trim()); - if(StringUtils.isBlank(value)){ + // 通过 `Properties` 对象的 `getProperty` 方法,传入去除首尾空白字符后的键(调用 `trim` 方法处理),从已加载的配置文件数据中获取对应的值, + // 这里获取到的值可能是 `null` 或者是配置文件中定义的实际字符串内容,可能包含首尾空白字符等情况,需要进一步处理。 + + if (StringUtils.isBlank(value)) { return null; } + // 使用 `Apache Commons Lang3` 库中的 `StringUtils` 工具类的 `isBlank` 方法判断获取到的属性值是否为空(包括 `null`、空字符串以及只包含空白字符的情况), + // 如果为空,则直接返回 `null`,表示没有获取到有效的配置信息。 + return value.trim(); + // 如果属性值不为空,再调用 `trim` 方法去除其首尾空白字符后返回,这样返回给调用者的就是一个去除了多余空白、相对“干净”的配置属性值, + // 便于调用者直接使用该值进行后续的业务逻辑操作,比如赋值给变量、拼接字符串等操作。 } - public static String getProperty(String key,String defaultValue){ - + /** + * 根据指定的键获取对应配置文件中的属性值,如果值为空(包括空白字符串)则返回默认值。 + * 此方法与 `getProperty(String key)` 类似,也是用于从配置文件中获取属性值,但当配置文件中对应键的值不存在或者为空时, + * 会返回传入的默认值,增强了获取配置信息的灵活性,避免因配置文件中某个属性缺失而导致业务逻辑出现问题, + * 同样会对获取到的值进行空白处理后返回,确保返回的是一个合适的、可直接使用的字符串值。 + * + * @param key 要获取属性值对应的键,与 `getProperty(String key)` 方法中的键参数含义相同,是一个字符串类型, + * 需确保该键在配置文件中已定义或者在未定义时能有合适的默认值传入,以保证业务逻辑正常运行。 + * @param defaultValue 当配置文件中对应键的值为空(空白字符串也算空)时返回的默认值,是一个字符串类型, + * 调用者可以根据业务需求传入诸如 `"localhost"`(作为默认IP地址)、`"admin"`(作为默认用户名)等合适的默认字符串值, + * 确保在配置文件缺失对应配置时,业务仍能以默认的配置信息正常开展。 + * @return 返回从配置文件中获取到的对应键的属性值,如果该值为空(空白字符串也算空)则返回传入的默认值, + * 调用者可以根据返回值进行后续的业务操作,无论配置文件中是否定义了对应属性,都能得到一个可用的字符串值进行处理。 + */ + public static String getProperty(String key, String defaultValue) { String value = props.getProperty(key.trim()); - if(StringUtils.isBlank(value)){ + // 同样先通过 `Properties` 对象的 `getProperty` 方法,传入去除首尾空白字符后的键,尝试从配置文件中获取对应的值, + // 获取到的值可能为空或者包含空白字符等情况,后续需要进一步判断和处理。 + + if (StringUtils.isBlank(value)) { value = defaultValue; } + // 使用 `StringUtils` 工具类的 `isBlank` 方法判断获取到的属性值是否为空,如果为空(包括 `null`、空字符串以及只包含空白字符的情况), + // 则将传入的默认值赋给 `value` 变量,这样就能保证始终有一个可用的字符串值返回给调用者,避免因配置文件中属性值缺失导致业务逻辑异常。 + return value.trim(); + // 最后对 `value` 变量(无论是从配置文件获取到的值还是传入的默认值)调用 `trim` 方法去除首尾空白字符后返回, + // 确保返回给调用者的是一个去除了多余空白、符合使用要求的字符串值,方便在业务逻辑中直接使用该值进行后续操作,比如赋值给变量、拼接字符串等操作。 } -} +} \ 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..1683f8a 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 @@ -14,49 +14,113 @@ import org.springframework.web.bind.annotation.RestController; * @Author swg. * @Date 2019/1/2 17:32 * @CONTACT 317758022@qq.com - * @DESC + * @DESC 该类是一个Spring框架中的RESTful风格的控制器类(`RestController`),用于处理与商品(`Product`)相关的各种HTTP请求, + * 并将请求转发到对应的业务逻辑层(通过 `IProductService` 接口)进行处理,然后返回相应的结果给客户端。 + * 主要提供了获取商品详情、商品列表查询、单个商品查询以及一些与Redis缓存初始化相关的补充接口等功能,在整个项目的商品模块中起到对外接口的作用。 */ @RestController +// `@RestController` 注解表明这个类是一个RESTful风格的控制器,它结合了 `@Controller` 和 `@ResponseBody` 的功能, +// 意味着类中的方法返回值会直接作为HTTP响应的主体内容(通常是JSON格式等)返回给客户端,而不需要额外配置视图解析等操作,适用于构建前后端分离项目中的接口层。 + @RequestMapping("/product") +// `@RequestMapping` 注解用于定义该控制器类中所有方法的基础请求路径,在这里将所有与商品相关的接口请求路径都统一以 `/product` 开头, +// 使得接口路径具有一定的层级和分类性,方便管理和识别,例如后续的 `/detail.do`、`/list.do` 等具体接口路径都是在这个基础路径下进行细化定义的。 + public class ProductController { + @Autowired private IProductService productService; + // 通过Spring的依赖注入(`@Autowired` 注解),自动将实现了 `IProductService` 接口的具体业务逻辑类注入到当前控制器中, + // 这样在控制器的各个方法中就可以方便地调用业务层提供的方法来处理商品相关的业务逻辑,实现了控制层与业务层的解耦,便于代码的维护和扩展。 + /** + * 获取商品详情的接口方法 + * 该方法用于处理获取指定商品详情信息的HTTP请求,接收一个商品ID作为参数,然后调用业务层的 `getPortalProductDetail` 方法获取商品详情信息, + * 并将业务层返回的结果(包装在 `ServerResponse` 中,其中包含了具体的商品详情数据以及请求的状态等信息)直接返回给客户端, + * 客户端可以根据返回的结果进行相应的展示或后续处理,比如在前端页面展示商品的详细描述、图片等信息。 + * + * @param productId 要获取详情的商品的唯一标识符,是一个整数类型的参数,通过这个参数可以在业务层从数据库等数据源中查找并获取对应的商品详细信息, + * 需要确保传入的商品ID是合法有效的,否则可能无法获取到正确的商品详情或者业务层会进行相应的错误处理(比如返回错误提示信息等)。 + * @return 返回一个 `ServerResponse` 对象,其泛型参数为 `ProductDetailVo`,表示包含商品详情视图对象(通常包含了商品详细属性、关联信息等经过处理后适合展示的数据结构)的响应结果, + * 这个响应结果会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端商品详情获取操作的成功与否以及对应的商品详情数据情况。 + */ @RequestMapping("detail.do") - public ServerResponse detail(Integer productId){ + public ServerResponse detail(Integer productId) { return productService.getPortalProductDetail(productId); } + /** + * 获取商品列表的接口方法 + * 此方法用于处理查询商品列表的HTTP请求,接收多个可选的请求参数,包括关键词(用于模糊搜索商品名称等)、商品分类ID、页码、每页数量以及排序规则等, + * 然后调用业务层的 `portalList` 方法按照这些参数进行商品列表的查询操作,最后将业务层返回的结果(包含商品列表信息以及分页相关数据,包装在 `ServerResponse` 中)返回给客户端, + * 客户端可以根据返回的结果展示商品列表、进行分页导航等操作,例如在电商网站的商品列表页面展示符合条件的商品列表以及实现翻页、排序等功能。 + * + * @param keyword 用于搜索商品的关键词,是一个字符串类型的参数,可选(通过 `required = false` 指定),如果传入关键词,则业务层会根据关键词对商品名称等相关字段进行模糊匹配查询, + * 例如可以传入商品名称中的部分文字来查找相关的商品,方便用户快速定位到感兴趣的商品,不传则表示不进行关键词搜索,返回所有商品或者按照其他条件筛选的商品列表。 + * @param categoryId 商品分类的唯一标识符,是一个整数类型的参数,同样可选,用于按照商品所属的分类来筛选商品列表,如果传入分类ID,则业务层会只返回该分类下的商品信息, + * 不传则表示不基于分类进行筛选,可用于获取全部分类或者按照其他条件(如关键词、排序等)筛选后的商品列表情况。 + * @param pageNum 要获取的商品列表的页码,是一个整数类型的参数,默认值为 `1`(通过 `defaultValue = "1"` 指定),表示如果客户端没有传入页码参数,则默认查询第一页的商品列表, + * 用于实现分页功能,方便客户端根据用户的翻页操作请求不同页码的商品列表信息,业务层会根据这个页码以及每页数量等参数计算并返回对应页的商品数据。 + * @param pageSize 每页显示的商品数量,是一个整数类型的参数,默认值为 `10`,用于控制每页展示的商品个数,与 `pageNum` 参数配合实现分页功能, + * 业务层会根据这个参数从数据库等数据源中获取指定数量的商品信息作为一页的内容返回给客户端,方便展示和浏览商品列表。 + * @param orderBy 商品列表的排序规则,是一个字符串类型的参数,默认值为空字符串,表示如果客户端没有传入排序规则,则业务层可能按照默认的顺序(如数据库默认排序或者业务定义的默认顺序)返回商品列表, + * 若传入排序规则(例如按照商品价格升序 `"price asc"` 或者按照销量降序 `"sales desc"` 等符合数据库排序语法的字符串),则业务层会按照指定的排序规则对商品列表进行排序后返回, + * 增强了商品列表展示的灵活性,满足不同用户对商品排序查看的需求。 + * @return 返回一个 `ServerResponse` 对象,其泛型参数为 `PageInfo`,`PageInfo` 通常包含了商品列表数据以及分页相关的信息(如总页数、总记录数等), + * 这个响应结果会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端商品列表查询操作的成功与否以及对应的商品列表和分页数据情况,方便客户端进行展示和分页操作。 + */ @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 = "pageSize", defaultValue = "10") int pageSize, + @RequestParam(value = "orderBy", defaultValue = "") String orderBy) { + return productService.portalList(keyword, categoryId, orderBy, pageNum, pageSize); } + /** + * 查询单个商品的接口方法 + * 该方法用于处理查询指定单个商品信息的HTTP请求,接收一个商品ID作为参数,然后调用业务层的 `queryProduct` 方法获取商品信息, + * 并将业务层返回的结果(包装在 `ServerResponse` 中,包含了商品的相关数据以及请求的状态等信息)返回给客户端, + * 客户端可以根据返回的结果进行相应的展示或后续处理,比如在前端页面展示商品的基本信息等,此方法与 `detail` 方法的区别可能在于返回的数据详细程度等方面有所不同, + * 根据业务需求可能 `detail` 方法返回更详细全面的商品信息,而这个方法返回相对简洁的基本商品信息,具体取决于业务层具体实现。 + * + * @param productId 要查询的商品的唯一标识符,是一个整数类型的参数,通过这个参数可以在业务层从数据库等数据源中查找并获取对应的商品信息, + * 需要确保传入的商品ID是合法有效的,否则可能无法获取到正确的商品信息或者业务层会进行相应的错误处理(比如返回错误提示信息等)。 + * @return 返回一个 `ServerResponse` 对象,这里没有指定泛型参数,说明返回的结果中商品信息的数据结构可能相对比较通用或者简单, + * 这个响应结果会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端商品查询操作的成功与否以及对应的商品信息情况。 + */ @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请求,通过调用业务层的 `preInitProductStcokToRedis` 方法来执行具体的缓存初始化操作, + * 并将业务层返回的结果(包装在 `ServerResponse` 中,包含了操作的成功与否以及相关提示信息等)返回给客户端, + * 在项目中常用于优化系统性能,减少频繁查询数据库获取商品库存的操作,提高系统响应速度,将库存数据提前缓存到Redis中便于快速读取。 + * + * @return 返回一个 `ServerResponse` 对象,其内部包含了将商品库存预置到Redis操作的结果信息,会被自动转换为合适的格式(如JSON)返回给客户端, + * 客户端可以根据返回结果判断操作是否成功,例如前端页面可以根据返回的成功信息提示用户缓存初始化完成或者在出现错误时展示相应的错误提示内容。 */ @RequestMapping("/preInitProductStcokToRedis.do") - public ServerResponse preInitProductStcokToRedis(){ + public ServerResponse preInitProductStcokToRedis() { return productService.preInitProductStcokToRedis(); } - /** * 补充接口2:预置所有商品到redis中 + * 此方法用于处理将所有商品相关信息预先初始化到Redis缓存中的HTTP请求,调用业务层的 `preInitProductListToRedis` 方法来完成具体的缓存初始化工作, + * 然后将业务层返回的结果(包装在 `ServerResponse` 中,包含了操作的成功与否以及相关提示信息等)返回给客户端, + * 同样是为了优化系统性能,将商品数据提前缓存到Redis中,后续客户端请求商品相关信息时可以直接从Redis中快速获取,减少数据库查询压力,提高系统整体的响应效率。 + * + * @return 返回一个 `ServerResponse` 对象,其内部包含了将所有商品预置到Redis操作的结果信息,会被自动转换为合适的格式(如JSON)返回给客户端, + * 客户端可以根据返回结果判断操作是否成功,比如在前端页面根据返回信息进行相应的提示或者后续业务逻辑根据操作结果进行进一步的处理等。 */ @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..b1dc9d2 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 @@ -31,121 +31,158 @@ import java.util.Map; * @Date 2019/1/2 17:32 * @CONTACT 317758022@qq.com * @DESC 后台商品服务 + * 该类是一个Spring框架下的RESTful风格的控制器类,用于处理后台管理系统中与商品相关的各种操作请求, + * 比如获取商品列表、搜索商品、上传商品图片、管理商品上下架状态以及保存或更新商品信息等功能, + * 通过调用相应的业务层服务(如 `IProductService`、`IFileService` 等)来完成具体的业务逻辑处理, + * 并将处理结果以合适的格式(通过 `ServerResponse` 包装)返回给客户端(通常是后台管理系统的前端页面),同时进行了必要的异常处理和日志记录,便于系统的维护和问题排查。 */ @RestController +// 表明这个类是一个RESTful风格的控制器,结合了 `@Controller` 和 `@ResponseBody` 的功能,使得类中的方法返回值会直接作为HTTP响应的主体内容(一般是JSON格式等)返回给客户端, +// 适用于构建前后端分离的后台管理系统接口层,无需额外配置视图解析等操作。 + @RequestMapping("/manage/product") +// 定义该控制器类中所有方法的基础请求路径,表明这些接口是用于后台管理系统中商品相关操作的,将接口路径统一以 `/manage/product` 开头,便于管理和区分不同模块的接口。 + @Slf4j +// 使用Lombok的 `@Slf4j` 注解自动生成名为 `log` 的 `SLF4J` 日志记录器,用于在类中记录不同级别(如 `info`、`error` 等)的日志信息,方便后续查看接口请求处理过程、排查问题等操作。 + public class ProductManageController { + @Autowired private IProductService productService; @Autowired private IFileService fileService; @Autowired private CommonCacheUtil commonCacheUtil; + // 通过Spring的依赖注入(`@Autowired` 注解),分别将实现了 `IProductService`(商品业务逻辑接口)、`IFileService`(文件相关业务逻辑接口)以及 `CommonCacheUtil`(缓存操作工具类)的具体实例注入到当前控制器中, + // 这样在各个接口方法中就可以方便地调用对应的业务方法和工具方法,实现了控制器层与业务层、工具类的解耦,便于代码的维护和扩展。 /** * 产品list + * 该方法用于处理获取商品列表的HTTP请求,接收页码和每页数量作为参数,默认页码为 `1`,每页数量为 `10`, + * 通过调用 `productService` 的 `list` 方法获取相应分页的商品列表信息,然后将结果(包装在 `ServerResponse` 中,包含商品列表及相关分页数据等)返回给客户端, + * 客户端(后台管理系统的商品列表页面)可以根据返回结果展示商品列表,并进行分页导航等操作。 + * + * @param pageNum 要获取的商品列表的页码,是一个整数类型的参数,通过 `defaultValue = "1"` 指定了默认值为 `1`,表示如果客户端没有传入页码参数,则默认查询第一页的商品列表, + * 用于实现分页功能,方便客户端根据用户操作请求不同页码的商品信息,业务层会依据此页码以及每页数量等参数计算并返回对应页的商品数据。 + * @param pageSize 每页显示的商品数量,是一个整数类型的参数,默认值为 `10`,用于控制每页展示的商品个数,与 `pageNum` 参数配合实现分页功能, + * 业务层会根据该参数从数据库等数据源中获取指定数量的商品作为一页的内容返回给客户端,便于展示和浏览商品列表。 + * @return 返回一个 `ServerResponse` 对象,其内部包含了分页后的商品列表信息以及相关分页数据(如总页数、总记录数等,通常由 `PageInfo` 类型来承载这些信息), + * 这个响应结果会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端商品列表获取操作的成功与否以及对应的商品列表和分页情况。 */ @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请求,接收商品名称、商品ID以及页码、每页数量作为参数,页码和每页数量有默认值, + * 通过调用 `productService` 的 `search` 方法按照传入的搜索条件(商品名称模糊搜索、根据商品ID精准搜索等)进行商品搜索操作, + * 并将搜索结果(包含符合条件的商品列表以及分页信息,包装在 `ServerResponse` 中,其泛型为 `PageInfo`)返回给客户端, + * 客户端(后台管理系统的搜索页面)可以根据返回结果展示搜索到的商品列表,方便管理员快速查找特定的商品。 + * + * @param productName 商品名称,是一个字符串类型的参数,用于进行模糊搜索商品,业务层会根据这个名称在数据库等数据源中查找名称包含该字符串的商品信息, + * 若不传该参数,则表示不基于商品名称进行搜索,可通过其他条件(如商品ID等)或者返回全部商品(结合分页等情况)进行展示。 + * @param productId 商品的唯一标识符,是一个整数类型的参数,用于精准查找特定的商品,如果传入商品ID,则业务层会优先查找该ID对应的商品信息, + * 不传该参数时,可结合商品名称等其他条件进行搜索或者返回符合其他条件的商品列表情况。 + * @param pageNum 要获取的商品列表的页码,与 `list` 方法中的页码参数含义相同,默认值为 `1`,用于实现分页功能,方便根据不同页码获取搜索结果中的商品列表, + * 业务层会根据该页码以及每页数量等参数计算并返回对应页的商品数据。 + * @param pageSize 每页显示的商品数量,与 `list` 方法中的每页数量参数含义相同,默认值为 `10`,用于控制每页展示的商品个数,与页码参数配合实现分页功能, + * 业务层会按照该参数从数据源中获取指定数量的商品作为一页的内容返回给客户端,便于展示搜索到的商品列表。 + * @return 返回一个 `ServerResponse` 对象,其泛型参数为 `PageInfo`,包含了符合搜索条件的商品列表数据以及分页相关的信息(如总页数、总记录数等), + * 这个响应结果会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端商品搜索操作的成功与否以及对应的商品列表和分页数据情况,方便客户端进行展示和进一步操作。 */ @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请求,接收一个文件类型的参数(通过 `MultipartFile` 表示上传的文件)以及 `HttpServletRequest` 对象用于获取服务器相关路径信息, + * 通过调用 `fileService` 的 `upload` 方法将文件上传到服务器指定路径下,并构建上传后图片的访问路径,将包含图片相关信息(文件名、访问路径等)的 `Map` 数据封装在 `ServerResponse` 中返回给客户端, + * 客户端(后台管理系统的图片上传功能模块)可以根据返回结果获取图片上传情况以及后续使用图片的访问路径等信息,同时记录了图片上传路径的日志信息,便于后续查看和排查上传问题。 + * + * @param file 要上传的文件,是一个 `MultipartFile` 类型的参数,代表从客户端(如浏览器)上传的文件对象,包含了文件的内容、文件名、文件类型等信息, + * 可以是商品图片文件,通过此参数将文件传递到服务器端进行上传操作,该参数可选(通过 `required = false` 指定),如果没有传入文件,则可能根据业务逻辑进行相应处理(如返回错误提示等)。 + * @param request `HttpServletRequest` 对象,用于获取服务器端的相关信息,在这里主要用于获取当前会话的上下文路径,确定文件上传的目标路径, + * 通过它可以获取到服务器上用于存放上传文件的实际路径,保证文件能够正确上传到指定的位置,方便后续的访问和管理。 + * @return 返回一个 `ServerResponse` 对象,其内部包含了一个 `Map` 类型的数据,这个 `Map` 中存放了上传图片的相关信息,比如文件名(`uri` 键对应的值)以及图片的完整访问路径(`url` 键对应的值), + * 响应结果会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端图片上传操作的成功与否以及对应的图片相关信息,方便客户端在需要展示图片或者关联图片到商品等操作时使用这些信息。 */ @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` 获取当前会话的 `ServletContext`,再调用其 `getRealPath` 方法获取服务器上 `upload` 目录的实际物理路径, + // 这个路径就是文件将要上传到的目标位置,确保文件能够被存储到服务器指定的目录下,便于后续的访问和管理,需要确保服务器上该目录具有相应的写入权限等条件。 + + String targetFileName = fileService.upload(file, path); + // 调用 `fileService` 的 `upload` 方法,传入要上传的文件对象和目标路径,执行文件上传操作,该方法会返回上传后文件在服务器上的文件名(可能经过了重命名等处理), + // 后续可以根据这个文件名构建完整的图片访问路径等操作,具体的文件上传逻辑(如文件类型校验、保存方式等)由 `fileService` 的 `upload` 方法实现。 - log.info("【上传的图片路径为:{}】",url); + String url = "http://img.oursnail.cn/" + targetFileName; + // 构建上传图片的完整访问路径,通常是将服务器的域名(这里假设为 `http://img.oursnail.cn/`)与上传后文件的文件名进行拼接, + // 得到一个可以通过网络访问该图片的URL地址,方便客户端后续通过这个地址来展示或使用该图片,例如在商品详情页面展示商品图片等场景。 + + log.info("【上传的图片路径为:{}】", 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` 为键存入 `Map`,将构建好的图片完整访问路径以 `url` 为键也存入 `Map`, + // 这样可以方便地将这些信息作为一个整体返回给客户端,客户端可以根据 `Map` 中的键获取相应的值进行后续操作。 + + log.info("【返回数据为:{}】", fileMap); + // 再次使用日志记录器记录将要返回给客户端的数据信息,便于在排查问题时了解接口返回的数据内容是否符合预期,确保数据的准确性和完整性。 + return ServerResponse.createBySuccess(fileMap); + // 通过 `ServerResponse` 的静态方法 `createBySuccess` 创建一个表示成功的响应对象,并将包含图片信息的 `Map` 作为参数传入, + // 这样就构建好了一个包含正确状态(成功)以及相关数据(图片信息)的响应结果,会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端图片上传操作成功以及对应的图片相关信息。 } /** * 产品详情 + * 该方法用于处理获取商品详情的HTTP请求,接收一个商品ID作为参数,通过调用 `productService` 的 `detail` 方法获取指定商品的详细信息, + * 并将结果(包含商品详情数据,包装在 `ServerResponse` 中,其泛型为 `ProductDetailVo`)返回给客户端, + * 客户端(后台管理系统的商品详情页面)可以根据返回结果展示商品的详细属性、关联信息等内容,方便管理员查看和管理商品的详细情况。 + * + * @param productId 要获取详情的商品的唯一标识符,是一个整数类型的参数,通过这个参数可以在业务层从数据库等数据源中查找并获取对应的商品详细信息, + * 需要确保传入的商品ID是合法有效的,否则可能无法获取到正确的商品详情或者业务层会进行相应的错误处理(比如返回错误提示信息等)。 + * @return 返回一个 `ServerResponse` 对象,其泛型参数为 `ProductDetailVo`,`ProductDetailVo` 通常是一个包含了商品详细属性、关联信息等经过处理后适合展示的数据结构, + * 这个响应结果会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端商品详情获取操作的成功与否以及对应的商品详情数据情况,方便客户端进行展示和进一步操作。 */ @RequestMapping("detail.do") - public ServerResponse detail(Integer productId){ + public ServerResponse detail(Integer productId) { return productService.detail(productId); } /** * 产品上下架 + * 该方法用于处理设置商品上下架状态的HTTP请求,接收商品ID和状态值作为参数,通过调用 `productService` 的 `set_sale_status` 方法来更新商品的销售状态, + * 并将结果(包含操作的成功与否以及相关提示信息,包装在 `ServerResponse` 中,其泛型为 `String`)返回给客户端, + * 客户端(后台管理系统的商品管理页面)可以根据返回结果判断操作是否成功,并相应地更新界面上商品的展示状态(上架或下架),方便管理员对商品的销售状态进行管控。 + * + * @param productId 要设置销售状态的商品的唯一标识符,是一个整数类型的参数,通过这个参数可以在业务层准确找到对应的商品记录进行状态更新操作, + * 需要确保传入的商品ID是合法有效的,否则可能无法正确更新商品状态或者业务层会进行相应的错误处理(比如返回错误提示信息等)。 + * @param status 商品的销售状态值,是一个整数类型的参数,具体的取值含义(如 `0` 表示下架,`1` 表示上架等)通常由业务层定义, + * 通过传入这个参数告知业务层要将商品设置为何种销售状态,业务层会根据该值更新数据库等数据源中商品对应的状态信息。 + * @return 返回一个 `ServerResponse` 对象,其泛型参数为 `String`,返回的字符串内容可能包含操作的结果提示信息(如成功提示或失败原因等), + * 这个响应结果会被自动转换为合适的格式(如JSON)返回给客户端,告知客户端商品销售状态设置操作的成功与否以及相关提示内容,方便客户端进行相应的展示和处理。 */ @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); + public ServerResponse set_sale_status(Integer productId, Integer status) { + return productService.set_sale_status(productId, status); } - - /** - * 富文本上传图片 - * 由于这里如果没有管理员权限,需要回复特定形式的信息,所以校验单独放在这里,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; - } - - - } +/** + * 新增OR更新产品 + * 该方法用于处理新增或更新商品信息的HTTP请求,接收一个 `Product` 类型的对象作为参数,这个对象包含了商品的各种属性信息(如名称、价格、描述等), + * 通过调用 `productService` 的 `saveOrUpdate + */ \ No newline at end of file