guanguan 9 months ago
parent 57aa7484fb
commit 693b88f742

@ -84,6 +84,11 @@
<groupId>joda-time</groupId> <groupId>joda-time</groupId>
<artifactId>joda-time</artifactId> <artifactId>joda-time</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -1,22 +1,46 @@
package com.njupt.swg.common.constants; package com.njupt.swg.common.constants;
/** /**
* Constants
* 便
* 使
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 13:19 * @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
public class Constants { public class Constants {
/**自定义状态码 start**/ /**
*
* 便
*/
public static final int RESP_STATUS_OK = 200; public static final int RESP_STATUS_OK = 200;
/**
* RESP_STATUS_OK200
* HTTP
*/
public static final int RESP_STATUS_NOAUTH = 401; public static final int RESP_STATUS_NOAUTH = 401;
/**
* RESP_STATUS_NOAUTH
* HTTP便使
*/
public static final int RESP_STATUS_INTERNAL_ERROR = 500; public static final int RESP_STATUS_INTERNAL_ERROR = 500;
/**
* RESP_STATUS_INTERNAL_ERROR
*
* 使HTTP
*/
public static final int RESP_STATUS_BADREQUEST = 400; public static final int RESP_STATUS_BADREQUEST = 400;
/**
* RESP_STATUS_BADREQUEST
* HTTP便使
*/
/**自定义状态码 end**/ /**
*
*/
} }

@ -0,0 +1,4 @@
package com.njupt.swg.common.exception;
public @interface ControllerAdvice {
}

@ -1,6 +1,5 @@
package com.njupt.swg.common.exception; package com.njupt.swg.common.exception;
import com.njupt.swg.common.constants.Constants; import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ServerResponse; import com.njupt.swg.common.resp.ServerResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -9,25 +8,66 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* ExceptionHandlerAdvice
* 使SpringWeb
*
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 13:21 * @Date 2019/1/1 13:21
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@ControllerAdvice @ControllerAdvice
// @ControllerAdvice注解表明这个类是一个全局的异常处理类它可以拦截整个应用程序中符合条件的异常
// 并进行统一的处理使得异常处理逻辑可以集中管理而不用在每个Controller中单独编写异常处理代码。
@ResponseBody @ResponseBody
// @ResponseBody注解表示该类中处理异常的方法返回的结果会直接作为响应体写入到HTTP响应中
// 通常返回的数据格式可以是JSON等方便客户端直接解析和处理响应内容。
@Slf4j @Slf4j
// @Slf4j是Lombok库提供的注解用于自动生成一个名为log的SLF4J日志记录器方便在类中记录日志信息
// 在这里主要用于记录异常相关的信息,便于后续排查问题。
public class ExceptionHandlerAdvice { public class ExceptionHandlerAdvice {
/**
* Exception
* Exception
*
*
* @param e Exception
* @return ServerResponse
* Constants.RESP_STATUS_INTERNAL_ERROR
*
*/
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){ public ServerResponse handleException(Exception e) {
log.error(e.getMessage(),e); log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); // 使用log记录异常的详细信息包括异常消息以及完整的堆栈信息方便后续排查问题
// 调用error方法记录错误级别日志因为这里处理的是异常情况属于应用程序中的错误场景。
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试");
// 通过ServerResponse的静态方法createByErrorCodeMessage创建一个包含错误状态码和错误消息的响应对象
// 此处使用了在Constants类中定义的表示服务器内部错误的状态码常量
// 向客户端返回一个统一格式的错误响应,告知客户端系统出现问题,建议稍后再尝试操作。
} }
/**
* SnailmallException
* SnailmallException
*
* @param e SnailmallException
*
* @return ServerResponseSnailmallExceptione.getExceptionStatus()
* 使e.getMessage()SnailmallException
*/
@ExceptionHandler(SnailmallException.class) @ExceptionHandler(SnailmallException.class)
public ServerResponse handleException(SnailmallException e){ public ServerResponse handleException(SnailmallException e) {
log.error(e.getMessage(),e); log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); // 同样使用log记录该特定异常的详细信息方便后续分析出现该异常的原因以及进行问题排查。
}
} return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage());
// 根据SnailmallException异常对象自身携带的状态码和消息来构建并返回相应的ServerResponse对象
// 向客户端返回符合该特定异常情况的响应内容,告知客户端具体的错误信息。
}
}

@ -4,22 +4,51 @@ import com.njupt.swg.common.resp.ResponseEnum;
import lombok.Getter; import lombok.Getter;
/** /**
* `SnailmallException` `RuntimeException`
* 便
* `@Getter` 便 `exceptionStatus`
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 13:18 * @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Getter @Getter
public class SnailmallException extends RuntimeException{ // @Getter 是Lombok库提供的注解它会自动为类中的私有成员变量生成对应的 `get` 方法,
// 在这里就是为 `exceptionStatus` 生成 `getExceptionStatus` 方法,方便外部获取该变量的值,
// 而不用手动编写 `get` 方法,简化了代码结构。
public class SnailmallException extends RuntimeException {
private int exceptionStatus = ResponseEnum.ERROR.getCode(); 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); 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); super(msg);
exceptionStatus = code; exceptionStatus = code;
} }
}
}

@ -3,6 +3,11 @@ package com.njupt.swg.common.resp;
import lombok.Getter; import lombok.Getter;
/** /**
* `ResponseEnum``enum`
*
* 使使
* `@Getter` Lombok`get` 便
*
* @Author swg. * @Author swg.
* @Date 2018/12/31 20:15 * @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
@ -10,16 +15,49 @@ import lombok.Getter;
*/ */
@Getter @Getter
public enum ResponseEnum { public enum ResponseEnum {
SUCCESS(0,"SUCCESS"), // 以下是枚举的各个常量实例,每个实例都代表一种特定的响应状态,并且通过构造函数传入相应的状态码(`code`)和描述信息(`desc`)。
ERROR(1,"ERROR"),
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), SUCCESS(0, "SUCCESS"),
NEED_LOGIN(10,"NEED_LOGIN"); /**
* `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 int code;
private String desc; private String desc;
ResponseEnum(int code,String desc){ /**
* `ResponseEnum` `code``desc`
*
* 使
*
* @param code 便
* @param desc 便
*/
ResponseEnum(int code, String desc) {
this.code = code; this.code = code;
this.desc = desc; this.desc = desc;
} }
} }

@ -8,71 +8,125 @@ import lombok.NoArgsConstructor;
import java.io.Serializable; import java.io.Serializable;
/** /**
* `ServerResponse``<T>`
*
* 使便
*
* @Author swg. * @Author swg.
* @Date 2018/12/31 20:11 * @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Getter @Getter
// 使用Lombok的 @Getter 注解,会自动为类中的私有成员变量(`status`、`msg`、`data`)生成对应的 `get` 方法,
// 方便外部获取这些变量的值,而无需手动编写 `get` 方法,简化了代码结构。
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
// 这个注解用于配置Jackson序列化的行为这里设置为 `NON_NULL`表示在将对象序列化为JSON等格式时
// 只会包含非空的属性,避免返回包含大量空值的冗余数据给客户端,优化响应数据的结构。
public class ServerResponse<T> implements Serializable { public class ServerResponse<T> implements Serializable {
// 实现 `Serializable` 接口,使得该类的对象可以被序列化和反序列化,便于在网络传输、持久化存储等场景中使用。
private int status; private int status;
// 用于存储响应的状态码,通过不同的状态码值可以表示请求处理的不同结果情况,例如成功、失败、需要登录等状态,
// 通常会参考项目中定义的状态码枚举(如 `ResponseEnum`)来设置合理的值。
private String msg; private String msg;
// 用于存放响应的提示消息,该消息可以是对响应状态的简单描述,比如成功时的提示语或者出现错误时的具体错误原因等,
// 方便客户端直观地了解请求处理的情况。
private T data; private T data;
// 泛型成员变量用于承载具体的响应数据内容根据不同的业务场景可以是各种类型的数据例如实体对象、列表、Map等
// 如果没有具体的数据需要返回(比如仅返回状态码和提示消息表示操作结果时),这个变量可以为 `null`。
public ServerResponse(){} public ServerResponse() {
}
// 默认的无参构造函数,方便在一些情况下创建 `ServerResponse` 对象,不过使用时可能需要后续再设置具体的属性值。
public ServerResponse(int status){ public ServerResponse(int status) {
this.status = status; this.status = status;
} }
public ServerResponse(int status,String msg){ // 这个构造函数接收一个状态码参数,用于在只需要设置响应状态码的场景下创建 `ServerResponse` 对象,
// 例如当仅想返回一个表示操作结果状态的空响应时,可以使用该构造函数创建对象后返回。
public ServerResponse(int status, String msg) {
this.status = status; this.status = status;
this.msg = msg; this.msg = msg;
} }
public ServerResponse(int status,T data){ // 接收状态码和提示消息两个参数的构造函数,适用于创建带有特定状态码和对应描述消息的响应对象,
// 常用于返回操作结果并告知客户端相关情况,但不需要返回具体业务数据的场景。
public ServerResponse(int status, T data) {
this.status = status; this.status = status;
this.data = data; this.data = data;
} }
public ServerResponse(int status,String msg,T data){ // 接收状态码和业务数据的构造函数,用于在操作成功且有具体数据需要返回给客户端时创建响应对象,
// 此时可以将正确的状态码以及获取到的业务数据传入,构造出符合要求的返回结果。
public ServerResponse(int status, String msg, T data) {
this.status = status; this.status = status;
this.msg = msg; this.msg = msg;
this.data = data; this.data = data;
} }
// 完整的构造函数,接收状态码、提示消息以及业务数据三个参数,能够满足各种情况下创建 `ServerResponse` 对象的需求,
// 可以根据具体的业务处理结果灵活设置各个参数的值来构造合适的返回响应。
@JsonIgnore @JsonIgnore
public boolean isSuccess(){ // 这个注解用于告诉Jackson在序列化对象时忽略该方法对应的属性在这里使得 `isSuccess` 方法不会作为一个属性出现在序列化结果中。
public boolean isSuccess() {
return this.status == ResponseEnum.SUCCESS.getCode(); return this.status == ResponseEnum.SUCCESS.getCode();
} }
// 定义了一个判断响应是否成功的方法,通过比较当前响应对象的状态码和 `ResponseEnum` 枚举中定义的成功状态码(`SUCCESS` 对应的代码)是否相等,
// 来确定此次响应是否代表操作成功,方便外部调用者快速判断请求处理的结果是否符合预期。
/** /**
* *
* 便 `ServerResponse`
*
*/ */
public static <T>ServerResponse<T> createBySuccess(){ public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
} }
public static <T>ServerResponse<T> createBySuccessMessage(String message){ // 这个静态方法创建一个表示成功状态的默认响应对象,使用 `ResponseEnum` 中定义的成功状态码和对应的描述信息,
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); // 适用于只需要简单返回成功标识而无需额外提示消息和业务数据的场景。
public static <T> ServerResponse<T> createBySuccessMessage(String message) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message);
} }
public static <T>ServerResponse<T> createBySuccess(T data){ // 创建一个成功状态的响应对象,允许传入自定义的提示消息,方便在成功情况下根据具体业务需求返回更有针对性的提示内容给客户端,
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); // 但此时不包含具体的业务数据。
public static <T> ServerResponse<T> createBySuccess(T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data);
} }
public static <T>ServerResponse<T> createBySuccess(String message,T data){ // 用于创建带有具体业务数据的成功状态响应对象,将传入的业务数据和默认的成功状态码组合起来构造 `ServerResponse` 对象,
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); // 适用于操作成功且有数据需要返回给客户端的情况。
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
} }
// 这个静态方法能够创建一个完整的成功状态响应对象,既包含自定义的提示消息又包含具体的业务数据,
// 可以根据实际业务场景灵活使用,全面地向客户端返回成功的操作结果以及相关信息。
/** /**
* *
* `ServerResponse` 便
*/ */
public static <T>ServerResponse<T> createByError(){ public static <T> ServerResponse<T> createByError() {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
}
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
} }
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){ // 创建一个表示一般性错误状态的响应对象,使用 `ResponseEnum` 中定义的错误状态码和对应的描述信息,
return new ServerResponse<>(code,msg); // 适用于在出现未细分的错误情况时返回给客户端,告知操作出现问题。
}
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
// 用于创建带有自定义错误提示消息的错误状态响应对象,传入的 `msg` 参数可以是具体的错误原因等内容,
// 使得在不同的业务错误场景下能够返回更明确的错误信息给客户端,帮助客户端了解出现问题的具体情况。
} public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
return new ServerResponse<>(code, msg);
}
// 这个静态方法更加灵活,接收自定义的状态码和错误提示消息两个参数,用于创建指定状态码和对应消息的错误状态响应对象,
// 适合根据具体业务逻辑中定义的各种错误状态码来准确返回相应的错误情况给客户端。
}

@ -7,66 +7,99 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
* `CategoryController`Spring `RestController`categoryHTTP
* 访
* `TODO`
* `GET`
*
* @Author swg. * @Author swg.
* @Date 2019/1/2 12:57 * @Date 2019/1/2 12:57
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
//TODO 这里首先实现业务 关于这里重复的鉴权,后面将会移植到网关中统一去做 // TODO 这里首先实现业务 关于这里重复的鉴权,后面将会移植到网关中统一去做
//TODO 先开放GET请求 // 此处表明当前代码专注于先实现业务功能,而对于鉴权部分,后续会将分散在这里重复进行的鉴权操作提取出来,放到网关层面进行统一的处理,
// 这样可以提高鉴权逻辑的复用性和可维护性,避免在每个接口中都重复编写相似的鉴权代码。
// TODO 先开放GET请求
// 说明当前阶段暂时只允许 `GET` 类型的HTTP请求访问这些接口后续可能会根据业务需求进一步开放其他类型的请求如 `POST`、`PUT`、`DELETE` 等),
// 这可能涉及到对接口安全性、功能完整性等方面的综合考虑。
@RestController @RestController
// `@RestController` 注解是Spring框架提供的它结合了 `@Controller` 和 `@ResponseBody` 的功能,
// 意味着这个类中的所有方法返回值都会直接作为响应体通常是JSON等格式返回给客户端而不需要额外配置视图解析等
// 适用于构建纯粹的RESTful风格的Web服务接口。
@RequestMapping("/manage/category/") @RequestMapping("/manage/category/")
// `@RequestMapping` 注解用于给这个控制器类中的所有接口方法配置一个基础的请求路径,
// 即所有该控制器下的接口请求路径都将以 `/manage/category/` 开头,这样便于对相关接口进行统一的路径管理和分类。
public class CategoryController { public class CategoryController {
@Autowired @Autowired
private ICategoryService categoryService; private ICategoryService categoryService;
// 通过Spring的依赖注入Dependency Injection机制使用 `@Autowired` 注解自动注入 `ICategoryService` 类型的实例,
// `ICategoryService` 应该是一个服务层接口,它定义了一系列与品类操作相关的业务逻辑方法,
// 在这里注入后,控制器类中的方法就可以调用服务层提供的这些方法来完成具体的业务处理。
/** /**
* () * ()
* HTTP `categoryId`
* `0` `categoryService` `getCategory`
* `ServerResponse` `ServerResponse`
*/ */
@RequestMapping("get_category.do") @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); ServerResponse response = categoryService.getCategory(categoryId);
return response; return response;
} }
/** /**
* *
* HTTP `categoryName` `parentId` `0`
* `categoryService` `addCategory`
* `ServerResponse`
*/ */
@RequestMapping("add_category.do") @RequestMapping("add_category.do")
public ServerResponse addCategory(String categoryName, @RequestParam(value = "parentId",defaultValue = "0")int parentId){ public ServerResponse addCategory(String categoryName, @RequestParam(value = "parentId", defaultValue = "0") int parentId) {
ServerResponse response = categoryService.addCategory(categoryName,parentId); ServerResponse response = categoryService.addCategory(categoryName, parentId);
return response; return response;
} }
/** /**
* *
* HTTP `categoryName` `categoryId`
* `categoryService` `updateCategoryName`
* `ServerResponse<String>` `<String>`
*
*/ */
@RequestMapping("set_category_name.do") @RequestMapping("set_category_name.do")
public ServerResponse<String> set_category_name(String categoryName,Integer categoryId){ public ServerResponse<String> set_category_name(String categoryName, Integer categoryId) {
return categoryService.updateCategoryName(categoryName,categoryId); return categoryService.updateCategoryName(categoryName, categoryId);
} }
/** /**
* *
* HTTP `categoryId` `0`
* `categoryService` `selectCategoryAndDeepChildrenById`
* `ServerResponse` 便
*/ */
@RequestMapping("get_deep_category.do") @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); return categoryService.selectCategoryAndDeepChildrenById(categoryId);
} }
/** /**
* *
* `categoryId`
* `categoryService` `getCategoryDetail`
* `ServerResponse` 便
*/ */
@RequestMapping("get_category_detail.do") @RequestMapping("get_category_detail.do")
public ServerResponse get_category_detail(Integer categoryId){ public ServerResponse get_category_detail(Integer categoryId) {
return categoryService.getCategoryDetail(categoryId); return categoryService.getCategoryDetail(categoryId);
} }
}
}

@ -1,24 +1,96 @@
package com.njupt.swg.dao; package com.njupt.swg.dao;
import com.njupt.swg.entity.Category; import com.njupt.swg.entity.Category;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
/**
* `CategoryMapper` MyBatis 访
* `Category`
* MyBatis SQL
* `Category` `Category`
*
* @Mapper MyBatis Mapper Spring MyBatis
* 使便
*/
@Mapper @Mapper
public interface CategoryMapper { public interface CategoryMapper {
/**
*
* `Category` `id`
* `Integer id`
* 1 1
*
* @param id `Category`
* @return
*/
int deleteByPrimaryKey(Integer id); int deleteByPrimaryKey(Integer id);
/**
* `Category`
* `Category` `Category` `Category record`
*
* 1 1
*
* @param record `Category`
* @return
*/
int insert(Category record); int insert(Category record);
/**
* `Category`
* `insert` `Category`
* `null`
*
*
* @param record `Category`
* @return
*/
int insertSelective(Category record); int insertSelective(Category record);
/**
* `Category`
* `Integer id` `Category` `Category`
* `Category` `null`
*
*
* @param id `Category`
* @return `Category` `null`
*/
Category selectByPrimaryKey(Integer id); Category selectByPrimaryKey(Integer id);
/**
* `Category`
* `insertSelective` `Category`
* `Category`
*
*
* @param record `Category`
* @return
*/
int updateByPrimaryKeySelective(Category record); int updateByPrimaryKeySelective(Category record);
/**
* `Category`
* 使 `Category` `Category`
* `null`
*
*
* @param record `Category`
* @return
*/
int updateByPrimaryKey(Category record); int updateByPrimaryKey(Category record);
/**
* `id`
* `Category` `id``Integer categoryId`
* `List<Category>` `Category`
* 便
*
* @param categoryId `id` `Category`
* @return `Category`
*/
List<Category> selectCategoryChildrenByParentId(Integer categoryId); List<Category> selectCategoryChildrenByParentId(Integer categoryId);
} }

@ -6,21 +6,49 @@ import lombok.NoArgsConstructor;
import java.util.Date; import java.util.Date;
/**
* `Category` `Category`
*
*
*
* @Data Lombok `getter``setter`
* `toString` `equals` `hashCode`
*
* @AllArgsConstructor Lombok
* 便 `Category`
*
* @NoArgsConstructor Lombok
* 使
*/
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class Category { public class Category {
private Integer id; private Integer id;
// 用于存储品类的唯一标识符,在数据库中通常对应主键字段,通过这个 `id` 可以唯一确定一个品类记录,
// 比如在进行数据库查询、更新、删除等操作时,往往需要依据这个 `id` 来定位具体的品类数据。
private Integer parentId; private Integer parentId;
// 表示该品类所属的父品类的 `id`,用于构建品类之间的层级关系,通过这个字段可以知道某个品类隶属于哪个上级品类,
// 有助于实现诸如查询某个品类的子品类、获取品类树结构等相关业务功能。
private String name; private String name;
// 存储品类的名称,是用于展示和区分不同品类的重要属性,比如在前端界面上呈现给用户的品类名称,
// 或者在业务逻辑中通过名称来进行品类的查找、筛选等操作时会用到这个属性。
private Boolean status; private Boolean status;
// 用于表示品类的当前状态,比如可以设定 `true` 表示该品类处于启用、可用状态,`false` 表示禁用、不可用状态等,
// 在业务中可以根据这个状态属性来决定是否展示该品类、是否允许对其进行相关操作等情况。
private Integer sortOrder; private Integer sortOrder;
// 用于确定品类在展示或者排序时的顺序,例如在一个列表中按照一定规则排列品类的先后顺序时,
// 就会依据这个 `sortOrder` 属性的值来进行排序,数值小的品类可能排在前面等,方便对品类进行有序的展示和管理。
private Date createTime; private Date createTime;
// 记录品类的创建时间,它可以用于跟踪品类数据的创建历史,比如查看某个品类是什么时候首次添加到系统中的,
// 在一些数据审计、数据分析等业务场景中可能会用到这个时间属性。
private Date updateTime; private Date updateTime;
// 用于记录品类数据最后一次更新的时间,通过对比 `createTime` 和 `updateTime`,可以了解品类数据的变更情况,
// 也有助于在一些业务逻辑中判断数据的时效性或者进行版本控制等相关操作。
} }

@ -9,35 +9,73 @@ import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
* `User`
*
* Lombok
*
* @Author swg. * @Author swg.
* @Date 2018/12/31 21:01 * @Date 2018/12/31 21:01
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Data @Data
// @Data 注解是Lombok库提供的一个便捷注解它会自动为类中的所有非静态、非 `final` 属性生成对应的 `getter` 和 `setter` 方法,
// 同时还会生成 `toString`、`equals` 和 `hashCode` 方法,这样就避免了手动编写这些重复性的代码,使代码结构更加简洁清晰。
@NoArgsConstructor @NoArgsConstructor
// @NoArgsConstructor 注解用于生成一个无参构造函数,在一些场景下(例如使用某些框架进行对象实例化时)可能需要类具有无参构造函数,
// 这个注解就满足了这一需求,方便对象的创建操作。
@AllArgsConstructor @AllArgsConstructor
// @AllArgsConstructor 注解会为类生成一个包含所有参数的构造函数,当需要一次性传入所有属性的值来创建 `User` 对象时,
// 就可以使用这个构造函数,提高了对象初始化的便利性。
@ToString @ToString
// @ToString 注解由Lombok库提供它会自动重写类的 `toString` 方法,使得在打印对象或者将对象转换为字符串表示形式时,
// 能够更直观地展示对象包含的各个属性及其对应的值,方便调试和查看对象的状态信息。
public class User implements Serializable { public class User implements Serializable {
// 实现 `Serializable` 接口意味着该类的对象可以被序列化和反序列化,这在很多场景下非常重要,
// 比如将用户对象存储到文件中、通过网络传输用户对象等操作时,都需要对象具备可序列化的特性。
private Integer id; private Integer id;
// 用于存储用户的唯一标识符,通常对应数据库表中的主键字段,通过这个 `id` 可以在系统中唯一确定一个用户,
// 在进行用户相关的数据库查询、更新、删除等操作时,往往需要依据这个 `id` 来精准定位具体的用户数据。
private String username; private String username;
// 代表用户的用户名,是用户在登录系统或者进行其他交互操作时使用的标识名称,具有唯一性(一般情况下),
// 在业务逻辑中常用于用户认证、权限验证以及在界面上展示用户相关信息等场景。
private String password; private String password;
// 存储用户的登录密码,是保障用户账号安全的关键属性,在用户登录验证环节会将用户输入的密码与该属性存储的值进行比对,
// 以确定用户身份的合法性,密码通常会进行加密存储,以提高安全性。
private String email; private String email;
// 用于存放用户的电子邮箱地址,可用于多种用途,比如找回密码、接收系统通知、验证用户身份等操作,
// 方便系统与用户进行信息沟通和交互,并且在一些业务逻辑中可能会对邮箱格式的合法性进行验证。
private String phone; private String phone;
// 存储用户的手机号码,同样可用于诸如短信验证码验证、找回密码、接收重要通知等功能,
// 在当今的移动互联网应用场景下,手机号往往也是用户重要的联系方式和身份验证依据之一。
private String question; private String question;
// 用于存储用户设置的密保问题,在用户忘记密码等情况下,可以通过回答正确的密保问题来重置密码,
// 这是一种常见的用户账号安全保障措施,增加了账号找回密码等操作的安全性和可靠性。
private String answer; private String answer;
// 对应于密保问题的答案,与 `question` 属性配合使用,用于验证用户在找回密码等操作时提供的答案是否正确,
// 只有答案匹配才能进行后续的密码重置等相关操作。
//角色0-管理员,1-普通用户 // 角色0-管理员,1-普通用户
private Integer role; private Integer role;
// 这个属性用于定义用户在系统中的角色,通过整数值来区分不同角色类型,这里规定 `0` 表示管理员角色,`1` 表示普通用户角色,
// 根据用户的角色不同,系统会赋予其不同的权限,例如管理员可能具有更多的系统管理、数据操作等权限,而普通用户则只能进行一些常规的操作。
private Date createTime; private Date createTime;
// 记录用户账号的创建时间,它可以用于跟踪用户在系统中的注册历史,了解用户何时加入系统,
// 在一些数据统计、用户行为分析以及审计等业务场景中可能会用到这个时间属性。
private Date updateTime; private Date updateTime;
// 用于记录用户账号信息最后一次更新的时间,通过对比 `createTime` 和 `updateTime`,可以知晓用户信息的变更情况,
// 比如用户修改了密码、更新了联系方式等操作后,这个时间就会相应更新,方便在业务逻辑中判断数据的时效性或者进行版本控制等相关操作。
} }

@ -16,115 +16,199 @@ import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
* `CategoryServiceImpl` `ICategoryService`
*
* `CategoryMapper`
* 使Spring `@Service` 便使使 `@Slf4j` 便
*
* @Author swg. * @Author swg.
* @Date 2019/1/2 12:54 * @Date 2019/1/2 12:54
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Service @Service
// `@Service` 注解是Spring框架提供的用于将当前类标记为服务层组件表明这个类包含了业务逻辑处理的相关代码
// 会被Spring容器自动扫描并管理使得其他类可以通过依赖注入的方式使用这个类的实例方便业务逻辑的分层和复用。
@Slf4j @Slf4j
public class CategoryServiceImpl implements ICategoryService{ // `@Slf4j` 是Lombok库提供的注解用于自动生成一个名为 `log` 的SLF4J日志记录器在类中可以方便地使用这个日志记录器记录各种级别的日志信息
// 例如记录业务操作的关键步骤、异常情况等,有助于后续的问题排查和系统监控。
public class CategoryServiceImpl implements ICategoryService {
@Autowired @Autowired
private CategoryMapper categoryMapper; private CategoryMapper categoryMapper;
// 通过Spring的依赖注入Dependency Injection机制使用 `@Autowired` 注解自动注入 `CategoryMapper` 类型的实例,
// `CategoryMapper` 是数据访问层接口,它定义了一系列操作品类数据的数据库方法,在这里注入后,服务层的业务方法就可以调用这些方法来与数据库进行交互,
// 实现对品类数据的增删改查等操作。
@Override @Override
public ServerResponse getCategory(Integer categoryId) { public ServerResponse getCategory(Integer categoryId) {
//1.校验参数 // 1.校验参数
if(categoryId == null){ if (categoryId == null) {
throw new SnailmallException("未找到该品类"); throw new SnailmallException("未找到该品类");
} }
//2.根据父亲id获取这个父亲下一级所有子ID // 首先对传入的品类 `id` 参数进行合法性校验,如果 `id` 为 `null`,说明参数不合法,此时抛出 `SnailmallException` 异常,
// 告知调用方没有找到对应的品类,遵循了先进行参数校验再进行业务操作的良好编程习惯,避免后续因参数问题导致的潜在错误。
// 2.根据父亲id获取这个父亲下一级所有子ID
List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId);
if(CollectionUtils.isEmpty(categoryList)){ // 调用 `CategoryMapper` 接口的 `selectCategoryChildrenByParentId` 方法,根据传入的 `categoryId`(作为父品类 `id`
// 从数据库中查询获取该父品类下一级的所有子品类记录,返回的结果是一个 `List<Category>` 类型的列表,包含了符合条件的子品类对象集合。
if (CollectionUtils.isEmpty(categoryList)) {
log.info("该节点下没有任何子节点"); log.info("该节点下没有任何子节点");
} }
// 使用Apache Commons Collections工具类的 `CollectionUtils.isEmpty` 方法判断获取到的子品类列表是否为空,
// 如果为空,则通过日志记录器 `log` 记录一条信息日志,表示当前节点下没有任何子节点,方便后续查看业务执行情况进行调试和监控。
return ServerResponse.createBySuccess(categoryList); return ServerResponse.createBySuccess(categoryList);
// 最后,使用 `ServerResponse` 类的静态方法 `createBySuccess` 创建一个表示成功的响应对象,并将获取到的子品类列表作为数据传入,
// 返回给调用方,告知操作成功并携带相应的子品类数据,如果列表为空则表示没有子品类数据可返回,但操作本身是成功执行的。
} }
@Override @Override
public ServerResponse addCategory(String categoryName, int parentId) { public ServerResponse addCategory(String categoryName, int parentId) {
//1.校验参数 // 1.校验参数
if(StringUtils.isBlank(categoryName)){ if (StringUtils.isBlank(categoryName)) {
throw new SnailmallException("品类名字不能为空"); throw new SnailmallException("品类名字不能为空");
} }
//2.创建类目 // 对传入的品类名称参数进行校验使用Apache Commons Lang3工具类的 `StringUtils.isBlank` 方法判断名称是否为空(包括 `null` 或者空字符串情况),
// 如果为空则抛出 `SnailmallException` 异常,提示品类名字不能为空,确保添加品类时必须有合法的名称。
// 2.创建类目
Category category = new Category(); Category category = new Category();
category.setName(categoryName); category.setName(categoryName);
category.setParentId(parentId); category.setParentId(parentId);
category.setStatus(true); category.setStatus(true);
// 创建一个 `Category` 实体对象,将传入的品类名称和父品类 `id` 设置到该对象的对应属性上,并默认将品类的状态设置为 `true`(表示启用、可用状态等),
// 准备将这个对象插入到数据库中,完成添加品类的操作。
int resultCount = categoryMapper.insert(category); int resultCount = categoryMapper.insert(category);
if(resultCount > 0){ // 调用 `CategoryMapper` 接口的 `insert` 方法,将创建好的 `Category` 实体对象插入到数据库中,该方法会返回受影响的行数,
// 用于判断插入操作是否成功执行,正常情况下如果成功插入一条记录,返回值应该为 `1`。
if (resultCount > 0) {
return ServerResponse.createBySuccessMessage("添加品类成功"); return ServerResponse.createBySuccessMessage("添加品类成功");
} }
return ServerResponse.createByErrorMessage("添加品类失败"); return ServerResponse.createByErrorMessage("添加品类失败");
// 根据插入操作返回的受影响行数判断,如果大于 `0`,说明插入成功,使用 `ServerResponse` 类的静态方法 `createBySuccessMessage` 创建一个表示成功的响应对象,
// 并传入提示消息 "添加品类成功" 返回给调用方;如果插入失败(返回值不大于 `0`),则使用 `createByErrorMessage` 方法创建一个表示失败的响应对象,
// 传入 "添加品类失败" 消息返回给调用方,告知调用方添加品类的操作结果情况。
} }
@Override @Override
public ServerResponse<String> updateCategoryName(String categoryName, Integer categoryId) { public ServerResponse<String> updateCategoryName(String categoryName, Integer categoryId) {
//1.校验参数 // 1.校验参数
if(StringUtils.isBlank(categoryName)){ if (StringUtils.isBlank(categoryName)) {
throw new SnailmallException("品类名字不能为空"); throw new SnailmallException("品类名字不能为空");
} }
//2.根据id获取品类 // 同样先对传入的品类名称参数进行校验,确保名称不为空,若为空则抛出 `SnailmallException` 异常,提示品类名字不能为空,
// 保证只有合法的名称才能进行后续的更新操作。
// 2.根据id获取品类
Category tmpCat = categoryMapper.selectByPrimaryKey(categoryId); Category tmpCat = categoryMapper.selectByPrimaryKey(categoryId);
if(tmpCat == null){ // 调用 `CategoryMapper` 接口的 `selectByPrimaryKey` 方法,根据传入的品类 `id` 从数据库中查询获取对应的 `Category` 实体对象,
// 如果能找到则返回该对象,若找不到(即 `tmpCat` 为 `null`)则说明要更新的品类不存在。
if (tmpCat == null) {
throw new SnailmallException("品类不存在"); throw new SnailmallException("品类不存在");
} }
//3.更新品类名称 // 如果根据 `id` 查询不到对应的品类对象,则抛出 `SnailmallException` 异常,告知调用方品类不存在,无法进行名称更新操作。
// 3.更新品类名称
Category category = new Category(); Category category = new Category();
category.setId(categoryId); category.setId(categoryId);
category.setName(categoryName); category.setName(categoryName);
// 创建一个新的 `Category` 实体对象,将传入的品类 `id` 和要更新的品类名称设置到该对象的对应属性上,
// 准备用这个对象来更新数据库中对应品类的名称信息,这里只设置了 `id` 和 `name` 属性,其他属性可能保持原有值不变(根据数据库操作逻辑)。
int resultCount = categoryMapper.updateByPrimaryKeySelective(category); int resultCount = categoryMapper.updateByPrimaryKeySelective(category);
if(resultCount > 0){ // 调用 `CategoryMapper` 接口的 `updateByPrimaryKeySelective` 方法,根据创建的 `Category` 实体对象中设置的非空属性值(这里主要是 `name` 属性),
// 对数据库中主键为对应 `id` 的品类记录进行选择性更新操作,即只更新设置了值的属性,该方法返回受影响的行数用于判断更新操作是否成功执行。
if (resultCount > 0) {
return ServerResponse.createBySuccessMessage("更新品类名称成功"); return ServerResponse.createBySuccessMessage("更新品类名称成功");
} }
return ServerResponse.createByErrorMessage("更新品类名称失败"); return ServerResponse.createByErrorMessage("更新品类名称失败");
// 根据更新操作返回的受影响行数判断,如果大于 `0`,说明更新成功,使用 `ServerResponse` 类的静态方法 `createBySuccessMessage` 创建一个表示成功的响应对象,
// 传入提示消息 "更新品类名称成功" 返回给调用方;如果更新失败(返回值不大于 `0`),则使用 `createByErrorMessage` 方法创建一个表示失败的响应对象,
// 传入 "更新品类名称失败" 消息返回给调用方,告知调用方更新品类名称的操作结果情况。
} }
@Override @Override
public ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId) { public ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId) {
//1、创建一个空Set用来存放不重复的品类对象--去重 // 1、创建一个空Set用来存放不重复的品类对象--去重
Set<Category> categorySet = Sets.newHashSet(); Set<Category> categorySet = Sets.newHashSet();
//2、递归获取所有的子节点儿子、孙子、等等包括自己也添加进去 // 使用Google Guava库的 `Sets.newHashSet` 方法创建一个空的 `Set` 集合,用于存放品类对象,这里的 `Set` 集合特性可以保证元素的唯一性,
findChildCategory(categorySet,categoryId); // 目的是在后续递归获取品类及其子节点信息的过程中避免重复添加相同的品类对象,起到去重的作用。
//3、将递归获取到的品类id取出来放进list中
// 2、递归获取所有的子节点儿子、孙子、等等包括自己也添加进去
findChildCategory(categorySet, categoryId);
// 调用私有方法 `findChildCategory`,传入创建好的 `categorySet` 和要查询的品类 `id`,开始递归地获取指定品类及其所有层级的子品类对象,
// 将获取到的品类对象添加到 `categorySet` 集合中,实现深度遍历品类树结构的功能。
// 3、将递归获取到的品类id取出来放进list中
List<Integer> categoryIdList = new ArrayList<>(); List<Integer> categoryIdList = new ArrayList<>();
if(categoryId != null){ if (categoryId!= null) {
for(Category category:categorySet){ for (Category category : categorySet) {
categoryIdList.add(category.getId()); categoryIdList.add(category.getId());
} }
} }
// 创建一个 `List<Integer>` 类型的列表,用于存放从 `categorySet` 集合中提取出来的品类 `id` 值,
// 如果传入的 `categoryId` 不为 `null`,则遍历 `categorySet` 中的每个品类对象,将其 `id` 属性值添加到 `categoryIdList` 列表中,
// 这样最终得到的列表就是包含了指定品类及其所有子品类的 `id` 集合,方便后续业务使用或者返回给调用方等操作。
return ServerResponse.createBySuccess(categoryIdList); return ServerResponse.createBySuccess(categoryIdList);
// 使用 `ServerResponse` 类的静态方法 `createBySuccess` 创建一个表示成功的响应对象,并将包含品类 `id` 列表的 `categoryIdList` 作为数据传入,
// 返回给调用方,告知操作成功并携带相应的品类 `id` 数据,方便调用方根据这些 `id` 进一步获取详细的品类信息等操作。
} }
private Set<Category> findChildCategory(Set<Category> categorySet,Integer categoryId){ private Set<Category> findChildCategory(Set<Category> categorySet, Integer categoryId) {
//4、如果自己不为空的话首先把自己添加进去如果自己为空这个递归分支就结束所以也是一个停止条件 // 4、如果自己不为空的话首先把自己添加进去如果自己为空这个递归分支就结束所以也是一个停止条件
Category category = categoryMapper.selectByPrimaryKey(categoryId); Category category = categoryMapper.selectByPrimaryKey(categoryId);
if(category != null){ if (category!= null) {
categorySet.add(category); categorySet.add(category);
} }
//5、根据父亲id获取下一级所有品类即先获取儿子们 // 首先根据传入的品类 `id` 通过 `CategoryMapper` 接口的 `selectByPrimaryKey` 方法从数据库中查询对应的 `Category` 实体对象,
// 如果查询到的对象不为 `null`,说明该品类存在,则将其添加到传入的 `categorySet` 集合中,这是递归过程中的一个基础操作,
// 同时如果查询结果为空,说明已经遍历到品类树的叶子节点或者不存在该 `id` 对应的品类,此时这个递归分支就结束,符合递归的停止条件设定。
// 5、根据父亲id获取下一级所有品类即先获取儿子们
List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId);
//6、根据每一个儿子再获取儿子的儿子们递归下去 // 调用 `CategoryMapper` 接口的 `selectCategoryChildrenByParentId` 方法,根据当前品类的 `id`(作为父品类 `id`
for(Category categoryItem:categoryList){ // 从数据库中查询获取该父品类下一级的所有子品类记录,返回的结果是一个 `List<Category>` 类型的列表,包含了符合条件的子品类对象集合,
findChildCategory(categorySet,categoryItem.getId()); // 这些就是当前品类的“儿子”品类。
// 6、根据每一个儿子再获取儿子的儿子们递归下去
for (Category categoryItem : categoryList) {
findChildCategory(categorySet, categoryItem.getId());
} }
// 遍历获取到的“儿子”品类列表,对于每个“儿子”品类,再次调用 `findChildCategory` 方法,传入当前的 `categorySet` 和“儿子”品类的 `id`
// 继续递归地获取“儿子”品类的子品类(即“孙子”品类等),如此循环下去,实现深度优先遍历整个品类树结构,将所有层级的品类对象添加到 `categorySet` 集合中。
return categorySet; return categorySet;
} }
@Override @Override
public ServerResponse getCategoryDetail(Integer categoryId) { public ServerResponse getCategoryDetail(Integer categoryId) {
if(categoryId == null){ if (categoryId == null) {
return ServerResponse.createByErrorMessage("参数不能为空"); return ServerResponse.createByErrorMessage("参数不能为空");
} }
// 先对传入的品类 `id` 参数进行合法性校验,如果 `id` 为 `null`,说明参数不合法,此时使用 `ServerResponse` 类的静态方法 `createByErrorMessage` 创建一个表示失败的响应对象,
// 传入提示消息 "参数不能为空" 返回给调用方,告知调用方参数不符合要求。
Category category = categoryMapper.selectByPrimaryKey(categoryId); Category category = categoryMapper.selectByPrimaryKey(categoryId);
if(category == null){ // 调用 `CategoryMapper` 接口的 `selectByPrimaryKey` 方法,根据传入的品类 `id` 从数据库中查询获取对应的 `Category` 实体对象,
// 如果能找到则返回该对象,若找不到则说明要获取详细信息的品类不存在。
if (category == null) {
return ServerResponse.createByErrorMessage("品类不存在"); return ServerResponse.createByErrorMessage("品类不存在");
} }
// 如果根据 `id` 查询不到对应的品类对象,则使用 `ServerResponse` 类的静态方法 `createByErrorMessage` 创建一个表示失败的响应对象,
// 传入提示消息 "品类不存在" 返回给调用方,告知调用方要获取详细信息的品类不存在;如果查询到了品类对象,则继续下一步操作。
return ServerResponse.createBySuccess(category); return ServerResponse.createBySuccess(category);
// 使用 `ServerResponse` 类的静态方法 `createBySuccess` 创建一个表示成功的响应对象,并将查询到的 `Category` 实体对象作为数据传入,
// 返回给调用方,告知操作成功并携带相应的品类详细信息,方便调用方获取和使用该品类的具体数据内容。
} }
}
}

@ -4,6 +4,10 @@ import com.njupt.swg.common.resp.ServerResponse;
import com.njupt.swg.entity.Category; import com.njupt.swg.entity.Category;
/** /**
* `ICategoryService`Category
* 使
* 便
*
* @Author swg. * @Author swg.
* @Date 2019/1/2 12:54 * @Date 2019/1/2 12:54
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
@ -11,19 +15,61 @@ import com.njupt.swg.entity.Category;
*/ */
public interface ICategoryService { public interface ICategoryService {
/** 根据类目id获取其下面所有的一级子类目 **/ /**
* id
* `id``categoryId` `ServerResponse`
*
* 便
*
* @param categoryId `id` `id`
* @return `ServerResponse` 便
*/
ServerResponse getCategory(Integer categoryId); ServerResponse getCategory(Integer categoryId);
/** 新建一个商品类目 **/ /**
*
* `categoryName` `id``parentId`
* `ServerResponse`
* 便
*
* @param categoryName
* @param parentId `id` `0`
* @return `ServerResponse`
*/
ServerResponse addCategory(String categoryName, int parentId); ServerResponse addCategory(String categoryName, int parentId);
/** 更新品类名称 **/ /**
*
* `categoryName` `id``categoryId`
* `ServerResponse<String>` `<String>`
* 便
*
* @param categoryName
* @param categoryId `id` `id`
* @return `ServerResponse<String>`
*/
ServerResponse<String> updateCategoryName(String categoryName, Integer categoryId); ServerResponse<String> updateCategoryName(String categoryName, Integer categoryId);
/** 递归查询出所有品类 **/ /**
*
* `id``categoryId`
* `ServerResponse`
* `id` 便
*
* @param categoryId `id` `id`
* @return `ServerResponse` 使
*/
ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId); ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId);
/** 被其他服务调用的接口 **/ /**
*
* `id``categoryId`
* `ServerResponse`
* 便
*
* @param categoryId `id` `id`
* @return `ServerResponse` 使
*/
ServerResponse getCategoryDetail(Integer categoryId); ServerResponse getCategoryDetail(Integer categoryId);
} }

@ -42,6 +42,10 @@
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-monitor</artifactId> <artifactId>spring-cloud-config-monitor</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
</dependencies> </dependencies>

@ -0,0 +1,4 @@
package com.njupt.swg;
public @interface EnableConfigServer {
}

@ -5,14 +5,38 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer; import org.springframework.cloud.config.server.EnableConfigServer;
/**
* `SnailmallConfigServerApplication`Spring Boot
* Spring Boot使
*
*/
@SpringBootApplication @SpringBootApplication
// `@SpringBootApplication` 是一个组合注解它整合了多个Spring Boot相关的注解例如 `@Configuration`、`@EnableAutoConfiguration` 和 `@ComponentScan` 等。
// `@Configuration` 表示这个类可以作为一个配置类用于定义Spring容器中的各种Bean以及配置信息
// `@EnableAutoConfiguration` 会根据项目中添加的依赖自动配置Spring应用上下文比如自动配置数据源、Web相关组件等使得开发者可以快速搭建应用而无需手动进行大量繁琐的配置
// `@ComponentScan` 则用于指定Spring要扫描的包路径以便自动发现并注册项目中的各种组件如 `@Controller`、`@Service`、`@Repository` 等标注的类到Spring容器中方便进行依赖注入等操作。
// 总的来说,`@SpringBootApplication` 注解让这个类具备了启动Spring Boot应用并自动配置相关组件的核心功能。
@EnableDiscoveryClient @EnableDiscoveryClient
// `@EnableDiscoveryClient` 注解用于启用服务发现客户端功能在微服务架构中往往会有服务注册与发现中心例如Eureka、Consul等
// 各个微服务通过这个注解可以将自己注册到服务发现中心,并能从其中发现其他的服务实例,方便不同微服务之间进行相互调用和协作,
// 使得服务之间的通信更加灵活和可管理,基于服务名称就能进行服务查找和调用,而不用硬编码具体的服务地址等信息。
@EnableConfigServer @EnableConfigServer
// `@EnableConfigServer` 注解用于启用Spring Cloud配置服务器功能它允许应用作为一个配置中心集中管理各个微服务的配置文件
// 其他微服务可以从这个配置服务器获取它们所需的配置信息,例如数据库连接配置、各种业务相关的参数配置等,实现了配置的集中化管理,
// 方便在多个微服务环境中统一维护和更新配置,提高配置管理的效率和灵活性。
public class SnailmallConfigServerApplication { public class SnailmallConfigServerApplication {
/**
* `main` JavaSpring Boot
* `SpringApplication.run` `SnailmallConfigServerApplication.class``args`
* Spring BootSpringTomcatWeb
* 使
*/
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SnailmallConfigServerApplication.class, args); SpringApplication.run(SnailmallConfigServerApplication.class, args);
} }
} }

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
@ -122,6 +122,11 @@
<groupId>commons-net</groupId> <groupId>commons-net</groupId>
<artifactId>commons-net</artifactId> <artifactId>commons-net</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -8,26 +8,44 @@ import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
/** /**
* `CommonCacheUtil` Redis
* 便
* `JedisPoolWrapper` `JedisPool` Redis
* `SnailmallException` 便
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 15:03 * @Date 2019/1/1 15:03
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Component @Component
// `@Component` 注解是Spring框架提供的用于将当前类标记为一个普通的Spring组件意味着这个类会被Spring容器扫描并管理
// 可以通过依赖注入的方式在其他类中使用该类的实例,在这里表明 `CommonCacheUtil` 是一个可被复用的组件,在整个项目的缓存相关操作中发挥作用。
@Slf4j @Slf4j
// `@Slf4j` 是Lombok库提供的注解用于自动生成一个名为 `log` 的SLF4J日志记录器在类中可以方便地使用这个日志记录器记录各种级别的日志信息
// 例如记录与 Redis 缓存交互过程中的关键步骤、出现的异常情况等,有助于后续的问题排查和系统监控。
public class CommonCacheUtil { public class CommonCacheUtil {
@Autowired @Autowired
private JedisPoolWrapper jedisPoolWrapper; private JedisPoolWrapper jedisPoolWrapper;
// 通过Spring的依赖注入Dependency Injection机制使用 `@Autowired` 注解自动注入 `JedisPoolWrapper` 类型的实例,
// `JedisPoolWrapper` 应该是一个对 `JedisPool`Jedis连接池用于管理与Redis的连接进行包装的类通过它可以获取到 `JedisPool` 实例,
// 进而获取 `Jedis`Redis客户端操作对象来与 Redis 缓存进行各种操作,实现了对 Redis 连接资源的有效管理和复用。
/** /**
* key * key
* Redis `key` `value` Redis
* Redis
*
* @param key Redis
* @param value Redis
* `key`
*/ */
public void cache(String key, String value) { public void cache(String key, String value) {
try { try {
JedisPool pool = jedisPoolWrapper.getJedisPool(); JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) { if (pool!= null) {
try (Jedis Jedis = pool.getResource()) { try (Jedis Jedis = pool.getResource()) {
Jedis.select(0); Jedis.select(0);
Jedis.set(key, value); Jedis.set(key, value);
@ -37,16 +55,26 @@ public class CommonCacheUtil {
log.error("redis存值失败", e); log.error("redis存值失败", e);
throw new SnailmallException("redis报错"); throw new SnailmallException("redis报错");
} }
// 首先尝试通过注入的 `jedisPoolWrapper` 获取 `JedisPool` 实例,如果获取到的 `JedisPool` 不为 `null`
// 则从连接池中获取一个 `Jedis` 资源(这是与 Redis 进行交互的客户端对象),调用 `Jedis` 的 `select` 方法选择 Redis 的第 `0` 个数据库默认数据库Redis可以有多个数据库
// 然后使用 `set` 方法将传入的 `key` 和 `value` 存入到 Redis 中。如果在这个过程中出现任何异常,
// 会通过日志记录器 `log` 记录错误信息(使用 `error` 级别日志记录,因为这属于操作失败的异常情况),
// 并抛出 `SnailmallException` 异常,告知调用方 Redis 操作出现报错,方便统一的异常处理以及后续排查问题。
} }
/** /**
* key * key
* Redis `key` `key` `value`
* `null`使
*
* @param key Redis Redis `null`
* @return Redis `key` `key` `null`便
*/ */
public String getCacheValue(String key) { public String getCacheValue(String key) {
String value = null; String value = null;
try { try {
JedisPool pool = jedisPoolWrapper.getJedisPool(); JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) { if (pool!= null) {
try (Jedis Jedis = pool.getResource()) { try (Jedis Jedis = pool.getResource()) {
Jedis.select(0); Jedis.select(0);
value = Jedis.get(key); value = Jedis.get(key);
@ -57,16 +85,29 @@ public class CommonCacheUtil {
throw new SnailmallException("redis报错"); throw new SnailmallException("redis报错");
} }
return value; return value;
// 同样先尝试获取 `JedisPool` 实例,若不为 `null`,则获取 `Jedis` 资源并选择第 `0` 个 Redis 数据库,
// 接着使用 `Jedis` 的 `get` 方法根据传入的 `key` 去获取对应的缓存值,将获取到的值赋给局部变量 `value`
// 如果在操作过程中出现异常,记录错误日志并抛出 `SnailmallException` 异常,最后将 `value`(可能为 `null` 或者获取到的实际缓存值)返回给调用者,
// 以便调用者知晓是否获取到了期望的缓存数据以及进行后续的业务处理。
} }
/** /**
* key * 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) { public long cacheNxExpire(String key, String value, int expire) {
long result = 0; long result = 0;
try { try {
JedisPool pool = jedisPoolWrapper.getJedisPool(); JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) { if (pool!= null) {
try (Jedis jedis = pool.getResource()) { try (Jedis jedis = pool.getResource()) {
jedis.select(0); jedis.select(0);
result = jedis.setnx(key, value); result = jedis.setnx(key, value);
@ -79,14 +120,22 @@ public class CommonCacheUtil {
} }
return result; return result;
// 先获取 `JedisPool` 实例,若不为 `null`,获取 `Jedis` 资源并选择第 `0` 个数据库,然后使用 `jedis` 的 `setnx` 方法尝试设置键值对,
// 将返回结果(表示是否设置成功)赋给 `result` 变量,接着使用 `expire` 方法为刚设置的键设置指定的过期时间(`expire` 参数指定的秒数),
// 如果在整个过程中出现异常,记录错误日志并抛出 `SnailmallException` 异常,最后将 `result``setnx` 操作的结果)返回给调用者,
// 方便调用者知晓是否成功存入了带有过期时间的缓存数据以及进行后续处理。
} }
/** /**
* key * key
* Redis `key` 使 Redis
*
*
* @param key Redis
*/ */
public void delKey(String key) { public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool(); JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) { if (pool!= null) {
try (Jedis jedis = pool.getResource()) { try (Jedis jedis = pool.getResource()) {
jedis.select(0); jedis.select(0);
try { try {
@ -97,8 +146,8 @@ public class CommonCacheUtil {
} }
} }
} }
// 先获取 `JedisPool` 实例,若不为 `null`,获取 `Jedis` 资源并选择第 `0` 个数据库,然后使用 `jedis` 的 `del` 方法尝试删除指定的 `key` 及其对应的数据,
// 如果在删除过程中出现异常,通过日志记录器记录错误信息,并抛出 `SnailmallException` 异常,告知调用方 Redis 删除操作出现报错,
// 方便统一的异常处理以及后续排查删除操作失败的原因等情况。
} }
}
}

@ -9,34 +9,71 @@ import redis.clients.jedis.JedisPoolConfig;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
/** /**
* `JedisPoolWrapper` `JedisPool`Jedis Redis
* `JedisPool` 便便 Redis
* Redis Redis
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 15:00 * @Date 2019/1/1 15:00
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC redisredishash * @DESC redisredishash
*/ */
@Component @Component
// `@Component` 注解是Spring框架提供的用于将当前类标记为一个普通的Spring组件会被Spring容器扫描并管理
// 意味着这个类的实例可以通过依赖注入的方式在其他类中使用,表明 `JedisPoolWrapper` 在整个项目的 Redis 连接管理方面起着重要作用,是可复用的一个组件。
@Slf4j @Slf4j
// `@Slf4j` 是Lombok库提供的注解用于自动生成一个名为 `log` 的SLF4J日志记录器在类中可以方便地使用这个日志记录器记录各种级别的日志信息
// 比如在这里用于记录 `JedisPool` 初始化过程中是成功还是出现异常等情况,便于后续的问题排查和系统监控。
public class JedisPoolWrapper { public class JedisPoolWrapper {
@Autowired @Autowired
private Parameters parameters; private Parameters parameters;
// 通过Spring的依赖注入Dependency Injection机制使用 `@Autowired` 注解自动注入 `Parameters` 类型的实例,
// `Parameters` 应该是一个用于存储各种参数配置的类,在这里主要是期望从中获取与 Redis 连接相关的配置参数,例如最大连接数、最大空闲连接数、连接等待时间等信息,
// 为后续创建 `JedisPool` 实例提供必要的参数依据。
private JedisPool jedisPool = null; private JedisPool jedisPool = null;
// 定义一个私有变量 `jedisPool`,用于存储创建好的 `JedisPool` 实例,初始值设为 `null`,在 `init` 方法中会根据配置参数进行实例化操作,
// 后续其他类可以通过 `getJedisPool` 方法获取这个实例来与 Redis 进行连接和交互操作。
@PostConstruct @PostConstruct
public void init(){ public void init() {
try { try {
JedisPoolConfig config = new JedisPoolConfig(); JedisPoolConfig config = new JedisPoolConfig();
// 创建一个 `JedisPoolConfig` 对象,它用于配置 `JedisPool` 的各种属性,例如连接池的最大连接数、最大空闲连接数、获取连接的最大等待时间等,
// 这些属性的合理设置对于 Redis 连接的性能和资源管理非常重要。
config.setMaxTotal(parameters.getRedisMaxTotal()); config.setMaxTotal(parameters.getRedisMaxTotal());
// 通过注入的 `parameters` 对象获取 Redis 连接池的最大连接数配置参数,并设置到 `JedisPoolConfig` 实例中,
// `setMaxTotal` 方法用于指定连接池中允许存在的最大连接数量,避免创建过多连接导致资源浪费或者系统负担过重等问题。
config.setMaxIdle(parameters.getRedisMaxIdle()); config.setMaxIdle(parameters.getRedisMaxIdle());
// 获取 Redis 连接池的最大空闲连接数配置参数,并设置到 `JedisPoolConfig` 实例中,
// `setMaxIdle` 方法用于设定连接池中最大的空闲连接数量,即当连接池中空闲连接数超过这个值时,多余的空闲连接会被释放,有助于合理利用资源。
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis()); 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连接池成功】"); log.info("【初始化redis连接池成功】");
}catch (Exception e){ // 如果 `JedisPool` 实例创建成功,通过日志记录器 `log` 记录一条信息日志,表示 Redis 连接池初始化成功,方便后续查看系统启动过程中相关组件的初始化情况。
log.error("【初始化redis连接池失败】",e); } catch (Exception e) {
log.error("【初始化redis连接池失败】", e);
// 如果在创建 `JedisPool` 实例的过程中出现任何异常,通过日志记录器 `log` 记录一条错误级别日志,详细记录异常信息(通过传入 `e` 参数),
// 方便后续排查初始化失败的原因,及时发现和解决与 Redis 连接池相关的问题。
} }
} }
public JedisPool getJedisPool() { public JedisPool getJedisPool() {
return jedisPool; return jedisPool;
} }
} // 定义了一个公共的方法 `getJedisPool`,用于对外提供获取 `JedisPool` 实例的接口,
// 其他类可以调用这个方法来获取已经初始化好(或者初始化失败为 `null` 的情况)的 `JedisPool` 实例,进而利用这个实例从连接池中获取 `Jedis` 对象来操作 Redis 缓存。
}

@ -5,24 +5,57 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* `Parameters` Redis
* Spring `@Value` `application.properties` `application.yml`
* `@Component` Spring 便使
* 使 `@Data` Lombok `getter``setter`
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 14:27 * @Date 2019/1/1 14:27
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Component @Component
// `@Component` 注解是Spring框架提供的用于将当前类标记为一个普通的Spring组件意味着这个类会被Spring容器扫描并管理
// 使得其他类可以通过依赖注入的方式使用该类的实例,在这里表明 `Parameters` 类作为一个可复用的组件,为整个项目提供参数配置相关的服务,
// 特别是在涉及 Redis 连接配置等方面发挥作用。
@Data @Data
// `@Data` 注解由Lombok库提供它会自动为类中的所有非静态、非 `final` 属性生成对应的 `getter` 和 `setter` 方法,
// 同时还会生成 `toString`、`equals` 和 `hashCode` 方法,避免了手动编写这些重复的、样板式的代码,让代码结构更加简洁清晰,
// 方便在其他地方获取和设置该类中定义的各个参数属性的值。
public class Parameters { public class Parameters {
/*****redis config start*******/ /*****redis config start*******/
@Value("${redis.host}") @Value("${redis.host}")
private String redisHost; private String redisHost;
// `@Value` 注解用于将配置文件(通常是 `application.properties` 或 `application.yml` 等格式)中的属性值注入到对应的成员变量中。
// 这里 `${redis.host}` 表示从配置文件中查找名为 `redis.host` 的配置项,将其值赋给 `redisHost` 变量,
// 该变量用于存储 Redis 服务器的主机地址,比如 `"localhost"` 或者具体的IP地址等是连接 Redis 服务器必不可少的配置信息。
@Value("${redis.port}") @Value("${redis.port}")
private int redisPort; private int redisPort;
// 同样通过 `@Value` 注解从配置文件中获取名为 `redis.port` 的配置项的值,并赋给 `redisPort` 变量,
// 这个变量用于存储 Redis 服务器的端口号,常见的 Redis 默认端口是 `6379`,但也可以根据实际部署情况进行配置修改,
// 与 `redisHost` 一起确定了 Redis 服务器的网络地址,以便后续建立连接。
@Value("${redis.max-idle}") @Value("${redis.max-idle}")
private int redisMaxTotal; private int redisMaxTotal;
// 此处存在一个可能的配置项命名混淆问题(按照语义推测可能应该是 `redis.max-total` 获取最大连接总数,不过以代码实际为准),
// 通过 `@Value` 注解从配置文件读取 `redis.max-idle` 配置项的值注入到 `redisMaxTotal` 变量,该变量用于定义 Redis 连接池相关的一个参数,
// 可能是用于控制连接池中某种数量相关的上限值(具体取决于项目对该参数的实际使用方式和含义设定)。
@Value("${redis.max-total}") @Value("${redis.max-total}")
private int redisMaxIdle; private int redisMaxIdle;
// 类似地,从配置文件中获取 `redis.max-total` 配置项的值赋给 `redisMaxIdle` 变量,同样是用于 Redis 连接池相关的参数配置,
// 按照常规理解,它可能是用于设定连接池中最大的空闲连接数量,不过具体含义还是要结合项目整体对该参数的使用逻辑来确定,
// 合理设置这些参数有助于优化 Redis 连接资源的管理和利用效率。
@Value("${redis.max-wait-millis}") @Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis; private int redisMaxWaitMillis;
// 通过 `@Value` 注解获取配置文件中 `redis.max-wait-millis` 配置项的值,并赋值给 `redisMaxWaitMillis` 变量,
// 这个变量用于设定在获取 Redis 连接时,如果连接池中没有可用连接,最长等待的时间(单位为毫秒),
// 超过这个时间将会抛出异常,以此避免长时间等待获取连接导致业务阻塞的情况发生,对系统的性能和稳定性有重要影响。
/*****redis config end*******/ /*****redis config end*******/
} }

@ -7,16 +7,33 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
/** /**
* `CategoryClient`Spring Cloud OpenFeign `category-service`
* `category-service` 使便
* HTTP便
*
* @Author swg. * @Author swg.
* @Date 2019/1/3 16:56 * @Date 2019/1/3 16:56
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@FeignClient("category-service") @FeignClient("category-service")
// `@FeignClient` 注解用于标识这是一个Feign客户端接口括号中的 `"category-service"` 是要调用的目标微服务在服务注册与发现中心注册的名称,
// 通过这个名称Feign会在运行时根据服务发现机制找到对应的微服务实例地址并发起HTTP请求与之通信实现跨微服务的调用功能。
public interface CategoryClient { public interface CategoryClient {
@RequestMapping("/manage/category/get_category_detail.do") @RequestMapping("/manage/category/get_category_detail.do")
ServerResponse getCategoryDetail(@RequestParam("categoryId") Integer categoryId); 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") @RequestMapping("/manage/category/get_deep_category.do")
ServerResponse getDeepCategory(@RequestParam(value = "categoryId") Integer categoryId); ServerResponse getDeepCategory(@RequestParam(value = "categoryId") Integer categoryId);
} // 此方法定义了对 `category-service` 微服务中 `/manage/category/get_deep_category.do` 接口的远程调用逻辑。
// 同样接收一个 `Integer` 类型的 `categoryId` 参数(通过 `@RequestParam` 注解绑定到请求参数中,默认参数名称就是 `"categoryId"`
// 用于向目标微服务请求递归获取指定 `categoryId` 的品类及其所有子品类信息,返回的 `ServerResponse` 对象包含了请求结果状态码、提示消息以及相关的品类数据(如品类 `id` 列表等形式),
// 方便调用者获取完整的品类层级结构相关信息,以进行后续的业务处理等操作。
}

@ -4,41 +4,87 @@ import com.google.common.collect.Sets;
import java.util.Set; import java.util.Set;
/** /**
* `Constants`便使
* 使使
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 13:19 * @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
public class Constants { public class Constants {
/**自定义状态码 start**/
/**
* start
* HTTP
* 使
*/
public static final int RESP_STATUS_OK = 200; public static final int RESP_STATUS_OK = 200;
// `RESP_STATUS_OK` 常量定义了表示操作成功的状态码,值为 `200`,通常在请求处理成功,无任何错误且能够正常返回期望的数据时使用该状态码,
// 符合HTTP协议中常用的成功状态码规范方便前端或者其他调用方根据这个状态码判断请求是否顺利完成并获取相应的数据。
public static final int RESP_STATUS_NOAUTH = 401; public static final int RESP_STATUS_NOAUTH = 401;
// `RESP_STATUS_NOAUTH` 常量表示未授权的状态码,值为 `401`,当客户端发起的请求需要身份验证但未提供有效的认证信息,
// 或者认证信息过期、无效等情况时,服务端会返回这个状态码给客户端,告知其需要重新进行身份认证才能继续操作,
// 遵循了HTTP协议中关于未授权情况的状态码约定有助于在涉及权限验证的业务场景中统一处理相关逻辑。
public static final int RESP_STATUS_INTERNAL_ERROR = 500; public static final int RESP_STATUS_INTERNAL_ERROR = 500;
// `RESP_STATUS_INTERNAL_ERROR` 常量定义了表示服务器内部错误的状态码,值为 `500`,当服务端在处理请求过程中发生了未预期的错误,
// 例如代码运行时出现异常、数据库操作失败等内部问题时,会返回这个状态码给客户端,提示客户端请求处理出现了服务器端的问题,
// 这也是符合HTTP协议中对于服务器内部错误情况的标准状态码设定方便客户端知晓请求失败的大致原因类别。
public static final int RESP_STATUS_BADREQUEST = 400; 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; int PRODUCT_ON = 1;
// `PRODUCT_ON` 常量表示产品处于开启、可用、上线等正常状态,值为 `1`,在业务逻辑中可以通过判断产品的状态是否等于这个值,
// 来确定产品是否可以正常展示、销售或者参与其他业务操作,方便统一管理产品的可用状态逻辑。
int PRODUCT_OFF = 2; int PRODUCT_OFF = 2;
// `PRODUCT_OFF` 常量代表产品处于关闭、下架等不可用状态,值为 `2`,当产品需要暂停销售、暂时隐藏等情况下,可以将其状态设置为这个值,
// 使得在相关业务处理中能够依据这个状态值进行相应的处理,例如不展示该产品给用户等操作。
int PRODUCT_DELETED = 3; int PRODUCT_DELETED = 3;
// `PRODUCT_DELETED` 常量用于表示产品已经被删除的状态,值为 `3`,当产品从系统中彻底删除后,可以用这个状态值来标记,
// 在涉及产品数据查询、展示等业务逻辑中,遇到这个状态值的产品记录可以进行相应的特殊处理(比如不返回给前端等),便于对产品的生命周期状态进行准确管理。
} }
public interface ProductListOrderBy{ public interface ProductListOrderBy {
Set<String> PRICE_ASC_DESC = Sets.newHashSet("price_desc","price_asc"); Set<String> PRICE_ASC_DESC = Sets.newHashSet("price_desc", "price_asc");
// `PRICE_ASC_DESC` 是一个 `Set<String>` 类型的常量集合通过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__"; 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; 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_"; public static final String PRODUCT_TOKEN_STOCK_PREFIX = "product__stock_";
// `PRODUCT_TOKEN_STOCK_PREFIX` 常量定义了在Redis中存储产品库存相关令牌Token可能与产品库存验证、操作权限等相关时的键前缀
// 与 `PRODUCT_TOKEN_PREFIX` 类似用于在Redis中对产品库存相关的键进行规范命名和分类管理方便后续对产品库存相关数据的操作和维护。
} }

@ -1,6 +1,5 @@
package com.njupt.swg.common.exception; package com.njupt.swg.common.exception;
import com.njupt.swg.common.constants.Constants; import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ServerResponse; import com.njupt.swg.common.resp.ServerResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -9,25 +8,51 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* `ExceptionHandlerAdvice`
* 使Spring
* 便使
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 13:21 * @Date 2019/1/1 13:21
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@ControllerAdvice @ControllerAdvice
// `@ControllerAdvice` 注解用于标识这个类是一个全局的控制器增强类,它可以对整个项目中所有标注了 `@Controller` 或者 `@RestController` 的控制器类中的方法进行增强处理,
// 在这里主要是用于全局异常处理,意味着可以捕获这些控制器方法在执行过程中抛出的异常,无论异常发生在哪个具体的控制器方法里,都能在这里进行统一的处理。
@ResponseBody @ResponseBody
// `@ResponseBody` 注解结合 `@ControllerAdvice` 使用表示这个类中处理异常的方法返回的结果会直接作为响应体通常是JSON等格式返回给客户端
// 而不需要经过视图解析等传统的MVC流程符合现在大多数基于RESTful风格的Web服务开发中对异常响应处理的需求能够直接将处理后的异常信息以合适的格式返回给调用方。
@Slf4j @Slf4j
// `@Slf4j` 是Lombok库提供的注解用于自动生成一个名为 `log` 的SLF4J日志记录器在类中可以方便地使用这个日志记录器记录各种级别的日志信息
// 在这里主要用于记录捕获到的异常信息,方便后续查看和分析系统出现异常的原因以及具体情况,有助于问题排查和系统监控。
public class ExceptionHandlerAdvice { public class ExceptionHandlerAdvice {
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){ public ServerResponse handleException(Exception e) {
log.error(e.getMessage(),e); log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试"); // 首先使用日志记录器 `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) @ExceptionHandler(SnailmallException.class)
public ServerResponse handleException(SnailmallException e){ public ServerResponse handleException(SnailmallException e) {
log.error(e.getMessage(),e); log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage()); // 同样先使用日志记录器记录 `SnailmallException` 类型异常的详细信息,方便后续排查该特定类型异常出现的原因以及具体情况。
}
} return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage());
// 对于 `SnailmallException` 这种项目中自定义的特定异常类型,通过调用 `ServerResponse` 类的静态方法 `createByErrorCodeMessage` 创建响应对象,
// 使用异常对象自身携带的错误状态码(`e.getExceptionStatus()`,通常在自定义异常类中定义了具体的错误状态码值)作为响应的状态码,
// 并传入异常对象的异常消息(`e.getMessage()`)作为提示消息,将这个根据自定义异常具体情况生成的响应对象返回给客户端,
// 这样就可以针对项目中自定义的异常进行个性化的响应处理,告知客户端具体的错误信息以及对应的错误状态码,方便客户端根据不同的错误情况进行相应的处理。
}
}

@ -4,22 +4,38 @@ import com.njupt.swg.common.resp.ResponseEnum;
import lombok.Getter; import lombok.Getter;
/** /**
* `SnailmallException` `RuntimeException`
* 便
* `@Getter` `getter` 便
*
* @Author swg. * @Author swg.
* @Date 2019/1/1 13:18 * @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Getter @Getter
public class SnailmallException extends RuntimeException{ // `@Getter` 注解由Lombok库提供它会自动为类中的所有非静态、非 `final` 属性生成对应的 `getter` 方法,
// 在这里就是为 `exceptionStatus` 属性生成 `get` 方法,使得在其他地方可以方便地获取该属性的值,
// 避免了手动编写 `getExceptionStatus` 这样的样板代码,让代码结构更加简洁清晰。
public class SnailmallException extends RuntimeException {
private int exceptionStatus = ResponseEnum.ERROR.getCode(); private int exceptionStatus = ResponseEnum.ERROR.getCode();
// 定义了一个私有整型变量 `exceptionStatus`,用于存储该异常对应的状态码,初始值设置为 `ResponseEnum.ERROR.getCode()`
// 这里推测 `ResponseEnum` 是一个枚举类,用于定义各种响应状态相关的枚举值(比如错误码等),`ERROR` 应该是表示错误的一个枚举项,
// 通过获取其 `getCode` 方法的值来设定默认的异常状态码,意味着如果没有显式指定状态码,异常对象默认使用这个预定义的错误状态码值。
public SnailmallException(String msg){ public SnailmallException(String msg) {
super(msg); super(msg);
} }
// 这是一个构造函数,接收一个字符串类型的参数 `msg`,用于创建 `SnailmallException` 异常对象。
// 它调用了父类(`RuntimeException`)的构造函数,并将传入的 `msg` 作为异常消息传递给父类,
// 这种方式适用于只需要传递异常消息,而使用默认异常状态码的情况,方便在业务逻辑中快速抛出该异常并携带相应的提示信息。
public SnailmallException(int code,String msg){ public SnailmallException(int code, String msg) {
super(msg); super(msg);
exceptionStatus = code; exceptionStatus = code;
} }
// 另一个构造函数,接收一个整型参数 `code` 和一个字符串参数 `msg`,同样先调用父类的构造函数将 `msg` 作为异常消息传递过去,
} // 然后将传入的 `code` 参数赋值给 `exceptionStatus` 属性,用于在需要指定特定的异常状态码以及异常消息的场景下创建异常对象,
// 使得可以根据不同的业务错误情况,灵活地设置合适的异常状态码和对应的提示消息,方便在全局异常处理等环节根据状态码进行不同的响应处理。
}

@ -3,23 +3,48 @@ package com.njupt.swg.common.resp;
import lombok.Getter; import lombok.Getter;
/** /**
* `ResponseEnum`
* 便
* `@Getter` `getter` 便
*
* @Author swg. * @Author swg.
* @Date 2018/12/31 20:15 * @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Getter @Getter
// `@Getter` 注解由Lombok库提供它会自动为枚举类中的所有非静态、非 `final` 属性生成对应的 `getter` 方法,
// 在这里就是为 `code` 和 `desc` 属性生成 `get` 方法,使得在其他地方可以方便地获取每个枚举项对应的状态码和描述信息,
// 无需手动编写获取这些属性值的方法,使代码结构更加简洁清晰。
public enum ResponseEnum { public enum ResponseEnum {
SUCCESS(0,"SUCCESS"), SUCCESS(0, "SUCCESS"),
ERROR(1,"ERROR"), // 定义了一个名为 `SUCCESS` 的枚举项,用于表示操作成功的状态。它的状态码为 `0`,对应的描述信息是 `"SUCCESS"`
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"), // 在项目中当某个操作顺利完成且符合预期时,可以使用这个枚举项来表示成功的返回状态,方便统一处理和判断操作是否成功。
NEED_LOGIN(10,"NEED_LOGIN");
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 int code;
private String desc; private String desc;
ResponseEnum(int code,String desc){ ResponseEnum(int code, String desc) {
this.code = code; this.code = code;
this.desc = desc; this.desc = desc;
} }
} // 这是枚举类的构造函数,用于初始化每个枚举项的状态码(`code`)和描述信息(`desc`)属性。
// 每个枚举项在定义时(如 `SUCCESS(0, "SUCCESS")`)都会调用这个构造函数,传入对应的状态码和描述字符串,
// 来完成自身属性的初始化,确保每个枚举项都有正确的状态码和相应的描述内容,方便后续通过 `getter` 方法获取使用。
}

@ -8,71 +8,133 @@ import lombok.NoArgsConstructor;
import java.io.Serializable; import java.io.Serializable;
/** /**
* `ServerResponse`
* 便
* JSON `getter`
*
* @Author swg. * @Author swg.
* @Date 2018/12/31 20:11 * @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
*/ */
@Getter @Getter
// `@Getter` 注解由Lombok库提供它会自动为类中的所有非静态、非 `final` 属性生成对应的 `getter` 方法,
// 在这里就是为 `status`、`msg` 和 `data` 属性生成 `get` 方法,使得在其他地方可以方便地获取这些属性的值,
// 避免了手动编写获取属性值的样板代码,让代码结构更加简洁清晰。
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
// `@JsonSerialize` 注解是由Jackson库提供的用于控制对象在进行JSON序列化时的行为。
// 这里 `include = JsonSerialize.Inclusion.NON_NULL` 的配置表示在序列化过程中,只会包含那些非 `null` 值的属性,
// 即如果某个属性的值为 `null`,在将 `ServerResponse` 对象转换为JSON格式时该属性将不会出现在最终的JSON数据中
// 这样可以减少不必要的数据传输使得返回的JSON数据更加精简、高效符合实际应用场景中对数据传输的优化需求。
public class ServerResponse<T> implements Serializable { public class ServerResponse<T> implements Serializable {
private int status; private int status;
// 用于存储操作结果的状态码,通过不同的状态码值来表示操作是成功还是出现了某种类型的错误等情况,
// 其取值通常会参考项目中定义的相关枚举(如 `ResponseEnum`)来进行标准化设置,方便统一判断和处理不同的返回状态。
private String msg; private String msg;
// 存放与操作结果相关的提示消息,用于向调用方传达更详细的信息,比如操作成功时可以是简单的成功提示,
// 出现错误时则可以是具体的错误原因描述等,增强了返回结果的可读性和对调用方的友好性。
private T data; private T data;
// 这是一个泛型属性,用于承载具体的业务数据内容,根据不同的业务操作,其类型可以是各种各样的,比如可以是一个实体对象、一个对象列表、一个简单的数据值等,
// 通过泛型的设计使得这个返回封装类能够灵活地适应不同业务场景下的数据返回需求。
public ServerResponse(){} public ServerResponse() {
}
// 默认构造函数,虽然这里没有具体的初始化逻辑,但在某些情况下(比如通过反射创建对象等场景),
// 拥有一个默认的无参构造函数是必要的,它可以满足一些框架或者特定代码逻辑对对象创建的要求。
public ServerResponse(int status){ public ServerResponse(int status) {
this.status = status; this.status = status;
} }
public ServerResponse(int status,String msg){ // 一个构造函数,接收一个 `int` 类型的状态码参数,用于初始化 `ServerResponse` 对象的 `status` 属性,
// 适用于只需要设置状态码,而提示消息和具体数据可以后续再进行设置或者根据默认情况处理的场景,方便在不同业务逻辑中根据具体情况灵活创建对象。
public ServerResponse(int status, String msg) {
this.status = status; this.status = status;
this.msg = msg; this.msg = msg;
} }
public ServerResponse(int status,T data){ // 该构造函数接收状态码和提示消息两个参数,分别用于初始化 `status` 和 `msg` 属性,
// 当需要明确指定操作结果的状态码以及对应的提示消息时,可以使用这个构造函数来创建 `ServerResponse` 对象,
// 常用于返回特定的提示信息给调用方,告知其操作的结果情况。
public ServerResponse(int status, T data) {
this.status = status; this.status = status;
this.data = data; 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.status = status;
this.msg = msg; this.msg = msg;
this.data = data; this.data = data;
} }
// 全参数构造函数,接收状态码、提示消息和数据三个参数,用于完整地初始化 `ServerResponse` 对象的所有属性,
// 在需要明确指定所有返回信息的情况下使用,比如在某些复杂业务逻辑中,根据不同的条件要返回特定的状态码、详细的提示消息以及具体的数据内容时,就可以调用这个构造函数来创建对象。
@JsonIgnore @JsonIgnore
public boolean isSuccess(){ public boolean isSuccess() {
return this.status == ResponseEnum.SUCCESS.getCode(); return this.status == ResponseEnum.SUCCESS.getCode();
} }
// 定义了一个使用 `@JsonIgnore` 注解标注的方法,`@JsonIgnore` 表示在JSON序列化时忽略这个方法即不会将这个方法的返回值作为JSON数据的一部分进行序列化
// 这个方法用于判断当前 `ServerResponse` 对象表示的操作结果是否成功,通过比较对象的 `status` 属性值与 `ResponseEnum.SUCCESS` 枚举项对应的状态码(通常表示成功的状态码)是否相等来确定,
// 方便在调用方接收到返回对象后,快速判断操作是否成功,进而进行相应的后续处理,比如根据成功与否决定是否提取数据进行展示等操作。
/** /**
* *
* 便 `ServerResponse`
* 便
*/ */
public static <T>ServerResponse<T> createBySuccess(){ public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc()); return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
} }
public static <T>ServerResponse<T> createBySuccessMessage(String message){ // 这个静态方法创建一个表示操作成功的 `ServerResponse` 对象,使用 `ResponseEnum.SUCCESS` 枚举项对应的状态码和描述信息来初始化对象的 `status` 和 `msg` 属性,
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message); // 适用于简单的成功情况,不需要返回具体业务数据时,直接返回一个标准的成功提示响应给调用方。
public static <T> ServerResponse<T> createBySuccessMessage(String message) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message);
} }
public static <T>ServerResponse<T> createBySuccess(T data){ // 创建一个表示操作成功的 `ServerResponse` 对象,使用 `ResponseEnum.SUCCESS` 枚举项对应的状态码作为 `status` 属性值,
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),data); // 并将传入的自定义消息 `message` 作为 `msg` 属性值,用于在操作成功且需要返回特定的自定义提示消息给调用方的场景下,方便地创建响应对象。
public static <T> ServerResponse<T> createBySuccess(T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data);
} }
public static <T>ServerResponse<T> createBySuccess(String message,T data){ // 创建一个表示操作成功的 `ServerResponse` 对象,将 `ResponseEnum.SUCCESS` 枚举项对应的状态码设置为 `status` 属性值,
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data); // 并把传入的业务数据 `data` 赋给 `data` 属性,适用于操作成功且需要返回具体业务数据给调用方的场景,方便快速封装并返回成功响应。
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
} }
// 创建一个表示操作成功的 `ServerResponse` 对象,使用 `ResponseEnum.SUCCESS` 枚举项对应的状态码作为 `status` 属性值,
// 传入的自定义消息 `message` 作为 `msg` 属性值,同时将业务数据 `data` 赋给 `data` 属性,适用于既需要返回自定义的成功提示消息又要返回具体业务数据的复杂成功场景下,
// 方便快捷地创建符合要求的响应对象并返回给调用方。
/** /**
* *
* `ServerResponse`
// 可以根据具体的失败情况(如不同的错误码、错误消息等)选择合适的方式来创建并返回相应的失败状态响应对象,
// 便于在业务逻辑中统一处理各种失败情况,返回规范的错误响应给调用方。
*/ */
public static <T>ServerResponse<T> createByError(){ public static <T> ServerResponse<T> createByError() {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc()); return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
}
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
} }
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){ // 创建一个表示操作失败的 `ServerResponse` 对象,使用 `ResponseEnum.ERROR` 枚举项对应的状态码和描述信息来初始化对象的 `status` 和 `msg` 属性,
return new ServerResponse<>(code,msg); // 适用于通用的、没有特定错误消息的失败情况,返回一个标准的表示失败的提示响应给调用方。
}
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
// 创建一个表示操作失败的 `ServerResponse` 对象,将 `ResponseEnum.ERROR` 枚举项对应的状态码作为 `status` 属性值,
// 并把传入的自定义错误消息 `msg` 作为 `msg` 属性值,用于在操作失败且需要返回特定的自定义错误消息给调用方的场景下,方便地创建响应对象。
} public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
return new ServerResponse<>(code, msg);
}
// 创建一个表示操作失败的 `ServerResponse` 对象,使用传入的自定义状态码 `code` 作为 `status` 属性值,
// 传入的自定义错误消息 `msg` 作为 `msg` 属性值,适用于需要返回特定的错误码和对应错误消息的失败场景,比如根据不同业务逻辑中的特定错误情况,
// 灵活地创建并返回相应的失败响应对象给调用方,方便调用方根据具体的错误码和消息进行相应的处理。
}

@ -2,73 +2,143 @@ package com.njupt.swg.common.utils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
/** /**
* cookie * `CookieUtil` `Cookie` `Cookie` `Cookie` `Cookie`
* `Cookie` `Cookie` 便
*
* @Slf4j `log` `SLF4J` 使便使 `Cookie`
*/ */
@Slf4j @Slf4j
public class CookieUtil { public class CookieUtil {
private final static String COOKIE_DOMAIN = "oursnail.cn"; 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 * cookie
* @param response * `token` `Cookie`
* @param token * `Cookie`
*
* @param response `HttpServletResponse` `Cookie`
* `Cookie` 使 `Cookie`
* @param token `Cookie` `token`
* `Cookie`
*/ */
public static void writeLoginToken(HttpServletResponse response,String token){ public static void writeLoginToken(HttpServletResponse response, String token) {
Cookie ck = new Cookie(COOKIE_NAME,token); Cookie ck = new Cookie(COOKIE_NAME, token);
// 创建一个新的 `Cookie` 对象,使用预定义的 `COOKIE_NAME` 作为 `Cookie` 的名称,传入的 `token` 作为 `Cookie` 的值,
// 这样就构建好了一个用于存储登录令牌信息的 `Cookie` 实体,后续需要对其设置各种属性以便正确地发送给客户端并进行管理。
ck.setDomain(COOKIE_DOMAIN); ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//设值在根目录 // 设置 `Cookie` 的作用域域名,使其与前面定义的 `COOKIE_DOMAIN` 常量一致,确保 `Cookie` 只会在指定的域名下生效,
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击 // 避免在不相关的域名中被错误地发送和接收,增强了 `Cookie` 使用的安全性和针对性。
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); 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); response.addCookie(ck);
// 通过 `HttpServletResponse` 对象的 `addCookie` 方法,将设置好属性的 `Cookie` 添加到响应中,使得客户端能够接收到这个 `Cookie` 并保存起来,
// 从而完成在用户登录时向客户端写入登录相关 `Cookie` 的操作。
} }
/** /**
* cookie * cookie
* @param request * `Cookie` `COOKIE_NAME` `Cookie`
* @return * `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(); Cookie[] cks = request.getCookies();
if(cks != null){ // 从 `HttpServletRequest` 对象中获取客户端发送过来的所有 `Cookie`,返回的是一个 `Cookie` 数组,
for(Cookie ck:cks){ // 如果客户端没有发送任何 `Cookie`,则这个数组为 `null`,需要进行相应的判断和处理。
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ if (cks!= null) {
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); 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(); return ck.getValue();
} }
// 通过 `StringUtils` 的 `equals` 方法(来自 `Apache Commons Lang3` 库,用于避免 `null` 值导致的空指针异常等情况,更安全地比较字符串),
// 比较当前 `Cookie` 的名称是否与预定义的 `COOKIE_NAME`(登录相关 `Cookie` 的名称)相等,如果相等,则说明找到了我们要的登录相关 `Cookie`
// 此时记录下这个 `Cookie` 的名称和值,并返回其值(也就是登录令牌 `token`)给调用者,用于后续的登录态验证等操作。
} }
} }
return null; return null;
// 如果遍历完所有的 `Cookie` 都没有找到名称为 `COOKIE_NAME` 的 `Cookie`,则返回 `null`,表示没有获取到有效的登录令牌,
// 调用者可以根据这个返回值知晓当前请求中不存在对应的登录 `Cookie`,进而进行相应的业务处理,比如提示用户重新登录等操作。
} }
/** /**
* *
* @param request * `Cookie`
* @param response * `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(); Cookie[] cks = request.getCookies();
if(cks != null){ // 首先获取客户端发送过来的所有 `Cookie`,为后续查找和处理登录相关 `Cookie` 做准备,与前面读取 `Cookie` 的操作类似,需要对可能为 `null` 的情况进行判断。
for(Cookie ck:cks) {
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ if (cks!= null) {
for (Cookie ck : cks) {
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
ck.setDomain(COOKIE_DOMAIN); ck.setDomain(COOKIE_DOMAIN);
// 重新设置 `Cookie` 的作用域域名,确保与之前写入 `Cookie` 时设置的域名一致,使得客户端能准确识别这个 `Cookie`
// 虽然在一般情况下如果域名没有变化可以不重新设置,但这里为了保证操作的严谨性和准确性,还是进行了设置操作。
ck.setPath("/"); ck.setPath("/");
ck.setMaxAge(0);//0表示消除此cookie // 同样重新设置 `Cookie` 的路径为根目录,保证其在整个域名下的所有路径中都能按照期望被处理,
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue()); // 与写入 `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); response.addCookie(ck);
// 通过 `HttpServletResponse` 对象的 `addCookie` 方法,将修改好属性(主要是设置 `maxAge` 为 `0`)的 `Cookie` 添加到响应中发送给客户端,
// 让客户端根据新的 `Cookie` 属性执行删除操作,完成注销时删除登录相关 `Cookie` 的流程,之后返回即可,因为找到了并处理了要删除的 `Cookie`,不需要再继续遍历剩余的 `Cookie` 了。
return; return;
} }
} }
} }
} }
}
}

@ -4,65 +4,163 @@ import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
/** /**
* `DateTimeUtil` `Date` `Date`
* 使 `Joda-Time` 便
* `main` 便
*
* @DESC * @DESC
*/ */
public class DateTimeUtil { 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); DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
// 通过 `Joda-Time` 库的 `DateTimeFormat` 类的 `forPattern` 方法,根据传入的 `formatStr` 参数创建一个 `DateTimeFormatter` 对象,
// 这个对象用于定义如何解析给定格式的日期时间字符串,它知道按照什么样的模式(比如年、月、日等各部分的位置和格式要求)去解析输入的字符串。
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
// 使用创建好的 `DateTimeFormatter` 对象的 `parseDateTime` 方法,对传入的 `dateTimeStr` 字符串进行解析,将其转换为 `Joda-Time` 库中的 `DateTime` 类型对象,
// `DateTime` 类型在 `Joda-Time` 库中是一个功能强大、便于操作的日期时间表示形式,它提供了很多方便的日期时间计算、比较等方法。
return dateTime.toDate(); 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; return StringUtils.EMPTY;
} }
// 首先判断传入的 `Date` 对象是否为 `null`,如果是 `null`,则直接返回一个空字符串,避免后续操作出现空指针异常等问题,
// 这样调用者在使用返回的字符串时无需额外担心 `null` 值的情况,可以更方便地进行处理,比如直接用于拼接其他字符串等操作。
DateTime dateTime = new DateTime(date); DateTime dateTime = new DateTime(date);
// 如果传入的 `Date` 对象不为 `null`,则使用 `Joda-Time` 库的 `DateTime` 类的构造函数将Java标准库中的 `Date` 类型对象转换为 `Joda-Time` 库中的 `DateTime` 类型对象,
// 以便后续利用 `Joda-Time` 库提供的更丰富的日期时间格式化功能进行操作。
return dateTime.toString(formatStr); 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 dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
// 创建一个 `DateTimeFormatter` 对象,使用类中预定义的 `STANDARD_FORMAT` 作为日期时间格式模式,
// 这样这个 `DateTimeFormatter` 就知道按照 `"yyyy-MM-dd HH:mm:ss"` 的格式规则去解析输入的字符串了。
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr); DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
// 利用创建好的 `DateTimeFormatter` 对象的 `parseDateTime` 方法,对传入的 `dateTimeStr` 字符串进行解析,将其转换为 `Joda-Time` 库中的 `DateTime` 类型对象。
return dateTime.toDate(); 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; return StringUtils.EMPTY;
} }
// 首先判断传入的 `Date` 类型对象是否为 `null`,如果是 `null`,则直接返回一个空字符串,避免后续操作出现空指针异常等问题。
DateTime dateTime = new DateTime(date); DateTime dateTime = new DateTime(date);
// 如果传入的 `Date` 对象不为 `null`,则将其转换为 `Joda-Time` 库中的 `DateTime` 类型对象,以便利用 `Joda-Time` 库的格式化功能进行操作。
return dateTime.toString(STANDARD_FORMAT); return dateTime.toString(STANDARD_FORMAT);
// 通过 `DateTime` 对象的 `toString` 方法,按照类中预定义的 `STANDARD_FORMAT``"yyyy-MM-dd HH:mm:ss"`)格式,将 `DateTime` 类型对象转换为对应的字符串表示形式并返回,
// 使得调用者可以得到符合默认标准格式的字符串用于各种业务场景下的展示、传输等操作。
} }
//Date -> 时间戳 /**
* `Date`
* `Date` 197011000
*
*
* @param date `Date` `null` `null`
* @return `Long`197011000 `Date`
* `Date` `null` `null`
* `ParseException` `SimpleDateFormat`
* @throws ParseException `ParseException` 使 `SimpleDateFormat`
* 使
*/
public static Long dateToChuo(Date date) throws ParseException { public static Long dateToChuo(Date date) throws ParseException {
if(date == null){ if (date == null) {
return 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(); 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 { public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time="1970-01-06 11:45:55"; String time = "1970-01-06 11:45:55";
Date date = format.parse(time); 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` 等相关时间转换方法的逻辑是否正确,
// 虽然这只是一个简单的测试示例,但可以在一定程度上帮助检查代码的功能是否符合预期,在实际开发中可以根据更多的测试用例来全面验证工具类的功能完整性和正确性。
} }
} }

@ -3,7 +3,6 @@ package com.njupt.swg.common.utils;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPClient;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
@ -12,80 +11,182 @@ import java.util.List;
/** /**
* @Author swg. * @Author swg.
* @Date 2018/1/11 14:32 * @Date 2018/1/11 14:32
* @DESC * @DESC FTPFTP便
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
*/ */
@Data @Data
// 通过Lombok的@Data注解自动为类中的非静态、非final属性如ip、port、user、pwd、ftpClient生成对应的getter、setter方法
// 以及toString、equals和hashCode方法减少了手动编写这些常规方法的代码量使代码更加简洁方便对类属性进行访问和操作。
@Slf4j @Slf4j
// 使用Lombok的@Slf4j注解自动生成名为log的SLF4J日志记录器用于在类中记录不同级别如info、error等的日志信息
// 方便后续对FTP文件上传过程中的关键步骤、异常情况等进行记录和追踪便于调试与问题排查。
public class FtpUtil { public class FtpUtil {
// 以下三个静态变量用于存储从配置文件中读取的FTP服务器相关配置信息通过PropertiesUtil工具类推测是自定义用于读取配置属性的类获取
// 这种方式便于统一管理FTP服务器的配置若配置发生变化只需在配置文件中修改对应属性值即可无需改动代码逻辑。
private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip"); private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip");
private static String ftpUser = PropertiesUtil.getProperty("ftp.user"); private static String ftpUser = PropertiesUtil.getProperty("ftp.user");
private static String ftpPass = PropertiesUtil.getProperty("ftp.pass"); private static String ftpPass = PropertiesUtil.getProperty("ftp.pass");
public FtpUtil(String ip,int port,String user,String pwd){ /**
* FtpUtilFTPIP
* FTPFTP
*
* @param ip FTPIP
* @param port FTPFTP21
* @param user FTP
* @param pwd FTP
*/
public FtpUtil(String ip, int port, String user, String pwd) {
this.ip = ip; this.ip = ip;
this.port = port; this.port = port;
this.user = user; this.user = user;
this.pwd = pwd; this.pwd = pwd;
} }
/**
* 使FTPIP21
* FtpUtiluploadFile便
*
* @param fileList FileFileFTP
* @return trueFTPfalse
* @throws IOException I/OIOException
*
*/
public static boolean uploadFile(List<File> fileList) throws IOException { public static boolean uploadFile(List<File> fileList) throws IOException {
FtpUtil ftpUtil = new FtpUtil(ftpIp,21,ftpUser,ftpPass); FtpUtil ftpUtil = new FtpUtil(ftpIp, 21, ftpUser, ftpPass);
// 根据从配置文件获取的默认FTP服务器配置信息创建FtpUtil实例准备使用该实例进行后续的文件上传操作。
log.info("开始连接ftp服务器"); log.info("开始连接ftp服务器");
boolean result = ftpUtil.uploadFile("img",fileList); // 记录日志信息提示即将开始连接FTP服务器便于在查看日志时了解文件上传操作的流程和进度。
log.info("开始连接ftp服务器,结束上传,上传结果:{}",result);
boolean result = ftpUtil.uploadFile("img", fileList);
// 调用实例的uploadFile方法另一个重载版本传入默认的远程路径"img"和文件列表,执行实际的文件上传操作,并获取上传结果。
log.info("开始连接ftp服务器,结束上传,上传结果:{}", result);
// 再次记录日志包含开始连接FTP服务器以及最终的上传结果信息方便后续查看整个上传过程是否成功以及排查可能出现的问题。
return result; return result;
} }
/**
private boolean uploadFile(String remotePath,List<File> fileList) throws IOException { * FTP
* FTPFTP
*
* @param remotePath FTPFTP"img"img
* FTP
* @param fileList uploadFile(List<File> fileList)
* @return truefalse
* @throws IOException FTPI/OIOException
*
*/
private boolean uploadFile(String remotePath, List<File> fileList) throws IOException {
boolean uploaded = true; boolean uploaded = true;
// 初始化上传结果变量uploaded为true默认假设文件上传操作能够成功完成后续若出现异常情况将其修改为false来表示上传失败。
FileInputStream fis = null; FileInputStream fis = null;
//连接FTP服务器 // 初始化文件输入流对象为null该输入流将用于读取本地文件内容以便后续通过FTP客户端上传到服务器在后续操作中会根据实际文件进行初始化。
log.info("【开始连接文件服务器】"); 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 { try {
ftpClient.changeWorkingDirectory(remotePath); ftpClient.changeWorkingDirectory(remotePath);
// 通过FTP客户端对象ftpClient的changeWorkingDirectory方法将FTP客户端在服务器端的当前工作目录切换到指定的远程路径下
// 确保后续上传的文件能够存储到正确的目标目录中注意该目录需在FTP服务器上实际存在且具有相应权限否则此操作会失败。
ftpClient.setBufferSize(1024); ftpClient.setBufferSize(1024);
// 设置FTP客户端的缓冲区大小为1024字节缓冲区用于临时存储文件数据在文件传输过程中合理设置缓冲区大小可以提高传输效率
// 这里设置为1024字节是一种常见的设置方式实际应用中可根据文件大小、网络状况等因素进行适当调整。
ftpClient.setControlEncoding("UTF-8"); ftpClient.setControlEncoding("UTF-8");
// 设置FTP客户端的控制编码为"UTF-8"这样在与FTP服务器进行命令交互、传输文件名等文本信息时能够保证字符编码的一致性
// 避免出现中文或其他特殊字符乱码的问题,提高文件上传操作的兼容性和稳定性。
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
// 将FTP客户端上传文件的类型设置为二进制文件类型FTPClient.BINARY_FILE_TYPE因为大多数实际应用中的文件如图像、文档等都是以二进制形式存储和传输的
// 这样设置能确保文件在上传过程中的完整性和正确性若上传的是纯文本文件根据情况也可设置为文本文件类型FTPClient.ASCII_FILE_TYPE
ftpClient.enterLocalPassiveMode(); ftpClient.enterLocalPassiveMode();
for(File fileItem : fileList){ // 将FTP客户端设置为被动模式enterLocalPassiveMode在很多网络环境下特别是客户端处于防火墙或网络地址转换NAT之后时
// 被动模式可以使FTP客户端更好地与服务器建立数据连接提高文件上传的成功率避免因网络配置问题导致的连接失败情况。
for (File fileItem : fileList) {
fis = new FileInputStream(fileItem); 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) { } catch (IOException e) {
log.error("上传文件异常",e); log.error("上传文件异常", e);
uploaded = false; uploaded = false;
e.printStackTrace(); e.printStackTrace();
// 如果在文件上传的过程中包括设置FTP客户端参数、读取文件或者执行上传操作等环节出现IOException异常
// 首先使用日志记录器记录一条错误级别日志记录异常相关信息通过传入e参数表示文件上传出现问题
// 然后将uploaded变量设置为false表示文件上传失败同时通过e.printStackTrace()打印出异常的详细堆栈信息,方便更深入地排查问题原因。
} finally { } finally {
fis.close(); if (fis!= null) {
fis.close();
}
ftpClient.disconnect(); ftpClient.disconnect();
// 在finally块中无论文件上传过程是否出现异常都需要进行资源清理操作。
// 首先判断文件输入流是否已被初始化不为null如果是则关闭该输入流避免资源泄露
// 然后通过FTP客户端对象的disconnect方法断开与FTP服务器的连接释放网络相关资源确保文件上传操作结束后系统资源被正确释放。
} }
} }
return uploaded; return uploaded;
} }
/**
* FTP
private boolean connectServer(String ip,int port,String user,String pwd){ * 便FTP
*
* @param ip FTPIPIP访
* @param port FTPFTP21IP
* @param user FTP
* @param pwd FTP
* @return FTPtrue
* false
*
*/
private boolean connectServer(String ip, int port, String user, String pwd) {
boolean isSuccess = false; boolean isSuccess = false;
ftpClient = new FTPClient(); ftpClient = new FTPClient();
// 创建一个FTPClient对象它是Apache Commons Net库提供的用于与FTP服务器进行交互的客户端类
// 通过该对象可以执行诸如连接服务器、登录、上传下载文件等一系列FTP相关操作此处先创建好对象为后续的连接和登录操作做准备。
try { try {
ftpClient.connect(ip); 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) { } catch (IOException e) {
log.error("连接FTP服务器异常",e); log.error("连接FTP服务器异常", e);
// 如果在连接服务器或者登录验证过程中出现IOException异常例如网络不通、服务器拒绝连接、用户名或密码错误等原因导致
// 使用日志记录器记录一条错误级别日志记录异常相关信息通过传入e参数方便后续查看和分析连接FTP服务器出现问题的原因。
} }
return isSuccess; return isSuccess;
} }
// 以下是类的私有属性分别用于存储FTP服务器连接相关的信息以及FTP客户端对象。
// 通过@Data注解自动生成了对应的getter和setter方法方便在类内部及外部通过公共方法间接访问对这些属性进行操作和获取其值。
private String ip; private String ip;
private int port; private int port;
private String user; private String user;
private String pwd; private String pwd;
private FTPClient ftpClient; private FTPClient ftpClient;
} }

@ -1,126 +1,187 @@
package com.njupt.swg.common.utils; package com.njupt.swg.common.utils;
import com.njupt.swg.entity.User;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.DeserializationConfig; import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize; 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.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
/** /**
* jackson * `JsonUtil`JSON
* Jackson `ObjectMapper` 便JavaJSON
* JSON
*
* @Slf4j `log` `SLF4J` 便
* 便
*/ */
@Slf4j @Slf4j
public class JsonUtil { public class JsonUtil {
// 创建一个静态的 `ObjectMapper` 对象它是Jackson库中核心的用于进行JSON序列化和反序列化操作的类。
// 在这里将其声明为静态的,使得整个类中的所有静态方法都可以共享使用这个对象,避免多次创建带来的开销,同时保证配置的一致性。
private static ObjectMapper objectMapper = new ObjectMapper(); private static ObjectMapper objectMapper = new ObjectMapper();
static { static {
//所有字段都列入进行转换 // 静态代码块,在类加载时执行,用于对 `ObjectMapper` 进行一系列的配置操作以定制JSON序列化和反序列化的行为。
// 所有字段都列入进行转换
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
//取消默认转换timestamp形式 // 通过设置序列化包含策略为 `ALWAYS`表示在将Java对象序列化为JSON字符串时无论对象的字段值是否为 `null`都会包含该字段在JSON结果中。
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); // 这样能确保完整的对象结构信息都被转换到JSON中但可能会使生成的JSON数据稍显冗余不过在某些场景下如需要完整保留对象结构信息用于后续解析等是很有用的。
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); // 取消默认转换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)); objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
//忽略json存在属性但是java对象不存在属性的错误 // 调用 `setDateFormat` 方法设置日期格式,这里使用了 `DateTimeUtil` 类中定义的标准日期时间格式(可以推测是类似 `yyyy-MM-dd HH:mm:ss` 的格式)。
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); // 这样做的目的是确保在序列化和反序列化过程中日期类型的字段都能按照统一的、明确的格式进行处理保证了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 * JavaJSON `null` `null`
* @param <T> * `IOException`JSON `null`
* @return * `ObjectMapper` JSON
*
* @param obj JavaJava
* `ObjectMapper` JSON
* @param <T> 使使
* @return JSON `null` `null`
*
*/ */
public static <T> String obj2String(T obj){ public static <T> String obj2String(T obj) {
if(obj == null){ if (obj == null) {
return null; return null;
} }
try { 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) { } catch (IOException e) {
log.warn("parse object to string error",e); log.warn("parse object to string error", e);
return null; return null;
// 如果在序列化过程中出现了 `IOException` 异常,使用自动生成的日志记录器 `log` 记录一条警告级别日志,记录异常相关信息(通过传入 `e` 参数),
// 表示对象转换为字符串时出现了问题,然后返回 `null`,告知调用者序列化操作失败,方便调用者根据返回值进行相应的错误处理,比如提示用户数据转换失败等操作。
} }
} }
/** /**
* 便 * 便
* @param obj * `obj2String` JavaJSONJSON
* @param <T> * 使JSON便JSON
* @return * `null` `null`
*
* @param obj Java `obj2String` JavaJSON
* @param <T> 使
* @return JSON `null` `null`
* JSON
* 便JSON
*/ */
public static <T> String obj2StringPretty(T obj){ public static <T> String obj2StringPretty(T obj) {
if(obj == null){ if (obj == null) {
return null; return null;
} }
try { 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) { } catch (IOException e) {
log.warn("parse object to string error",e); log.warn("parse object to string error", e);
return null; return null;
// 如果在转换过程中出现 `IOException` 异常,使用日志记录器记录一条警告级别日志,记录异常相关信息,
// 然后返回 `null`,告知调用者转换失败,方便调用者根据返回值进行相应的错误处理,例如检查对象结构是否符合序列化要求等操作。
} }
} }
/** /**
* *
* @param str * JSONJavaJSON `null` `StringUtils`
* @param clazz * Java `null` `null` `IOException`JSONJava
* @param <T> * `null` `ObjectMapper` Java
* @return *
* @param str JSON `ObjectMapper` Java
* Java
* @param clazz JavaJava `User` `Integer`
* `ObjectMapper` JSONJSON
* @param <T> Java `clazz` 使使
* @return Java `null` `null`
* 使
*/ */
public static <T> T String2Obj(String str,Class<T> clazz){ public static <T> T String2Obj(String str, Class<T> clazz) {
if(StringUtils.isEmpty(str) || clazz == null){ if (StringUtils.isEmpty(str) || clazz == null) {
return null; return null;
} }
try { 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) { } catch (IOException e) {
log.warn("parse string to obj error",e); log.warn("parse string to obj error", e);
return null; return null;
// 如果在反序列化过程中出现 `IOException` 异常,使用日志记录器记录一条警告级别日志,记录异常相关信息,
// 然后返回 `null`告知调用者反序列化失败方便调用者根据返回值进行相应的错误处理例如检查JSON字符串格式是否正确、类结构是否匹配等操作。
} }
} }
/** /**
* *
* @param str * JSON `List<User>``Map<String, List<Integer>>`
* @param typeReference * `TypeReference` JSON `TypeReference` `null` `null`
* @param <T> * `IOException` `null` `ObjectMapper`
* @return *
* @param str JSON `ObjectMapper`
* JSON `TypeReference`
* @param typeReference `TypeReference` 使
* `new TypeReference<List<User>>() {}` `ObjectMapper` JSON `List<User>`
* @param <T> `typeReference` 使
* @return Java`typeReference` `null` `null`
*
*/ */
public static <T> T Str2Obj(String str, TypeReference typeReference){ public static <T> T Str2Obj(String str, Class<User> typeReference) {
if(StringUtils.isEmpty(str) || typeReference == null){ 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);
return null; return null;
} }
}
/**
*
* @param str
* @param collectionClass
* @param elementClasses
* @param <T>
* @return
*/
public static <T> T Str2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses);
try { 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) { } catch (IOException e) {
log.warn("parse string to obj error",e); log.warn("parse string to obj error", e);
return null; return null;
// 如果在反序列化过程中出现 `
} }
} }
} }

@ -3,47 +3,109 @@ package com.njupt.swg.common.utils;
import java.security.MessageDigest; import java.security.MessageDigest;
/** /**
* MD5 * `MD5Util`MD5
* MD5
* 使
*/ */
public class MD5Util { public class MD5Util {
/**
*
*
* MD5
*
* @param b MD5
* @return
*/
private static String byteArrayToHexString(byte b[]) { private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer(); StringBuffer resultSb = new StringBuffer();
// 创建一个 `StringBuffer` 对象,用于高效地拼接字符,因为在循环中需要逐个将字节转换后的十六进制字符添加进来,
// `StringBuffer` 相较于普通的字符串拼接操作(使用 `+` 运算符)在性能上更优,特别是在大量字符拼接的场景下。
for (int i = 0; i < b.length; i++) for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i])); resultSb.append(byteToHexString(b[i]));
// 遍历传入的字节数组,对于每个字节,调用 `byteToHexString` 方法将其转换为十六进制字符表示形式,
// 然后将转换后的字符添加到 `resultSb` 这个 `StringBuffer` 对象中,通过循环完成整个字节数组的转换和拼接操作。
return resultSb.toString(); return resultSb.toString();
// 最后将 `StringBuffer` 对象转换为普通的字符串并返回,得到由字节数组转换而来的十六进制字符串结果,可用于展示或者后续的处理,比如作为加密后的最终结果返回给调用者。
} }
/**
*
*
* `byteArrayToHexString`
*
* @param b `0x1A` `"1a"`
* @return `byteArrayToHexString`
*/
private static String byteToHexString(byte b) { private static String byteToHexString(byte b) {
int n = b; int n = b;
if (n < 0) if (n < 0)
n += 256; n += 256;
// 如果传入的字节值是负数在Java中字节类型是有符号的范围是 `-128` 到 `127`),将其转换为无符号整数范围(`0` 到 `255`
// 这样便于后续按照常规的十六进制转换规则进行操作,确保转换结果的正确性,因为十六进制表示通常是基于无符号整数的概念来进行转换的。
int d1 = n / 16; int d1 = n / 16;
int d2 = n % 16; int d2 = n % 16;
// 通过整数除法和取余运算,分别获取该字节值对应的十六进制表示中的高位数字和低位数字,例如对于字节值 `26`(十六进制为 `0x1A`
// `d1` 的值为 `1`(对应十六进制的高位数字),`d2` 的值为 `10`(对应十六进制的低位数字,在十六进制中用 `a` 表示)。
return hexDigits[d1] + hexDigits[d2]; 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 origin MD5
* @param charsetname * MD5
* @return * @param charsetname `"utf-8"``"gbk"` `null` 使
*
* @return MD5`MessageDigest` `null`
*
*/ */
private static String MD5Encode(String origin, String charsetname) { private static String MD5Encode(String origin, String charsetname) {
String resultString = null; String resultString = null;
try { try {
resultString = new String(origin); resultString = new String(origin);
// 先将传入的原始字符串赋值给 `resultString`,这里其实可以考虑是否有必要重新创建一个新的字符串对象,
// 不过当前这样做可能是为了后续在处理过程中不直接修改传入的原始字符串参数,保持其原始性,具体可以根据实际需求进一步优化。
MessageDigest md = MessageDigest.getInstance("MD5"); MessageDigest md = MessageDigest.getInstance("MD5");
// 通过 `MessageDigest` 类的静态方法 `getInstance` 获取一个实现了MD5算法的 `MessageDigest` 实例,
// 这个实例将用于对字符串进行MD5摘要计算也就是执行实际的加密操作`"MD5"` 参数指定了要使用的加密算法名称确保获取到的是MD5相关的实现类实例。
if (charsetname == null || "".equals(charsetname)) if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString.getBytes())); resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
else else
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname))); resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
// 根据传入的字符编码名称情况进行不同的处理,如果字符编码名称为 `null` 或者空字符串,表示使用默认的字符编码来将字符串转换为字节数组,
// 然后通过 `md.digest` 方法对字节数组进行MD5摘要计算得到加密后的字节数组结果再调用 `byteArrayToHexString` 方法将其转换为十六进制字符串,赋值给 `resultString`
// 如果指定了有效的字符编码名称则按照指定的编码将字符串转换为字节数组后再进行MD5摘要计算和十六进制字符串转换操作同样将结果赋值给 `resultString`。
} catch (Exception exception) { } catch (Exception exception) {
// 当前代码只是简单地捕获了可能出现的异常,并没有做进一步的处理,比如记录日志、抛出更具体的业务相关异常等,
// 在实际应用中可以根据具体需求完善异常处理逻辑,以便更好地排查加密过程中出现问题的原因以及向调用者反馈更准确的错误信息。
} }
return resultString.toUpperCase(); return resultString.toUpperCase();
// 最后将得到的十六进制字符串结果转换为大写形式返回,这样做通常是为了统一加密结果的格式表示,便于后续比较、存储等操作,
// 例如将小写的十六进制字符串 `"abcdef"` 转换为 `"ABCDEF"` 后返回给调用者作为MD5加密后的最终结果。
} }
/**
* 使UTF-8MD5
* `MD5Encode` `"utf-8"`
* 便使UTF-8MD5
* 便使
*
* @param origin MD5 `MD5Encode`
* @return MD5使UTF-8使
*
*/
public static String MD5EncodeUtf8(String origin) { public static String MD5EncodeUtf8(String origin) {
//这里可以加盐 //这里可以加盐
return MD5Encode(origin, "utf-8"); return MD5Encode(origin, "utf-8");
@ -51,9 +113,13 @@ public class MD5Util {
public static void main(String[] args) { public static void main(String[] args) {
System.out.println(MD5EncodeUtf8("123456")); System.out.println(MD5EncodeUtf8("123456"));
// 这是一个简单的 `main` 方法,用于对 `MD5EncodeUtf8` 方法进行测试,在这里传入字符串 `"123456"` 作为原始字符串进行MD5加密操作
// 并将加密后的结果输出到控制台方便开发人员在本地快速验证MD5加密功能是否正常工作实际应用中可以根据更多的测试用例来全面测试加密逻辑的正确性和稳定性。
} }
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
} // 定义一个私有静态的字符串数组,用于存储十六进制中 `0` 到 `15` 对应的字符表示,
// 在 `byteToHexString` 方法中通过字节值转换后的十六进制数字作为索引,从这个数组中获取对应的字符来拼接成十六进制字符串,
// 是实现字节到十六进制字符串转换过程中的一个基础数据结构,方便了十六进制表示的转换操作。
}

@ -2,7 +2,6 @@ package com.njupt.swg.common.utils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.Properties; import java.util.Properties;
@ -10,37 +9,93 @@ import java.util.Properties;
/** /**
* @Author swg. * @Author swg.
* @Date 2018/1/10 14:56 * @Date 2018/1/10 14:56
* @DESC * @DESC 便 `parameter.properties`
*
*
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
*/ */
@Slf4j @Slf4j
// 使用Lombok的 `@Slf4j` 注解,自动生成名为 `log` 的 `SLF4J` 日志记录器,方便在类中记录不同级别(如 `error` 等)的日志信息,
// 此处主要用于记录配置文件读取异常的情况,便于后续排查问题。
public class PropertiesUtil { public class PropertiesUtil {
private static Properties props; private static Properties props;
// 定义一个静态的 `Properties` 对象,`Properties` 类常用于读取和操作配置文件中的键值对信息,
// 将其声明为静态的,以便在整个类的不同方法中共享使用,存储从配置文件中加载的所有属性数据。
static { static {
String fileName = "parameter.properties"; String fileName = "parameter.properties";
props = new Properties(); props = new Properties();
try { 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) { } 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()); String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){ // 通过 `Properties` 对象的 `getProperty` 方法,传入去除首尾空白字符后的键(调用 `trim` 方法处理),从已加载的配置文件数据中获取对应的值,
// 这里获取到的值可能是 `null` 或者是配置文件中定义的实际字符串内容,可能包含首尾空白字符等情况,需要进一步处理。
if (StringUtils.isBlank(value)) {
return null; return null;
} }
// 使用 `Apache Commons Lang3` 库中的 `StringUtils` 工具类的 `isBlank` 方法判断获取到的属性值是否为空(包括 `null`、空字符串以及只包含空白字符的情况),
// 如果为空,则直接返回 `null`,表示没有获取到有效的配置信息。
return value.trim(); 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()); String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){ // 同样先通过 `Properties` 对象的 `getProperty` 方法,传入去除首尾空白字符后的键,尝试从配置文件中获取对应的值,
// 获取到的值可能为空或者包含空白字符等情况,后续需要进一步判断和处理。
if (StringUtils.isBlank(value)) {
value = defaultValue; value = defaultValue;
} }
// 使用 `StringUtils` 工具类的 `isBlank` 方法判断获取到的属性值是否为空,如果为空(包括 `null`、空字符串以及只包含空白字符的情况),
// 则将传入的默认值赋给 `value` 变量,这样就能保证始终有一个可用的字符串值返回给调用者,避免因配置文件中属性值缺失导致业务逻辑异常。
return value.trim(); return value.trim();
// 最后对 `value` 变量(无论是从配置文件获取到的值还是传入的默认值)调用 `trim` 方法去除首尾空白字符后返回,
// 确保返回给调用者的是一个去除了多余空白、符合使用要求的字符串值,方便在业务逻辑中直接使用该值进行后续操作,比如赋值给变量、拼接字符串等操作。
} }
} }

@ -14,49 +14,113 @@ import org.springframework.web.bind.annotation.RestController;
* @Author swg. * @Author swg.
* @Date 2019/1/2 17:32 * @Date 2019/1/2 17:32
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC SpringRESTful`RestController``Product`HTTP
* `IProductService`
* Redis
*/ */
@RestController @RestController
// `@RestController` 注解表明这个类是一个RESTful风格的控制器它结合了 `@Controller` 和 `@ResponseBody` 的功能,
// 意味着类中的方法返回值会直接作为HTTP响应的主体内容通常是JSON格式等返回给客户端而不需要额外配置视图解析等操作适用于构建前后端分离项目中的接口层。
@RequestMapping("/product") @RequestMapping("/product")
// `@RequestMapping` 注解用于定义该控制器类中所有方法的基础请求路径,在这里将所有与商品相关的接口请求路径都统一以 `/product` 开头,
// 使得接口路径具有一定的层级和分类性,方便管理和识别,例如后续的 `/detail.do`、`/list.do` 等具体接口路径都是在这个基础路径下进行细化定义的。
public class ProductController { public class ProductController {
@Autowired @Autowired
private IProductService productService; private IProductService productService;
// 通过Spring的依赖注入`@Autowired` 注解),自动将实现了 `IProductService` 接口的具体业务逻辑类注入到当前控制器中,
// 这样在控制器的各个方法中就可以方便地调用业务层提供的方法来处理商品相关的业务逻辑,实现了控制层与业务层的解耦,便于代码的维护和扩展。
/**
*
* HTTPID `getPortalProductDetail`
* `ServerResponse`
*
*
* @param productId
* ID
* @return `ServerResponse` `ProductDetailVo`
* JSON
*/
@RequestMapping("detail.do") @RequestMapping("detail.do")
public ServerResponse<ProductDetailVo> detail(Integer productId){ public ServerResponse<ProductDetailVo> detail(Integer productId) {
return productService.getPortalProductDetail(productId); return productService.getPortalProductDetail(productId);
} }
/**
*
* HTTPID
* `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") @RequestMapping("list.do")
public ServerResponse<PageInfo> list(@RequestParam(value = "keyword",required = false)String keyword, public ServerResponse<PageInfo> list(
@RequestParam(value = "categoryId",required = false)Integer categoryId, @RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "pageNum",defaultValue = "1")int pageNum, @RequestParam(value = "categoryId", required = false) Integer categoryId,
@RequestParam(value = "pageSize",defaultValue = "10")int pageSize, @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "orderBy",defaultValue = "")String orderBy){ @RequestParam(value = "pageSize", defaultValue = "10") int pageSize,
return productService.portalList(keyword,categoryId,orderBy,pageNum,pageSize); @RequestParam(value = "orderBy", defaultValue = "") String orderBy) {
return productService.portalList(keyword, categoryId, orderBy, pageNum, pageSize);
} }
/**
*
* HTTPID `queryProduct`
* `ServerResponse`
* `detail`
* `detail`
*
* @param productId
* ID
* @return `ServerResponse`
* JSON
*/
@RequestMapping("/queryProduct.do") @RequestMapping("/queryProduct.do")
public ServerResponse queryProduct(@RequestParam("productId") Integer productId){ public ServerResponse queryProduct(@RequestParam("productId") Integer productId) {
return productService.queryProduct(productId); return productService.queryProduct(productId);
} }
/** /**
* 1redis * 1redis
* RedisHTTP `preInitProductStcokToRedis`
* `ServerResponse`
* Redis便
*
* @return `ServerResponse` RedisJSON
*
*/ */
@RequestMapping("/preInitProductStcokToRedis.do") @RequestMapping("/preInitProductStcokToRedis.do")
public ServerResponse preInitProductStcokToRedis(){ public ServerResponse preInitProductStcokToRedis() {
return productService.preInitProductStcokToRedis(); return productService.preInitProductStcokToRedis();
} }
/** /**
* 2redis * 2redis
* RedisHTTP `preInitProductListToRedis`
* `ServerResponse`
* RedisRedis
*
* @return `ServerResponse` RedisJSON
*
*/ */
@RequestMapping("/preInitProductListToRedis.do") @RequestMapping("/preInitProductListToRedis.do")
public ServerResponse preInitProductListToRedis(){ public ServerResponse preInitProductListToRedis() {
return productService.preInitProductListToRedis(); return productService.preInitProductListToRedis();
} }
}
}

@ -31,121 +31,158 @@ import java.util.Map;
* @Date 2019/1/2 17:32 * @Date 2019/1/2 17:32
* @CONTACT 317758022@qq.com * @CONTACT 317758022@qq.com
* @DESC * @DESC
* SpringRESTful
*
* `IProductService``IFileService`
* `ServerResponse` 便
*/ */
@RestController @RestController
// 表明这个类是一个RESTful风格的控制器结合了 `@Controller` 和 `@ResponseBody` 的功能使得类中的方法返回值会直接作为HTTP响应的主体内容一般是JSON格式等返回给客户端
// 适用于构建前后端分离的后台管理系统接口层,无需额外配置视图解析等操作。
@RequestMapping("/manage/product") @RequestMapping("/manage/product")
// 定义该控制器类中所有方法的基础请求路径,表明这些接口是用于后台管理系统中商品相关操作的,将接口路径统一以 `/manage/product` 开头,便于管理和区分不同模块的接口。
@Slf4j @Slf4j
// 使用Lombok的 `@Slf4j` 注解自动生成名为 `log` 的 `SLF4J` 日志记录器,用于在类中记录不同级别(如 `info`、`error` 等)的日志信息,方便后续查看接口请求处理过程、排查问题等操作。
public class ProductManageController { public class ProductManageController {
@Autowired @Autowired
private IProductService productService; private IProductService productService;
@Autowired @Autowired
private IFileService fileService; private IFileService fileService;
@Autowired @Autowired
private CommonCacheUtil commonCacheUtil; private CommonCacheUtil commonCacheUtil;
// 通过Spring的依赖注入`@Autowired` 注解),分别将实现了 `IProductService`(商品业务逻辑接口)、`IFileService`(文件相关业务逻辑接口)以及 `CommonCacheUtil`(缓存操作工具类)的具体实例注入到当前控制器中,
// 这样在各个接口方法中就可以方便地调用对应的业务方法和工具方法,实现了控制器层与业务层、工具类的解耦,便于代码的维护和扩展。
/** /**
* list * list
* HTTP `1` `10`
* `productService` `list` `ServerResponse`
*
*
* @param pageNum `defaultValue = "1"` `1`
* 便
* @param pageSize `10` `pageNum`
* 便
* @return `ServerResponse` `PageInfo`
* JSON
*/ */
@RequestMapping("/list.do") @RequestMapping("/list.do")
public ServerResponse list(@RequestParam(value = "pageNum",defaultValue = "1") int pageNum, public ServerResponse list(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize",defaultValue = "10") int pageSize){ @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {
return productService.list(pageNum,pageSize); return productService.list(pageNum, pageSize);
} }
/** /**
* *
* HTTPID
* `productService` `search` ID
* `ServerResponse` `PageInfo`
* 便
*
* @param productName
* ID
* @param productId IDID
*
* @param pageNum `list` `1`便
*
* @param pageSize `list` `10`
* 便
* @return `ServerResponse` `PageInfo`
* JSON便
*/ */
@RequestMapping("search.do") @RequestMapping("search.do")
public ServerResponse<PageInfo> search(String productName, public ServerResponse<PageInfo> search(String productName,
Integer productId, Integer productId,
@RequestParam(value = "pageNum",defaultValue = "1") int pageNum, @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize",defaultValue = "10") int pageSize){ @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") @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 path = request.getSession().getServletContext().getRealPath("upload");
String targetFileName = fileService.upload(file,path); // 通过 `HttpServletRequest` 获取当前会话的 `ServletContext`,再调用其 `getRealPath` 方法获取服务器上 `upload` 目录的实际物理路径,
String url = "http://img.oursnail.cn/"+targetFileName; // 这个路径就是文件将要上传到的目标位置,确保文件能够被存储到服务器指定的目录下,便于后续的访问和管理,需要确保服务器上该目录具有相应的写入权限等条件。
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(); Map fileMap = Maps.newHashMap();
fileMap.put("uri",targetFileName); fileMap.put("uri", targetFileName);
fileMap.put("url",url); fileMap.put("url", url);
log.info("【返回数据为:{}】",fileMap); // 创建一个新的 `Map` 对象,用于存放图片相关的信息,将上传后文件的文件名以 `uri` 为键存入 `Map`,将构建好的图片完整访问路径以 `url` 为键也存入 `Map`
// 这样可以方便地将这些信息作为一个整体返回给客户端,客户端可以根据 `Map` 中的键获取相应的值进行后续操作。
log.info("【返回数据为:{}】", fileMap);
// 再次使用日志记录器记录将要返回给客户端的数据信息,便于在排查问题时了解接口返回的数据内容是否符合预期,确保数据的准确性和完整性。
return ServerResponse.createBySuccess(fileMap); return ServerResponse.createBySuccess(fileMap);
// 通过 `ServerResponse` 的静态方法 `createBySuccess` 创建一个表示成功的响应对象,并将包含图片信息的 `Map` 作为参数传入,
// 这样就构建好了一个包含正确状态成功以及相关数据图片信息的响应结果会被自动转换为合适的格式如JSON返回给客户端告知客户端图片上传操作成功以及对应的图片相关信息。
} }
/** /**
* *
* HTTPID `productService` `detail`
* `ServerResponse` `ProductDetailVo`
* 便
*
* @param productId
* ID
* @return `ServerResponse` `ProductDetailVo``ProductDetailVo`
* JSON便
*/ */
@RequestMapping("detail.do") @RequestMapping("detail.do")
public ServerResponse<ProductDetailVo> detail(Integer productId){ public ServerResponse<ProductDetailVo> detail(Integer productId) {
return productService.detail(productId); return productService.detail(productId);
} }
/** /**
* *
* HTTPID `productService` `set_sale_status`
* `ServerResponse` `String`
* 便
*
* @param productId
* ID
* @param status `0` `1`
*
* @return `ServerResponse` `String`
* JSON便
*/ */
@RequestMapping("set_sale_status.do") @RequestMapping("set_sale_status.do")
public ServerResponse<String> set_sale_status(Integer productId,Integer status){ public ServerResponse<String> set_sale_status(Integer productId, Integer status) {
return productService.set_sale_status(productId,status); return productService.set_sale_status(productId, status);
}
/**
* OR
*/
@RequestMapping("save.do")
public ServerResponse<String> productSave(Product product){
return productService.saveOrUpdateProduct(product);
} }
/**
*
* zuul
*/
@RequestMapping("richtext_img_upload.do")
public Map richtextImgUpload(@RequestParam(value = "upload_file",required = false) MultipartFile file, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)){
throw new SnailmallException("用户未登录,无法获取当前用户信息");
}
//2.从redis中获取用户信息
String userStr = commonCacheUtil.getCacheValue(loginToken);
if(userStr == null){
throw new SnailmallException("用户未登录,无法获取当前用户信息");
}
User user = JsonUtil.Str2Obj(userStr,User.class);
Map resultMap = Maps.newHashMap();
if(user == null){
resultMap.put("success",false);
resultMap.put("msg","请登录管理员");
return resultMap;
}
String path = httpServletRequest.getSession().getServletContext().getRealPath("upload");
String targetFileName = fileService.upload(file, path);
if (StringUtils.isBlank(targetFileName)) {
resultMap.put("success", false);
resultMap.put("msg", "上传失败");
return resultMap;
}
String url = PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.oursnail.cn/")+targetFileName;
resultMap.put("success", true);
resultMap.put("msg", "上传成功");
resultMap.put("file_path", url);
log.info("【返回数据为:{}】",resultMap);
httpServletResponse.addHeader("Access-Control-Allow-Headers", "X-File-Name");
return resultMap;
}
} }
/**
* OR
* HTTP `Product`
* `productService` `saveOrUpdate
*/
Loading…
Cancel
Save