chenxuan_branch
pb4nq52pf 9 months ago
parent 57aa7484fb
commit b869e6fe81

@ -4,13 +4,27 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* SnailmallCategoryServiceApplicationSpring Boot
* Spring Boot使
*/
@SpringBootApplication
// @SpringBootApplication是一个组合注解它整合了多个Spring相关的注解包括@Configuration表示这是一个配置类
// @EnableAutoConfiguration开启自动配置根据项目依赖自动配置各种Spring组件和功能以及@ComponentScan扫描指定包及其子包下的所有Spring组件如@Component、@Service、@Controller等注解标记的类
// 方便快捷地构建一个Spring Boot应用减少了手动配置的工作量。
@EnableDiscoveryClient
// @EnableDiscoveryClient注解用于启用服务发现客户端功能在微服务架构中当应用需要注册到服务注册中心如Eureka、Consul等并能够被其他服务发现和调用时
// 使用该注解来开启相关的功能,使得本应用可以将自身的服务信息注册上去,并且可以发现其他已注册的服务,方便服务之间的相互调用和协作。
public class SnailmallCategoryServiceApplication {
/**
* mainJavaSpringApplication.runSpring Boot
* ClassSnailmallCategoryServiceApplication.classargs
* Spring BootWeb使
*/
public static void main(String[] args) {
SpringApplication.run(SnailmallCategoryServiceApplication.class, args);
}
}
}

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

@ -1,6 +1,5 @@
package com.njupt.swg.common.exception;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ServerResponse;
import lombok.extern.slf4j.Slf4j;
@ -9,25 +8,49 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* ExceptionHandlerAdvice
* Spring
*
* @Author swg.
* @Date 2019/1/1 13:21
* @CONTACT 317758022@qq.com
* @DESC
*/
@ControllerAdvice
// 表明这个类可以包含多个 @ExceptionHandler 注解定义的方法,用于处理不同类型的异常,是一种全局的异常处理机制。
@ResponseBody
// 表示该类中的方法返回的数据直接作为响应体写入HTTP响应中通常返回JSON等格式的数据而不是跳转到视图页面。
@Slf4j
// Lombok注解用于自动生成一个名为log的SLF4J日志记录器方便在类中记录日志信息。
public class ExceptionHandlerAdvice {
/**
* Exception
*
*
* @param e Exception
* @return ServerResponse ServerResponse
* 使Constants.RESP_STATUS_INTERNAL_ERROR
*
*/
@ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试");
public ServerResponse handleException(Exception e) {
log.error(e.getMessage(), e);
// 记录异常信息到日志中方便后续排查问题error方法会记录错误级别较严重的日志信息第一个参数是日志消息模板第二个参数是异常对象本身用于输出详细的堆栈跟踪等信息。
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试");
}
/**
* SnailmallException
* SnailmallException
*
* @param e SnailmallException
* @return ServerResponse ServerResponseSnailmallExceptione.getExceptionStatus()
* e.getMessage()
*/
@ExceptionHandler(SnailmallException.class)
public ServerResponse handleException(SnailmallException e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage());
public ServerResponse handleException(SnailmallException e) {
log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage());
}
}
}

@ -4,22 +4,44 @@ import com.njupt.swg.common.resp.ResponseEnum;
import lombok.Getter;
/**
* SnailmallExceptionJavaRuntimeException
* 便
*
* @Author swg.
* @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com
* @DESC
*/
@Getter
public class SnailmallException extends RuntimeException{
// Lombok的注解用于自动生成对应的getter方法在这里就是为exceptionStatus字段生成get方法方便获取该字段的值。
public class SnailmallException extends RuntimeException {
/**
* ResponseEnum.ERROR.getCode()
* 使
*/
private int exceptionStatus = ResponseEnum.ERROR.getCode();
public SnailmallException(String msg){
/**
* SnailmallException
* RuntimeException
* 使ResponseEnum.ERROR.getCode()
*
* @param msg 便
*/
public SnailmallException(String msg) {
super(msg);
}
public SnailmallException(int code,String msg){
/**
* SnailmallException
* RuntimeExceptionexceptionStatus
*
*
* @param code 便
* @param msg msg
*/
public SnailmallException(int code, String msg) {
super(msg);
exceptionStatus = code;
}
}
}

@ -3,23 +3,52 @@ package com.njupt.swg.common.resp;
import lombok.Getter;
/**
* ResponseEnum
*
* 便使
*
* @Author swg.
* @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com
* @DESC
*/
@Getter
// Lombok的注解用于自动为枚举类中的成员变量这里的code和desc生成对应的getter方法方便获取这些变量的值。
public enum ResponseEnum {
SUCCESS(0,"SUCCESS"),
ERROR(1,"ERROR"),
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"),
NEED_LOGIN(10,"NEED_LOGIN");
/**
* 0"SUCCESS"
* 使
*/
SUCCESS(0, "SUCCESS"),
/**
* 1"ERROR"
*
*/
ERROR(1, "ERROR"),
/**
* 2"ILLEGAL_ARGUMENTS"
* 使
*/
ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"),
/**
* 10"NEED_LOGIN"
*
*/
NEED_LOGIN(10, "NEED_LOGIN");
private int code;
private String desc;
ResponseEnum(int code,String desc){
/**
*
* SUCCESSERROR
*
*
* @param code
* @param desc 便
*/
ResponseEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
}

@ -4,75 +4,118 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* ServerResponse
* 使
*
* @Author swg.
* @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com
* @DESC
*/
@Getter
// 通过Lombok的@Getter注解自动为类中的私有成员变量status、msg、data生成对应的getter方法方便外部代码获取这些变量的值遵循了Java的封装原则。
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
// 此注解用于配置Jackson序列化框架的行为这里指定只序列化那些非空的属性。这样在将ServerResponse对象转换为JSON等格式进行传输时
// 可以避免出现包含大量null值的冗余属性减少数据传输量并使返回的数据结构更加清晰简洁。
public class ServerResponse<T> implements Serializable {
// 声明对象可序列化使得该类的实例能够在网络传输例如通过HTTP协议返回给客户端或者持久化存储如保存到文件等场景中被正确处理
// 保证数据的完整性和可恢复性。
private int status;
// 用于存储响应的状态码,通过不同的状态码来表示请求处理的结果情况,例如成功、失败、需要登录等,通常会和项目中定义的状态码枚举等进行对应。
private String msg;
// 存放与响应状态相关的提示消息,用于向客户端传达更详细的、易于理解的信息,比如操作成功的具体描述或者失败的原因等。
private T data;
// 泛型成员变量,用于承载具体的业务数据,根据不同的业务请求,这个字段可以是各种类型的数据,例如查询用户信息时可以是用户对象,
// 查询商品列表时可以是商品列表对象等,体现了该返回封装类的通用性。
public ServerResponse(){}
public ServerResponse() {
// 默认的无参构造方法方便在一些情况下创建一个空的ServerResponse对象后续可以再通过setter方法如果有或者其他方式来填充具体的值。
}
public ServerResponse(int status){
public ServerResponse(int status) {
// 构造方法用于创建一个只指定状态码的ServerResponse对象适用于某些只需传达响应状态不需要额外提示消息和业务数据的场景。
this.status = status;
}
public ServerResponse(int status,String msg){
public ServerResponse(int status, String msg) {
// 构造方法用于创建带有状态码和提示消息的ServerResponse对象常用于需要向客户端反馈处理结果及对应简单说明的情况。
this.status = status;
this.msg = msg;
}
public ServerResponse(int status,T data){
public ServerResponse(int status, T data) {
// 构造方法创建包含状态码和业务数据的ServerResponse对象当业务处理成功且有具体数据需要返回给客户端时可以使用此时提示消息可能使用默认值。
this.status = status;
this.data = data;
}
public ServerResponse(int status,String msg,T data){
public ServerResponse(int status, String msg, T data) {
// 最完整的构造方法创建同时包含状态码、提示消息以及业务数据的ServerResponse对象适用于各种需要详细反馈响应情况的业务场景。
this.status = status;
this.msg = msg;
this.data = data;
}
@JsonIgnore
public boolean isSuccess(){
// 使用@JsonIgnore注解标记该方法告诉Jackson序列化框架在将对象转换为JSON等格式时忽略这个方法即不会把该方法作为一个属性进行序列化。
public boolean isSuccess() {
// 用于判断当前响应是否表示成功的逻辑方法通过比较存储的状态码和ResponseEnum中定义的表示成功的状态码通常是约定好的一个特定值来确定。
return this.status == ResponseEnum.SUCCESS.getCode();
}
/**
*
* 便ServerResponse
*/
public static <T>ServerResponse<T> createBySuccess(){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
public static <T> ServerResponse<T> createBySuccess() {
// 创建一个表示成功状态的ServerResponse对象使用ResponseEnum中定义的默认成功状态码和对应的默认成功描述信息进行初始化
// 适用于不需要额外自定义提示消息和返回数据的简单成功场景,比如简单的操作成功反馈。
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
}
public static <T>ServerResponse<T> createBySuccessMessage(String message){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message);
public static <T> ServerResponse<T> createBySuccessMessage(String message) {
// 创建一个表示成功状态的ServerResponse对象但可以自定义提示消息使用ResponseEnum中定义的成功状态码同时传入自定义的消息内容
// 方便在成功情况下向客户端传达更符合具体业务情况的提示内容,比如操作成功后的一些特定说明等。
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) {
// 创建表示成功状态且带有具体业务数据的ServerResponse对象使用成功状态码和传入的业务数据进行初始化
// 适用于业务处理成功并且有相关数据需要返回给客户端的场景,比如查询操作成功后返回查询到的数据。
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data);
}
public static <T>ServerResponse<T> createBySuccess(String message,T data){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data);
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
// 创建同时带有自定义提示消息和具体业务数据的成功状态的ServerResponse对象结合了上述两个方法的功能
// 更全面地满足各种需要详细反馈成功信息和相关数据的业务场景需求。
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
}
/**
*
* ServerResponse便
*/
public static <T>ServerResponse<T> createByError(){
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> createByError() {
// 创建一个表示一般错误状态的ServerResponse对象使用ResponseEnum中定义的默认错误状态码和对应的默认错误描述信息进行初始化
// 适用于没有更具体错误分类,只需简单反馈操作出现错误的情况。
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
}
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
}
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
// 创建一个表示错误状态且可以自定义错误提示消息的ServerResponse对象使用ResponseEnum中定义的错误状态码同时传入自定义的错误消息内容
// 便于根据具体的错误原因向客户端传达更准确的错误信息,比如参数错误、权限不足等具体的错误提示。
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
}
public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
// 创建一个可以指定具体错误状态码和错误提示消息的ServerResponse对象更加灵活地适应各种不同错误码对应不同错误情况的场景
// 通过传入自定义的状态码和消息,能够准确地反馈具体的错误状态和原因,比如不同业务模块下的特定错误处理等。
return new ServerResponse<>(code, msg);
}
}

@ -7,66 +7,85 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* CategoryControllerSpring MVCRESTful
*
*
* @Author swg.
* @Date 2019/1/2 12:57
* @CONTACT 317758022@qq.com
* @DESC
*/
//TODO 这里首先实现业务 关于这里重复的鉴权,后面将会移植到网关中统一去做
// 此处的TODO注释表明当前控制器内存在重复鉴权的情况后续计划将鉴权逻辑统一移植到网关层去处理以优化和简化鉴权流程避免代码重复提高系统的可维护性。
//TODO 先开放GET请求
// 此TODO注释说明目前暂时先开放GET请求方式可能意味着后续还会根据业务需求对其他请求方式如POST、PUT、DELETE等进行相应的开发和配置。
@RestController
// 该注解表明这个类是一个RESTful风格的控制器Spring会自动将返回的对象转换为JSON等格式响应给客户端并且处理HTTP请求与方法的映射关系。
@RequestMapping("/manage/category/")
// 用于定义这个控制器类中所有接口方法的基础请求路径即该控制器下的接口URL都将以"/manage/category/"开头。
public class CategoryController {
@Autowired
// Spring的依赖注入注解自动将实现了ICategoryService接口的实例注入到此处方便在控制器方法中调用服务层的业务逻辑方法。
private ICategoryService categoryService;
/**
* ()
* ID0
* categoryService.getCategoryServerResponse
*
*/
@RequestMapping("get_category.do")
public ServerResponse getCategory(@RequestParam(value = "categoryId",defaultValue = "0") Integer categoryId){
public ServerResponse getCategory(@RequestParam(value = "categoryId", defaultValue = "0") Integer categoryId) {
ServerResponse response = categoryService.getCategory(categoryId);
return response;
}
/**
*
* IDID0
* categoryService.addCategoryServerResponse
* 便
*/
@RequestMapping("add_category.do")
public ServerResponse addCategory(String categoryName, @RequestParam(value = "parentId",defaultValue = "0")int parentId){
ServerResponse response = categoryService.addCategory(categoryName,parentId);
public ServerResponse addCategory(String categoryName, @RequestParam(value = "parentId", defaultValue = "0") int parentId) {
ServerResponse response = categoryService.addCategory(categoryName, parentId);
return response;
}
/**
*
* ID
* categoryService.updateCategoryNameServerResponse<String>
*
*/
@RequestMapping("set_category_name.do")
public ServerResponse<String> set_category_name(String categoryName,Integer categoryId){
return categoryService.updateCategoryName(categoryName,categoryId);
public ServerResponse<String> set_category_name(String categoryName, Integer categoryId) {
return categoryService.updateCategoryName(categoryName, categoryId);
}
/**
*
* ID0
* categoryService.selectCategoryAndDeepChildrenById
* ServerResponse
*/
@RequestMapping("get_deep_category.do")
public ServerResponse get_deep_category(@RequestParam(value = "categoryId",defaultValue = "0") Integer categoryId){
public ServerResponse get_deep_category(@RequestParam(value = "categoryId", defaultValue = "0") Integer categoryId) {
return categoryService.selectCategoryAndDeepChildrenById(categoryId);
}
/**
*
* 便ID
* categoryService.getCategoryDetailServerResponse
*
*/
@RequestMapping("get_category_detail.do")
public ServerResponse get_category_detail(Integer categoryId){
public ServerResponse get_category_detail(Integer categoryId) {
return categoryService.getCategoryDetail(categoryId);
}
}
}

@ -1,24 +1,70 @@
package com.njupt.swg.dao;
import com.njupt.swg.entity.Category;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CategoryMapperMyBatis访Category
* MyBatis
*
* @MapperMyBatisMapperMyBatis
* 使SQLXML
*/
@Mapper
public interface CategoryMapper {
/**
*
* ID
*
* 10
*/
int deleteByPrimaryKey(Integer id);
/**
*
* CategoryID
* MyBatisSQLXML
* 1
*/
int insert(Category record);
/**
* null
* insertCategory
* nullnull
* 1
*/
int insertSelective(Category record);
/**
*
* MyBatis
* Category
* null
*/
Category selectByPrimaryKey(Integer id);
/**
* null
* CategorynullMyBatisSQL
* 00
*/
int updateByPrimaryKeySelective(Category record);
/**
* null
* CategoryMyBatisSQL
* 0
*/
int updateByPrimaryKey(Category record);
/**
* ID
* IDMyBatisID
* List<Category>Category
*
*/
List<Category> selectCategoryChildrenByParentId(Integer categoryId);
}

@ -3,24 +3,51 @@ package com.njupt.swg.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* Category
* 便Java
*/
@Data
// @Data注解是由Lombok库提供的一个强大的注解它能够自动为类中的非-final字段生成对应的Getter和Setter方法
// 同时还会重写toString()、hashCode()以及equals()等常用方法。这样可以极大地减少代码的冗余,让代码更加简洁,提高开发效率。
@AllArgsConstructor
// 此注解指示Lombok为该类自动生成一个包含所有参数的构造方法。这意味着可以使用所有成员变量的值来便捷地创建Category类的实例对象
// 方便在初始化对象时一次性传入所有必要的属性值例如new Category(1, 2, "示例品类", true, 3, new Date(), new Date())。
@NoArgsConstructor
// 与@AllArgsConstructor相对应@NoArgsConstructor注解让Lombok生成一个无参构造方法。
// 在很多Java框架如MyBatis在进行对象实例化等操作时或者某些默认初始化场景下无参构造方法是必不可少的
// 确保了类具有默认的构造形式,能以更灵活的方式被实例化。
public class Category {
private Integer id;
// 该成员变量用于存储品类的唯一标识符,在数据库层面,它通常对应着数据表中的主键字段。
// 通过这个唯一的ID值可以在整个系统中准确无误地定位、区分每一个不同的品类记录
// 无论是进行数据的查询、更新还是删除等操作,都依赖于这个关键的标识信息。
private Integer parentId;
// 代表该品类所属的父品类的唯一标识符,用于构建品类之间的层级关系,体现了品类的父子层级结构特点。
// 例如若存在“电子产品”品类其ID为10下属有“手机”品类其parentId就可以设置为10通过这样的关联可以清晰地梳理出整个品类的树形结构。
private String name;
// 存储品类的具体名称,是用于直观展示和区分不同品类的重要属性。
// 比如在电商系统中,品类名称可以是“服装”“食品”“家居用品”等,用户可以通过这个名称快速识别品类所涵盖的商品范围。
private Boolean status;
// 这个布尔类型的变量用于表示品类当前所处的状态,常见的用途是标记该品类是否处于可用、启用等有效状态。
// 例如当status为true时表示该品类在业务逻辑中是有效的可以正常参与各类业务操作如展示、销售等
// 而当status为false时则意味着该品类可能处于停用、隐藏等不可用状态。
private Integer sortOrder;
// 用于确定品类在展示或者排序过程中的顺序位置,在一些需要按照特定顺序展示品类列表的业务场景中发挥作用。
// 例如在电商系统的前端页面品类列表可以根据sortOrder的值从小到大进行排序展示数字越小的品类越靠前排列方便用户浏览查找。
private Date createTime;
// 记录了品类记录在数据库中最初被创建的时间点,它在业务操作中有诸多用途,
// 比如可以用于数据的追溯,查看某个品类是什么时候添加到系统中的;也可以用于数据分析,统计不同时间段内品类的新增情况等。
private Date updateTime;
// 用于存储品类记录在数据库中最后一次被更新的时间,它能够帮助了解品类信息的更新历史和时效性。
// 例如,业务人员可以通过这个时间来判断品类相关数据是否是最新的,或者在一些数据同步、备份的场景中,依据这个时间来确定操作的范围等。
}

@ -4,40 +4,73 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
/**
* User
* 便Java
*
* @Author swg.
* @Date 2018/12/31 21:01
* @CONTACT 317758022@qq.com
* @DESC
*/
@Data
// @Data注解由Lombok提供它会自动为类中的非-final字段生成对应的Getter和Setter方法同时重写toString()、hashCode()以及equals()方法,
// 减少了手动编写这些常规代码的工作量,使代码更加简洁,便于在获取和设置对象属性以及对象打印输出等场景中使用。
@NoArgsConstructor
// @NoArgsConstructor注解指示Lombok为该类生成一个无参构造方法。在一些Java框架例如MyBatis进行对象实例化时或者默认初始化的情况下
// 无参构造方法是必需的,确保类能以一种通用的、默认的方式被实例化。
@AllArgsConstructor
// 此注解让Lombok自动生成一个包含所有参数的构造方法这样可以方便地使用所有成员变量的值来创建User类的实例对象
// 比如new User(1, "user1", "pass1", "user1@example.com", "123456789", "question1", "answer1", 1, new Date(), new Date())
// 适用于需要一次性传入所有属性值来初始化对象的场景。
@ToString
// @ToString注解用于让Lombok自动重写toString()方法使得在打印输出User类对象时能够以一种清晰、易读的格式展示对象的各个属性值
// 方便在调试等场景中查看对象的具体内容。
public class User implements Serializable {
// 实现Serializable接口表示该类的对象可以被序列化和反序列化便于在网络传输如在分布式系统中传递用户信息或者持久化存储如保存用户数据到文件等情况时使用
// 确保对象数据的完整性和可恢复性。
private Integer id;
// 用于存储用户在系统中的唯一标识符通常作为数据库中用户表的主键字段通过这个ID可以在整个系统中准确地定位、区分每一个不同的用户
// 是进行用户相关的数据查询、更新、删除等操作的重要依据。
private String username;
// 存储用户登录系统时所使用的用户名,是用户在系统中对外展示的身份标识之一,用户通过输入这个用户名以及对应的密码来进行登录操作,
// 并且在系统的很多交互场景中(如显示用户信息、进行权限判断等)都会用到该属性。
private String password;
// 存放用户登录系统的密码,是保障用户账号安全的关键信息,在用户进行登录验证以及修改密码等业务操作时会涉及到对该字段的处理,
// 需要进行妥善的加密存储以及安全验证等操作来保护用户的隐私和账号安全。
private String email;
// 用于记录用户的电子邮箱地址,在系统中可以用于多种用途,比如找回密码、接收系统通知、验证用户身份等,方便与用户进行非实时的信息沟通以及相关业务流程的推进。
private String phone;
// 存储用户的手机号码,同样也是一种重要的联系方式,可用于接收验证码、短信通知等,并且在一些需要手机号验证或者基于手机号进行服务拓展(如手机号登录等)的业务场景中发挥作用。
private String question;
// 代表用户设置的用于找回密码的密保问题,当用户忘记密码时,可以通过回答正确这个密保问题来重置密码,
// 是一种辅助保障用户账号可找回性和安全性的机制,需要用户在注册或者账号设置阶段进行合理设置。
private String answer;
// 对应密保问题的答案与question字段配合使用只有用户输入的答案与预先设置的答案一致时才能成功进行密码重置等相关操作
// 属于用户账号安全体系中的重要一环,同样需要妥善保护其保密性。
//角色0-管理员,1-普通用户
private Integer role;
// 用于标识用户在系统中所扮演的角色通过不同的整数值来区分不同权限等级的用户这里约定0表示管理员角色具有系统的最高权限
// 可以进行各种系统管理操作如管理用户、配置系统参数等1表示普通用户角色其权限相对受限只能进行一些普通的业务操作如查看信息、下单购物等
// 在系统进行权限判断和功能限制等业务逻辑处理时会依据这个角色属性来决定用户能否执行相应的操作。
private Date createTime;
// 记录用户账号在数据库中被创建的时间,有助于了解用户的注册历史情况,比如统计不同时间段内的用户新增数量、查看用户的注册顺序等,
// 并且在一些数据追溯以及业务分析场景中具有一定的参考价值。
private Date updateTime;
// 用于存储用户信息最后一次在数据库中被更新的时间,通过这个时间可以判断用户信息的时效性,了解用户是否近期有过信息变更,
// 同时在一些数据同步、备份以及数据一致性维护等业务场景中也能起到辅助作用。
}

@ -16,115 +16,174 @@ import java.util.List;
import java.util.Set;
/**
* CategoryServiceImplICategoryServiceCategory
* 访CategoryMapperServerResponse
*
* @Author swg.
* @Date 2019/1/2 12:54
* @CONTACT 317758022@qq.com
* @DESC
*/
@Service
// @Service注解用于标记这个类是Spring框架中的一个服务层组件Spring会自动扫描并将其纳入到容器管理中方便进行依赖注入等操作。
@Slf4j
public class CategoryServiceImpl implements ICategoryService{
// 使用Lombok的@Slf4j注解自动生成一个名为log的SLF4J日志记录器用于在类中记录日志信息方便在业务处理过程中记录关键操作、异常情况等便于后续排查问题。
public class CategoryServiceImpl implements ICategoryService {
@Autowired
// Spring的依赖注入注解通过该注解将CategoryMapper接口的实现类自动注入到此处使得本服务类能够调用数据访问层的方法与数据库进行交互。
private CategoryMapper categoryMapper;
/**
*
* IDID
*
*
* @param categoryId IDnull
* @return ServerResponse
*/
@Override
public ServerResponse getCategory(Integer categoryId) {
//1.校验参数
if(categoryId == null){
// 1.校验参数
if (categoryId == null) {
// 如果品类ID为空说明传入的参数不符合要求抛出SnailmallException异常提示“未找到该品类”由上层统一处理异常情况并返回相应错误响应给客户端。
throw new SnailmallException("未找到该品类");
}
//2.根据父亲id获取这个父亲下一级所有子ID
// 2.根据父亲id获取这个父亲下一级所有子ID
List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId);
if(CollectionUtils.isEmpty(categoryList)){
if (CollectionUtils.isEmpty(categoryList)) {
// 如果获取到的品类列表为空,意味着该品类节点下没有任何子节点,记录一条信息级别的日志,方便后续查看业务执行情况以及排查问题。
log.info("该节点下没有任何子节点");
}
return ServerResponse.createBySuccess(categoryList);
}
/**
*
* ID
* 访
*
* @param categoryName
* @param parentId IDID
* @return ServerResponse
*/
@Override
public ServerResponse addCategory(String categoryName, int parentId) {
//1.校验参数
if(StringUtils.isBlank(categoryName)){
// 1.校验参数
if (StringUtils.isBlank(categoryName)) {
// 如果品类名称为空字符串包括null和空字符串情况不符合业务要求抛出异常提示“品类名字不能为空”。
throw new SnailmallException("品类名字不能为空");
}
//2.创建类目
// 2.创建类目
Category category = new Category();
category.setName(categoryName);
category.setParentId(parentId);
category.setStatus(true);
int resultCount = categoryMapper.insert(category);
if(resultCount > 0){
if (resultCount > 0) {
return ServerResponse.createBySuccessMessage("添加品类成功");
}
return ServerResponse.createByErrorMessage("添加品类失败");
}
/**
*
* ID
* ID访
*
*
* @param categoryName
* @param categoryId ID
* @return ServerResponse<String>
*/
@Override
public ServerResponse<String> updateCategoryName(String categoryName, Integer categoryId) {
//1.校验参数
if(StringUtils.isBlank(categoryName)){
// 1.校验参数
if (StringUtils.isBlank(categoryName)) {
throw new SnailmallException("品类名字不能为空");
}
//2.根据id获取品类
// 2.根据id获取品类
Category tmpCat = categoryMapper.selectByPrimaryKey(categoryId);
if(tmpCat == null){
if (tmpCat == null) {
throw new SnailmallException("品类不存在");
}
//3.更新品类名称
// 3.更新品类名称
Category category = new Category();
category.setId(categoryId);
category.setName(categoryName);
int resultCount = categoryMapper.updateByPrimaryKeySelective(category);
if(resultCount > 0){
if (resultCount > 0) {
return ServerResponse.createBySuccessMessage("更新品类名称成功");
}
return ServerResponse.createByErrorMessage("更新品类名称失败");
}
/**
*
* Set
* IDID
*
* @param categoryId IDnull
* @return ServerResponse IDID
*/
@Override
public ServerResponse selectCategoryAndDeepChildrenById(Integer categoryId) {
//1、创建一个空Set用来存放不重复的品类对象--去重
// 1、创建一个空Set用来存放不重复的品类对象--去重
Set<Category> categorySet = Sets.newHashSet();
//2、递归获取所有的子节点儿子、孙子、等等包括自己也添加进去
findChildCategory(categorySet,categoryId);
//3、将递归获取到的品类id取出来放进list中
// 2、递归获取所有的子节点儿子、孙子、等等包括自己也添加进去
findChildCategory(categorySet, categoryId);
// 3、将递归获取到的品类id取出来放进list中
List<Integer> categoryIdList = new ArrayList<>();
if(categoryId != null){
for(Category category:categorySet){
if (categoryId!= null) {
for (Category category : categorySet) {
categoryIdList.add(category.getId());
}
}
return ServerResponse.createBySuccess(categoryIdList);
}
private Set<Category> findChildCategory(Set<Category> categorySet,Integer categoryId){
//4、如果自己不为空的话首先把自己添加进去如果自己为空这个递归分支就结束所以也是一个停止条件
/**
* Set
*
*
*
* @param categorySet
* @param categoryId ID
* @return Set<Category> 便ID
*/
private Set<Category> findChildCategory(Set<Category> categorySet, Integer categoryId) {
// 4、如果自己不为空的话首先把自己添加进去如果自己为空这个递归分支就结束所以也是一个停止条件
Category category = categoryMapper.selectByPrimaryKey(categoryId);
if(category != null){
if (category!= null) {
categorySet.add(category);
}
//5、根据父亲id获取下一级所有品类即先获取儿子们
// 5、根据父亲id获取下一级所有品类即先获取儿子们
List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId);
//6、根据每一个儿子再获取儿子的儿子们递归下去
for(Category categoryItem:categoryList){
findChildCategory(categorySet,categoryItem.getId());
// 6、根据每一个儿子再获取儿子的儿子们递归下去
for (Category categoryItem : categoryList) {
findChildCategory(categorySet, categoryItem.getId());
}
return categorySet;
}
/**
*
* IDID
*
*
* @param categoryId IDnull
* @return ServerResponse ID
*/
@Override
public ServerResponse getCategoryDetail(Integer categoryId) {
if(categoryId == null){
if (categoryId == null) {
return ServerResponse.createByErrorMessage("参数不能为空");
}
Category category = categoryMapper.selectByPrimaryKey(categoryId);
if(category == null){
if (category == null) {
return ServerResponse.createByErrorMessage("品类不存在");
}
return ServerResponse.createBySuccess(category);
}
}
}

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

@ -5,14 +5,36 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* SnailmallConfigServerApplicationSpring Boot
* 使
*/
@SpringBootApplication
// @SpringBootApplication注解是一个复合注解它整合了多个重要的Spring相关注解功能。
// 其中,@Configuration注解表示这个类本身就是一个配置类可以在里面定义各种Bean以及配置信息
// @EnableAutoConfiguration注解用于开启Spring Boot的自动配置机制会根据项目中添加的依赖自动配置相应的Spring组件和功能极大地简化了配置过程
// @ComponentScan注解则负责扫描指定包及其子包下的所有被Spring组件注解如@Component、@Service、@Controller等标记的类将它们纳入Spring容器进行管理。
// 通过使用这个复合注解能够便捷地搭建起一个基础的Spring Boot应用框架。
@EnableDiscoveryClient
// @EnableDiscoveryClient注解在微服务架构环境下有着重要作用。它用于启用服务发现客户端的功能
// 意味着这个配置服务器应用可以将自身的服务信息注册到服务注册中心例如Eureka、Consul等常用的服务注册与发现工具
// 同时也能够发现其他已注册在服务注册中心的服务,方便在分布式系统中实现服务之间的相互调用与协作,确保配置服务器能更好地融入整个微服务生态系统。
@EnableConfigServer
// @EnableConfigServer注解是Spring Cloud专门用于启用配置服务器功能的关键注解。
// 当应用中添加了这个注解后Spring Cloud会自动将该应用配置成一个配置服务器它能够从各种配置源如本地文件系统、Git仓库等读取配置文件
// 并对外提供配置信息的获取服务,使得其他微服务应用可以从这个配置服务器获取它们所需的配置内容,实现了配置的集中管理和动态更新等功能。
public class SnailmallConfigServerApplication {
/**
* mainJavaSpring Boot
* SpringApplication.runClassSnailmallConfigServerApplication.classargs
* Spring BootWeb
* 使
*/
public static void main(String[] args) {
SpringApplication.run(SnailmallConfigServerApplication.class, args);
}
}
}

@ -8,14 +8,51 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
/**
* SnailmallProductServiceApplicationSpring Boot
* 使Spring BootFeign使便
*/
@SpringBootApplication
// @SpringBootApplication是一个组合注解它相当于同时使用了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 这三个注解。
// @Configuration注解表明这个类是一个Java配置类可用于定义Bean等配置信息@EnableAutoConfiguration注解启用了Spring Boot的自动配置机制
// 会根据项目中添加的依赖自动配置各种组件如数据库连接、Web相关配置等减少了手动配置的工作量@ComponentScan注解用于指定Spring要扫描的组件所在的包路径
// 默认会扫描该类所在包及其子包下的所有带有Spring相关注解如 @Component、@Service、@Controller等的类将它们注册为Spring容器中的Bean方便进行依赖注入等操作。
@EnableDiscoveryClient
// @EnableDiscoveryClient注解用于启用服务发现功能在微服务架构中当项目作为一个服务运行时这个注解会让服务能够注册到服务注册中心如Eureka、Consul等
// 并且可以从注册中心发现其他服务的信息,方便实现服务之间的调用和交互,使得各个微服务能够动态地感知到彼此的存在,便于构建分布式的系统架构。
@EnableFeignClients
// @EnableFeignClients注解用于开启Feign客户端功能。Feign是一个声明式的HTTP客户端框架它可以简化微服务之间的HTTP接口调用
// 通过定义接口并使用注解来描述HTTP请求的相关信息如请求方法、请求路径、请求参数等就可以方便地调用其他微服务提供的接口
// 而无需手动编写复杂的HTTP请求代码提高了微服务之间通信的便利性和代码的可读性、可维护性。
public class SnailmallProductServiceApplication {
/**
* SpringApplicationrunSpring Boot
* SnailmallProductServiceApplication.classargs
* Spring BootWebTomcat
*
* @param args
* Spring Boot使
*/
public static void main(String[] args) {
SpringApplication.run(SnailmallProductServiceApplication.class, args);
}
}
/**
* BeanPropertySourcesPlaceholderConfigurerBean
* BeanSpring @PropertySource
* @Value
* 便
*
* @return PropertySourcesPlaceholderConfigurer PropertySourcesPlaceholderConfigurer
* Spring
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}

@ -8,48 +8,71 @@ import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* CommonCacheUtilRedis
*
* 便Redis
*
* @Author swg.
* @Date 2019/1/1 15:03
* @CONTACT 317758022@qq.com
* @DESC
*/
@Component
// @Component注解用于将这个类标记为Spring框架中的一个组件使得Spring能够扫描并管理这个类方便进行依赖注入等操作
// 意味着可以在其他需要使用这个缓存工具类的地方通过依赖注入的方式获取其实例。
@Slf4j
// 使用Lombok的@Slf4j注解会自动生成一个名为log的SLF4J日志记录器用于在类中记录各种操作的日志信息
// 特别是在缓存操作出现异常等情况时,方便记录详细的错误信息,便于后续排查问题。
public class CommonCacheUtil {
@Autowired
// Spring的依赖注入注解用于将JedisPoolWrapper类型的对象注入到当前类中
// 通过这个被注入的对象可以获取到JedisPool实例进而操作Redis缓存实现了与缓存配置的解耦方便灵活替换缓存相关的配置和实现。
private JedisPoolWrapper jedisPoolWrapper;
/**
* key
* Redis
* JedisPoolJedisRedis
*
* @param key RedisRedis
* @param value JSONRedis
*/
public void cache(String key, String value) {
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
Jedis.set(key, value);
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
// 选择Redis的数据库编号这里选择0号数据库通常在一个Redis实例中可以配置多个数据库通过编号进行区分便于不同业务场景使用不同数据库来隔离数据。
jedis.select(0);
jedis.set(key, value);
}
}
} catch (Exception e) {
log.error("redis存值失败", e);
// 如果在向Redis存储值的过程中出现异常记录错误日志并抛出SnailmallException异常告知上层调用者Redis操作报错由上层统一处理异常情况。
throw new SnailmallException("redis报错");
}
}
/**
* key
* RedisJedisPoolJedis
* null
*
* @param key 使Redis
* @return String Redisnull
*/
public String getCacheValue(String key) {
String value = null;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
value = Jedis.get(key);
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
value = jedis.get(key);
}
}
} catch (Exception e) {
@ -61,12 +84,19 @@ public class CommonCacheUtil {
/**
* key
* Redis使setnx
* setnx10
*
* @param key RedisRedis
* @param value Redis
* @param expire RedisRedis
* @return long setnx1Redis0
*/
public long cacheNxExpire(String key, String value, int expire) {
long result = 0;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
result = jedis.setnx(key, value);
@ -83,10 +113,14 @@ public class CommonCacheUtil {
/**
* key
* RedisJedis
*
*
* @param key RedisRedis
*/
public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
@ -98,7 +132,4 @@ public class CommonCacheUtil {
}
}
}
}
}

@ -5,38 +5,70 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import javax.annotation.PostConstruct;
/**
* JedisPoolWrapperJedisPoolJedisJedisPool
* 便Redis便JedisRedis
* RedisRedisHash
*
* @Author swg.
* @Date 2019/1/1 15:00
* @CONTACT 317758022@qq.com
* @DESC redisredishash
*/
@Component
// @Component注解用于将该类标记为Spring框架中的一个组件使得Spring能够扫描并管理这个类这样就可以通过依赖注入等方式在其他类中使用它
// 保证了类的实例化和生命周期由Spring容器进行管控便于在项目中灵活集成和复用。
@Slf4j
// 通过Lombok的@Slf4j注解自动生成一个名为log的SLF4J日志记录器用于在类中记录日志信息方便记录如连接池初始化过程中的成功、失败等关键情况
// 有助于后续排查问题以及查看系统运行状态。
public class JedisPoolWrapper {
@Autowired
// Spring的依赖注入注解用于将Parameters类型的对象注入到当前类中。这里的Parameters类应该是用于存放Redis相关配置参数的
// 通过注入这个对象本类能够获取到如Redis最大连接数、最大空闲连接数、最大等待时间等配置信息进而对Jedis连接池进行合理配置。
private Parameters parameters;
private JedisPool jedisPool = null;
// 用于存储JedisPool对象JedisPool是Jedis客户端提供的连接池对象通过它可以管理与Redis服务器的连接避免频繁创建和销毁连接提高性能
// 初始化为null会在后续的初始化方法中根据配置参数进行实例化。
@PostConstruct
public void init(){
// @PostConstruct注解标记的方法会在类实例化后依赖注入完成时自动被调用用于执行一些初始化的操作。在这里就是用于初始化Jedis连接池的相关配置并创建JedisPool对象。
public void init() {
try {
JedisPoolConfig config = new JedisPoolConfig();
// 创建JedisPoolConfig对象它用于配置Jedis连接池的各种属性如连接池中的最大连接数、最大空闲连接数、获取连接的最大等待时间等。
config.setMaxTotal(parameters.getRedisMaxTotal());
// 通过注入的Parameters对象获取Redis最大连接数配置参数并设置到JedisPoolConfig中用于限制连接池中总共可以创建的最大连接数量
// 避免因创建过多连接导致系统资源耗尽等问题。
config.setMaxIdle(parameters.getRedisMaxIdle());
// 获取Redis最大空闲连接数参数并设置到JedisPoolConfig中规定了连接池中允许空闲的最大连接数量合理设置可以提高连接的复用效率减少资源浪费。
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx");
// 获取Redis获取连接的最大等待时间参数单位为毫秒设置到JedisPoolConfig中当连接池中的连接耗尽时若获取连接的等待时间超过这个设定值就会抛出异常
// 用于防止长时间等待获取连接而导致系统阻塞等情况。
jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx");
// 根据配置好的JedisPoolConfig对象以及从Parameters对象获取到的Redis服务器的主机地址、端口号等信息创建JedisPool对象
// 其中2000表示连接超时时间单位为毫秒"xxx"这里应该是密码如果Redis服务器设置了密码认证的话实际使用中需替换为正确密码
// 通过这个JedisPool对象就可以获取Jedis客户端连接与Redis服务器进行交互了。
log.info("【初始化redis连接池成功】");
}catch (Exception e){
log.error("【初始化redis连接池失败】",e);
// 记录日志表示Jedis连接池初始化成功方便在查看日志时确认连接池是否正常创建以便后续排查可能出现的与Redis连接相关的问题。
} catch (Exception e) {
log.error("【初始化redis连接池失败】", e);
// 如果在初始化Jedis连接池过程中出现异常记录错误日志详细记录异常信息方便后续查找问题根源排查初始化失败的原因。
}
}
public JedisPool getJedisPool() {
return jedisPool;
}
}
// 对外提供获取JedisPool对象的方法使得其他类如操作Redis缓存的工具类等能够获取到这个已经配置好的连接池对象进而获取Jedis客户端连接来操作Redis。
}

@ -5,24 +5,54 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* ParametersRedis
* 使Springapplication.propertiesapplication.yml
* 便使使
*
* @Author swg.
* @Date 2019/1/1 14:27
* @CONTACT 317758022@qq.com
* @DESC
*/
@Component
// @Component注解用于将这个类标记为Spring框架中的一个组件使得Spring能够扫描并管理这个类将其纳入Spring容器中
// 这样就可以通过依赖注入等方式在其他类中方便地使用这个类的实例,便于在项目中实现配置参数的统一管理和共享使用。
@Data
// @Data注解是由Lombok库提供的一个便捷注解它会自动为类中的非-final字段生成对应的Getter和Setter方法
// 同时还会重写toString()、hashCode()以及equals()等方法,减少了手动编写这些常规代码的工作量,使代码更加简洁,方便对类中成员变量的访问和操作。
public class Parameters {
/*****redis config start*******/
@Value("${redis.host}")
// @Value注解用于从Spring的配置文件例如application.properties或application.yml等中按照指定的属性键读取对应的值
// 并将其注入到被标注的成员变量中。这里表示从配置文件中读取名为"redis.host"的属性值将其注入到redisHost变量中
// 该属性值通常用于指定Redis服务器的主机地址比如"localhost"或者具体的IP地址等以便在代码中能够正确连接到Redis服务器。
private String redisHost;
@Value("${redis.port}")
// 同样通过@Value注解从配置文件中读取名为"redis.port"的属性值注入到redisPort变量中用于指定Redis服务器的端口号
// 常见的Redis默认端口号是6379通过配置文件可以灵活修改该端口号以适应不同的部署环境或自定义设置。
private int redisPort;
@Value("${redis.max-idle}")
// 从配置文件中读取"redis.max-idle"属性值注入到redisMaxTotal变量中不过从变量名来看这里可能存在命名混淆
// 按照一般的理解,"redis.max-idle"对应的应该是最大空闲连接数而变量名却为redisMaxTotal通常代表最大连接总数
// 可能需要进一步确认配置文件中的属性含义与这里变量使用的一致性暂且认为此处是按照实际配置文件的语义进行赋值用于配置Jedis连接池的相关参数
// 表示连接池中允许的最大空闲连接数量,合理设置该值可以提高连接的复用效率,避免过多空闲连接占用资源。
private int redisMaxTotal;
@Value("${redis.max-total}")
// 读取配置文件中"redis.max-total"属性值注入到redisMaxIdle变量中同样存在变量名与常规语义不太匹配的情况
// 正常"redis.max-total"一般表示连接池允许创建的最大连接总数此处注入到名为redisMaxIdle的变量通常理解为最大空闲连接数
// 需检查配置文件与代码逻辑的对应关系暂且按当前代码逻辑理解为用于设置Jedis连接池相关参数这里设定连接池的最大连接总数
// 限制了连接池中总共可以创建的连接数量,防止因创建过多连接导致系统资源紧张等问题。
private int redisMaxIdle;
@Value("${redis.max-wait-millis}")
// 从配置文件获取"redis.max-wait-millis"属性值注入到redisMaxWaitMillis变量中这个属性用于指定获取连接时的最大等待时间单位为毫秒
// 在连接池中的连接耗尽时,如果获取连接的等待时间超过这个设定值,就会抛出异常,以此来避免长时间等待获取连接而导致系统阻塞等情况发生,
// 合理设置该参数有助于保障系统的响应性能和稳定性。
private int redisMaxWaitMillis;
/*****redis config end*******/
}
}

@ -7,16 +7,42 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* CategoryClientSpring Cloud OpenFeign"category-service"
* Feign便便
*
* @Author swg.
* @Date 2019/1/3 16:56
* @CONTACT 317758022@qq.com
* @DESC
*/
@FeignClient("category-service")
// @FeignClient注解用于指定要调用的远程服务的名称这里是"category-service"Spring Cloud会根据这个名称去服务注册中心如Eureka等查找对应的服务实例
// 并通过动态代理等机制生成实现该接口的代理类,使得接口中的方法调用能够被转发到远程服务对应的接口上进行实际执行,从而实现远程服务调用。
public interface CategoryClient {
/**
*
* "category-service"HTTPFeign
* IDServerResponse便
*
* @param categoryId 使
*
* @return ServerResponse
*
*/
@RequestMapping("/manage/category/get_category_detail.do")
ServerResponse getCategoryDetail(@RequestParam("categoryId") Integer categoryId);
/**
*
* "category-service"
* HTTPIDServerResponse便
*
* @param categoryId
* null
* @return ServerResponse ID
*
*/
@RequestMapping("/manage/category/get_deep_category.do")
ServerResponse getDeepCategory(@RequestParam(value = "categoryId") Integer categoryId);
}
}

@ -4,41 +4,78 @@ import com.google.common.collect.Sets;
import java.util.Set;
/**
* Constants便
* 使使
*
* @Author swg.
* @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com
* @DESC
*/
public class Constants {
/**自定义状态码 start**/
/**
* start
* HTTP
* 便使
*/
public static final int RESP_STATUS_OK = 200;
// RESP_STATUS_OK常量表示请求处理成功的状态码对应常见的HTTP 200状态码表示客户端发起的请求已被服务器成功处理并返回了预期的结果
// 在业务逻辑中可以通过判断响应的状态码是否等于该常量来确定操作是否顺利完成。
public static final int RESP_STATUS_NOAUTH = 401;
// RESP_STATUS_NOAUTH常量代表未授权的状态码类似于HTTP 401状态码意味着客户端发起的请求需要用户进行身份认证但当前用户未提供有效的认证凭据或者认证失败
// 在涉及需要权限验证的业务场景中,当收到该状态码的响应时,可以提示用户进行登录或重新认证等操作。
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
// RESP_STATUS_INTERNAL_ERROR常量表示服务器内部错误的状态码等同于HTTP 500状态码说明服务器在处理请求时发生了内部错误无法正常完成请求的处理
// 当业务逻辑中接收到该状态码的响应时,通常可以记录错误日志并向用户提示服务器出现问题,建议稍后再试等信息。
public static final int RESP_STATUS_BADREQUEST = 400;
// RESP_STATUS_BADREQUEST常量用于表示请求参数错误的状态码类似HTTP 400状态码表明客户端发送的请求存在格式错误、参数缺失或者不符合要求等问题
// 导致服务器无法正确解析和处理该请求,在进行请求参数校验等业务逻辑中可以根据该状态码来反馈参数错误相关的提示信息给用户。
/**自定义状态码 end**/
/**
* end
*/
/** 产品的状态 **/
public interface Product{
/**
*
*
*
*/
public interface Product {
int PRODUCT_ON = 1;
// PRODUCT_ON常量表示产品处于正常上线、可用的状态通常意味着该产品可以被展示、销售等在产品状态判断的业务逻辑中可以用该常量来标识产品是否处于可操作状态。
int PRODUCT_OFF = 2;
// PRODUCT_OFF常量代表产品处于下线、停用的状态比如产品暂时缺货、不符合销售条件等情况时可将其状态设置为该值
// 在业务中可以根据这个状态值来决定是否隐藏该产品或者不允许对其进行某些操作。
int PRODUCT_DELETED = 3;
// PRODUCT_DELETED常量表示产品已被删除的状态当产品从系统中彻底移除后可以用这个常量来标记其最终状态
// 在数据查询、展示等业务逻辑中可以依据该状态值过滤掉已删除的产品信息。
}
public interface ProductListOrderBy{
Set<String> PRICE_ASC_DESC = Sets.newHashSet("price_desc","price_asc");
public interface ProductListOrderBy {
// ProductListOrderBy内部接口用于定义产品列表排序相关的常量集合这里定义了一个包含按照价格升序和降序排序标识的集合
// 在进行产品列表排序的业务场景中,可以通过判断传入的排序参数是否在这个集合中来确定是否是合法的价格排序方式,并按照相应规则进行排序操作。
Set<String> PRICE_ASC_DESC = Sets.newHashSet("price_desc", "price_asc");
}
/***redis product**/
/**
* redis product
* Redis使Redis
* 便Redis
*/
public static final String PRODUCT_TOKEN_PREFIX = "product__";
// PRODUCT_TOKEN_PREFIX常量定义了在Redis中存储产品相关数据时使用的键的前缀通过添加这个前缀可以方便地对产品相关的缓存数据进行分类和查找
// 例如存储产品详情信息时,缓存键可能是"product__产品ID"这样的格式便于在Redis中统一管理产品相关的缓存项。
public static final int PRODUCT_EXPIRE_TIME = 60 * 60 * 24 * 300;
// PRODUCT_EXPIRE_TIME常量设定了产品相关数据在Redis缓存中的过期时间单位为秒这里计算后表示缓存有效期为300天
// 通过设置合适的过期时间可以保证缓存数据的时效性,避免长时间使用过期数据而导致业务逻辑出现问题,同时也能合理利用缓存资源。
public static final String PRODUCT_TOKEN_STOCK_PREFIX = "product__stock_";
}
// PRODUCT_TOKEN_STOCK_PREFIX常量定义了在Redis中存储产品库存相关数据时使用的键的前缀与前面的产品数据前缀类似
// 只是用于专门区分和标识与产品库存相关的缓存数据,方便在操作产品库存缓存时准确地进行键的定位和数据的处理。
}

@ -1,6 +1,5 @@
package com.njupt.swg.common.exception;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ServerResponse;
import lombok.extern.slf4j.Slf4j;
@ -9,25 +8,59 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* ExceptionHandlerAdviceSpring
*
* 便
*
* @Author swg.
* @Date 2019/1/1 13:21
* @CONTACT 317758022@qq.com
* @DESC
*/
@ControllerAdvice
// @ControllerAdvice注解用于标识这个类是一个全局的异常处理类它可以对整个项目中的控制器层@Controller注解标记的类抛出的异常进行统一处理
// 相当于一个全局的异常拦截器,能够捕获多种类型的异常并按照定义的方法进行相应的处理操作。
@ResponseBody
// @ResponseBody注解表示将方法的返回值直接作为响应体返回给客户端而不是去寻找对应的视图进行渲染
// 在这里用于确保异常处理方法返回的ServerResponse对象能够以JSON等格式直接响应给客户端符合RESTful风格的接口返回要求。
@Slf4j
// 使用Lombok的@Slf4j注解自动生成一个名为log的SLF4J日志记录器用于在异常处理过程中记录详细的异常信息
// 方便后续查看日志来排查问题、分析系统出现异常的原因以及进行相关的监控统计等工作。
public class ExceptionHandlerAdvice {
/**
*
* @ExceptionHandler
* ServerResponse
* 使Constants
*
* @param e Exception
* 便
* @return ServerResponse
*
*/
@ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试");
public ServerResponse handleException(Exception e) {
log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试");
}
/**
* SnailmallException
* SnailmallException
* SnailmallExceptionServerResponse
*
*
* @param e SnailmallException
*
* @return ServerResponse
*
*/
@ExceptionHandler(SnailmallException.class)
public ServerResponse handleException(SnailmallException e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage());
public ServerResponse handleException(SnailmallException e) {
log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage());
}
}
}

@ -4,22 +4,50 @@ import com.njupt.swg.common.resp.ResponseEnum;
import lombok.Getter;
/**
* SnailmallExceptionJavaRuntimeException
* Java
* 便
*
* @Author swg.
* @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com
* @DESC
*/
@Getter
public class SnailmallException extends RuntimeException{
// @Getter注解由Lombok库提供它会自动为类中的私有成员变量这里是exceptionStatus生成对应的Getter方法
// 方便外部代码获取该变量的值遵循了Java的封装原则同时减少了手动编写Getter方法的工作量使代码更加简洁。
public class SnailmallException extends RuntimeException {
private int exceptionStatus = ResponseEnum.ERROR.getCode();
// 定义一个私有成员变量exceptionStatus用于存储异常对应的状态码默认初始化为ResponseEnum.ERROR.getCode()
// 这里的ResponseEnum应该是一个枚举类用于定义各种响应状态相关的枚举值默认情况下该异常的状态码采用这个默认的错误状态码值
// 当然也可以通过特定的构造方法来重新设置这个状态码,以便更准确地表示不同业务场景下的异常情况。
public SnailmallException(String msg){
/**
* SnailmallException
* RuntimeException
* 使ResponseEnum.ERROR.getCode()
* 使
*
* @param msg
* 便
*/
public SnailmallException(String msg) {
super(msg);
}
public SnailmallException(int code,String msg){
/**
* SnailmallException
* RuntimeException
* exceptionStatus
*
*
* @param code 使
* ResponseEnum便
* @param msg msg便
*/
public SnailmallException(int code, String msg) {
super(msg);
exceptionStatus = code;
}
}
}

@ -3,23 +3,49 @@ package com.njupt.swg.common.resp;
import lombok.Getter;
/**
* ResponseEnum
* 使
* 便
*
* @Author swg.
* @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com
* @DESC
*/
@Getter
// @Getter注解由Lombok库提供它会自动为枚举类中的私有成员变量这里是code和desc生成对应的Getter方法
// 方便外部代码获取这些变量的值遵循了Java的封装原则同时减少了手动编写Getter方法的工作量使代码更加简洁。
public enum ResponseEnum {
SUCCESS(0,"SUCCESS"),
ERROR(1,"ERROR"),
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"),
NEED_LOGIN(10,"NEED_LOGIN");
SUCCESS(0, "SUCCESS"),
// SUCCESS枚举值表示操作成功的状态对应的状态码为0描述信息为"SUCCESS",在业务逻辑中当某个操作顺利完成并需要返回成功的响应时,
// 可以使用这个枚举值来构建相应的返回对象如ServerResponse对象向客户端传达操作成功的消息以及对应的状态码方便客户端进行相应处理。
ERROR(1, "ERROR"),
// ERROR枚举值代表操作出现错误的一般情况状态码设定为1描述为"ERROR",用于在业务逻辑发生一般性错误(没有更具体的错误分类时),
// 通过返回包含该枚举值相关信息的响应对象告知客户端操作失败,客户端可以根据这个统一的错误标识进行相应的提示展示等操作。
ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"),
// ILLEGAL_ARGUMENTS枚举值用于表示传入的参数不合法的情况状态码为2描述为"ILLEGAL_ARGUMENTS"
// 当业务逻辑中对客户端传入的请求参数进行校验发现不符合要求(如参数缺失、格式错误等)时,就可以使用这个枚举值返回相应的错误响应,
// 让客户端知晓是参数方面出现了问题,并提示用户修正参数后重新发起请求。
NEED_LOGIN(10, "NEED_LOGIN");
// NEED_LOGIN枚举值表示需要用户登录的状态状态码为10描述为"NEED_LOGIN",在涉及需要权限验证且用户未登录的业务场景中,
// 通过返回包含该枚举值信息的响应对象,提示客户端当前操作需要用户先进行登录,引导用户去登录页面完成登录操作后再继续。
private int code;
// 用于存储每个枚举值对应的状态码,通过这些状态码可以在项目的不同地方(如全局异常处理、返回结果判断等场景)统一识别和区分不同的响应状态,
// 便于进行逻辑处理和向客户端准确反馈具体的情况。
private String desc;
// 存放每个枚举值对应的描述信息,是对相应状态的一种文字性描述,更加直观易懂,方便客户端在接收到响应后展示给用户或者开发人员查看,
// 以便更好地理解服务端操作的结果以及出现的情况。
ResponseEnum(int code,String desc){
ResponseEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
// 枚举类的构造方法用于初始化每个枚举值对应的状态码和描述信息在定义枚举值如SUCCESS(0, "SUCCESS")等)时会调用这个构造方法,
// 将传入的状态码和描述信息赋值给对应的成员变量,确保每个枚举值都有正确的状态码和描述与之关联。
}

@ -4,75 +4,135 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* ServerResponse
* 使
* 便使
* 便
*
* @Author swg.
* @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com
* @DESC
*/
@Getter
// @Getter注解由Lombok库提供它会自动为类中的非-final成员变量这里是status、msg和data生成对应的Getter方法
// 方便外部代码获取这些变量的值遵循了Java的封装原则同时减少了手动编写Getter方法的工作量使代码更加简洁。
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
// @JsonSerialize注解用于配置Jackson用于JSON序列化和反序列化的库的序列化行为这里设置了include属性为JsonSerialize.Inclusion.NON_NULL
// 表示在将ServerResponse对象序列化为JSON格式时只包含那些非null值的属性避免返回的JSON数据中出现不必要的null字段优化了数据传输量并且使返回的JSON结构更简洁明了。
public class ServerResponse<T> implements Serializable {
// 实现Serializable接口表示该类的对象可以被序列化和反序列化这在网络传输如将响应结果从服务端发送到客户端或者持久化存储等场景中是必要的
// 确保对象数据能够完整地在不同环境中传递和恢复。
private int status;
// 用于存储响应的状态码,通过这个状态码可以表示操作的结果是成功还是失败以及具体是哪种类型的成功或失败情况,
// 通常会与项目中定义的状态码枚举如ResponseEnum中的值相对应方便统一管理和判断响应状态。
private String msg;
// 存放响应的提示消息,是对操作结果的一种文字性描述,用于更直观地告知客户端(如前端页面或者其他调用服务的客户端)操作的情况,
// 例如成功时可以是“操作成功”之类的消息,失败时可以是具体的错误原因提示等,方便用户理解响应内容。
private T data;
// 泛型成员变量,用于存储具体的业务数据,如果操作成功并且有需要返回的数据(如查询操作返回的查询结果集等),就可以将这些数据存放在这里,
// 通过泛型可以适应不同类型的数据返回需求,提高了类的通用性和灵活性。
public ServerResponse(){}
public ServerResponse() {
}
// 无参构造方法主要用于在一些需要默认初始化ServerResponse对象的场景中比如后续可能通过Setter方法等方式再去设置具体的状态码、消息和数据内容
// 提供了一种灵活的对象创建方式,虽然在当前代码中没有看到直接使用该无参构造方法后再设置属性的情况,但保留它可以增加代码的扩展性。
public ServerResponse(int status){
public ServerResponse(int status) {
this.status = status;
}
public ServerResponse(int status,String msg){
// 只传入状态码的构造方法用于创建一个只指定状态码的ServerResponse对象适用于一些只需要关注响应状态码的场景
// 例如在某些简单的错误提示或者初步判断响应是否成功的情况下,可以先使用这个构造方法创建对象,后续再根据需要决定是否添加消息和数据内容。
public ServerResponse(int status, String msg) {
this.status = status;
this.msg = msg;
}
public ServerResponse(int status,T data){
// 传入状态码和消息的构造方法方便创建一个带有特定状态码和相应提示消息的ServerResponse对象
// 常用于需要明确告知客户端操作结果(成功或失败以及具体原因)但暂时没有具体数据需要返回的情况,比如一些验证失败、权限不足等提示场景。
public ServerResponse(int status, T data) {
this.status = status;
this.data = data;
}
public ServerResponse(int status,String msg,T data){
// 传入状态码和数据的构造方法用于当操作成功并且有具体业务数据要返回给客户端时创建相应的ServerResponse对象
// 通过泛型指定具体的数据类型,将数据封装到对象中,使得客户端可以获取到操作成功后的相关数据内容,例如查询接口返回查询结果等情况会使用到这个构造方法。
public ServerResponse(int status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
// 完整传入状态码、消息和数据的构造方法提供了最全面的创建ServerResponse对象的方式适用于各种需要详细指定响应状态、提示消息以及返回数据的业务场景
// 可以根据具体的业务逻辑需求灵活运用这个构造方法来构建准确的返回对象。
@JsonIgnore
public boolean isSuccess(){
// @JsonIgnore注解用于指示Jackson在序列化和反序列化过程中忽略被标注的方法或成员变量在这里标注在isSuccess方法上
// 意味着该方法不会被包含在序列化后的JSON数据中因为它只是一个用于在服务端内部判断响应是否成功的工具方法不需要传递给客户端。
public boolean isSuccess() {
return this.status == ResponseEnum.SUCCESS.getCode();
}
// 用于判断当前ServerResponse对象表示的响应是否为成功状态的方法通过比较存储的状态码与ResponseEnum枚举中定义的表示成功的状态码SUCCESS.getCode())是否相等,
// 来返回一个布尔值表示是否成功方便在业务逻辑中如控制器层接收到服务层返回的ServerResponse对象后快速判断操作是否成功执行进而进行后续的处理操作。
/**
*
* 便ServerResponse使ResponseEnum.SUCCESS.getCode()
*
*/
public static <T>ServerResponse<T> createBySuccess(){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
}
public static <T>ServerResponse<T> createBySuccessMessage(String message){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message);
// 创建一个只包含默认成功状态码和默认成功描述信息的ServerResponse对象的静态方法适用于那些不需要额外返回消息和数据
// 只需简单告知客户端操作成功的基本情况的业务场景,返回的对象可以直接作为响应返回给客户端,体现了操作的成功结果。
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);
// 创建一个带有自定义提示消息的成功状态的ServerResponse对象的静态方法通过传入一个字符串参数作为提示消息
// 可以在操作成功的基础上向客户端传达更具体、个性化的成功信息,比如“添加品类成功”等业务相关的成功提示内容,增强了返回信息的针对性和实用性。
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){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),message,data);
// 创建一个包含具体业务数据的成功状态的ServerResponse对象的静态方法利用泛型传入要返回的数据内容
// 适用于查询等操作成功后需要将查询结果返回给客户端的业务场景,将查询到的数据封装在返回对象中传递给客户端供其进一步处理和展示。
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
}
// 创建一个既包含自定义提示消息又包含具体业务数据的成功状态的ServerResponse对象的静态方法是最全面的创建成功响应的方式
// 可以同时向客户端传达详细的成功信息以及相关的数据内容,满足各种复杂的业务逻辑中对成功返回结果的多样化需求。
/**
*
* ServerResponse
*
*/
public static <T>ServerResponse<T> createByError(){
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> createByError() {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
}
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
}
// 创建一个只包含默认错误状态码和默认错误描述信息的ServerResponse对象的静态方法用于一般性的错误情况
// 当没有更具体的错误分类或者不需要详细说明错误原因时,可以使用这个方法返回一个简单表示操作失败的对象给客户端,让客户端知晓操作未成功。
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
// 创建一个带有自定义错误提示消息的失败状态的ServerResponse对象的静态方法通过传入具体的错误消息字符串
// 可以更精准地向客户端传达操作失败的具体原因,比如“品类名字不能为空”等业务相关的错误提示,方便用户了解问题所在并进行相应处理。
}
public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
return new ServerResponse<>(code, msg);
}
// 创建一个可以自定义状态码和错误提示消息的失败状态的ServerResponse对象的静态方法最为灵活
// 适用于需要根据不同业务模块或者不同错误类型来指定特定的状态码(可能与项目中定义的各种错误状态码枚举对应)以及相应错误消息的场景,
// 使得返回的失败响应能够准确地反映具体的错误情况,便于客户端进行针对性的处理。
}

@ -2,48 +2,92 @@ package com.njupt.swg.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* cookie
* CookieUtilHTTP CookieCookie
* TokenCookieCookieCookie便WebCookie
* Cookie
*
* @Slf4jlogSLF4J便Cookie便Cookie
*/
@Slf4j
public class CookieUtil {
private final static String COOKIE_DOMAIN = "oursnail.cn";
private final static String COOKIE_NAME = "snailmall_login_token";
// 定义一个表示Cookie作用域名的常量指定了该Cookie在哪个域名下有效这里设置为"oursnail.cn"意味着只有在访问该域名及其子域名下的页面时浏览器才会携带这个Cookie
// 用于限定Cookie的作用范围使其符合项目的域名部署要求保障Cookie的使用安全性和有效性。
private final static String COOKIE_NAME = "snailmall_login_token";
// 定义一个表示Cookie名称的常量用于唯一标识与登录相关的这个特定Cookie在后续的读取、写入和删除操作中通过这个名称来定位对应的Cookie
// 项目中其他地方如果需要操作这个登录相关的Cookie就可以依据这个统一的名称来进行增强了代码的一致性和可维护性。
/**
* cookie
* @param response
* @param token
* TokenCookieCookie
* CookieHttpServletResponse使Cookie便Cookie
*
* @param response HttpServletResponseCookieServlet
* Cookie
* @param token TokenCookie
* CookieToken
*/
public static void writeLoginToken(HttpServletResponse response,String token){
Cookie ck = new Cookie(COOKIE_NAME,token);
public static void writeLoginToken(HttpServletResponse response, String token) {
Cookie ck = new Cookie(COOKIE_NAME, token);
// 创建一个新的Cookie对象使用预定义的COOKIE_NAME作为Cookie的名称将传入的登录Token作为Cookie的值这样就构建了一个与登录相关的特定Cookie实例
// 后续对这个Cookie设置属性后添加到响应中就可以发送给客户端浏览器进行存储和后续使用。
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//设值在根目录
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的作用域名将其设置为之前定义的COOKIE_DOMAIN常量值确保该Cookie只在指定的域名"oursnail.cn"及其子域名)下有效,
// 避免Cookie在不相关的域名下被误使用增强了Cookie使用的安全性和针对性。
ck.setPath("/");
// 设置Cookie的路径为根目录"/"表示该Cookie在整个域名下的所有路径页面请求时都会被浏览器自动携带发送给服务器
// 例如访问"oursnail.cn"下的任何子路径页面(如"/user", "/product"等浏览器都会带上这个Cookie方便在整个Web应用中统一验证用户登录状态等操作。
ck.setHttpOnly(true);
// 设置Cookie的HttpOnly属性为true这意味着该Cookie不能通过JavaScript等脚本语言进行访问只能在HTTP请求和响应中由浏览器自动处理
// 这样可以有效避免跨站脚本攻击XSS攻击防止恶意脚本获取到登录相关的Cookie信息保障用户登录信息的安全性。
ck.setMaxAge(60 * 60 * 24 * 365);
// 设置Cookie的最大存活时间这里设置为一年通过计算得出的秒数60秒 * 60分钟 * 24小时 * 365天单位是秒
// 表示该Cookie在客户端浏览器上存储的有效时长超过这个时间后浏览器会自动删除该Cookie-1表示永久有效
// 如果不设置这个属性或者设置为0以外的负数Cookie就不会写入硬盘持久化存储只会临时保存在内存中且只在当前页面有效关闭页面后就会消失。
log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue());
// 使用自动生成的日志记录器记录一条信息记录即将写入的Cookie的名称和值方便后续查看日志了解Cookie写入操作的具体情况有助于排查可能出现的与Cookie相关的问题。
response.addCookie(ck);
// 将设置好的Cookie添加到HttpServletResponse对象中这样在响应发送给客户端浏览器浏览器就会接收到这个Cookie并按照设置的属性进行存储和后续使用。
}
/**
* cookie
* @param request
* @return
* HTTPCookieCookie
* CookieCOOKIE_NAMECookieTokennull
* 便Token
*
* @param request HttpServletRequestServlet
* CookieCookie
* @return String CookieTokenCookienull
* Token
*/
public static String readLoginToken(HttpServletRequest request){
public static String readLoginToken(HttpServletRequest request) {
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks){
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 从HttpServletRequest对象中获取所有的Cookie数组这些Cookie是客户端浏览器在发送请求时自动携带过来的
// 如果没有Cookie则返回null所以需要先进行非空判断后再进行后续的查找操作。
if (cks!= null) {
for (Cookie ck : cks) {
log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 遍历所有获取到的Cookie对于每个Cookie都记录其名称和值到日志中方便查看请求中携带的Cookie具体情况有助于排查问题以及了解请求中的Cookie信息全貌。
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
return ck.getValue();
// 通过使用StringUtils的equals方法比较当前Cookie的名称与预定义的COOKIE_NAME是否相等如果相等说明找到了与登录相关的Cookie
// 则记录该Cookie的名称和值到日志中并返回其值即登录Token以便后续业务逻辑使用这个Token进行登录状态验证等操作。
}
}
}
@ -52,23 +96,43 @@ public class CookieUtil {
/**
*
* @param request
* @param response
* CookieCookie
* Cookie0Cookie使CookieCookie
*
* @param request HttpServletRequestCookie便Cookie
* Cookie
* @param response HttpServletResponseCookie
* 使CookieCookie
*/
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks) {
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
// 首先从HttpServletRequest对象中获取客户端发送过来的所有Cookie数组以便后续查找要删除的登录Cookie同样需要先判断是否为空再进行遍历操作。
if (cks!= null) {
for (Cookie ck : cks) {
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
ck.setDomain(COOKIE_DOMAIN);
// 在找到名称与预定义的COOKIE_NAME相等的登录Cookie后先重新设置其作用域名确保与之前设置的一致保证删除操作的准确性和有效性
// 使其作用范围仍然限定在指定的域名下避免误删其他无关的Cookie或者出现删除操作不符合预期的情况。
ck.setPath("/");
ck.setMaxAge(0);//0表示消除此cookie
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 同样设置Cookie的路径为根目录"/"保持与写入时的设置一致确保在整个域名下的所有路径页面请求时都能正确删除该Cookie
// 使得无论在哪个页面执行注销操作都能对该登录Cookie进行有效的删除处理。
ck.setMaxAge(0);
// 设置Cookie的最大存活时间为0表示让浏览器立即删除这个Cookie当浏览器接收到这个属性设置后的Cookie响应时会自动将其从本地存储中移除
// 从而实现注销时清除登录相关Cookie的目的达到清除用户登录状态的效果。
log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 记录要删除的Cookie的名称和值到日志中方便查看注销操作中Cookie删除的具体情况有助于后续排查可能出现的与Cookie删除相关的问题。
response.addCookie(ck);
// 将修改后的Cookie添加到HttpServletResponse对象中这样在响应发送给客户端浏览器浏览器会根据设置的属性最大存活时间为0删除对应的Cookie
// 完成注销操作中对登录Cookie的删除流程之后浏览器再发送请求时就不会携带这个已删除的登录Cookie了。
return;
}
}
}
}
}
}

@ -4,65 +4,151 @@ import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @DESC
* DateTimeUtil `joda-time` Java `SimpleDateFormat`
* `Date` `Date` 便
*
*/
public class DateTimeUtil {
//joda-time
// joda-time
// 引入 `joda-time` 库来进行更方便、灵活的日期时间处理操作相比Java原生的日期时间类如 `java.util.Date` 和 `java.text.SimpleDateFormat`
// `joda-time` 提供了更简洁、易读且不易出错的API用于日期时间的格式化、解析以及各种运算等操作。
//str->Date
//Date->str
// str->Date
// Date->str
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
// 定义了一个表示标准日期时间格式的常量字符串,格式为 "yyyy-MM-dd HH:mm:ss",用于在没有指定具体格式的情况下,作为默认的日期时间格式化和解析的格式模板,
// 确保整个项目中对于常见的日期时间表示形式能够统一进行处理,方便数据的交互和展示等操作。
public static Date strToDate(String dateTimeStr, String formatStr){
/**
* `Date`
* 使 `joda-time` `DateTimeFormat` `DateTime` `Date`
*
* @param dateTimeStr `formatStr` "yyyy-MM-dd" `formatStr`
* `dateTimeStr` "2024-01-01"
* @param formatStr `dateTimeStr` "yyyy-MM-dd HH:mm:ss"
* `dateTimeStr`
* @return Date `Date`
* `joda-time`
*/
public static Date strToDate(String dateTimeStr, String formatStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
// 根据传入的格式字符串创建 `DateTimeFormatter` 对象,它是 `joda-time` 库中用于定义日期时间格式化和解析规则的关键类,
// 可以按照指定的格式模式对日期时间字符串进行解析或者将日期时间对象格式化为字符串。
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
// 使用创建好的 `DateTimeFormatter` 对象对传入的日期时间字符串进行解析,得到 `DateTime` 对象,`DateTime` 是 `joda-time` 库中表示具体日期时间的核心类,
// 它包含了丰富的日期时间操作方法并且可以方便地与Java原生的 `Date` 对象进行转换。
return dateTime.toDate();
// 将 `DateTime` 对象转换为Java原生的 `Date` 对象并返回使得转换后的结果可以在基于Java标准日期时间处理的业务逻辑中进行使用例如存储到数据库等操作。
}
public static String dateToStr(Date date,String formatStr){
if(date == null){
/**
* `Date`
* `Date` `null` `null` 使 `joda-time` `DateTime` `Date`
*
*
* @param date `Date` `null`
*
* @param formatStr "yyyy-MM-dd"
*
* @return String `Date` `null`
* 便使
*/
public static String dateToStr(Date date, String formatStr) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
// 使用 `joda-time` 库的 `DateTime` 类将传入的 `Date` 对象进行包装,创建一个对应的 `DateTime` 对象,
// 以便后续利用 `DateTime` 类提供的格式化方法将其转换为字符串。
return dateTime.toString(formatStr);
// 调用 `DateTime` 对象的 `toString` 方法,按照传入的格式字符串将其转换为对应的日期时间字符串并返回,实现了从 `Date` 对象到指定格式字符串的转换功能。
}
//固定好格式
public static Date strToDate(String dateTimeStr){
/**
* "yyyy-MM-dd HH:mm:ss" `Date`
* 使 `STANDARD_FORMAT` `strToDate`
* 便
*
* @param dateTimeStr "yyyy-MM-dd HH:mm:ss" "2024-01-01 12:00:00"
* `Date`
* @return Date `Date`
* `joda-time`
*/
public static Date strToDate(String dateTimeStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
// 根据预定义的标准格式字符串创建 `DateTimeFormatter` 对象,用于后续按照这个固定格式对日期时间字符串进行解析操作。
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
// 使用创建好的 `DateTimeFormatter` 对象对传入的日期时间字符串进行解析,得到 `DateTime` 对象。
return dateTime.toDate();
// 将 `DateTime` 对象转换为Java原生的 `Date` 对象并返回,使得可以在项目的相关业务逻辑中使用这个转换后的 `Date` 对象,比如进行日期时间比较、存储等操作。
}
public static String dateToStr(Date date){
if(date == null){
/**
* `Date` "yyyy-MM-dd HH:mm:ss"
* 使 `STANDARD_FORMAT` `Date` `null` `null`
* `dateToStr` 便 `Date`
*
* @param date `Date` `null`
*
* @return String `Date` `null`
* "yyyy-MM-dd HH:mm:ss" 便
*/
public static String dateToStr(Date date) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
// 使用 `joda-time` 库的 `DateTime` 类将传入的 `Date` 对象进行包装,创建对应的 `DateTime` 对象,以便后续按照标准格式进行字符串转换操作。
return dateTime.toString(STANDARD_FORMAT);
// 调用 `DateTime` 对象的 `toString` 方法,按照预定义的标准格式("yyyy-MM-dd HH:mm:ss")将其转换为对应的日期时间字符串并返回,
// 实现了将 `Date` 对象转换为标准格式字符串的功能,满足项目中对日期时间数据格式统一展示的需求。
}
//Date -> 时间戳
/**
* `Date` 19701100:00:00 UTC
* 使Java `SimpleDateFormat` "yyyy-MM-dd HH:mm:ss" `Date`
* `Date` `Date`
* `ParseException`
*
* @param date `Date`
* `null` `null`
* @return Long `Date` 19701100:00:00 UTC `Date` `null` `null`
* 使
* @throws ParseException 使 `SimpleDateFormat` `Date`
* 使 `try-catch`
*/
public static Long dateToChuo(Date date) throws ParseException {
if(date == null){
if (date == null) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 创建一个 `SimpleDateFormat` 对象,使用预定义的标准格式("yyyy-MM-dd HH:mm:ss")作为格式化模板,
// 用于将 `Date` 对象格式化为对应的字符串,确保日期时间格式的准确性以及符合后续解析为时间戳的要求。
return format.parse(String.valueOf(date)).getTime();
// 先将传入的 `Date` 对象转换为字符串(通过 `String.valueOf` 方法),再使用创建好的 `SimpleDateFormat` 对象对这个字符串进行解析,重新得到 `Date` 对象,
// 这一步主要是为了确保日期时间格式符合预期以及兼容一些底层对 `Date` 处理的要求,然后通过调用 `getTime` 方法获取这个 `Date` 对象对应的时间戳(毫秒数)并返回,
// 实现了将 `Date` 对象转换为时间戳的功能。
}
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
String time="1970-01-06 11:45:55";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = "1970-01-06 11:45:55";
Date date = format.parse(time);
System.out.print("Format To times:"+date.getTime());
System.out.print("Format To times:" + date.getTime());
// 主方法用于简单测试 `dateToChuo` 方法(虽然这里实际并没有直接调用该方法进行测试,但功能类似,都是将日期时间字符串转换为 `Date` 对象后获取时间戳),
// 通过创建 `SimpleDateFormat` 对象,按照标准格式解析传入的日期时间字符串,得到 `Date` 对象,然后输出该 `Date` 对象对应的时间戳数值,
// 可以在本地运行该主方法来验证日期时间转换为时间戳的功能是否正常,方便在开发过程中进行简单的功能测试和调试。
}
}
}

@ -10,73 +10,193 @@ import java.io.IOException;
import java.util.List;
/**
* @Author swg.
* FtpUtilFTPFile Transfer Protocol
* FTP便FTP便
*
* @Author swg
* @Date 2018/1/11 14:32
* @DESC
* @CONTACT 317758022@qq.com
*/
@Data
// @Data注解由Lombok库提供会自动为类中的非-final字段生成对应的Getter和Setter方法
// 并且重写toString()、hashCode()以及equals()等方法,减少了手动编写这些常规代码的工作量,使代码更加简洁,方便操作类中的成员变量。
@Slf4j
// 使用Lombok的@Slf4j注解自动生成一个名为log的SLF4J日志记录器用于在类中记录FTP文件上传操作相关的日志信息
// 比如记录连接服务器、上传文件过程中的成功、失败以及出现的异常等情况,方便后续查看日志来追踪问题、分析操作流程。
public class FtpUtil {
private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip");
// 以下三个静态变量用于存储从配置文件中读取的FTP服务器相关配置信息通过PropertiesUtil工具类此处未展示其代码但推测用于读取配置属性来获取相应的值。
// 这种方式使得FTP服务器的配置可以灵活配置在外部文件中方便根据不同的部署环境如开发环境、测试环境、生产环境进行修改而无需改动代码本身。
private static String ftpUser = PropertiesUtil.getProperty("ftp.user");
private static String ftpPass = PropertiesUtil.getProperty("ftp.pass");
// 存储FTP服务器的IP地址通过配置文件获取确定要连接的FTP服务器所在的网络位置是建立FTP连接的基础信息之一。
private static String ftpPass = PropertiesUtil.getProperty("ftp.pass");
// 存储FTP服务器的用户名用于后续连接FTP服务器时进行身份验证确保只有合法授权的用户能够登录并操作FTP服务器。
/**
* FtpUtilFTPIP
* 便FTP使
*
* @param ip FTPIPIP使IP
* @param port FTPFTP21FTP
* @param user FTPFTP
* @param pwd FTP访
*/
public FtpUtil(String ip,int port,String user,String pwd){
this.ip = ip;
this.port = port;
this.user = user;
this.pwd = pwd;
}
/**
* FTP
* 使FTPIP21FtpUtil
* uploadFile便
*
* @param fileList FTPFileFile
* FTP
* @return boolean trueFTPfalse
*
* @throws IOException FTPIOException
* FTP
* 使try-catch
*/
public static boolean uploadFile(List<File> fileList) throws IOException {
FtpUtil ftpUtil = new FtpUtil(ftpIp,21,ftpUser,ftpPass);
// 使用默认的FTP服务器配置信息创建FtpUtil实例为后续连接服务器并上传文件做准备这里使用配置文件中获取的IP地址、默认端口21以及对应的用户名和密码。
log.info("开始连接ftp服务器");
// 记录日志信息提示开始尝试连接FTP服务器方便后续查看日志来了解文件上传操作的执行顺序以及排查可能出现的连接问题。
boolean result = ftpUtil.uploadFile("img",fileList);
// 调用实例的uploadFile方法传入默认的远程路径这里是"img"可推测是FTP服务器上用于存放上传文件的某个文件夹路径和要上传的文件列表
// 执行实际的文件上传操作并获取上传结果以布尔值表示成功与否将结果存储在result变量中。
log.info("开始连接ftp服务器,结束上传,上传结果:{}",result);
// 再次记录日志,告知文件上传操作已结束,并显示上传的结果,方便后续查看整个文件上传过程的情况,若上传失败可根据日志进一步查找具体原因。
return result;
}
/**
* FTP
* FTPFTP
* FTP
* FTP
*
* @param remotePath FTPFTP"img"FTP"img"
* FTP
* @param fileList FTPfileList
*
* @return boolean trueFTPfalse
*
* @throws IOException FTPFTPFTP
* IOExceptionFTP使try-catch
*
*/
private boolean uploadFile(String remotePath,List<File> fileList) throws IOException {
boolean uploaded = true;
// 初始化上传结果变量为true表示默认情况下假设文件上传操作能够成功完成后续如果在上传过程中出现异常情况会将该变量修改为false来表示上传失败。
FileInputStream fis = null;
// 定义一个文件输入流对象用于读取本地文件的内容初始化为null在后续遍历文件列表上传文件时会对其进行实例化操作用于将文件内容通过FTP客户端上传到服务器
// 最后需要在适当的地方关闭该输入流,以释放系统资源,避免资源泄漏。
//连接FTP服务器
log.info("【开始连接文件服务器】");
// 记录日志信息提示开始进行连接FTP服务器的操作便于后续查看日志了解文件上传操作的流程以及排查连接相关的问题。
if(connectServer(this.ip,this.port,this.user,this.pwd)){
// 调用connectServer方法尝试连接FTP服务器并使用传入的IP地址、端口号、用户名和密码进行登录验证
// 如果连接和登录成功即connectServer方法返回true则进入下面的代码块执行后续的文件上传相关操作否则直接返回uploaded变量的值此时为false表示连接失败导致无法上传文件
try {
ftpClient.changeWorkingDirectory(remotePath);
// 通过FTP客户端对象ftpClient将工作目录切换到指定的远程路径下确保后续上传的文件会被放置到这个正确的路径中
// 如果指定的远程路径不存在或者当前用户没有权限切换到该目录等情况将会抛出IOException异常需要在后续的异常处理中进行相应的处理。
ftpClient.setBufferSize(1024);
// 设置FTP客户端的缓冲区大小为1024字节缓冲区用于在文件传输过程中临时存储数据合理设置缓冲区大小可以提高文件传输效率
// 根据实际的网络环境和文件大小等因素可以适当调整这个值,不过一般使用默认的或者常见的合适大小即可。
ftpClient.setControlEncoding("UTF-8");
// 设置FTP客户端的控制编码为"UTF-8"确保在与FTP服务器进行命令交互等过程中使用统一的字符编码避免出现编码不一致导致的命令解析错误等问题
// 特别是在处理包含中文等多字节字符的文件名等情况时,合适的编码设置尤为重要。
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
// 设置FTP客户端传输文件的类型为二进制文件类型适用于上传各种类型的文件如图像、视频、文档等
// 与ASCII文件类型主要用于纯文本文件传输区分开来确保文件在传输过程中不会出现格式损坏等问题保证文件的完整性。
ftpClient.enterLocalPassiveMode();
// 将FTP客户端设置为被动模式在这种模式下FTP服务器会主动向客户端发起数据连接适用于客户端位于防火墙后面等网络环境
// 可以提高文件传输的成功率,避免因网络限制导致无法建立数据连接而影响文件上传操作。
for(File fileItem : fileList){
fis = new FileInputStream(fileItem);
// 对于文件列表中的每一个文件创建一个文件输入流对象用于读取该文件的内容以便后续通过FTP客户端将文件内容上传到服务器
// 如果文件不存在或者当前用户没有权限读取该文件等情况将会抛出IOException异常需要在异常处理中进行相应的处理。
ftpClient.storeFile(fileItem.getName(),fis);
// 使用FTP客户端对象将从本地文件读取的内容上传到FTP服务器以文件的实际名称通过fileItem.getName()获取)作为在服务器上存储的文件名,
// 如果在上传过程中出现权限问题、网络传输错误等情况可能会抛出IOException异常影响文件上传的结果后续会在异常处理中进行相应处理。
}
} catch (IOException e) {
log.error("上传文件异常",e);
uploaded = false;
e.printStackTrace();
// 如果在文件上传相关的操作如切换目录、设置参数、上传文件等过程中出现IOException异常记录详细的错误日志
// 将表示上传结果的uploaded变量设置为false表示文件上传失败同时打印异常的堆栈信息方便更深入地排查问题所在。
} finally {
fis.close();
ftpClient.disconnect();
// 在无论文件上传是否成功的情况下都需要关闭文件输入流避免资源泄漏以及断开与FTP服务器的连接释放网络资源等
// 通过在finally块中执行这些操作确保资源能够被正确释放即使在出现异常的情况下也能保证程序的资源管理的正确性。
}
}
return uploaded;
}
/**
* FTP
* FTPClientIPFTP使
* 便
*
* @param ip FTPIPFTPIP
* IPIPFTPIPIP
* @param port FTPFTP21FTP
* FTP
* @param user FTPFTP
*
* @param pwd FTPFTP
* FTP
* @return boolean FTPtrue
* falseFTP
* uploadFile
*/
private boolean connectServer(String ip,int port,String user,String pwd){
boolean isSuccess = false;
// 初始化连接成功的结果变量为false表示默认情况下假设连接操作可能会失败后续根据实际的连接和登录情况来更新这个变量的值。
ftpClient = new FTPClient();
// 创建一个FTPClient对象它是Apache Commons Net库提供的用于操作FTP服务器的核心类通过这个对象可以进行连接服务器、登录、文件传输等一系列操作。
try {
ftpClient.connect(ip);
// 使用FTPClient对象尝试连接到指定IP地址的FTP服务器若无法连接比如IP地址不可达、服务器未启动等原因会抛出IOException异常
// 需要在后续的异常处理中进行相应的记录和处理,以判断连接失败的原因。
isSuccess = ftpClient.login(user,pwd);
// 在连接成功的基础上使用提供的用户名和密码尝试登录FTP服务器通过FTPClient对象的login方法进行登录操作
// 如果用户名或密码错误、用户没有相应权限等情况登录操作会失败返回false并将其赋值给isSuccess变量
// 若登录成功则将isSuccess设置为true表示可以进行后续的文件上传等操作。
} catch (IOException e) {
log.error("连接FTP服务器异常",e);
// 如果在连接FTP服务器或者登录过程中出现IOException异常记录详细的错误日志方便后续查看日志排查是网络问题还是认证问题等导致的连接异常情况。
}
return isSuccess;
}
@ -84,8 +204,23 @@ public class FtpUtil {
private String ip;
// 用于存储FTP服务器的IP地址通过构造方法或者默认配置从配置文件读取进行赋值在连接FTP服务器等操作中会使用到这个IP地址信息
// 确保与实际要连接的FTP服务器的网络位置相对应是进行FTP操作的关键参数之一。
private int port;
// 表示FTP服务器的端口号同样可以通过构造方法传入或者使用默认值通常为21用于指定与FTP服务器建立连接时的端口
// 不同的FTP服务器部署环境可能会使用不同的端口通过这个成员变量可以灵活配置端口号保证能够准确连接到目标FTP服务器。
private String user;
// 存储FTP服务器的用户名用于在连接FTP服务器时进行用户身份认证必须是FTP服务器上合法有效的用户名
// 通过合适的方式(如配置文件读取、构造方法传入等)获取正确的用户名,以确保能够成功登录服务器进行文件上传等操作。
private String pwd;
// 存储的是与上述用户名对应的FTP服务器密码。它与用户名配合使用作为登录FTP服务器的凭证用于验证用户的身份合法性
// 保障只有授权的用户能够访问和操作FTP服务器。同样其值可通过构造方法设定或者依据配置文件中的默认配置来获取并且要注意密码的保密性避免在代码中明文暴露。
private FTPClient ftpClient;
// FTPClient类型的成员变量它是Apache Commons Net库中用于操作FTP服务器的核心类对象。
// 通过这个对象可以实现与FTP服务器建立连接如调用其connect方法、进行用户登录login方法、切换工作目录changeWorkingDirectory方法、设置文件传输相关参数如缓冲区大小、文件类型等以及执行文件上传storeFile方法等一系列操作
// 是整个FTP文件上传功能实现的关键对象在类中的多个方法中都会对其进行操作来完成与FTP服务器的交互过程。
}

@ -8,119 +8,190 @@ import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import java.io.IOException;
import java.text.SimpleDateFormat;
/**
* jackson
* JsonUtilJSONJackson使CodehausJackson
* ObjectMapperJavaJSONJSONJava
* 便JSON便
*
* @Slf4jlogSLF4JJSON
* 便
*/
@Slf4j
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
// 创建一个静态的ObjectMapper对象ObjectMapper是Jackson库中用于进行JSON序列化和反序列化的核心类
// 通过这个对象可以配置各种序列化和反序列化的规则,并执行具体的转换操作,将其定义为静态成员变量,方便在整个类的各个静态方法中共享使用。
static {
//所有字段都列入进行转换
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
// 通过设置ObjectMapper的SerializationInclusion属性为JsonSerialize.Inclusion.ALWAYS指定在序列化Java对象为JSON字符串时
// 将对象的所有字段都包含进转换结果中无论字段的值是否为null这样可以保证完整的对象数据结构能在JSON中体现避免遗漏字段信息。
//取消默认转换timestamp形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// 配置ObjectMapper取消默认将日期类型字段转换为时间戳形式的行为通常Jackson默认会把日期对象转换为时间戳进行序列化
// 这里设置为false后会按照后续配置的日期格式通过setDateFormat方法进行日期的序列化使得日期在JSON中的表示更符合常规的日期格式要求便于阅读和理解。
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
// 设置ObjectMapper在序列化空的Java Bean即没有任何属性值的对象为JSON时忽略可能出现的错误默认情况下序列化空Bean可能会抛出异常
// 通过将此配置设置为false使得在遇到这种情况时能够正常返回空的JSON对象如"{}"),而不会导致程序中断,增强了序列化操作的容错性。
//统一时间的格式
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
// 为ObjectMapper设置统一的日期格式这里使用了DateTimeUtil类中定义的STANDARD_FORMAT常量格式为"yyyy-MM-dd HH:mm:ss"
// 确保在序列化和反序列化过程中日期类型的数据都按照这个标准格式进行处理使得整个项目中对于日期的JSON表示形式保持一致方便数据的交互和处理。
//忽略json存在属性但是java对象不存在属性的错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 配置ObjectMapper在进行反序列化将JSON字符串转换为Java对象忽略JSON字符串中存在但Java对象中不存在对应属性的情况默认情况下这种情况会抛出异常
// 通过将此配置设置为false使得反序列化过程能够更加灵活即使JSON数据比Java对象定义的属性多一些也可以正常进行反序列化只处理Java对象中定义的属性对应的JSON数据避免不必要的错误抛出。
}
/**
*
* @param obj
* @param <T>
* @return
* JavaJSONnullnullnull
* null使ObjectMapperJSONIOExceptionnull
* String
*
* @param obj JavaJavaJacksonJackson
* POJOPlain Old Java Objectnullnull
* @param <T> JSON
*
* @return String JavaJSONnullnull
* JSONObjectMapper
* JSON
*/
public static <T> String obj2String(T obj){
if(obj == null){
public static <T> String obj2String(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj);
} catch (IOException e) {
log.warn("parse object to string error",e);
log.warn("parse object to string error", e);
return null;
}
}
/**
* 便
* @param obj
* @param <T>
* @return
* obj2StringJavaJSONJSON
* 便JSONnullnullnull
* IOExceptionnullString
*
* @param obj Javaobj2StringobjJacksonJava
* POJOJSONnullnull
* @param <T> obj2StringJSON
*
* @return String JavaJSONnullnull
* JSON便JSON
* JSON使JSON
*/
public static <T> String obj2StringPretty(T obj){
if(obj == null){
public static <T> String obj2StringPretty(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (IOException e) {
log.warn("parse object to string error",e);
log.warn("parse object to string error", e);
return null;
}
}
/**
*
* @param str
* @param clazz
* @param <T>
* @return
* JSONJavaJSONJavanull
* nullnullnull使ObjectMapperJSONJava
* IOExceptionnullStringJSONString
* JSONJava
*
* @param str JSONObjectMapperJSONJava
* JSONJavaJSONnull
* @param clazz JavaClassJSONJava
* User.classJSONUserJacksonGetterSetter
* nullnull
* @param <T> Javaclazz
* 使JSONJava
* @return <T> JSONJavaJSONnullnull
* JavaJSONJSON
* Java
*/
public static <T> T String2Obj(String str,Class<T> clazz){
if(StringUtils.isEmpty(str) || clazz == null){
public static <T> T String2Obj(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz);
return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz);
} catch (IOException e) {
log.warn("parse string to obj error",e);
log.warn("parse string to obj error", e);
return null;
}
}
/**
*
* @param str
* @param typeReference
* @param <T>
* @return
* JSONJava使
* JSONTypeReferencenullnullnull
* nullTypeReference使ObjectMapperJSONJava
* IOExceptionnullTypeReferenceStringJSONString
* JSONJava
*
* @param str JSONObjectMapperJSONJava
* JSONnull
* @param typeReference JavaTypeReferenceTypeReference
* List<User>TypeReferenceObjectMapperJSON
* nullnull
* @param <T> JavatypeReference
* JSONJavaJSON
* @return <T> JSONJavaJSONtypeReferencenullnull
* JavaJSON
* JSONJava
*/
public static <T> T Str2Obj(String str, TypeReference typeReference){
if(StringUtils.isEmpty(str) || typeReference == null){
public static <T> T Str2Obj(String str, TypeReference typeReference) {
if (StringUtils.isEmpty(str) || typeReference == null) {
return null;
}
try {
return (T) (typeReference.getType().equals(String.class)?str:objectMapper.readValue(str,typeReference));
return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference));
} catch (IOException e) {
log.warn("parse string to obj error",e);
log.warn("parse string to obj error", e);
return null;
}
}
/**
*
* @param str
* @param collectionClass
* @param elementClasses
* @param <T>
* @return
* ObjectMapperTypeFactoryJavaType
* Java使ObjectMapperJSONJava
* IOExceptionnull
*
* @param str JSONObjectMapperJSONJava
* JSONnull
* @param collectionClass JavaList.classSet.class
* ObjectMapper
* nullnull
* @param elementClasses Java
* List<User, Role>User.classRole.classelementClasses
* collectionClassnullnull
* @param <T> JavacollectionClasselementClassesJavaType
* JSONJava
* @return <T> JSONJavaJSONcollectionClassnullnull
* JavaJSON
* JSONJava
*/
public static <T> T Str2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses);
public static <T> T Str2Obj(String str, Class<?> collectionClass, Class<?>... elementClasses) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
try {
return objectMapper.readValue(str,javaType);
return objectMapper.readValue(str, javaType);
} catch (IOException e) {
log.warn("parse string to obj error",e);
log.warn("parse string to obj error", e);
return null;
}
}
}
}

@ -3,47 +3,115 @@ package com.njupt.swg.common.utils;
import java.security.MessageDigest;
/**
* MD5
* MD5UtilMD5MD5
* MD5Message-Digest Algorithm 5
* MD5UTF-8便
*/
public class MD5Util {
/**
*
* byteToHexStringStringBuffer
*
*
* @param b MD5便使
* MD5使
* @return String
* MD5
*/
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
// 创建一个StringBuffer对象用于拼接字节对应的十六进制字符StringBuffer是可变的字符序列适合在循环中频繁追加字符的操作
// 相比String的不可变特性使用它可以提高性能避免过多的字符串拼接产生的临时对象开销。
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
// 遍历传入的字节数组对于每个字节调用byteToHexString方法将其转换为十六进制表示形式并追加到resultSb对象中
// 通过循环处理完整个字节数组后resultSb就包含了所有字节对应的十六进制字符的拼接结果。
return resultSb.toString();
// 将StringBuffer对象转换为不可变的String对象并返回得到最终的十六进制字符串完成字节数组到十六进制字符串的转换过程。
}
/**
*
* Java -128 127256
* hexDigits
*
*
* @param b byteArrayToHexString
* MD5便
* @return String 2
* 10"0a"15"0f"
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
// 由于Java中的字节是有符号的取值范围是 -128 到 127而在十六进制表示中我们需要使用无符号整数0 到 255
// 所以当字节值为负数时通过加上256将其转换为无符号整数范围以便后续正确计算十六进制表示形式。
int d1 = n / 16;
int d2 = n % 16;
// 分别计算转换后的无符号整数对应的十六进制数的高位和低位数字通过除以16取整得到高位数字十六进制的十位通过取模16得到低位数字十六进制的个位
// 例如对于整数20除以16得到高位数字1取模16得到低位数字4对应十六进制表示就是"14"。
return hexDigits[d1] + hexDigits[d2];
// 通过查找预定义的十六进制字符数组hexDigits获取对应高位和低位数字的十六进制字符然后将它们拼接起来返回
// 得到单个字节对应的十六进制字符串表示形式例如对于字节值为10经过计算和查找字符数组后返回的十六进制字符串就是"0a"。
}
/**
* MD5
* MD5MD5
* 使
* MD5MessageDigest使使
* MessageDigestMD5byteArrayToHexString
* MD5
*
* @param origin
* @param charsetname
* @return
* @param origin MD5MD5
*
* @param charsetname "utf-8""gbk"
* null使MD5
* MD5
* @return String MD5便使
* MD5
*/
private static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
// 这里创建了一个新的字符串对象复制传入的原始字符串其实可以直接使用origin创建新对象会有额外的内存开销不过在当前代码逻辑下功能上是等效的。
MessageDigest md = MessageDigest.getInstance("MD5");
// 通过Java的安全框架获取MD5加密算法对应的MessageDigest实例MessageDigest是用于计算消息摘要如MD5、SHA等算法的抽象类
// 这里指定"MD5"算法名称来获取用于MD5加密操作的具体实例后续可以通过这个实例对数据进行MD5加密计算。
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
// 根据传入的字符编码情况进行不同的处理,如果字符编码参数为空或空字符串,就使用默认字符编码将原始字符串转换为字节数组,
// 然后传入MessageDigest实例的digest方法进行MD5加密计算得到加密后的字节数组再通过byteArrayToHexString方法将字节数组转换为十六进制字符串
// 如果传入了有效的字符编码名称则按照指定的字符编码将原始字符串转换为字节数组后进行MD5加密和后续的转换操作最终得到加密后的十六进制字符串表示形式的结果。
} catch (Exception exception) {
// 当前代码中捕获了异常但没有进行任何处理,这可能不太合适,在实际应用中建议根据具体的业务需求进行相应的异常处理,
// 比如记录日志、抛出更具体的业务异常等操作,以便更好地排查问题和反馈给调用者加密操作出现了异常情况。
}
return resultString.toUpperCase();
// 将得到的MD5加密后的十六进制字符串结果转换为大写形式返回这样在不同环境下返回的加密结果格式统一便于后续的比较、验证等操作
// 例如在存储用户密码的MD5加密值或者验证密码是否匹配等场景中统一使用大写形式可以避免因大小写差异导致的验证错误等问题。
}
/**
* UTF-8MD5
* MD5Encode"utf-8"UTF-8MD5便
* MD5Encode
* 便使
*
* @param origin MD5MD5Encodeorigin
* UTF-8便
* @return String UTF-8MD5使
*
*/
public static String MD5EncodeUtf8(String origin) {
//这里可以加盐
return MD5Encode(origin, "utf-8");
@ -51,9 +119,13 @@ public class MD5Util {
public static void main(String[] args) {
System.out.println(MD5EncodeUtf8("123456"));
// 主方法提供了一个简单的测试示例调用MD5EncodeUtf8方法对字符串"123456"进行MD5加密并将加密后的结果输出到控制台
// 方便在本地运行代码时快速查看MD5加密功能是否正常验证加密方法是否按照预期对输入字符串进行了加密处理
// 在实际开发过程中可以通过修改传入的字符串参数来测试不同内容的MD5加密结果也可以在此基础上添加更多的测试逻辑比如验证加密结果的格式等。
}
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}
// 定义一个私有的静态字符串数组用于存储十六进制的数字和字母对应的字符表示在byteToHexString方法中通过查找这个数组来获取字节对应的十六进制字符
// 数组的索引对应十六进制数的高位或低位数字例如索引0对应十六进制字符"0"索引10对应十六进制字符"a"等,方便将字节值转换为十六进制字符串表示形式。
}

@ -2,45 +2,98 @@ package com.njupt.swg.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;
/**
* @Author swg.
* PropertiesUtilJava `Properties` 便
* 便FTP
* 使便
*
* @Author swg
* @Date 2018/1/10 14:56
* @DESC
* @CONTACT 317758022@qq.com
*/
@Slf4j
// 使用Lombok的@Slf4j注解自动生成一个名为log的SLF4J日志记录器用于在配置文件读取出现异常时记录错误信息方便后续查看日志来了解配置文件加载失败的原因等情况
// 保证在出现问题时能够快速定位并排查相关的异常情况。
public class PropertiesUtil {
private static Properties props;
// 定义一个静态的 `Properties` 类对象,`Properties` 类是Java中用于处理配置文件通常是 `.properties` 格式)的常用类,
// 它可以存储键值对形式的配置信息,这里将其定义为静态成员变量,方便在整个类的不同方法中共享使用,用于存储从配置文件中读取的所有属性数据。
static {
String fileName = "parameter.properties";
// 指定要读取的配置文件的名称,这里固定为 "parameter.properties",表示程序默认会尝试从类路径下查找并读取这个名称的配置文件,
// 当然,根据实际需求也可以考虑通过其他方式传入不同的文件名,使得读取的配置文件更具灵活性,不过当前代码是使用固定的这个文件名进行操作。
props = new Properties();
// 创建一个 `Properties` 对象实例,用于后续加载配置文件中的属性数据,在内存中创建一个空的属性集合,等待从文件中读取数据填充进去。
try {
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName), "UTF-8"));
// 通过类加载器ClassLoader获取指定配置文件fileName的输入流getResourceAsStream方法并使用 `InputStreamReader` 将其包装为字符流,
// 指定字符编码为 "UTF-8",确保能够正确读取配置文件中的中文等多字节字符内容,然后调用 `Properties` 对象的 `load` 方法,将配置文件中的键值对数据加载到 `props` 对象中,
// 如果配置文件不存在、格式错误或者读取过程中出现其他I/O相关的问题会抛出 `IOException` 异常,需要在 `catch` 块中进行相应的处理。
} catch (IOException e) {
log.error("配置文件读取异常",e);
log.error("配置文件读取异常", e);
// 如果在加载配置文件过程中出现 `IOException` 异常,使用自动生成的日志记录器记录详细的错误信息,包括异常堆栈信息,
// 方便后续查看日志来排查是文件路径问题、编码问题还是其他I/O异常导致的配置文件读取失败情况不过当前代码只是记录了日志没有进行其他额外的恢复或提示操作
// 在实际应用中可以根据具体需求考虑添加更多的处理逻辑,比如尝试使用默认配置或者提示用户配置文件读取失败等情况。
}
}
public static String getProperty(String key){
/**
* `null`
* `Properties` `null`
* 使
*
* @param key `props`
* `null`
* `trim`
* @return String `null`
* 使IP
*/
public static String getProperty(String key) {
String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){
// 调用 `Properties` 对象的 `getProperty` 方法,传入去除首尾空白字符后的键(通过 `trim` 方法处理),尝试获取对应的属性值,
// 从之前加载的配置文件数据(存储在 `props` 对象中)中查找与该键匹配的属性值,如果找到则返回对应的字符串值,若不存在则返回 `null`。
if (StringUtils.isBlank(value)) {
return null;
}
return value.trim();
// 使用 `StringUtils` 的 `isBlank` 方法判断获取到的属性值是否为空(空白字符串或者 `null`),如果是则返回 `null`
// 若属性值不为空,则再次调用 `trim` 方法去除首尾空白字符后返回,确保返回的属性值在格式上更加规范,避免因空白字符带来的潜在问题,方便后续使用。
}
public static String getProperty(String key,String defaultValue){
/**
*
* `Properties` 使
*
*
* @param key `getProperty`
*
* @param defaultValue
* "3306"
*
* @return String
* 使便使
*/
public static String getProperty(String key, String defaultValue) {
String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){
// 首先调用 `Properties` 对象的 `getProperty` 方法,传入去除首尾空白字符后的键,尝试从配置文件中获取对应的属性值,操作与 `getProperty` 方法中的获取值步骤类似,
// 如果找到对应的值则返回该字符串值,若不存在则返回 `null`。
if (StringUtils.isBlank(value)) {
value = defaultValue;
}
return value.trim();
// 使用 `StringUtils` 的 `isBlank` 方法判断获取到的属性值是否为空(空白字符串或者 `null`),如果为空则将传入的默认值赋给 `value` 变量,
// 最后再次调用 `trim` 方法去除 `value` 的首尾空白字符后返回,确保返回的属性值无论是从配置文件获取的还是使用默认值,都具有规范的格式,便于后续在项目中使用,
// 比如作为数据库连接的相关配置参数、服务器相关配置等使用,避免因格式问题导致的配置错误以及程序异常等情况。
}
}
}

@ -11,52 +11,138 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* ProductControllerSpring MVCRESTfulProductHTTP
* @AutowiredIProductService
* ServerResponse
*
* @Author swg.
* @Date 2019/1/2 17:32
* @CONTACT 317758022@qq.com
* @DESC
*/
@RestController
// @RestController注解是Spring 4.0引入的一个组合注解,它等同于同时使用了@Controller和@ResponseBody注解。
// @Controller用于标记该类是一个Spring MVC的控制器类负责处理HTTP请求@ResponseBody表示该类中所有的方法返回值都会直接写入HTTP响应体中
// 通常用于返回JSON数据等格式的响应内容这里将整个类标记为 @RestController意味着这个类中的方法主要用于提供RESTful API接口返回的数据格式适合直接被客户端如前端页面、移动端应用等消费。
@RequestMapping("/product")
// @RequestMapping注解用于将HTTP请求映射到对应的控制器类的方法上这里将类级别的请求路径设置为"/product"
// 意味着该类中所有的方法处理的请求路径都是在"/product"这个基础路径之下,方便对一组相关的接口进行统一的路径管理和归类,
// 例如后续的接口方法的请求路径就是在"/product"后面再添加具体的子路径来区分不同的功能接口。
public class ProductController {
@Autowired
private IProductService productService;
// 使用Spring的依赖注入机制通过 @Autowired注解自动装配IProductService接口的实现类实例到当前变量中
// IProductService应该是定义了一系列与商品相关的业务逻辑方法的接口其具体实现类会在Spring的配置中被实例化并注入到这里
// 这样在控制器的各个方法中就可以直接调用该服务层接口的方法来处理具体的业务,实现了控制层与业务层的解耦,方便业务逻辑的扩展和替换。
/**
* HTTP"/product/detail.do"
* IDproductIdgetPortalProductDetailProductDetailVo
* ServerResponse
*
*
* @param productId HTTP
* IDID
* ID
* @return ServerResponse<ProductDetailVo> ServerResponseProductDetailVo
* ServerResponse
* ProductDetailVoVOView Object
* ServerResponseProductDetailVo
* ServerResponse便
*/
@RequestMapping("detail.do")
public ServerResponse<ProductDetailVo> detail(Integer productId){
public ServerResponse<ProductDetailVo> detail(Integer productId) {
return productService.getPortalProductDetail(productId);
}
/**
* HTTP"/product/list.do"
* keywordIDcategoryIdpageNumpageSizeorderBy
* portalListPageInfo
* ServerResponse
*
* @param keyword @RequestParamrequired = false
*
* 便
* @param categoryId IDrequired = false
* ID
*
* @param pageNum "1" @RequestParamdefaultValue = "1"
* 便23
*
* @param pageSize "10"defaultValue = "10"
* 10
* @param orderBy defaultValue = ""
* "price desc"
* 便
* @return ServerResponse<PageInfo> ServerResponsePageInfo
* ServerResponsePageInfoPageHelper
*
* ServerResponsePageInfo便
* ServerResponse
*/
@RequestMapping("list.do")
public ServerResponse<PageInfo> list(@RequestParam(value = "keyword",required = false)String keyword,
@RequestParam(value = "categoryId",required = false)Integer categoryId,
@RequestParam(value = "pageNum",defaultValue = "1")int pageNum,
@RequestParam(value = "pageSize",defaultValue = "10")int pageSize,
@RequestParam(value = "orderBy",defaultValue = "")String orderBy){
return productService.portalList(keyword,categoryId,orderBy,pageNum,pageSize);
public ServerResponse<PageInfo> list(
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "categoryId", required = false) Integer categoryId,
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "categoryId", defaultValue = "10") int pageSize,
@RequestParam(value = "orderBy", defaultValue = "") String orderBy) {
return productService.portalList(keyword, categoryId, orderBy, pageNum, pageSize);
}
/**
* HTTP"/product/queryProduct.do"
* IDproductIdqueryProduct
* ServerResponse
* detail
*
* @param productId HTTP
* detailproductIdID
* ID
* @return ServerResponse ServerResponse
* queryProductServerResponse
* ServerResponse便
*
*/
@RequestMapping("/queryProduct.do")
public ServerResponse queryProduct(@RequestParam("productId") Integer productId){
public ServerResponse queryProduct(@RequestParam("productId") Integer productId) {
return productService.queryProduct(productId);
}
/**
* 1redis
* RedisHTTP"/product/preInitProductStcokToRedis.do"
* preInitProductStcokToRedisRedis
* ServerResponse
* 使
*
* @return ServerResponse ServerResponseRedis
* ServerResponse
* RedisServerResponse
* 便
*/
@RequestMapping("/preInitProductStcokToRedis.do")
public ServerResponse preInitProductStcokToRedis(){
public ServerResponse preInitProductStcokToRedis() {
return productService.preInitProductStcokToRedis();
}
/**
* 2redis
* RedisHTTP"/product/preInitProductListToRedis.do"
* preInitProductListToRedisRedis
* ServerResponse
*
*
* @return ServerResponse ServerResponseRedis
* ServerResponse
* RedisServerResponse
* 便
*/
@RequestMapping("/preInitProductListToRedis.do")
public ServerResponse preInitProductListToRedis(){
public ServerResponse preInitProductListToRedis() {
return productService.preInitProductListToRedis();
}
}
}

@ -27,125 +27,177 @@ import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* ProductManageControllerSpring MVCRESTfulHTTP
*
* 使便
*
* @Author swg.
* @Date 2019/1/2 17:32
* @CONTACT 317758022@qq.com
* @DESC
*/
@RestController
// 表明该类是一个Spring RESTful风格的控制器意味着类中的方法返回值会直接作为HTTP响应体的内容返回通常用于返回JSON格式的数据等
// 适用于构建API接口方便前后端分离架构下与前端进行数据交互。
@RequestMapping("/manage/product")
// 将该控制器类下所有方法对应的请求路径统一设置在 "/manage/product" 前缀之下,便于对后台商品相关接口进行统一管理和分类,
// 例如后续各个具体方法的请求路径都是基于这个前缀进行拓展的。
@Slf4j
// 使用Lombok的 @Slf4j注解自动生成名为log的SLF4J日志记录器用于在方法执行过程中记录关键信息、异常情况等方便后续查看日志排查问题、跟踪操作流程。
public class ProductManageController {
@Autowired
private IProductService productService;
// 通过Spring的依赖注入机制自动装配IProductService接口的实现类实例IProductService应该定义了众多与商品业务逻辑相关的方法
// 如查询商品列表、获取商品详情、更新商品信息等,在本控制器的多个方法中会调用其相应方法来处理具体的业务操作,实现控制层与业务层的解耦。
@Autowired
private IFileService fileService;
// 注入IFileService接口的实现类实例该接口大概率是用于处理文件相关操作的服务比如文件上传功能在本类的图片上传相关方法中会调用其方法来完成实际的文件上传逻辑。
@Autowired
private CommonCacheUtil commonCacheUtil;
// 注入CommonCacheUtil实例这应该是一个用于操作缓存可能是Redis等缓存系统的工具类在获取用户信息等需要缓存数据支持的操作中会用到
// 通过它可以方便地从缓存中读取、写入数据,提高系统性能,减少重复查询数据库等操作。
/**
* list
* HTTP "/manage/product/list.do"
* pageNumpageSize110list
* ServerResponse
*
* @param pageNum "1" @RequestParam
* 便
* @param pageSize "10"10
* @return ServerResponse ServerResponse
* ServerResponse
* 便
*/
@RequestMapping("/list.do")
public ServerResponse list(@RequestParam(value = "pageNum",defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize",defaultValue = "10") int pageSize){
return productService.list(pageNum,pageSize);
public ServerResponse list(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {
return productService.list(pageNum, pageSize);
}
/**
*
* HTTP "/manage/product/search.do"
* productNameIDproductIdpageNumpageSize
* searchPageInfoServerResponse
* 便ID
*
* @param productName
*
* @param productId IDIDID
* 使
* @param pageNum "1"
* @param pageSize "10"10
* @return ServerResponse<PageInfo> ServerResponsePageInfo
* ServerResponsePageInfo
* ServerResponsePageInfo便
*
*/
@RequestMapping("search.do")
public ServerResponse<PageInfo> search(String productName,
Integer productId,
@RequestParam(value = "pageNum",defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize",defaultValue = "10") int pageSize){
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {
return productService.search(productName,productId,pageNum,pageSize);
return productService.search(productName, productId, pageNum, pageSize);
}
/**
*
* HTTP "/manage/product/upload.do"
* MultipartFilefileHttpServletRequest
* fileServiceupload访URLMap
* ServerResponse便
*
* @param file @RequestParam使
* MultipartFileSpring
* @param request HttpServletRequestServlet
* 便
* @return ServerResponse ServerResponse
* ServerResponseuri访URLurlMap
* 便
*/
@RequestMapping("upload.do")
public ServerResponse upload(@RequestParam(value = "upload_file",required = false) MultipartFile file, HttpServletRequest request){
public ServerResponse upload(@RequestParam(value = "upload_file", required = false) MultipartFile file, HttpServletRequest request) {
String path = request.getSession().getServletContext().getRealPath("upload");
String targetFileName = fileService.upload(file,path);
String url = "http://img.oursnail.cn/"+targetFileName;
// 获取服务器上用于存放上传文件的真实路径通过HttpServletRequest对象获取当前会话Session的Servlet上下文ServletContext
// 再获取名为 "upload" 的目录的真实路径,该路径就是后续文件要上传保存的位置,确保文件能够正确存储在服务器指定的地方。
String targetFileName = fileService.upload(file, path);
// 调用fileService的upload方法将接收到的文件对象和文件保存路径传递进去执行实际的文件上传操作
// 该方法会返回上传后文件在服务器上的目标文件名可能经过了重命名等处理方便后续构建文件的访问URL等操作。
String url = "http://img.oursnail.cn/" + targetFileName;
// 构建文件的访问URL根据业务需求将服务器域名这里是 "http://img.oursnail.cn/")与上传后的目标文件名拼接起来,
// 得到完整的可用于在浏览器等客户端访问该文件的URL地址便于后续在前端展示图片或者其他相关操作中使用这个URL来引用上传的文件。
log.info("【上传的图片路径为:{}】",url);
log.info("【上传的图片路径为:{}】", url);
// 使用日志记录上传的图片的访问URL信息方便后续查看文件上传的结果以及在调试时确认文件是否上传到了正确的位置同时也有助于排查可能出现的文件访问问题。
Map fileMap = Maps.newHashMap();
fileMap.put("uri",targetFileName);
fileMap.put("url",url);
log.info("【返回数据为:{}】",fileMap);
fileMap.put("uri", targetFileName);
fileMap.put("url", url);
// 创建一个Map对象用于存放文件相关的关键信息将上传后文件的目标文件名uri和构建好的文件访问URLurl放入Map中
// 方便将这些信息统一包装在ServerResponse对象中返回给客户端使得客户端能够获取到文件的相关详细信息便于后续展示等操作。
log.info("【返回数据为:{}】", fileMap);
// 再次使用日志记录要返回给客户端的包含文件信息的Map对象内容便于后续查看返回的数据结构以及排查数据传递过程中可能出现的问题确保返回的数据符合预期。
return ServerResponse.createBySuccess(fileMap);
// 通过ServerResponse的静态方法createBySuccess创建一个表示成功的ServerResponse对象并将包含文件信息的fileMap作为成功结果数据传入
// 最终将这个ServerResponse对象返回给客户端告知客户端图片上传操作已成功完成并传递相关文件信息供客户端使用。
}
/**
*
* HTTP "/manage/product/detail.do"
* IDproductIddetailProductDetailVo
* ServerResponse
*
* @param productId HTTP
* ID
* IDServerResponse
* @return ServerResponse<ProductDetailVo> ServerResponseProductDetailVo
* ServerResponseProductDetailVo
* ServerResponseProductDetailVo便
*
*/
@RequestMapping("detail.do")
public ServerResponse<ProductDetailVo> detail(Integer productId){
public ServerResponse<ProductDetailVo> detail(Integer productId) {
return productService.detail(productId);
}
/**
*
* HTTP "/manage/product/set_sale_status.do"
* IDproductIdstatusset_sale_status
* ServerResponse
* 便
*
* @param productId
* IDID
* @param status 10
* 使
* @return ServerResponse<String> ServerResponseString
* ServerResponse
* ServerResponse便
*
*/
@RequestMapping("set_sale_status.do")
public ServerResponse<String> set_sale_status(Integer productId,Integer 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;
public ServerResponse<String> set_sale_status(Integer productId, Integer status) {
return productService.set_sale_status(productId, status);
}
}
/**
* OR
* HTTP "/manage/product/save.do"
* ProductsaveOrUpdateProduct
* ServerResponse
* 便
*
* @param product Product
* ID
*/

@ -5,26 +5,180 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* ProductMapperMyBatisProduct
* CRUDMyBatisSQLXML使
*
*
* @MapperMyBatisMyBatisMapper
* SpringMyBatis使Service
* 便
*/
@Mapper
public interface ProductMapper {
/**
* ID
* ID
* 0ID0ID
*
* @param id ID
* MyBatisSQLDELETE FROM product_table WHERE id = #{id}
* ID
* @return int 10
*
*/
int deleteByPrimaryKey(Integer id);
/**
*
* Productrecord
* MyBatisXML
* SQLINSERT INTO product_table (column1, column2,...) VALUES (#{property1}, #{property2},...)
* 00
*
* @param record Product
*
* Product便MyBatis
* @return int 10
*
*/
int insert(Product record);
/**
*
* insertProduct
* MyBatisSQLXMLSQL
*
*
* @param record Product
*
* ProductMyBatis
* @return int 10
*
*/
int insertSelective(Product record);
/**
* ID
* idMyBatisSQLSELECT * FROM product_table WHERE id = #{id}
* Product
* null便
*
* @param id MyBatis
* IDnull
* Productnull
* @return Product Product
* null
*/
Product selectByPrimaryKey(Integer id);
/**
* ID
* Productrecord
* MyBatisSQLXMLSQLUPDATE product_table SET column1 = #{property1}, column2 = #{property2} WHERE id = #{id}property1property2
* 00
*
* @param record Product
*
* ProductMyBatis
* @return int 00
*
*/
int updateByPrimaryKeySelective(Product record);
/**
* ID
* updateByPrimaryKeySelectiveProductrecord
* XMLSQLUPDATE product_table SET column1 = #{property1}, column2 = #{property2},... WHERE id = #{id}
* 00
*
* @param record Product
*
* Product
*
* @return int 10
* 便
*/
int updateByPrimaryKey(Product record);
/**
*
* SQLSELECT * FROM product_table
* ProductListProduct
* List便
*
* @return List<Product> ListProduct
* List
*
*/
List<Product> selectList();
/**
* ID
* productNameIDproductIdMyBatis
* SQLXMLSQLSELECT * FROM product_table WHERE product_name LIKE '%#{productName}%' AND id = #{productId}
* ProductListProduct
* List便
*
* @param productName MyBatisSQL
* MyBatis
* nullSQLID
* @param productId IDMyBatis
* nullIDIDSQL
* ID
* @return List<Product> ListProductID
* List
*
*/
List<Product> selectProductByNameAndId(@Param("productName") String productName, @Param("productId") Integer productId);
/**
* ID
* IDproductNameIDproductId
* MyBatisSQL
*
* @Param("productName") String productName
* SQLMyBatisSQL使 LIKE '%#{productName}%'
* 便productNamenull
* productId便IDID
*
* @Param("productId") Integer productIdID
* SQL WHERE id = #{productId}
* productIdnullIDSQLproductName
* productNameproductId
*
* @return List<Product>ListProduct
* ID
* List便
*
*/
List<Product> selectByNameAndCategoryIds(@Param("productName") String productName, @Param("categoryIdList") List<Integer> categoryIdList);
/**
* ID
* IDproductNameIDcategoryIdList
* MyBatisSQLSQL
*
* @Param("productName") String productName
* `selectProductByNameAndId` MyBatisSQL LIKE '%#{productName}%'
* productNamenull
* categoryIdList便
*
* @Param("categoryIdList") List<Integer> categoryIdListID
* SQL IN WHERE category_id IN (#{categoryIdList}) MyBatis
* IDcategoryIdListSQL
* productNameproductNamecategoryIdList
*
*
* @return List<Product>ListProduct
* ID
* List便
*
*/
Integer selectStockByProductId(Integer id);
/**
* ID
* IDid
* MyBatisSQL
*
* @Param("id") Integer id
* SQL WHERE product_id = #{id} MyBatisSQL
* id
*
* @return Integer
* ID
* IDnull
*
*/
}

@ -3,20 +3,43 @@ package com.njupt.swg.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* Category
* 使LombokGetterSetter便
*/
@Data
// @Data注解是Lombok提供的一个便捷注解它会自动为类中的所有非静态、非final字段生成Getter、Setter方法同时还会生成toString、equals和hashCode方法
// 这样就无需手动编写这些重复的代码,提高了代码开发效率,使得在其他类中可以方便地获取和设置该类对象的属性值,以及进行对象之间的比较、打印等操作。
@AllArgsConstructor
// @AllArgsConstructor注解会为类生成一个包含所有参数的构造方法这个构造方法接受类中所有属性作为参数方便在创建对象时一次性初始化所有属性
// 例如可以通过 new Category(1, 0, "电子产品", true, 1) 这样的方式来创建一个Category对象传入对应的属性值进行初始化适用于需要完整初始化对象的场景。
@NoArgsConstructor
// @NoArgsConstructor注解会为类生成一个无参构造方法在一些框架如Spring在进行依赖注入、MyBatis在创建对象实例等场景或者需要默认创建对象的情况下无参构造方法是必要的
// 它提供了一种简单的创建对象的方式后续可以再通过Setter方法等去设置具体的属性值保证了类在不同使用场景下的灵活性。
public class Category {
private Integer id;
// 用于存储商品分类的唯一标识符通常对应数据库表中的主键字段通过这个ID可以唯一确定一个商品分类在数据库操作如查询、更新、删除等以及业务逻辑中
// 可以依据这个ID来定位和处理特定的商品分类记录例如通过分类ID查找该分类下的所有商品等操作。
private Integer parentId;
// 表示当前商品分类的父分类的ID用于构建商品分类的层级关系若parentId为0或者null通常表示该分类是顶级分类没有上级分类
// 通过这个字段可以实现分类的树形结构,方便进行分类的层级展示、查询子分类、查找上级分类等相关操作,例如在电商平台中展示商品分类目录时,
// 可以依据parentId来展示不同层级的分类以及它们之间的包含关系。
private String name;
// 存储商品分类的名称,是用于直观展示和区分不同商品分类的重要属性,例如"电子产品"、"服装"、"食品"等,用户在浏览商品或者后台管理分类时,
// 通过这个名称可以清楚地了解每个分类所涵盖的商品范围,在业务逻辑中也常根据分类名称进行模糊查询、匹配等操作,比如查找名称包含特定关键字的分类等情况。
private Boolean status;
// 用于表示商品分类的状态一般是布尔类型常见的取值含义可以是true表示分类可用、处于激活状态false表示分类不可用、被禁用等情况
// 在业务中可以根据这个状态来决定是否展示该分类下的商品、是否允许对该分类进行编辑等操作例如在后台管理系统中只展示状态为true的分类给用户操作。
private Integer sortOrder;
// 用于确定商品分类在展示或者排序时的顺序整数类型的排序序号数值越小通常表示在列表中越靠前的位置例如可以按照sortOrder的值从小到大排列分类
// 在前端展示分类列表或者后台管理分类顺序时,可以依据这个字段来调整分类的展示顺序,方便用户查看和操作分类,使其更符合业务需求和用户体验。
}

@ -4,37 +4,75 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
/**
* Product
* Lombok便
*/
@Data
// @Data注解是Lombok提供的一个实用注解它会自动为类中的所有非静态、非final字段生成Getter、Setter方法同时还会生成toString、equals和hashCode方法。
// 这样一来,在其他类中就能便捷地获取和设置该类对象的各个属性值,并且在进行对象比较、打印输出等操作时也更加方便,无需手动编写这些重复的代码,提高了开发效率。
@AllArgsConstructor
// @AllArgsConstructor注解会为类生成一个包含所有参数的构造方法这个构造方法接受类中所有属性作为参数。
// 例如,可以通过类似 new Product(1, 100, "商品名称", "副标题", "主图路径", "子图路径列表", "商品详情", new BigDecimal("9.99"), 100, 1, new Date(), new Date()) 这样的方式创建一个Product对象
// 一次性传入所有属性值进行初始化,适用于在明确知道所有属性值且需要完整初始化对象的场景下使用。
@NoArgsConstructor
// @NoArgsConstructor注解会为类生成一个无参构造方法在很多场景下比如Spring框架进行依赖注入、MyBatis框架创建对象实例等无参构造方法是必不可少的。
// 它提供了一种简单创建对象的方式后续可以再通过Setter方法等去逐个设置具体的属性值使得类在不同的使用场景下更加灵活能满足多样化的需求。
public class Product {
private Integer id;
// 用于存储商品的唯一标识符通常对应数据库表中的主键字段通过这个ID可以在整个系统中唯一确定一个商品
// 在数据库操作如查询、更新、删除商品记录等以及各种业务逻辑处理中都依靠这个ID来精准定位和操作特定的商品例如根据商品ID获取商品详情、更新商品信息等操作。
private Integer categoryId;
// 表示该商品所属的分类的ID用于建立商品与商品分类之间的关联关系通过这个字段可以知道商品属于哪一个分类
// 在业务逻辑中常用于根据分类查找商品、展示分类下的商品列表等操作比如在电商平台中按照分类展示不同类型的商品或者通过分类ID筛选出该分类下的所有商品信息等情况。
private String name;
// 存储商品的名称,这是用于直观展示和区分不同商品的重要属性,用户在浏览商品、搜索商品或者后台管理商品时,
// 通过商品名称能够快速了解商品的大致内容,在业务逻辑里也常常会根据商品名称进行模糊查询、精确匹配等操作,例如搜索名称包含特定关键字的商品等情况。
private String subtitle;
// 用于存放商品的副标题,一般可以对商品名称进行补充说明,提供更多关于商品特点、优势等方面的简要信息,
// 虽然不像商品名称那样是必填且唯一标识商品的关键属性,但可以帮助用户更全面地了解商品内容,在展示商品详情或者商品列表时,可以作为辅助信息展示给用户。
private String mainImage;
// 存储商品的主图路径信息通常指向服务器上存储该商品主图片的具体位置可能是相对路径或者完整的URL地址具体取决于项目配置
// 在前端页面展示商品时,会依据这个路径来加载并显示商品的主图片,让用户能够直观地看到商品的外观等特征,是商品展示环节中很重要的一个属性。
private String subImages;
// 用于保存商品的子图片路径信息,一般是以某种特定格式(如逗号分隔的字符串等)存储多个子图片的路径,同样指向服务器上对应的图片存储位置,
// 这些子图片可以从不同角度、细节展示商品,丰富用户对商品的视觉认知,在前端展示商品详情页面时,会根据这些路径加载并展示相应的子图片,增强商品展示效果。
private String detail;
// 存储商品的详细描述信息,包含了商品的功能、参数、使用方法、材质等各方面详细的文字介绍内容,
// 用户在查看商品详情时,通过这个属性可以深入了解商品的具体情况,以便做出购买决策,在后台管理商品时,也会对这个属性进行编辑、更新等操作来完善商品的介绍内容。
private BigDecimal price;
// 用于表示商品的价格采用BigDecimal类型是为了更精确地处理数值避免浮点数运算带来的精度损失问题尤其是在涉及货币计算等对精度要求较高的场景下
// 这个属性明确了商品的售价,在业务逻辑中会用于计算订单总价、展示商品价格、进行价格比较等各种与价格相关的操作。
private Integer stock;
// 表示商品的库存数量,即当前可售卖的商品数量,在电商业务中,库存管理是很重要的一部分,会根据这个库存数量来判断商品是否还有货、能否进行销售等情况,
// 例如在用户下单时需要检查库存是否充足,后台管理系统也会对库存数量进行更新操作(如进货增加库存、销售减少库存等)。
private Integer status;
// 用于体现商品的状态信息一般可以通过不同的整数值来定义不同的状态含义具体含义由业务规则确定例如常见的可以是1表示商品上架、可售卖状态0表示商品下架、不可售卖状态等
// 在业务逻辑中会根据这个状态来决定是否在前端展示商品、是否允许用户购买等操作,后台管理系统也会对商品的状态进行修改操作(如上下架商品)。
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date createTime;
// 使用 @JsonFormat注解对日期类型的创建时间属性进行格式化设置指定其在序列化和反序列化过程中的格式为 "yyyy-MM-dd HH:mm:ss.SSS"
// 也就是精确到毫秒的年月日时分秒格式这样在将商品对象转换为JSON字符串如接口返回数据给前端或者从JSON字符串转换为商品对象如接收前端传入的数据
// 日期属性能够按照统一的、易读的格式进行处理便于数据的传输和展示createTime属性记录了商品在系统中被创建的时间常用于记录商品的历史信息、查询商品创建顺序等操作。
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date updateTime;
// 同样使用 @JsonFormat注解进行格式化的日期属性用于记录商品信息最后一次被更新的时间格式也是精确到毫秒的 "yyyy-MM-dd HH:mm:ss.SSS"
// 在业务逻辑中,当商品的任何属性发生修改时,一般会更新这个时间字段,方便跟踪商品信息的变更历史,例如查看商品最近一次修改是什么时候等情况。
}

@ -4,40 +4,77 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
/**
* User
* 使Lombok便
*
* @Author swg.
* @Date 2018/12/31 21:01
* @CONTACT 317758022@qq.com
* @DESC
*/
@Data
// @Data注解是Lombok提供的一个强大的注解它会自动为类中的所有非静态、非final字段生成Getter、Setter方法同时还会生成toString、equals和hashCode方法。
// 这样一来,在其他类中就能够方便快捷地获取和设置该类对象的各个属性值,并且在进行对象比较、打印输出等操作时也无需手动编写这些重复的代码,极大地提高了开发效率。
@NoArgsConstructor
// @NoArgsConstructor注解会为类生成一个无参构造方法在很多场景下例如Spring框架进行依赖注入、MyBatis框架创建对象实例等无参构造方法是必不可少的。
// 它提供了一种简单创建用户对象的方式后续可以再通过Setter方法等去逐个设置具体的属性值使得类在不同的使用场景下更加灵活能满足多样化的业务需求。
@AllArgsConstructor
// @AllArgsConstructor注解会为类生成一个包含所有参数的构造方法这个构造方法接受类中所有属性作为参数。
// 例如,可以通过类似 new User(1, "user1", "password1", "user1@example.com", "1234567890", "question1", "answer1", 1, new Date(), new Date()) 这样的方式创建一个User对象
// 一次性传入所有属性值进行初始化,适用于在明确知道所有属性值且需要完整初始化对象的场景下使用。
@ToString
// @ToString注解会自动为类生成一个toString方法默认情况下它会包含类名以及所有非静态、非transient字段的值方便在调试、日志输出等场景中快速查看用户对象的具体内容
// 例如在打印用户对象时,能够直观地看到各个属性的值,便于排查问题和了解对象的状态。
public class User implements Serializable {
// 实现Serializable接口表示该类的对象可以被序列化和反序列化这在很多场景下是非常重要的比如将用户对象存储到文件、在网络中传输如分布式系统中的远程调用等情况
// 通过序列化可以将对象转换为字节流进行持久化或传输,反序列化则可以从字节流重新恢复为对象,确保用户对象能够在不同的环境和操作中正确地保存和恢复其状态。
private Integer id;
// 用于存储用户的唯一标识符通常对应数据库表中的主键字段通过这个ID可以在整个系统中唯一确定一个用户
// 在数据库操作如查询、更新、删除用户记录等以及各种业务逻辑处理中都依靠这个ID来精准定位和操作特定的用户例如根据用户ID获取用户详细信息、更新用户资料等操作。
private String username;
// 存储用户的用户名,这是用户在登录系统、进行各种操作时用于标识自己的重要属性,通常要求具有唯一性(具体取决于业务规则),
// 用户在注册时会设置自己的用户名,后续登录或者系统内的交互操作中,通过这个用户名来区分不同的用户,在业务逻辑里也常常会根据用户名进行查找、验证等操作,例如验证用户名是否存在、根据用户名查找用户信息等情况。
private String password;
// 用于存放用户的登录密码密码通常会经过加密处理如使用MD5等加密算法后存储以保障用户账户的安全性
// 在用户登录时,会将输入的密码进行同样方式的加密后与存储的加密密码进行比对,以此来验证用户身份,同时在修改密码等业务场景中,也会对这个属性进行更新操作。
private String email;
// 存储用户的电子邮箱地址,可用于用户注册验证、找回密码、接收系统通知等功能,例如在用户注册时可能会发送一封验证邮件到这个邮箱地址,
// 在业务逻辑中,有时也会根据邮箱地址来查找用户、验证邮箱的唯一性等操作,确保每个用户的邮箱地址在系统中是唯一且有效的。
private String phone;
// 用于保存用户的手机号码,手机号码同样在很多业务场景中有重要作用,比如用于手机验证码登录、绑定账号、接收短信通知等功能,
// 在一些需要验证用户身份或者与用户进行即时通讯的场景下,手机号码是很关键的信息,并且也可能会验证其唯一性以及格式的合法性。
private String question;
// 存储用户设置的密保问题,密保问题常用于用户找回密码等安全验证场景,当用户忘记密码时,可以通过回答预先设置的密保问题来证明自己的身份,进而重置密码,
// 在业务逻辑中,会在用户设置密保、找回密码等相关操作时涉及对这个属性的处理,确保密保问题的合理性以及与用户答案的匹配性。
private String answer;
// 对应于用户设置的密保问题的答案是与question属性配合使用的关键信息用于在找回密码等安全验证环节中与用户输入的答案进行比对
// 只有当用户输入的答案与存储的这个答案一致时,才允许进行后续的密码重置等操作,保障用户账户的安全性和找回密码流程的合法性。
//角色0-管理员,1-普通用户
private Integer role;
// 用于表示用户在系统中的角色通过整数值来区分不同的角色类型这里约定0表示管理员角色1表示普通用户角色具体的角色划分和对应数值可以根据业务规则调整
// 在权限控制相关的业务逻辑中,会依据这个角色属性来决定用户能够访问哪些资源、执行哪些操作,例如管理员可能具有更多的系统管理权限,而普通用户只能进行一些常规的操作。
private Date createTime;
// 记录用户在系统中被创建的时间,一般在用户注册成功时会自动设置这个时间字段,通过存储创建时间,可以方便地查询用户注册的先后顺序、统计不同时间段内的新增用户数量等,
// 在数据统计、用户行为分析等业务场景中,这个属性是很有价值的基础数据,并且在进行数据库操作时,也需要注意对这个字段的正确维护(如在插入新用户记录时赋值等情况)。
private Date updateTime;
// 用于记录用户信息最后一次被更新的时间,当用户修改了自己的用户名、密码、联系方式等任何属性信息时,一般会同步更新这个时间字段,
// 它有助于跟踪用户信息的变更历史,例如查看用户最近一次修改资料是什么时候,也方便在一些数据一致性检查、审计等业务场景中使用,了解用户信息的动态变化情况。
}

@ -11,50 +11,107 @@ import java.io.IOException;
import java.util.UUID;
/**
* FileServiceImplIFileService
* MultipartFileFTPFtpUtil
* 访便
*
* @Author swg.
* @Date 2019/1/3 19:13
* @CONTACT 317758022@qq.com
* @DESC
*/
@Service
// @Service注解用于标记该类是Spring框架中的一个服务层组件意味着这个类会被Spring容器管理在其他地方如控制层可以通过依赖注入的方式使用这个类的实例
// 同时Spring会对其进行相关的生命周期管理以及一些配置和增强操作如AOP切面等若有配置的话方便业务逻辑的组织和复用。
@Slf4j
public class FileServiceImpl implements IFileService{
// 使用Lombok的 @Slf4j注解自动生成名为log的SLF4J日志记录器用于在文件上传的各个步骤中记录关键信息如文件名称、路径等以及出现异常情况时记录详细的错误信息
// 方便后续查看日志来了解文件上传的执行情况排查可能出现的问题例如文件上传失败的原因、是否成功传输到FTP服务器等情况。
public class FileServiceImpl implements IFileService {
/**
* MultipartFile
*
* @param file MultipartFileSpring
* 便
* @param path FTP
* "/upload"
* @return String
* 访null
*/
@Override
public String upload(MultipartFile file, String path) {
String fileName = file.getOriginalFilename();
// 获取上传文件的原始文件名,这个文件名是客户端上传文件时原本的名称,例如客户端上传了一个名为 "abc.jpg" 的图片文件,这里获取到的就是 "abc.jpg"
// 通过这个原始文件名可以提取文件的扩展名等信息,用于后续生成新的文件名以及进行相关的文件操作判断等情况。
//扩展名
//abc.jpg
String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);
String uploadFileName = UUID.randomUUID().toString()+"."+fileExtensionName;
log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}",fileName,path,uploadFileName);
String fileExtensionName = fileName.substring(fileName.lastIndexOf(".") + 1);
// 从原始文件名中提取文件的扩展名,通过查找文件名中最后一个 "." 出现的位置,然后取其后面的字符串部分,得到文件的扩展名,
// 例如对于文件名 "abc.jpg",通过该操作获取到的扩展名就是 "jpg",用于后续构建新的文件名(确保新文件名保留正确的文件类型扩展名),便于识别文件类型以及正确的访问和处理文件。
String uploadFileName = UUID.randomUUID().toString() + "." + fileExtensionName;
// 使用Java的UUID通用唯一识别码生成一个随机的字符串并与获取到的文件扩展名拼接起来组成新的文件名
// 这样做的目的是为了避免文件名冲突(特别是在多用户同时上传文件或者重复上传同名文件的情况下),确保每个上传的文件在服务器上有一个唯一的标识名称,
// 例如生成的新文件名可能类似 "550e8400-e29b-41d4-a716-446655440000.jpg",方便后续文件的存储、管理以及访问操作。
log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}", fileName, path, uploadFileName);
// 使用日志记录器记录文件上传操作开始时的关键信息,包括原始文件名、上传的目标路径以及生成的新文件名,方便后续查看日志了解文件上传的初始情况,
// 同时在出现问题时也可以通过这些记录来排查是哪个文件在哪个路径下上传出现了异常等情况。
File fileDir = new File(path);
if(!fileDir.exists()){
if (!fileDir.exists()) {
fileDir.setWritable(true);
fileDir.mkdirs();
}
log.info("【文件上传路径为:{}】",fileDir);
File targetFile = new File(path,uploadFileName);
// 根据传入的文件上传路径创建一个File对象用于表示对应的目录如果该目录不存在则先设置其可写权限确保后续可以创建文件等操作
// 然后通过mkdirs方法创建该目录及其所有必要的父目录如果不存在的话保证文件上传时有对应的本地存储目录可用避免因目录不存在导致文件保存失败的情况。
log.info("【文件上传路径为:{}】", fileDir);
// 使用日志记录创建好的文件上传路径对应的File对象信息方便后续查看实际使用的文件存储目录情况确认目录是否创建正确以及是否符合预期等情况
// 也有助于排查可能因目录问题导致的文件上传异常情况。
File targetFile = new File(path, uploadFileName);
// 根据文件上传路径和新生成的文件名创建一个用于保存上传文件的目标File对象这个对象代表了文件在本地服务器上最终要保存的位置和对应的文件名
// 后续会将上传的文件内容写入到这个目标文件中,完成文件在本地服务器的临时存储操作。
try {
file.transferTo(targetFile);
// 将上传的MultipartFile对象中的文件内容传输并保存到之前创建的本地目标文件targetFile
// 这个操作会将客户端上传的文件数据实际写入到服务器本地的文件系统中完成文件在本地服务器的临时存储如果这个过程出现I/O异常等问题会抛出IOException异常
// 例如文件权限不足、磁盘空间不足等原因可能导致传输失败需要在catch块中进行相应的异常处理。
//文件已经上传成功了
log.info("【文件上传本地服务器成功】");
// 使用日志记录文件成功上传到本地服务器的信息,方便后续查看文件上传的执行进度以及确认本地存储这一步是否成功完成,
// 若后续出现问题如无法上传到FTP服务器等情况可以通过这个记录来判断是否是本地存储环节之后出现的问题。
FtpUtil.uploadFile(Lists.newArrayList(targetFile));
// 调用FtpUtil工具类的uploadFile方法将包含目标文件targetFile的列表传递进去执行将文件上传到FTP服务器的操作
// FtpUtil类应该是专门用于处理FTP文件传输相关逻辑的工具类通过它实现与FTP服务器的连接、文件上传等功能若这个过程出现异常如FTP连接失败、权限问题等
// 会在FtpUtil内部进行相应的处理可能记录日志、抛出异常等情况具体取决于FtpUtil的实现这里只是调用其方法来触发上传到FTP服务器的操作。
//已经上传到ftp服务器上
log.info("【文件上传到文件服务器成功】");
// 使用日志记录文件成功上传到FTP服务器的信息表明文件已经从本地服务器进一步传输到了FTP服务器上完成了整个文件上传流程中的关键步骤
// 通过这个记录可以方便后续查看文件是否完整地按照预期上传到了指定的FTP服务器若出现问题如文件在FTP服务器上不可访问等情况可以据此排查是FTP上传环节出现的问题。
targetFile.delete();
log.info("【删除本地文件】");
// 在文件成功上传到FTP服务器后删除本地临时保存的文件以释放本地服务器的磁盘空间避免不必要的文件冗余存储
// 通过这个操作本地服务器只起到一个临时中转存储的作用最终文件存储在FTP服务器上而本地只保留相关的文件上传记录如文件名等信息用于后续访问等操作
} catch (IOException e) {
log.error("上传文件异常",e);
log.error("上传文件异常", e);
// 如果在文件上传过程包括本地保存或者上传到FTP服务器等操作中出现IOException异常使用日志记录器记录详细的错误信息包括异常堆栈信息
// 方便后续查看日志来排查具体是哪个环节出现了I/O相关的问题例如是文件传输失败、FTP连接异常还是其他文件操作异常等情况同时返回null表示文件上传失败。
return null;
}
//A:abc.jpg
//B:abc.jpg
return targetFile.getName();
// 返回上传后文件在服务器上对应的文件名这里是经过重命名后的新文件名即之前生成的uploadFileName
// 这个文件名可以在其他地方如数据库中记录、返回给前端用于构建文件访问链接等使用用于标识和访问已经上传到FTP服务器上的文件完成文件上传操作并返回相应的文件名结果。
}
}
}

@ -3,11 +3,30 @@ package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* IFileService
* 使
* 便
*
* @Author swg.
* @Date 2019/1/3 19:12
* @CONTACT 317758022@qq.com
* @DESC
*/
public interface IFileService {
/**
*
* MultipartFile
*
*
* @param file MultipartFileSpring
* 便
*
* @param path
*
* "/upload" "upload" FTP访
* @return String
* 访
* null
*/
String upload(MultipartFile file, String path);
}
}

@ -6,6 +6,11 @@ import com.njupt.swg.entity.Product;
import com.njupt.swg.vo.ProductDetailVo;
/**
* IProductServiceProduct
*
* 便
* 使
*
* @Author swg.
* @Date 2019/1/2 17:36
* @CONTACT 317758022@qq.com

@ -25,288 +25,637 @@ import java.util.Date;
import java.util.List;
/**
* ProductServiceImplIProductService
* ProductMapperCommonCacheUtilCategoryClient
* /
* 使
*
* @Author swg.
* @Date 2019/1/2 17:36
* @CONTACT 317758022@qq.com
* @DESC
*/
@Service
// @Service注解用于标记该类是Spring框架中的服务层组件意味着这个类会被Spring容器管理其他地方如控制层可以通过依赖注入的方式使用这个类的实例
// 同时Spring会对其进行相关的生命周期管理以及一些配置和增强操作如AOP切面等若有配置的话方便业务逻辑的组织和复用。
@Slf4j
// 使用Lombok的 @Slf4j注解自动生成名为log的SLF4J日志记录器用于在商品业务逻辑处理的各个步骤中记录关键信息如操作的参数、执行结果等以及出现异常情况时记录详细的错误信息
// 方便后续查看日志来了解商品业务的执行情况,排查可能出现的问题,例如商品查询失败的原因、更新状态不成功的原因等情况。
public class ProductServiceImpl implements IProductService{
@Autowired
private ProductMapper productMapper;
// 通过Spring的依赖注入机制注入ProductMapper接口的实现类实例用于与数据库进行交互执行如查询商品记录、更新商品信息等持久化操作
// ProductMapper中定义了一系列针对商品数据表的操作方法这个实例在本类的多个业务方法中都会被调用以获取或更新商品相关的数据。
@Autowired
private CategoryClient categoryClient;
// 注入CategoryClient实例CategoryClient通常是基于Feign框架实现的用于调用商品分类相关服务的客户端接口
// 通过它可以远程调用其他服务(可能是独立的商品分类服务微服务)提供的接口,获取商品分类的详细信息等内容,在一些需要涉及商品分类信息处理的业务方法中会用到它。
@Autowired
private CommonCacheUtil commonCacheUtil;
// 注入CommonCacheUtil实例用于操作缓存通常是Redis缓存等情况实现如缓存商品数据、从缓存中获取商品数据、删除缓存中的商品相关键值对等功能
// 在整个商品业务逻辑中,通过合理使用缓存可以提高数据获取的效率,减少对数据库的频繁访问,提升系统的整体性能。
/**
*
*
* @param pageNum 1
*
* @param pageSize 1010
*
* @return ServerResponse ServerResponse
* ServerResponse便
*
*/
@Override
public ServerResponse list(int pageNum, int pageSize) {
//1.pagehelper对下一行取出的集合进行分页
PageHelper.startPage(pageNum,pageSize);
// 使用PageHelper插件启动分页功能它会拦截后续执行的查询语句通过ProductMapper进行的查询并根据传入的页码和每页数量参数对查询结果进行分页处理
// 例如如果查询到的总商品数量为100条传入pageNum为2pageSize为10那么将会获取到第11 - 20条商品记录方便在后台展示分页的商品列表。
List<Product> productList = productMapper.selectList();
// 通过ProductMapper的selectList方法从数据库中获取所有的商品记录列表这里获取到的是原始的Product实体对象列表后续需要对其进行一些格式转换等操作
// 以适配返回给前端展示的需求,例如将其转换为包含部分商品属性的视图对象列表等情况。
List<ProductListVo> productListVoList = Lists.newArrayList();
for(Product product:productList){
ProductListVo productListVo = assembleProductListVo(product);
productListVoList.add(productListVo);
}
// 遍历从数据库获取到的商品列表调用assembleProductListVo方法将每个Product实体对象转换为ProductListVo视图对象
// ProductListVo对象可能只包含了前端展示所需的部分商品属性这样可以减少返回给前端的数据量同时保证展示的信息是前端关心的关键内容
// 将转换后的视图对象依次添加到productListVoList列表中用于后续构建包含分页信息的返回结果。
//2.返回给前端的还需要一些其他的分页信息,为了不丢失这些信息,需要进行下面的处理
PageInfo pageInfo = new PageInfo(productList);
pageInfo.setList(productListVoList);
// 创建PageInfo对象它会自动封装从数据库查询到的商品列表的分页相关信息如总记录数、总页数、当前页数据列表等
// 然后将转换后的视图对象列表productListVoList设置到PageInfo对象中替换掉原本的包含所有商品属性的列表确保返回给前端的分页信息是准确且符合展示需求的
// 最后将包含分页商品列表信息的PageInfo对象包装在ServerResponse对象中返回给调用者后台管理系统前端
return ServerResponse.createBySuccess(pageInfo);
}
/**
* idname
*
* @param productName
* 使 LIKE '%%'
* ID便IDID
* @param productId IDID
* IDnullID
* productName
* @param pageNum list
*
* @param pageSize listpageSize
*
* @return ServerResponse<PageInfo> ServerResponsePageInfo
* ServerResponsePageInfo
* ServerResponsePageInfo便
*
*/
@Override
public ServerResponse<PageInfo> search(String productName, Integer productId, int pageNum, int pageSize) {
//开始准备分页
PageHelper.startPage(pageNum,pageSize);
// 同样使用PageHelper插件启动分页功能为后续的商品搜索结果进行分页准备后续查询到的符合条件的商品记录将会按照传入的页码和每页数量参数进行分页处理。
//如果有内容可以先在这里封装好直接传到sql中去
if(StringUtils.isNotBlank(productName)){
productName = new StringBuilder().append("%").append(productName).append("%").toString();
}
// 如果传入的商品名称参数不为空字符串,对其进行模糊查询格式的处理,通过在名称前后添加 "%" 符号,将其转换为 LIKE 语句中可用的模糊查询格式,
// 例如,原本传入的商品名称为 "手机",处理后变为 "%手机%",这样在数据库查询时就能查找名称中包含 "手机" 关键字的所有商品记录了。
List<Product> productList = productMapper.selectProductByNameAndId(productName,productId);
// 通过ProductMapper的selectProductByNameAndId方法传入处理后的商品名称和商品ID参数从数据库中查询符合条件的商品记录列表
// 这个方法内部会根据传入的参数构建相应的SQL查询语句基于MyBatis的映射机制执行数据库查询操作获取满足条件的商品记录。
//转换一下传给前端显示
List<ProductListVo> productListVoList = Lists.newArrayList();
for(Product product:productList){
ProductListVo productListVo = assembleProductListVo(product);
productListVoList.add(productListVo);
}
// 遍历查询到的商品列表调用assembleProductListVo方法将每个Product实体对象转换为ProductListVo视图对象
// 目的同样是为了提取前端展示所需的部分商品属性减少返回给前端的数据量将转换后的视图对象依次添加到productListVoList列表中用于后续构建返回结果。
PageInfo pageInfo = new PageInfo(productList);
pageInfo.setList(productListVoList);
// 创建PageInfo对象来封装分页相关信息将包含商品列表的PageInfo对象的原始商品列表替换为转换后的视图对象列表
// 确保返回给前端的分页信息中包含的是经过处理、符合展示需求的商品数据最后将包含分页商品列表信息的PageInfo对象包装在ServerResponse对象中返回给调用者后台管理系统前端
return ServerResponse.createBySuccess(pageInfo);
}
/**
*
*
* @param productId
* ID
* IDServerResponse便
* @return ServerResponse<ProductDetailVo> ServerResponseProductDetailVo
* ServerResponseProductDetailVoVOView Object
* ServerResponseProductDetailVo便
*
*/
@Override
public ServerResponse<ProductDetailVo> detail(Integer productId) {
//1校验参数
if(productId == null){
throw new SnailmallException("参数不正确");
}
// 首先对传入的商品ID参数进行校验如果参数为null说明传入的参数不合法抛出SnailmallException异常
// 这个异常可能会在更上层(如控制层)被捕获并处理,最终给前端返回相应的错误提示信息,告知用户参数不正确,无法进行商品详情查询操作。
//1-在售 2-下架 3-删除
Product product = productMapper.selectByPrimaryKey(productId);
if(product == null){
return ServerResponse.createByErrorMessage("商品不存在");
}
// 通过ProductMapper的selectByPrimaryKey方法根据传入的商品ID从数据库中查询对应的商品记录如果查询结果为null
// 表示数据库中不存在该ID对应的商品此时返回一个包含错误信息的ServerResponse对象告知前端商品不存在调用者如后台管理系统前端可以根据返回结果进行相应的提示展示。
ProductDetailVo productDetailVo = assembleProductDetailVo(product);
// 如果查询到了商品记录调用assembleProductDetailVo方法将查询到的Product实体对象转换为ProductDetailVo视图对象
// ProductDetailVo对象包含了更丰富、更适合前端展示的商品详细属性信息用于后续返回给前端展示商品详情。
return ServerResponse.createBySuccess(productDetailVo);
// 最后将包含转换好的ProductDetailVo视图对象的ServerResponse对象以成功状态返回这个响应对象会被传递到前端如后台管理系统的对应界面
// 前端可以从ServerResponse中获取到表示成功的状态码以及ProductDetailVo对象中的详细商品信息进而在页面上进行商品详情的展示完成整个查看商品详情的业务操作流程
// 向用户提供了期望的商品详细信息展示功能,同时通过前面的参数校验和商品存在性判断等操作,保证了整个流程的健壮性和对各种情况的合理处理。
}
@Override
public ServerResponse<String> set_sale_status(Integer productId, Integer status) {
//1.校验参数
if(productId == null || status == null){
// 1.校验参数
if (productId == null || status == null) {
return ServerResponse.createByErrorMessage("参数不正确");
}
//2.更新状态
// 首先对传入的参数进行合法性校验商品IDproductId和要设置的销售状态status在这个业务操作中都是必不可少的。
// 如果其中任何一个参数为null说明传入的参数不符合要求无法进行后续的商品销售状态更新操作
// 此时直接返回一个包含错误提示信息“参数不正确”的ServerResponse对象告知调用者可能是前端页面或者其他调用该服务的模块参数存在问题
// 这样可以保证业务操作在正确的参数输入前提下进行,避免因参数缺失或错误导致的异常情况,提高系统的健壮性。
// 2.更新状态
Product product = new Product();
product.setId(productId);
product.setStatus(status);
//3.删除该商品缓存
// 创建一个新的Product对象并通过Setter方法设置其ID和状态属性。这里的目的是构建一个只包含要更新的关键信息商品ID和新的销售状态的商品对象
// 用于后续传递给数据持久层通过productMapper进行数据库更新操作采用这种方式可以灵活地指定要更新的字段而不需要获取整个商品对象的所有属性再去更新
// 尤其在只需要更新部分字段(这里就是状态字段)的场景下,更加高效且符合业务逻辑,减少不必要的数据操作。
// 3.删除该商品缓存
commonCacheUtil.delKey(Constants.PRODUCT_TOKEN_PREFIX);
// 在更新商品销售状态之前先调用commonCacheUtil的delKey方法删除与商品相关的缓存。
// 原因是商品状态发生了改变,之前缓存中的商品信息可能已经不符合最新情况了,为了避免后续读取缓存时获取到旧的、不准确的商品状态数据,
// 需要先将对应的缓存数据删除,使得下次获取该商品信息时能够从数据库重新加载最新的数据并更新缓存,保证数据的一致性和准确性。
// Constants.PRODUCT_TOKEN_PREFIX应该是一个定义好的常量字符串用于标识商品相关缓存的键值的前缀部分通过这个前缀可以定位到所有与商品相关的缓存项进行统一的删除操作。
int rowCount = productMapper.updateByPrimaryKeySelective(product);
if(rowCount > 0){
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+productId,JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME);
// 通过productMapper数据持久层接口通常基于MyBatis等框架实现的updateByPrimaryKeySelective方法将构建好的包含新状态信息的Product对象传递进去
// 执行根据商品主键这里就是productId更新商品记录中指定字段这里就是状态字段因为使用的是updateByPrimaryKeySelective方法只会更新传入对象中非空的字段的操作
// 该方法会返回受影响的行数即实际更新的商品记录行数如果返回值大于0表示数据库中对应的商品记录的状态字段已成功更新否则表示更新操作未成功执行。
if (rowCount > 0) {
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + productId, JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME);
return ServerResponse.createBySuccessMessage("更新产品状态成功");
}
// 如果受影响的行数大于0说明商品状态在数据库中更新成功了接下来需要重新将更新后的商品信息缓存起来方便后续快速获取。
// 调用commonCacheUtil的cacheNxExpire方法以商品IDproductId拼接上之前定义的商品缓存键值前缀Constants.PRODUCT_TOKEN_PREFIX作为缓存的键
// 将更新后的商品对象转换为字符串通过JsonUtil的obj2String方法可能是将Java对象序列化为JSON字符串形式方便存储在缓存中作为缓存的值
// 并设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME应该是一个预定义的表示缓存有效时长的常量确保缓存数据在一定时间后会自动失效避免缓存数据长期占用内存且过时的问题。
// 最后返回一个包含成功提示信息“更新产品状态成功”的ServerResponse对象表示商品销售状态更新操作成功完成调用者如前端页面可以根据这个响应进行相应的提示展示等操作。
return ServerResponse.createByErrorMessage("更新产品状态失败");
// 如果数据库更新操作中受影响的行数不大于0即更新商品状态失败了返回一个包含错误提示信息“更新产品状态失败”的ServerResponse对象
// 告知调用者商品销售状态更新操作没有成功,方便调用者(比如前端页面可以给用户展示相应的提示,告知用户状态更新失败的情况)进行相应的处理,保证业务流程的完整性以及对操作结果的合理反馈。
}
@Override
public ServerResponse<String> saveOrUpdateProduct(Product product) {
//1.校验参数
if(product == null || product.getCategoryId()==null){
// 1.校验参数
if (product == null || product.getCategoryId() == null) {
return ServerResponse.createByErrorMessage("参数不正确");
}
//2.设置一下主图,主图为子图的第一个图
if(StringUtils.isNotBlank(product.getSubImages())){
// 首先进行参数的合法性校验。在这里传入的Product对象以及其中的商品分类IDcategoryId是关键参数
// 如果Product对象为null说明没有接收到有效的商品信息或者商品分类ID为null意味着缺少必要的商品分类关联信息
// 这两种情况都不符合业务逻辑要求无法进行后续的保存或更新操作所以直接返回一个包含错误提示信息“参数不正确”的ServerResponse对象
// 告知调用者(可能是前端页面或者其他调用该服务的模块)参数存在问题,以此保证业务操作在正确的参数输入前提下进行,避免因参数缺失或错误导致的异常情况,增强系统的健壮性。
// 2.设置一下主图,主图为子图的第一个图
if (StringUtils.isNotBlank(product.getSubImages())) {
String[] subImages = product.getSubImages().split(",");
if(subImages.length > 0){
if (subImages.length > 0) {
product.setMainImage(subImages[0]);
}
}
//3.看前端传过来的产品id是否存在存在则为更新否则为新增
if(product.getId() != null){
//删除该商品缓存
// 这段代码的目的是处理商品主图的设置逻辑。如果传入的商品对象中的子图信息subImages不为空字符串说明存在子图相关信息
// 首先通过逗号将子图信息字符串分割为字符串数组假设子图信息是以逗号分隔的多个图片路径等情况然后判断数组长度是否大于0
// 如果大于0表示存在至少一个子图按照业务规则将第一个子图作为商品的主图通过设置商品对象的主图属性setMainImage方法来完成主图的赋值操作
// 这样可以保证商品在展示等场景下有合理的主图信息,同时体现了一种常见的业务逻辑处理方式,即根据已有的子图来确定主图。
// 3.看前端传过来的产品id是否存在存在则为更新否则为新增
if (product.getId()!= null) {
// 删除该商品缓存
commonCacheUtil.delKey(Constants.PRODUCT_TOKEN_PREFIX);
int rowCount = productMapper.updateByPrimaryKeySelective(product);
if(rowCount > 0){
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+product.getId(),JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME);
if (rowCount > 0) {
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + product.getId(), JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME);
return ServerResponse.createBySuccessMessage("更新产品成功");
}
return ServerResponse.createByErrorMessage("更新产品失败");
}else {
//新增
} else {
// 新增
product.setCreateTime(new Date());//这两句可能多余因为xml中已经保证了就先放这里
product.setUpdateTime(new Date());
int rowCount = productMapper.insert(product);
if(rowCount > 0){
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+product.getId(),JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME);
if (rowCount > 0) {
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + product.getId(), JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME);
return ServerResponse.createBySuccessMessage("新增产品成功");
}
return ServerResponse.createByErrorMessage("新增产品失败");
}
// 这部分代码通过判断传入商品对象的ID是否存在来区分是执行更新操作还是新增操作
// - 如果商品IDproduct.getId()不为null说明前端传递过来的是已存在商品的相关信息要执行更新操作。
// - 首先调用commonCacheUtil的delKey方法删除该商品对应的缓存原因是商品信息即将被更新之前缓存中的商品数据已经过时
// 为了保证后续获取商品信息时能获取到最新的数据需要先清除旧的缓存Constants.PRODUCT_TOKEN_PREFIX是用于标识商品缓存键的前缀常量通过它可以定位并删除对应的缓存项。
// - 接着通过productMapper的updateByPrimaryKeySelective方法将传入的商品对象传递进去执行更新操作该方法会根据商品的主键即商品ID更新数据库中对应的商品记录
// 并且只会更新传入对象中非空的字段这种方式更灵活可以只更新部分需要修改的字段然后获取实际更新的行数rowCount
// - 如果rowCount大于0表示数据库中商品记录更新成功此时需要将更新后的商品信息重新缓存起来方便后续快速获取。
// 通过调用commonCacheUtil的cacheNxExpire方法以商品ID拼接上缓存键前缀作为缓存的键将商品对象转换为字符串通过JsonUtil的obj2String方法可能是序列化为JSON字符串方便存储在缓存中作为缓存的值
// 并设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME最后返回一个包含成功提示信息“更新产品成功”的ServerResponse对象表示商品更新操作成功完成调用者如前端页面可据此进行相应提示展示等操作。
// - 如果rowCount不大于0说明商品更新操作失败了返回一个包含错误提示信息“更新产品失败”的ServerResponse对象告知调用者更新操作未成功方便前端进行相应提示告知用户情况。
// - 如果商品IDproduct.getId()为null说明前端传递的是一个新的商品信息要执行新增操作。
// - 先设置商品的创建时间createTime和更新时间updateTime为当前时间虽然代码中备注了这两句可能多余因为在对应的xml配置中可能已经做了相关时间的默认设置但这里先保留设置操作
// - 然后通过productMapper的insert方法将商品对象插入到数据库中获取实际插入的行数rowCount
// - 如果rowCount大于0表示商品新增操作成功同样需要将新插入的商品信息缓存起来操作方式与更新成功后的缓存设置类似通过commonCacheUtil的cacheNxExpire方法进行缓存设置
// 最后返回一个包含成功提示信息“新增产品成功”的ServerResponse对象告知调用者商品新增操作已成功方便前端进行相应展示等操作。
// - 如果rowCount不大于0说明商品新增操作失败了返回一个包含错误提示信息“新增产品失败”的ServerResponse对象告知调用者新增操作未成功以便前端提示用户相应情况。
}
@Override
public ServerResponse<ProductDetailVo> getPortalProductDetail(Integer productId) {
if(productId == null){
// 首先进行参数校验判断传入的商品ID是否为null。在前台门户获取商品详情的业务场景中
// 商品ID是定位特定商品的关键依据如果传入的商品ID为null就无法明确要查询详情的具体商品不符合正常的业务逻辑
// 所以直接返回一个包含错误提示信息“参数不正确”的ServerResponse对象告知调用者可能是前台页面或者其他调用该服务的模块参数存在问题
// 保证业务操作在参数合法有效的前提下进行,避免因错误参数引发后续不必要的操作和异常情况,增强系统的健壮性。
if (productId == null) {
return ServerResponse.createByErrorMessage("参数不正确");
}
Product product = productMapper.selectByPrimaryKey(productId);
if(product == null){
// 通过ProductMapper接口的selectByPrimaryKey方法依据传入的商品ID从数据库中精确查询对应的商品记录。
// ProductMapper通常是基于数据持久层框架如MyBatis生成的接口其定义了与数据库中商品表交互的相关方法selectByPrimaryKey方法就是按照主键即商品ID来查找一条商品记录
// 这一步是获取商品详情的核心数据库查询操作,只有先从数据库获取到商品的原始数据,才能进行后续的判断以及数据转换等工作,为最终返回适合前台展示的商品详情信息做准备。
if (product == null) {
return ServerResponse.createByErrorMessage("商品不存在");
}
if(product.getStatus() != Constants.Product.PRODUCT_ON){
// 对从数据库查询到的商品记录进行判断如果返回的product对象为null说明在数据库中并没有找到与传入的productId对应的商品
// 这种情况下直接返回一个包含“商品不存在”错误提示信息的ServerResponse对象告知调用者比如前台页面当前要查看详情的商品不存在
// 使得前台可以根据这个响应结果向用户展示相应的提示内容,保证业务流程在面对商品不存在这种情况时能合理反馈,维持系统的完整性。
if (product.getStatus()!= Constants.Product.PRODUCT_ON) {
return ServerResponse.createByErrorMessage("产品已下架或删除");
}
// 当查询到商品记录即product对象不为null进一步检查商品的状态。Constants.Product.PRODUCT_ON应该是一个预定义的表示商品上架状态的常量
// 如果商品的状态不等于这个上架状态常量,意味着商品可能已经下架或者被删除了,此时不符合在前台门户展示商品详情的业务需求,
// 所以返回一个包含“产品已下架或删除”错误提示信息的ServerResponse对象告知调用者例如前台页面该商品不能展示详情
// 让前台能够相应地提示用户,避免展示不可用的商品信息给用户,确保前台展示的商品信息都是有效的、可供查看的。
ProductDetailVo productDetailVo = assembleProductDetailVo(product);
// 当商品存在product不为null且处于上架状态product.getStatus()符合要求调用assembleProductDetailVo方法对查询到的Product实体对象进行处理
// assembleProductDetailVo方法的作用是将从数据库获取的Product实体类对象转换为ProductDetailVo视图对象。ProductDetailVo作为视图对象
// 它会提取、封装那些适合在前台门户展示给用户查看商品详情的属性信息可能会对Product实体类中的一些属性进行筛选、格式转换或者添加一些方便前台展示的辅助信息等操作
// 例如对日期类型的属性进行格式化处理,使其更符合前台展示的格式要求,以此来优化展示给用户的商品详情内容,提升用户体验。
return ServerResponse.createBySuccess(productDetailVo);
// 最后将包含转换好的ProductDetailVo视图对象的ServerResponse对象以成功状态返回这个ServerResponse对象会传递到前台比如前台门户的对应页面
// 前台可以从中获取到表示成功的状态码以及ProductDetailVo对象里详细的商品信息进而在页面上展示商品详情给用户查看完成整个前台门户获取商品详情的业务操作流程
// 为用户提供了准确且符合业务要求的商品详情展示功能,同时通过前面的参数校验、商品存在性判断以及状态检查等操作,保障了整个流程的健壮性和对各种情况的合理处理。
}
@Override
public ServerResponse<PageInfo> portalList(String keyword, Integer categoryId, String orderBy, int pageNum, int pageSize) {
//准备盛放categoryIds
// 准备盛放categoryIds
List<Integer> categoryIdList = Lists.newArrayList();
//如果categoryId不为空
if(categoryId != null){
//对于这里,直接强转出错了,所以我就序列化处理了一下
// 创建一个空的整数列表categoryIdList用于后续存放商品分类ID相关的数据它的作用是在根据分类筛选商品时存储符合条件的多个分类ID
// 比如当查询某个父分类下的所有商品包括子分类商品这个列表会存储该父分类及其所有子分类的ID方便后续进行基于分类的商品查询操作。
// 如果categoryId不为空
if (categoryId!= null) {
// 对于这里,直接强转出错了,所以我就序列化处理了一下
ServerResponse response = categoryClient.getCategoryDetail(categoryId);
// 通过categoryClient通常是基于Feign等远程调用框架实现的用于与商品分类服务进行交互的客户端的getCategoryDetail方法
// 根据传入的categoryId尝试获取对应的商品分类详细信息返回的是一个ServerResponse对象它包装了操作结果成功与否以及相关的数据等信息
Object object = response.getData();
String objStr = JsonUtil.obj2String(object);
Category category = JsonUtil.Str2Obj(objStr,Category.class);
if(category == null && StringUtils.isBlank(keyword)){
////直接返回空
PageHelper.startPage(pageNum,pageSize);
Category category = JsonUtil.Str2Obj(objStr, Category.class);
// 从ServerResponse对象中获取返回的数据部分getData方法这个数据可能是一个对象先将其转换为字符串通过JsonUtil的obj2String方法可能是序列化为JSON字符串格式方便后续处理
// 然后再将这个字符串反序列化为Category类型的对象通过JsonUtil的Str2Obj方法用于将JSON字符串转换回Java对象这样就能获取到具体的商品分类对象了方便后续判断和使用。
if (category == null && StringUtils.isBlank(keyword)) {
// 如果根据categoryId获取到的商品分类对象为null并且传入的关键词keyword也是空字符串意味着既没有有效的分类信息也没有关键词信息来筛选商品
// 在这种情况下,直接返回一个空的商品列表分页信息。
PageHelper.startPage(pageNum, pageSize);
List<ProductListVo> productListVoList = Lists.newArrayList();
PageInfo pageInfo = new PageInfo(productListVoList);
return ServerResponse.createBySuccess(pageInfo);
// 首先使用PageHelper启动分页功能传入当前页码pageNum和每页显示数量pageSize虽然此时没有实际的商品数据但分页相关的设置还是需要进行的
// 然后创建一个空的ProductListVo类型的列表ProductListVo是用于前台展示的商品列表视图对象类型包含部分商品属性将其封装到PageInfo对象中
// 最后通过ServerResponse的createBySuccess方法将包含空列表的PageInfo对象包装起来以成功状态返回表示没有符合条件的商品数据调用者如前台页面可以根据这个响应进行相应的展示比如显示一个空的商品列表页面。
}
//说明category还是存在的
// 说明category还是存在的
categoryIdList = (List<Integer>) categoryClient.getDeepCategory(categoryId).getData();
// 如果获取到的商品分类对象不为null说明categoryId对应的分类是存在的此时调用categoryClient的getDeepCategory方法
// 这个方法可能是用于获取指定分类及其所有子分类的ID列表根据业务逻辑推测从返回的ServerResponse对象中获取数据部分并强转成List<Integer>类型,
// 赋值给categoryIdList后续就可以基于这个包含多个分类ID的列表去数据库中查询属于这些分类的商品了。
}
//如果keyword不为空
if(StringUtils.isNotBlank(keyword)){
// 如果keyword不为空
if (StringUtils.isNotBlank(keyword)) {
keyword = new StringBuilder().append("%").append(keyword).append("%").toString();
}
//如果orderBy不为空
if(StringUtils.isNotBlank(orderBy)){
if(Constants.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)){
// 当传入的关键词keyword不为空字符串时对其进行模糊查询格式的处理通过在关键词前后添加 "%" 符号,将其转换为 LIKE 语句中可用的模糊查询格式,
// 例如,原本传入的关键词为 "手机",处理后变为 "%手机%",这样在数据库查询时就能查找名称中包含 "手机" 关键字的所有商品记录了,方便实现根据关键词模糊搜索商品的功能。
// 如果orderBy不为空
if (StringUtils.isNotBlank(orderBy)) {
if (Constants.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) {
String[] orderByArray = orderBy.split("_");
//特定的格式
PageHelper.orderBy(orderByArray[0]+" "+orderByArray[1]);
// 特定的格式
PageHelper.orderBy(orderByArray[0] + " " + orderByArray[1]);
}
}
PageHelper.startPage(pageNum,pageSize);
//模糊查询
List<Product> productList = productMapper.selectByNameAndCategoryIds(StringUtils.isBlank(keyword)?null:keyword,
categoryIdList.size()==0?null:categoryIdList);
//封装返回对象
// 当传入的排序规则orderBy字符串不为空时先进行合法性校验判断它是否在预定义的合法排序规则集合Constants.ProductListOrderBy.PRICE_ASC_DESC应该是定义了允许的排序规则内容的常量集合
// 如果在集合中说明是合法的排序规则将orderBy字符串按照下划线"_")进行分割,得到一个包含排序字段和排序方式(如 "price" 和 "asc" 或者 "price" 和 "desc" 等情况)的字符串数组,
// 然后通过PageHelper的orderBy方法按照特定格式排序字段 + " " + 排序方式)传入参数,设置后续数据库查询结果的排序规则,例如按照价格升序或者降序排列商品列表等,以满足前台展示商品列表时不同的排序需求。
PageHelper.startPage(pageNum, pageSize);
// 使用PageHelper启动分页功能传入当前页码pageNum和每页显示数量pageSize参数这样后续通过ProductMapper进行的商品查询操作将会按照设定的分页规则返回对应页的商品数据
// 便于在前台分页展示商品列表,提升用户查看商品时的体验,避免一次性返回大量数据影响性能和展示效果。
// 模糊查询
List<Product> productList = productMapper.selectByNameAndCategoryIds(StringUtils.isBlank(keyword)? null : keyword,
categoryIdList.size() == 0? null : categoryIdList);
// 通过ProductMapper的selectByNameAndCategoryIds方法进行模糊查询根据处理后的关键词如果keyword为空则传入null以及分类ID列表如果categoryIdList为空则传入null
// 从数据库中查询符合条件的商品记录列表这个方法内部会根据传入的参数构建相应的SQL查询语句基于MyBatis的映射机制执行数据库查询操作获取满足条件的商品记录实现根据关键词和分类筛选商品的功能。
// 封装返回对象
List<ProductListVo> productListVoList = Lists.newArrayList();
for(Product product : productList){
for (Product product : productList) {
ProductListVo productListVo = assembleProductListVo(product);
productListVoList.add(productListVo);
}
//返回
// 遍历查询到的商品列表调用assembleProductListVo方法将每个Product实体对象转换为ProductListVo视图对象
// ProductListVo对象是专门为了前台展示而设计的它可能只包含了部分商品属性如商品名称、主图、价格等前台关心的关键信息通过这样的转换可以减少返回给前台的数据量同时保证展示的信息是符合前台展示需求的
// 将转换后的视图对象依次添加到productListVoList列表中用于后续构建包含分页信息的返回结果。
// 返回
PageInfo pageInfo = new PageInfo(productList);
pageInfo.setList(productListVoList);
return ServerResponse.createBySuccess(pageInfo);
// 创建PageInfo对象来封装分页相关信息如总记录数、总页数、当前页数据列表等它会根据传入的原始商品列表productList自动填充这些信息
// 然后将包含商品列表的PageInfo对象的原始商品列表替换为转换后的视图对象列表productListVoList确保返回给前台的分页信息中包含的是经过处理、符合展示需求的商品数据
// 最后将包含分页商品列表信息的PageInfo对象包装在ServerResponse对象中通过createBySuccess方法以成功状态返回调用者如前台页面可以接收到这个响应
// 从中获取分页的商品列表信息并展示在页面上,完成整个前台门户获取商品分页列表的业务操作流程,为用户提供了可根据关键词、分类、排序规则分页查看商品列表的功能。
}
@Override
public ServerResponse queryProduct(Integer productId) {
//1.校验参数
if(productId == null){
// 1.校验参数
if (productId == null) {
return ServerResponse.createByErrorMessage("参数错误");
}
//2.去redis中查询没有则把商品重新添加进redis中
String redisProductStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+productId);
if (redisProductStr == null){
// 首先进行参数校验判断传入的商品IDproductId是否为null。在查询商品的业务场景中商品ID是定位特定商品的关键依据
// 如果传入的商品ID为null就无法明确要查询的具体商品不符合正常的业务逻辑所以直接返回一个包含“参数错误”提示信息的ServerResponse对象
// 告知调用者(可能是其他业务模块或者前端页面等)参数存在问题,以此保证业务操作在正确的参数输入前提下进行,避免因错误参数引发后续不必要的操作和异常情况,增强系统的健壮性。
// 2.去redis中查询没有则把商品重新添加进redis中
String redisProductStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + productId);
// 通过commonCacheUtil这应该是一个用于操作缓存的工具类实例的getCacheValue方法尝试从Redis缓存中获取指定键由Constants.PRODUCT_TOKEN_PREFIX与传入的商品ID拼接而成的字符串作为键对应的缓存值
// 缓存值在这里预期是存储的商品相关信息可能是经过序列化后的字符串形式将获取到的缓存值赋值给redisProductStr变量后续根据这个值来判断商品信息是否已存在于缓存中。
if (redisProductStr == null) {
Product product = productMapper.selectByPrimaryKey(productId);
if(product == null){
if (product == null) {
return ServerResponse.createByErrorMessage("商品不存在");
}
if(product.getStatus() != Constants.Product.PRODUCT_ON){
// 如果从缓存中获取到的值为null说明缓存中不存在该商品的信息此时需要从数据库中查询商品信息。
// 通过ProductMapper的selectByPrimaryKey方法依据传入的商品ID从数据库中精确查询对应的商品记录这是获取商品原始数据的关键步骤
// 如果查询到的product对象为null意味着数据库中也不存在该商品直接返回一个包含“商品不存在”提示信息的ServerResponse对象告知调用者商品不存在方便调用者如前端页面进行相应的提示展示等操作。
if (product.getStatus()!= Constants.Product.PRODUCT_ON) {
return ServerResponse.createByErrorMessage("商品已经下架或者删除");
}
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+productId,JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME);
// 当从数据库中查询到商品记录即product不为null进一步检查商品的状态。Constants.Product.PRODUCT_ON应该是一个预定义的表示商品上架状态的常量
// 如果商品的状态不等于这个上架状态常量,意味着商品已经下架或者被删除了,此时不符合正常查询并返回可用商品信息的业务需求,
// 所以返回一个包含“商品已经下架或者删除”提示信息的ServerResponse对象告知调用者该商品不可用让调用者例如前端页面能够相应地提示用户避免返回无效的商品信息。
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + productId, JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME);
// 当商品存在product不为null且处于上架状态product.getStatus()符合要求需要将从数据库获取到的商品信息缓存到Redis中方便后续查询时能够直接从缓存获取提高查询效率。
// 通过调用commonCacheUtil的cacheNxExpire方法以商品ID拼接上缓存键前缀Constants.PRODUCT_TOKEN_PREFIX作为缓存的键
// 将商品对象转换为字符串通过JsonUtil的obj2String方法可能是将Java对象序列化为JSON字符串形式方便存储在缓存中作为缓存的值
// 并设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME应该是一个预定义的表示缓存有效时长的常量确保缓存数据在一定时间后会自动失效避免缓存数据长期占用内存且过时的问题。
}
//2.获取商品
Product product = JsonUtil.Str2Obj(commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+productId),Product.class);
// 2.获取商品
Product product = JsonUtil.Str2Obj(commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + productId), Product.class);
// 无论之前是从缓存中获取到了商品信息即redisProductStr不为null的情况还是刚刚将数据库中查询到的商品信息缓存后这里都需要从缓存中获取商品信息并转换为Product对象。
// 通过JsonUtil的Str2Obj方法将从缓存中获取到的缓存值通过commonCacheUtil的getCacheValue方法再次获取确保获取到最新的缓存内容转换为Product类型的Java对象
// 这样就能得到适合后续业务处理或返回给调用者的商品对象形式了。
return ServerResponse.createBySuccess(product);
// 最后将包含查询到的商品对象的ServerResponse对象以成功状态返回这个ServerResponse对象会被传递给调用者比如其他业务模块或者前端页面等
// 调用者可以从中获取到表示成功的状态码以及商品对象中的详细商品信息,进而进行相应的业务处理或展示操作,完成整个查询商品的业务操作流程,
// 实现了先从缓存查询商品信息,缓存不存在时从数据库获取并缓存,最终返回可用商品信息的功能,提高了商品查询的效率以及数据的可用性。
}
private ProductListVo assembleProductListVo(Product product) {
// 创建一个新的ProductListVo对象ProductListVo是专门设计用于在前端展示商品列表时使用的视图对象
// 它包含了部分商品属性这些属性是经过筛选的符合前端展示商品列表时通常所需要展示的关键信息通过将Product实体对象转换为ProductListVo对象
// 可以减少传递给前端的数据量,同时保证前端获取到的是真正需要展示给用户查看的信息,提升性能以及用户体验。
ProductListVo productListVo = new ProductListVo();
productListVo.setId(product.getId());
// 将传入的Product实体对象的ID属性赋值给ProductListVo对象的ID属性商品ID是唯一标识商品的关键信息在前端展示商品列表时
// 通常需要展示每个商品对应的ID方便后续进行一些与商品相关的操作如查看详情、编辑等操作时可以通过ID来定位具体商品
productListVo.setSubtitle(product.getSubtitle());
// 把Product实体对象中的副标题subtitle属性赋值给ProductListVo对象的副标题属性副标题可以对商品进行进一步的补充说明
// 在商品列表展示中,能够帮助用户更详细地了解商品的一些特点或者额外信息,丰富展示内容。
productListVo.setMainImage(product.getMainImage());
// 将Product实体对象的主图mainImage属性赋值给ProductListVo对象的主图属性主图是商品在列表中最直观展示给用户的视觉元素
// 用户可以通过主图快速识别商品的大致外观等信息,所以在商品列表视图对象中需要包含主图信息,方便前端进行展示。
productListVo.setPrice(product.getPrice());
// 把Product实体对象的价格price属性传递给ProductListVo对象的价格属性价格是用户在浏览商品列表时非常关注的信息之一
// 展示价格能够让用户快速了解商品的价值情况,便于用户进行比较和筛选商品。
productListVo.setCategoryId(product.getCategoryId());
// 将Product实体对象的商品分类IDcategoryId属性赋值给ProductListVo对象的分类ID属性商品分类信息有助于用户了解商品所属的类别
// 在一些有分类筛选或者导航功能的前端页面中展示分类ID可以方便后续基于分类进行相关操作如筛选同一分类下的其他商品等
productListVo.setName(product.getName());
// 把Product实体对象的商品名称name属性赋值给ProductListVo对象的商品名称属性商品名称是最基本且重要的展示信息
// 用户主要通过商品名称来识别和区分不同的商品,所以必须包含在用于前端展示的视图对象中。
productListVo.setStatus(product.getStatus());
// 将Product实体对象的商品状态status属性传递给ProductListVo对象的商品状态属性商品状态信息例如是否上架等状态可以让用户了解商品当前的可用性
// 方便用户知晓哪些商品是可以进行购买等操作的,哪些是不可用的。
productListVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://image.snail.com/"));
// 通过PropertiesUtil工具类的getProperty方法获取图片服务器的HTTP前缀地址默认值为 "http://image.snail.com/"
// 并将其赋值给ProductListVo对象的ImageHost属性。这个属性的作用是与商品的图片路径如主图、子图等路径相结合
// 构成完整的图片URL地址方便前端能够正确地展示商品的图片因为在存储图片路径时可能只是相对路径或者部分路径需要加上这个前缀才能形成可访问的完整URL。
return productListVo;
// 最后返回组装好的ProductListVo对象这个对象包含了适合前端展示商品列表的关键属性信息可供后续在业务逻辑中返回给前端进行展示使用。
}
private ProductDetailVo assembleProductDetailVo(Product product){
private ProductDetailVo assembleProductDetailVo(Product product) {
ProductDetailVo productDetailVo = new ProductDetailVo();
// 创建一个新的ProductDetailVo对象ProductDetailVo是用于在前端展示商品详细信息时使用的视图对象
// 它相比ProductListVo包含了更丰富、更全面的商品属性信息旨在为用户提供查看商品所有重要细节的功能同样通过将Product实体对象转换为这个视图对象
// 可以对数据进行整理和格式转换,使其更符合前端展示详细信息的需求。
productDetailVo.setId(product.getId());
// 将传入的Product实体对象的ID属性赋值给ProductDetailVo对象的ID属性商品ID用于唯一标识商品在展示商品详细信息时
// 依然需要展示这个关键标识,方便用户在查看详情页面与其他相关页面(如列表页返回等操作)进行关联和定位该商品。
productDetailVo.setSubtitle(product.getSubtitle());
// 把Product实体对象中的副标题subtitle属性赋值给ProductDetailVo对象的副标题属性副标题能进一步补充说明商品的特点等信息
// 在详细信息页面展示可以让用户更全面地了解商品情况。
productDetailVo.setMainImage(product.getMainImage());
// 将Product实体对象的主图mainImage属性赋值给ProductDetailVo对象的主图属性主图在商品详情页面也是重要的展示元素
// 方便用户直观地看到商品外观,辅助用户了解商品。
productDetailVo.setSubImages(product.getSubImages());
// 把Product实体对象的子图subImages属性赋值给ProductDetailVo对象的子图属性子图可以从更多角度展示商品的外观、细节等情况
// 在商品详情页面完整展示商品图片信息能够让用户更全面地了解商品的样子,所以需要包含子图信息在视图对象中。
productDetailVo.setPrice(product.getPrice());
// 把Product实体对象的价格price属性传递给ProductDetailVo对象的价格属性价格是用户在查看商品详情时关注的重要信息之一
// 明确商品价格有助于用户决定是否购买该商品。
productDetailVo.setCategoryId(product.getCategoryId());
// 将Product实体对象的商品分类IDcategoryId属性赋值给ProductDetailVo对象的分类ID属性展示商品所属分类信息
// 能让用户了解商品在整个商品体系中的归类情况,同时方便用户通过分类导航查看同类的其他商品等操作。
productDetailVo.setDetail(product.getDetail());
// 把Product实体对象的详细描述detail属性赋值给ProductDetailVo对象的详细描述属性详细描述通常包含了商品的各种详细规格、功能特点、使用说明等内容
// 在商品详情页面展示这些详细信息能够满足用户深入了解商品的需求。
productDetailVo.setName(product.getName());
// 把Product实体对象的商品名称name属性赋值给ProductDetailVo对象的商品名称属性商品名称始终是重要的展示信息
// 用户通过名称来快速识别商品,在详情页面同样需要展示明确的商品名称。
productDetailVo.setStatus(product.getStatus());
// 将Product实体对象的商品状态status属性传递给ProductDetailVo对象的商品状态属性展示商品当前的状态如是否上架等
// 让用户清楚该商品是否可进行购买等操作,提供必要的商品可用性信息。
productDetailVo.setStock(product.getStock());
// 把Product实体对象的库存stock属性赋值给ProductDetailVo对象的库存属性库存信息对于用户来说也是比较关注的内容
// 特别是在一些限量销售或者用户考虑购买数量的场景下,了解商品的库存情况有助于用户做出购买决策。
productDetailVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://image.snail.com/"));
//返回给前端还需要一下该商品所处品类的父品类id所以需要去品类服务中去查询一下这里就要用到Feign
if(categoryClient.getCategoryDetail(product.getCategoryId()).isSuccess()){
// 通过PropertiesUtil工具类获取图片服务器的HTTP前缀地址默认值为 "http://image.snail.com/"并赋值给ProductDetailVo对象的ImageHost属性
// 作用与在ProductListVo中类似是为了与商品的图片路径结合形成完整可访问的图片URL确保前端能够正确展示商品的所有图片。
// 返回给前端还需要一下该商品所处品类的父品类id所以需要去品类服务中去查询一下这里就要用到Feign
if (categoryClient.getCategoryDetail(product.getCategoryId()).isSuccess()) {
ServerResponse response = categoryClient.getCategoryDetail(product.getCategoryId());
Object object = response.getData();
String objStr = JsonUtil.obj2String(object);
Category category = (Category) JsonUtil.Str2Obj(objStr,Category.class);
Category category = (Category) JsonUtil.Str2Obj(objStr, Category.class);
productDetailVo.setParentCategoryId(category.getParentId());
}else {
} else {
productDetailVo.setParentCategoryId(0);
}
// 通过categoryClient通常是基于Feign框架实现的用于调用商品分类服务的客户端的getCategoryDetail方法根据商品的分类ID去查询对应的商品分类详细信息
// 如果查询操作成功通过isSuccess方法判断从返回的ServerResponse对象中获取数据部分先将其转换为字符串通过JsonUtil的obj2String方法
// 再将字符串反序列化为Category类型的对象通过JsonUtil的Str2Obj方法然后获取该分类对象的父品类ID并赋值给ProductDetailVo对象的ParentCategoryId属性
// 这样在前端展示商品详情时就能展示商品所属品类的父品类信息了,方便用户了解商品在分类层级中的位置等情况。
// 如果查询失败将ProductDetailVo对象的ParentCategoryId属性设置为0这里0可能是一个表示默认或者无父品类的约定值保证该属性有一个合理的默认值避免出现空值等异常情况。
productDetailVo.setCreateTime(DateTimeUtil.dateToStr(product.getCreateTime()));
// 使用DateTimeUtil工具类的dateToStr方法将Product实体对象中的创建时间createTime通常是Date类型转换为字符串格式
// 并赋值给ProductDetailVo对象的CreateTime属性将日期类型转换为字符串是为了方便前端直接展示时间信息符合前端展示的格式要求让用户能直观地看到商品的创建时间。
productDetailVo.setUpdateTime(DateTimeUtil.dateToStr(product.getUpdateTime()));
// 同样地通过DateTimeUtil工具类将Product实体对象中的更新时间updateTime通常是Date类型转换为字符串格式
// 赋值给ProductDetailVo对象的UpdateTime属性使得前端能够展示商品信息的最后更新时间方便用户了解商品信息的时效性。
return productDetailVo;
// 最后返回组装好的ProductDetailVo对象这个对象包含了丰富且适合前端展示商品详细信息的各种属性可供后续在业务逻辑中返回给前端进行商品详情展示使用。
}
@Override
public ServerResponse preInitProductStcokToRedis() {
// 通过productMapper数据持久层接口通常基于MyBatis等框架实现用于与数据库中的商品表进行交互的selectList方法
// 从数据库中查询获取所有的商品记录列表这个列表包含了系统中所有商品的相关信息以Product实体对象形式存在后续将基于这个列表来筛选并缓存商品的库存信息到Redis中。
List<Product> productList = productMapper.selectList();
for(Product product:productList){
for (Product product : productList) {
Integer productId = product.getId();
Integer stock = product.getStock();
if(productId != null && stock != null && product.getStatus().equals(Constants.Product.PRODUCT_ON)){
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_STOCK_PREFIX+String.valueOf(productId),String.valueOf(stock),Constants.PRODUCT_EXPIRE_TIME);
// 遍历从数据库获取到的商品列表对于每一个商品对象获取其商品IDproductId和库存stock属性值
// 商品ID用于唯一标识商品是后续在Redis缓存中构建缓存键以及关联商品相关信息的关键库存信息则是要缓存到Redis中的核心数据内容方便后续业务快速获取商品库存情况。
if (productId!= null && stock!= null && product.getStatus().equals(Constants.Product.PRODUCT_ON)) {
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_STOCK_PREFIX + String.valueOf(productId), String.valueOf(stock), Constants.PRODUCT_EXPIRE_TIME);
}
// 对每个商品进行条件判断如果商品ID不为null库存也不为null并且商品的状态通过Constants.Product.PRODUCT_ON常量来判断该常量应该表示商品处于上架等可用状态符合要求
// 说明这个商品的库存信息是有效的、需要缓存到Redis中的。此时调用commonCacheUtil这是一个用于操作Redis缓存的工具类实例的cacheNxExpire方法
// 以特定的缓存键由Constants.PRODUCT_TOKEN_STOCK_PREFIX与商品ID的字符串形式拼接而成Constants.PRODUCT_TOKEN_STOCK_PREFIX应该是一个预定义的用于标识商品库存缓存键前缀的常量字符串作为键
// 将库存值转换为字符串形式通过String.valueOf方法作为缓存的值同时设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME这也是一个预定义的表示缓存有效时长的常量
// 这样就把商品的库存信息缓存到了Redis中并且在过期时间后缓存会自动失效避免缓存数据长期占用内存且过时的问题方便后续业务场景如商品销售、库存查询等操作能快速从缓存获取准确的库存信息提高系统性能。
}
return ServerResponse.createBySuccessMessage("预置库存成功");
// 当完成对所有符合条件商品的库存信息缓存操作后返回一个包含成功提示信息“预置库存成功”的ServerResponse对象
// 告知调用者可能是其他业务模块或者系统管理相关的操作触发了这个方法调用库存信息预置到Redis缓存的操作已顺利完成方便调用者根据这个返回结果进行相应的后续处理如记录日志、提示用户操作成功等
}
@Override
public ServerResponse preInitProductListToRedis() {
// 同样先通过productMapper的selectList方法从数据库中获取所有的商品记录列表这个列表包含了系统中全部商品的详细信息
// 后续将基于这个列表来筛选并缓存商品的详细信息以Product实体对象形式表示的各种属性信息到Redis中方便后续业务场景快速获取商品的完整信息减少数据库查询压力提升系统响应速度。
List<Product> productList = productMapper.selectList();
for(Product product:productList){
for (Product product : productList) {
Integer productId = product.getId();
if(productId != null && product.getStatus().equals(Constants.Product.PRODUCT_ON)){
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX+String.valueOf(productId),JsonUtil.obj2String(product),Constants.PRODUCT_EXPIRE_TIME);
// 遍历商品列表获取每个商品的商品IDproductId商品ID用于唯一标识商品在缓存操作中是构建缓存键以及关联商品对应信息的关键依据
// 通过它可以准确地在Redis中定位和存储、获取特定商品的详细信息。
if (productId!= null && product.getStatus().equals(Constants.Product.PRODUCT_ON)) {
commonCacheUtil.cacheNxExpire(Constants.PRODUCT_TOKEN_PREFIX + String.valueOf(productId), JsonUtil.obj2String(product), Constants.PRODUCT_EXPIRE_TIME);
}
// 对每个商品进行条件判断如果商品ID不为null并且商品的状态依据Constants.Product.PRODUCT_ON常量判断是否处于可用状态符合要求
// 说明这个商品的详细信息是需要缓存到Redis中的。此时调用commonCacheUtil的cacheNxExpire方法
// 以特定的缓存键由Constants.PRODUCT_TOKEN_PREFIX与商品ID的字符串形式拼接而成Constants.PRODUCT_TOKEN_PREFIX是用于标识商品相关缓存键前缀的常量字符串作为键
// 将整个商品对象转换为字符串形式通过JsonUtil的obj2String方法可能是将Java对象序列化为JSON字符串形式方便存储在缓存中作为缓存的值
// 同时设置缓存的过期时间为Constants.PRODUCT_EXPIRE_TIME以此将商品的详细信息缓存到Redis中确保缓存数据在一定时间后会自动失效维持缓存数据的时效性和内存占用的合理性
// 方便后续如商品详情查询等业务操作能优先从缓存获取信息,提升系统的整体性能和响应效率。
}
return ServerResponse.createBySuccessMessage("预置商品信息成功");
// 当完成对所有符合条件商品的详细信息缓存操作后返回一个包含成功提示信息“预置商品信息成功”的ServerResponse对象
// 告知调用者例如系统初始化过程中触发此操作或者管理员手动执行预置信息操作等情况商品详细信息预置到Redis缓存的操作已成功完成
// 调用者可以根据这个返回结果进行相应的后续处理(如记录日志、展示操作成功提示给用户等),保证业务流程的完整性以及对操作结果的合理反馈。
}

Loading…
Cancel
Save