p2 #11

Merged
pb4nq52pf merged 3 commits from branch_ltt into feature/cx 10 months ago

@ -12,44 +12,61 @@ import redis.clients.jedis.JedisPool;
* @Date 2019/1/1 15:03
* @CONTACT 317758022@qq.com
* @DESC
* Redis
*
* JedisPoolWrapperJedis
*/
@Component
@Slf4j
public class CommonCacheUtil {
// 自动注入JedisPoolWrapper用于获取Jedis连接池通过它来操作Redis
@Autowired
private JedisPoolWrapper jedisPoolWrapper;
/**
* key
* Redis
* SnailmallExceptionRedis
* @param key
* @param value
*/
public void cache(String key, String value) {
try {
// 获取Jedis连接池
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
Jedis.set(key, value);
if (pool!= null) {
// 从连接池中获取Jedis资源使用try-with-resources语句自动关闭资源
try (Jedis jedis = pool.getResource()) {
// 选择Redis的第0个数据库通常可以根据实际情况选择不同的数据库
jedis.select(0);
// 将键值对存入Redis
jedis.set(key, value);
}
}
} catch (Exception e) {
// 记录Redis存值失败的错误日志
log.error("redis存值失败", e);
// 抛出SnailmallException异常表示Redis报错
throw new SnailmallException("redis报错");
}
}
/**
* key
* Redis
* SnailmallExceptionRedisnull
* @param key
* @return null
*/
public String getCacheValue(String key) {
String value = null;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
value = Jedis.get(key);
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
value = jedis.get(key);
}
}
} catch (Exception e) {
@ -61,12 +78,19 @@ public class CommonCacheUtil {
/**
* key
* Redis
* 使SETNX
* SnailmallExceptionRedisSETNX
* @param key
* @param value
* @param expire
* @return SETNX10
*/
public long cacheNxExpire(String key, String value, int expire) {
long result = 0;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
result = jedis.setnx(key, value);
@ -83,10 +107,13 @@ public class CommonCacheUtil {
/**
* key
* Redis
* SnailmallExceptionRedis
* @param key
*/
public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
@ -98,7 +125,4 @@ public class CommonCacheUtil {
}
}
}
}
}

@ -13,31 +13,65 @@ import javax.annotation.PostConstruct;
* @Author swg.
* @Date 2019/1/1 15:00
* @CONTACT 317758022@qq.com
* @DESC redisredishash
* @DESC JedisPoolRedis
* redishash
* SpringJedisPool使
*/
@Component
@Slf4j
public class JedisPoolWrapper {
// 通过Spring的依赖注入机制自动注入Parameters对象。
// Parameters类应该是用于存放各类配置参数的在这里主要用于获取与Redis连接相关的配置信息。
@Autowired
private Parameters parameters;
// 用于保存JedisPool实例初始化为null会在后续的初始化方法中根据配置情况进行实例化。
// 这个连接池对象是与Redis服务器建立连接、获取Jedis客户端实例的关键所在。
private JedisPool jedisPool = null;
/**
* @PostConstruct
* JedisPoolJedisPool
* 便
*/
@PostConstruct
public void init(){
public void init() {
try {
// 创建JedisPoolConfig对象它用于配置JedisPool连接池的各种行为和参数限制。
JedisPoolConfig config = new JedisPoolConfig();
// 设置JedisPool连接池允许创建的最大连接数量。
// 通过从注入的Parameters对象中获取对应的配置参数值来进行设置确保连接池的规模符合预期配置。
config.setMaxTotal(parameters.getRedisMaxTotal());
// 设置JedisPool连接池中最大的空闲连接数量。
// 空闲连接可以被重复利用合理设置该值有助于提高资源利用效率同样从Parameters获取配置值进行设置。
config.setMaxIdle(parameters.getRedisMaxIdle());
// 设置当从连接池中获取连接时,如果没有可用连接,最长等待的时间(单位为毫秒)。
// 防止因为长时间等待连接导致应用程序阻塞这里也是依据Parameters中的配置来设定。
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx");
// 使用配置好的JedisPoolConfig对象以及从Parameters获取的Redis服务器主机地址、端口号、超时时间这里设置为2000毫秒和密码此处示例密码为"xxx"实际应替换为真实密码等信息创建JedisPool实例。
// 这个实例将作为后续与Redis服务器进行交互的基础通过它可以获取Jedis客户端来执行各种Redis操作。
jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx");
// 如果初始化过程顺利记录一条信息日志表明Redis连接池初始化成功方便在运行时查看初始化状态。
log.info("【初始化redis连接池成功】");
}catch (Exception e){
log.error("【初始化redis连接池失败】",e);
} catch (Exception e) {
// 如果在初始化JedisPool连接池过程中出现异常记录一条错误日志包含详细的异常信息通过e参数传递异常堆栈等内容
// 这样在排查问题时可以快速定位到是在初始化连接池时出现的错误,并根据异常详情分析具体原因。
log.error("【初始化redis连接池失败】", e);
}
}
/**
* JedisPool
* RedisJedisPoolJedisRedis
* @return JedisPooljedisPoolnullnull
*/
public JedisPool getJedisPool() {
return jedisPool;
}
}
}

@ -11,23 +11,44 @@ import java.util.List;
* @Date 2019/1/1 14:27
* @CONTACT 317758022@qq.com
* @DESC
* ParametersSpring@Valueapplication.propertiesapplication.yml
* Spring@Component便使
* 使Lombok@DataGetterSettertoString便
*/
@Component
@Data
public class Parameters {
/**
* Redis
* Redis@Value
*/
/*****redis config start*******/
// 从配置文件中读取Redis服务器的主机地址配置对应的配置文件中的属性键为"redis.host"
@Value("${redis.host}")
private String redisHost;
// 从配置文件中读取Redis服务器的端口号配置对应的配置文件中的属性键为"redis.port",会被自动解析并赋值为整数类型
@Value("${redis.port}")
private int redisPort;
// 此处变量名可能存在混淆按照语义理解应该是获取Redis连接池的最大空闲连接数但变量名写的是redisMaxTotal可能需要确认是否准确。
// 从配置文件中读取对应配置,配置文件中的属性键为"redis.max-idle",赋值为整数类型
@Value("${redis.max-idle}")
private int redisMaxTotal;
// 此处同样变量名可能存在混淆按照语义理解应该是获取Redis连接池的最大连接数但变量名写的是redisMaxIdle可能需要核对准确性。
// 从配置文件中读取对应配置,配置文件中的属性键为"redis.max-total",赋值为整数类型
@Value("${redis.max-total}")
private int redisMaxIdle;
// 从配置文件中读取获取Redis连接时的最大等待时间单位为毫秒配置对应的配置文件中的属性键为"redis.max-wait-millis",赋值为整数类型
@Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis;
/*****redis config end*******/
// 用于存放不需要安全验证的管理员路径列表配置从配置文件中读取以逗号分隔的字符串并通过split方法解析为List<String>类型。
// 配置文件中对应的属性键为"security.noneSecurityAdminPaths"
@Value("#{'${security.noneSecurityAdminPaths}'.split(',')}")
private List<String> noneSecurityAdminPaths;
}
}

@ -9,24 +9,44 @@ import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
/**
*
* C - Cross O - Origin R - Resource S - Sharing
* 访
* CORS"Cross-Origin Resource Sharing"
* 使SpringSpringBeanSpring使
*/
@Configuration
public class CorsConfig {
/**
* @BeanBeanSpringCorsFilter
* CorsFilter访
*/
@Bean
public CorsFilter corsFilter() {
// 创建一个基于URL的CorsConfigurationSource对象它用于根据不同的URL路径来配置对应的CorsConfiguration跨域配置
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 创建一个CorsConfiguration对象用于具体设置跨域相关的各项配置参数比如允许的源、请求头、请求方法等。
final CorsConfiguration config = new CorsConfiguration();
// 设置是否允许跨域请求中包含认证信息如Cookie等这里设置为true表示允许携带认证信息进行跨域访问。
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("*")); //http:www.a.com
// 设置允许的跨域请求源Origin这里使用Arrays.asList("*")表示允许所有来源的请求进行跨域访问。
// 在实际更严格的场景下通常应该明确指定具体的域名等来源例如Arrays.asList("http://www.a.com")只允许来自该域名的跨域请求。
config.setAllowedOrigins(Arrays.asList("*"));
// 设置允许的请求头信息使用Arrays.asList("*")表示允许所有类型的请求头进行跨域请求,不过在实际应用中,也可以根据需求明确列出允许的具体请求头列表。
config.setAllowedHeaders(Arrays.asList("*"));
// 设置允许的请求方法如GET、POST、PUT等同样通过Arrays.asList("*")表示允许所有的请求方法进行跨域访问,也可以按需具体指定。
config.setAllowedMethods(Arrays.asList("*"));
// 设置预检请求OPTIONS请求的有效期单位为秒这里设置为300秒表示在这个时间范围内相同配置的跨域请求不需要再次发送预检请求。
config.setMaxAge(300l);
// 将配置好的CorsConfiguration应用到所有路径"/**"表示匹配所有的URL路径也就是对整个应用的所有接口都应用此跨域配置。
source.registerCorsConfiguration("/**", config);
// 创建并返回CorsFilter对象这个对象会在Spring Web应用中对进入的请求进行过滤根据配置好的跨域规则来处理跨域相关的逻辑。
return new CorsFilter(source);
}
}
}

@ -5,42 +5,75 @@ package com.njupt.swg.constants;
* @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com
* @DESC
* Constants
* 便
*/
public class Constants {
/**
*
* 便使
*/
/**自定义状态码 start**/
// 表示请求成功的状态码对应HTTP状态码中的200表示操作顺利完成符合预期。
public static final int RESP_STATUS_OK = 200;
// 表示未授权的状态码对应HTTP状态码中的401通常用于用户在没有提供有效认证信息如未登录或者权限不足等情况访问受保护资源时返回该状态码。
public static final int RESP_STATUS_NOAUTH = 401;
// 表示服务器内部错误的状态码对应HTTP状态码中的500意味着服务器在处理请求过程中出现了不可预期的错误情况。
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
// 表示请求参数错误的状态码对应HTTP状态码中的400常用于客户端发送的请求参数不符合要求、格式错误等场景。
public static final int RESP_STATUS_BADREQUEST = 400;
/**自定义状态码 end**/
/**
* RediskeyRedis便
*/
/***redis user相关的key以这个打头**/
public static final String TOKEN_PREFIX = "user_";
/**
* Redis
* 使便
*/
/**
* redis
*/
public interface RedisCacheExtime{
int REDIS_SESSION_EXTIME = 60* 30;//30分钟
public interface RedisCacheExtime {
// 定义用户登录信息在Redis中的缓存过期时间为30分钟这里通过计算将分钟换算成秒60秒 * 30分钟进行表示。
int REDIS_SESSION_EXTIME = 60 * 30; // 30分钟
}
/**
*
* 使
*/
/** 用户注册判断重复的参数类型 start **/
// 表示用于判断用户注册时邮箱是否重复的参数类型对应的字符串常量,方便在相关验证逻辑中统一使用该标识。
public static final String EMAIL = "email";
// 表示用于判断用户注册时用户名是否重复的参数类型对应的字符串常量,同样便于在用户名重复性验证逻辑中统一引用。
public static final String USERNAME = "username";
/** 用户注册判断重复的参数类型 end **/
/**
*
* 便使
*/
/** 用户角色 **/
public interface Role{
int ROLE_CUSTOME = 0;//普通用户
int ROLE_ADMIN = 1;//管理员用户
public interface Role {
// 表示普通用户角色对应的整数值为0用于在系统中标识具有普通权限的用户类型。
int ROLE_CUSTOME = 0; // 普通用户
// 表示管理员用户角色对应的整数值为1用于区分具有管理权限、可进行系统管理操作的用户类型。
int ROLE_ADMIN = 1; // 管理员用户
}
/**
*
*/
/**用户注册分布式锁路径***/
public static final String USER_REGISTER_DISTRIBUTE_LOCK_PATH = "/user_reg";
}
}

@ -11,17 +11,31 @@ import org.springframework.web.bind.annotation.RestController;
* @Date 2019/1/2 21:21
* @CONTACT 317758022@qq.com
* @DESC
* ErrorHandleControllerSpring MVC
* Spring BootErrorController
* 使@RestControllerJSON
*/
@RestController
public class ErrorHandleController implements ErrorController {
/**
* ErrorController
* "/error"
* @return "/error"
*/
@Override
public String getErrorPath() {
return "/error";
}
/**
* 使@RequestMapping"/error"访"/error"
* ServerResponse
* ServerResponsecreateByErrorCodeMessage使ResponseEnum
* @return ServerResponse
*/
@RequestMapping("/error")
public ServerResponse error() {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆或者权限不足");
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆或者权限不足");
}
}
}

@ -12,32 +12,44 @@ import java.util.Date;
* @Author swg.
* @Date 2018/12/31 21:01
* @CONTACT 317758022@qq.com
* @DESC
* @DESC
* 便
* Serializable使便
* 使Lombok
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Serializable {
// 用户的唯一标识符,通常对应数据库表中的主键字段,用于区分不同的用户个体。
private Integer id;
// 用户登录时使用的用户名,具有唯一性,方便用户进行身份认证和在系统中标识自己。
private String username;
// 用户登录时使用的密码,用于验证用户身份的重要信息,需要进行妥善的加密存储和安全处理。
private String password;
// 用户的电子邮箱地址,可用于账号找回、信息通知等功能,也需要保证其唯一性以及格式的有效性。
private String email;
// 用户的手机号码,同样可用于多种业务场景,比如短信验证、联系用户等,格式上需要符合手机号码的规范要求。
private String phone;
// 用户设置的密保问题,用于在忘记密码等情况下辅助验证用户身份,增加账号的安全性。
private String question;
// 用户针对密保问题设置的答案与question属性配合使用用于验证用户身份的真实性。
private String answer;
//角色0-管理员,1-普通用户
// 用于标识用户在系统中的角色0表示管理员角色拥有较高的系统管理权限1表示普通用户角色权限相对受限只能进行普通的业务操作。
private Integer role;
// 用户账号创建的时间,记录了用户首次注册进入系统的时间节点,通常由系统自动生成并赋值。
private Date createTime;
// 用户信息最近一次更新的时间,每当用户修改个人信息等操作后,该时间会相应更新,便于跟踪用户信息的变更情况。
private Date updateTime;
}

@ -8,18 +8,36 @@ import lombok.Getter;
* @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com
* @DESC
* SnailmallExceptionJavaRuntimeException
* 使便
* 使Lombok@GetterexceptionStatusGetter便
*/
@Getter
public class SnailmallException extends RuntimeException{
public class SnailmallException extends RuntimeException {
// 用于存储异常对应的状态码默认初始化为ResponseEnum.ERROR.getCode(),也就是使用预定义的错误状态码值,不过可以通过构造方法进行重新赋值。
private int exceptionStatus = ResponseEnum.ERROR.getCode();
public SnailmallException(String msg){
/**
* msgSnailmallException
* msgRuntimeException便
* 使
* @param msg
*/
public SnailmallException(String msg) {
super(msg);
}
public SnailmallException(int code,String msg){
/**
* codemsg
* code便
* msgRuntimeException
*
* @param code
* @param msg
*/
public SnailmallException(int code, String msg) {
super(msg);
exceptionStatus = code;
}
}
}

@ -26,43 +26,67 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst
* @Author swg.
* @Date 2019/1/3 10:21
* @CONTACT 317758022@qq.com
* @DESC controller
* userID,controller
* @DESC controller
* userIDcontroller
* AdminUserFilterZuulFilterZuul
* Spring@ComponentSpring便使@Slf4j便
*/
@Slf4j
@Component
public class AdminUserFilter extends ZuulFilter {
// 自动注入CommonCacheUtil用于从缓存如Redis中获取相关数据比如用户信息等辅助进行权限校验等操作。
@Autowired
private CommonCacheUtil commonCacheUtil;
// 自动注入Parameters对象用于获取应用程序的配置参数例如获取不需要进行安全校验的路径等配置信息。
@Autowired
private Parameters parameters;
/**
* PRE_TYPE
* @return PRE_TYPE
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
*
* PRE_DECORATION_FILTER_ORDER1便
* @return
*/
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER-1;
return PRE_DECORATION_FILTER_ORDER - 1;
}
/**
*
* HttpServletRequestURL
* URLURL"manage"
* URL"upload"
* URL
* true
* @return truefalse
*/
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//获取当前请求的url
// 获取当前请求的url
String url = request.getRequestURI();
//前端的路径不在这里校验,直接放过
if (!url.contains("manage")){
log.info("【{}不需要进行权限校验】",url);
// 前端的路径不在这里校验,直接放过
if (!url.contains("manage")) {
log.info("【{}不需要进行权限校验】", url);
return false;
}
if(url.contains("upload")){
log.info("【{}不需要进行权限校验】",url);
if (url.contains("upload")) {
log.info("【{}不需要进行权限校验】", url);
return false;
}
//从配置文件获取所有门户需要校验的路径
// 从配置文件获取所有门户需要校验的路径
// String[] passUrls = parameters.getNoneSecurityAdminPaths().toArray(new String[parameters.getNoneSecurityAdminPaths().size()]);
// for(String str:passUrls){
// //指定的路径比较特殊,也不在这里校验
@ -71,47 +95,57 @@ public class AdminUserFilter extends ZuulFilter {
// return false;
// }
// }
log.info("【{}----需要进行权限校验,必须是管理员身份才可以进入】",url);
log.info("【{}----需要进行权限校验,必须是管理员身份才可以进入】", url);
return true;
}
/**
* shouldFiltertrue
* HttpServletRequest
* CookieloginToken
* Redis
* URL"manage"
* null
* @return ServerResponsenullZuulException
* @throws ZuulException Zuul
*/
@Override
public ServerResponse run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//校验是否为管理员身份
// 校验是否为管理员身份
String loginToken = CookieUtil.readLoginToken(request);
log.info("【获取cookie----{}】",loginToken);
if(StringUtils.isEmpty(loginToken)){
log.info("【获取cookie----{}】", loginToken);
if (StringUtils.isEmpty(loginToken)) {
// // 过滤该请求,不对其进行路由
// requestContext.setSendZuulResponse(false);
// //返回错误代码
// requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH);
// return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
this.returnMsg(requestContext);
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
//throw new SnailmallException("用户未登录,无法获取当前用户信息");
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息");
// throw new SnailmallException("用户未登录,无法获取当前用户信息");
}
//2.从redis中获取用户信息
// 2.从redis中获取用户信息
String userStr = commonCacheUtil.getCacheValue(loginToken);
log.info("【从redis中获取用户信息:{}】",userStr);
if(userStr == null){
log.info("【从redis中获取用户信息:{}】", userStr);
if (userStr == null) {
// // 过滤该请求,不对其进行路由
// requestContext.setSendZuulResponse(false);
// //返回错误代码
// requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH);
// return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息"); //SnailmallException(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
this.returnMsg(requestContext);
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息");
}
String url = request.getRequestURI();
log.info("【获取当前url:{}】",url);
if(url.contains("manage")){
log.info("【获取当前url:{}】", url);
if (url.contains("manage")) {
log.info("【来到了管理后台,需要校验权限】");
User currentUser = JsonUtil.Str2Obj(userStr,User.class);
log.info("【当前登陆的用户为:{}】",currentUser);
if(!currentUser.getRole().equals(Constants.Role.ROLE_ADMIN)){
//不是管理员报错
User currentUser = JsonUtil.Str2Obj(userStr, User.class);
log.info("【当前登陆的用户为:{}】", currentUser);
if (!currentUser.getRole().equals(Constants.Role.ROLE_ADMIN)) {
// 不是管理员报错
log.error("【当前登陆用户不是管理员身份】");
// 过滤该请求,不对其进行路由
// requestContext.setSendZuulResponse(false);
@ -119,18 +153,25 @@ public class AdminUserFilter extends ZuulFilter {
// requestContext.setResponseStatusCode(Constants.RESP_STATUS_NOAUTH);
// return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
this.returnMsg(requestContext);
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息");
//throw new SnailmallException("用户权限不够");
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息");
// throw new SnailmallException("用户权限不够");
}
}
return null;
}
//返回没权限消息
private void returnMsg(RequestContext ctx){
/**
*
* JSON"application/json; charset=utf-8"Zuul
* Constants.RESP_STATUS_OK
* ServerResponseJSON便
* @param ctx
*/
// 返回没权限消息
private void returnMsg(RequestContext ctx) {
ctx.getResponse().setContentType("application/json; charset=utf-8");
ctx.setSendZuulResponse(false); //令zuul过滤该请求不对其进行路由
ctx.setSendZuulResponse(false); // 令zuul过滤该请求不对其进行路由
ctx.setResponseStatusCode(Constants.RESP_STATUS_OK);
ctx.setResponseBody(JsonUtil.obj2String(ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登录,无法获取当前用户信息")));
ctx.setResponseBody(JsonUtil.obj2String(ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登录,无法获取当前用户信息")));
}
}
}

@ -15,38 +15,64 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst
* @Author swg.
* @Date 2019/1/3 11:21
* @CONTACT 317758022@qq.com
* @DESC
* @DESC RateLimitFilterZuulFilterZuul
* Spring@Component便Spring
*/
@Component
public class RateLimitFilter extends ZuulFilter {
//放100个令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
// 使用Google Guava库中的RateLimiter来创建一个令牌桶这里设置令牌桶的初始令牌数量为100个意味着一开始桶里有100个令牌可供请求获取用于控制请求速率。
// RateLimiter是基于令牌桶算法实现的一个限流器通过控制令牌的发放和获取来限制请求的频率。
private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
/**
* PRE_TYPE
*
* @return PRE_TYPE
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
*
* SERVLET_DETECTION_FILTER_ORDER1便
* @return
*/
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER - 1;
}
/**
* true
* Zuulrun
* @return truefalsetrue
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* shouldFiltertrue
* HttpServletRequestRATE_LIMITER
* RATE_LIMITER.tryAcquire()true
* RATE_LIMITER.tryAcquire()false
*
* @return nullnull
* @throws ZuulException Zuul
*/
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
if(!RATE_LIMITER.tryAcquire()){
//没有取到一个令牌的话,可以这样返回信息给前端
context.set("状态码",401);
context.set("error.message","用户没有获取到令牌");
if (!RATE_LIMITER.tryAcquire()) {
// 没有取到一个令牌的话,可以这样返回信息给前端(当前只是简单设置了上下文信息,还需完善后续返回给前端的完整逻辑)
context.set("状态码", 401);
context.set("error.message", "用户没有获取到令牌");
}
return null;
}
}
}

@ -6,20 +6,39 @@ import lombok.Getter;
* @Author swg.
* @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ResponseEnum
* 使便
* 使Lombok@GettercodedescGetter便
*/
@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;
}
}
}

@ -10,66 +10,156 @@ import java.io.Serializable;
* @Author swg.
* @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ServerResponse
* 便
* Serializable使便
* 使Lombok@GetterGetter使JacksonJSONnull
*/
@Getter
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ServerResponse<T> implements Serializable {
// 用于存储响应的状态码,通过不同的状态码来告知客户端请求的处理结果情况,例如成功、失败以及具体的错误类型等。
private int status;
// 用于存储响应的提示消息,是对状态码含义的一种文字描述,方便客户端直观地了解请求处理的相关情况,比如操作成功的提示或者错误原因说明等。
private String msg;
// 用于存储具体的业务数据其类型是泛型参数T意味着可以根据不同的业务场景返回不同类型的数据比如可以是一个用户对象、商品列表等具体的业务相关实体数据。
private T data;
private ServerResponse(int status){
/**
* ServerResponse使
*
* @param status
*/
private ServerResponse(int status) {
this.status = status;
}
private ServerResponse(int status,String msg){
/**
* ServerResponse
* 使
* @param status
* @param msg
*/
private ServerResponse(int status, String msg) {
this.status = status;
this.msg = msg;
}
private ServerResponse(int status,T data){
/**
* ServerResponse
*
* @param status
* @param data T
*/
private ServerResponse(int status, T data) {
this.status = status;
this.data = data;
}
private ServerResponse(int status,String msg,T data){
/**
* ServerResponse
*
* @param status
* @param msg
* @param data T
*/
private ServerResponse(int status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
/**
* 使@JsonIgnoreJSON
* ServerResponseResponseEnumSUCCESS
* @return truefalse
*/
@JsonIgnore
public boolean isSuccess(){
public boolean isSuccess() {
return this.status == ResponseEnum.SUCCESS.getCode();
}
/**
*
* 便ServerResponse
* ServerResponse使ResponseEnum
*
* @param <T>
* @return ServerResponse
*/
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);
}
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);
public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
}
/**
*
* ServerResponse使ResponseEnum
*
* @param <T>
* @param message
* @return ServerResponse
*/
public static <T>ServerResponse<T> createByError(){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc());
public static <T> ServerResponse<T> createBySuccessMessage(String message) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message);
}
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
/**
* ServerResponse使ResponseEnum
*
* @param <T> data
* @param data T
* @return ServerResponse
*/
public static <T> ServerResponse<T> createBySuccess(T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data);
}
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
/**
* ServerResponse使ResponseEnum
*
* @param <T> data
* @param message
* @param data T
* @return ServerResponse
*/
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
}
/**
* 便ServerResponse便
* ServerResponse使ResponseEnum
*
* @param <T>
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByError() {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
}
/**
* ServerResponse使ResponseEnum
* msg
* @param <T>
* @param msg
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
}
/**
* ServerResponse
* codemsg
* @param <T>
* @param code
* @param msg
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
return new ServerResponse<>(code, msg);
}
}

@ -8,67 +8,109 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* cookie
* @Author
* @Date
* @CONTACT
* @DESC CookieUtilCookieCookieCookieCookie
* 使CookieCookieCookie使@Slf4j便
*/
@Slf4j
public class CookieUtil {
// 定义Cookie的域名用于指定该Cookie在哪个域名下有效这里固定设置为"oursnail.cn"意味着只有访问该域名下的页面时对应的Cookie才会被携带和使用。
private final static String COOKIE_DOMAIN = "oursnail.cn";
private final static String COOKIE_NAME = "snailmall_login_token";
// 定义Cookie的名称用于标识登录相关的Cookie在整个项目中通过这个固定的名称来获取和操作对应的Cookie这里设置为"snailmall_login_token"。
private final static String COOKIE_NAME = "snailmall_login_token";
/**
* cookie
* @param response
* @param token
* Cookie
* CookieHTTP访Cookie使Cookie
* @param response HttpServletResponseCookie
* @param token Cookie
*/
public static void writeLoginToken(HttpServletResponse response,String token){
Cookie ck = new Cookie(COOKIE_NAME,token);
public static void writeLoginToken(HttpServletResponse response, String token) {
// 创建一个新的Cookie对象名称为固定的COOKIE_NAME值为传入的token参数
Cookie ck = new Cookie(COOKIE_NAME, token);
// 设置Cookie的域名使其在指定的域名COOKIE_DOMAIN下有效确保只有访问该域名的页面时浏览器才会发送这个Cookie到服务器端。
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//设值在根目录
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的路径为根目录"/"意味着该Cookie在该域名下的所有页面路径都会被携带方便在整个网站范围内都能使用这个Cookie进行相关操作。
ck.setPath("/");
// 设置Cookie为HttpOnly属性为true这样可以防止通过JavaScript等脚本语言来访问该Cookie避免了可能的脚本攻击风险提高了Cookie的安全性。
ck.setHttpOnly(true);
// 设置Cookie的最大存活时间这里设置为一年以秒为单位进行换算60秒 * 60分钟 * 24小时 * 365天表示该Cookie在客户端浏览器上保存的时长超过这个时间后浏览器会自动删除该Cookie。
// 如果设置为-1则表示永久有效若不设置这个属性maxAge不设置Cookie就不会写入硬盘只会保存在内存中仅在当前页面会话有效关闭页面后就会失效。
ck.setMaxAge(60 * 60 * 24 * 365);
// 记录写入的Cookie的名称和值的日志信息方便在调试或者查看操作记录时了解具体情况。
log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue());
// 将设置好的Cookie添加到响应对象中这样浏览器在接收到响应时就会保存这个Cookie信息。
response.addCookie(ck);
}
/**
* cookie
* @param request
* @return
* HTTPCookie
* CookieCookieCookieCOOKIE_NAMECookienull
* @param request HttpServletRequestCookie
* @return Cookienull
*/
public static String readLoginToken(HttpServletRequest request){
public static String readLoginToken(HttpServletRequest request) {
// 获取请求中的所有Cookie数组如果没有Cookie则返回null
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks){
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if (cks!= null) {
for (Cookie ck : cks) {
// 记录每个Cookie的名称和值的日志信息方便查看请求中携带的Cookie情况
log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 通过比较Cookie的名称与预定义的登录Cookie名称COOKIE_NAME是否相等来判断是否为我们要找的登录Cookie
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 如果找到对应的登录Cookie记录其名称和值的日志信息并返回其值
log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
return ck.getValue();
}
}
}
return null;
}
/**
*
* @param request
* @param response
* Cookie
* CookieCookieCOOKIE_NAMECookie0CookieCookie
* @param request HttpServletRequestCookie
* @param response HttpServletResponseCookie使Cookie
*/
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
// 获取请求中的所有Cookie数组如果没有Cookie则返回null
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks) {
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
if (cks!= null) {
for (Cookie ck : cks) {
// 通过比较Cookie的名称与预定义的登录Cookie名称COOKIE_NAME是否相等来判断是否为我们要删除的登录Cookie
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 设置要删除的Cookie的域名确保与之前设置的域名一致以便准确删除对应的Cookie
ck.setDomain(COOKIE_DOMAIN);
// 设置Cookie的路径为根目录"/"与之前写入时的路径设置保持一致确保删除的是对应的那个Cookie
ck.setPath("/");
ck.setMaxAge(0);//0表示消除此cookie
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的最大存活时间为0表示让浏览器立即删除这个Cookie
ck.setMaxAge(0);
// 记录要删除的Cookie的名称和值的日志信息方便查看操作情况
log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 将设置好要删除状态的Cookie添加到响应对象中浏览器接收到这个响应后就会删除对应的Cookie
response.addCookie(ck);
return;
}
}
}
}
}
}

@ -10,59 +10,99 @@ import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @DESC
* @DESC DateTimeUtilDate便
* Joda-Time便使JavaSimpleDateFormat
*/
public class DateTimeUtil {
//joda-time
//str->Date
//Date->str
// joda-time
// 以下定义了一个常量表示时间格式化的标准格式用于在没有指定特定格式时按照此通用格式进行日期时间的字符串与Date对象之间的转换操作格式为"yyyy-MM-dd HH:mm:ss",符合常见的日期时间展示格式要求。
// str->Date
// Date->str
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static Date strToDate(String dateTimeStr, String formatStr){
/**
* Date
* formatStrDateTimeFormatter使dateTimeStrDateTimeDateTimeJavaDate
* @param dateTimeStr formatStr
* @param formatStr "yyyy-MM-dd HH:mm:ss"
* @return DateJoda-Time
*/
public static Date strToDate(String dateTimeStr, String formatStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date,String formatStr){
if(date == null){
/**
* Date
* DatenullDateDateTimeformatStr
* @param date Datenull
* @param formatStr Date
* @return Datenull
*/
public static String dateToStr(Date date, String formatStr) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(formatStr);
}
//固定好格式
public static Date strToDate(String dateTimeStr){
/**
* "yyyy-MM-dd HH:mm:ss"Date
* STANDARD_FORMATDateTimeFormatter使DateTimeDate
* 便
* @param dateTimeStr "yyyy-MM-dd HH:mm:ss"
* @return DateJoda-Time
*/
public static Date strToDate(String dateTimeStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date){
if(date == null){
/**
* Date"yyyy-MM-dd HH:mm:ss"
* DatenullDateDateTimeSTANDARD_FORMAT
* Date便
* @param date Datenull
* @return Datenull
*/
public static String dateToStr(Date date) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(STANDARD_FORMAT);
}
//Date -> 时间戳
/**
* Date19701100:00:00 UTC
* DatenullnullSimpleDateFormat"yyyy-MM-dd HH:mm:ss"DateDateDate
* 使
* @param date Datenullnull
* @return Datenullnull19701100:00:00 UTC
* @throws ParseException 使SimpleDateFormat
*/
public static Long dateToChuo(Date date) throws ParseException {
if(date == null){
if (date == null) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.parse(String.valueOf(date)).getTime();
}
/**
* SimpleDateFormatDateDate
* maindateToChuo
* @param args 使
* @throws ParseException 使SimpleDateFormatmain
*/
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());
}
}
}

@ -13,114 +13,130 @@ import java.io.IOException;
import java.text.SimpleDateFormat;
/**
* jackson
* @Author
* @Date
* @CONTACT
* @DESC JsonUtilJacksonJackson便JavaJSONJSONJava
* ObjectMapper使@Slf4j
*/
@Slf4j
public class JsonUtil {
// 创建一个ObjectMapper对象它是Jackson库中用于进行JSON序列化和反序列化的核心类后续通过配置它的各种属性来实现特定的序列化和反序列化规则。
private static ObjectMapper objectMapper = new ObjectMapper();
// 静态代码块在类加载时执行用于对ObjectMapper对象进行一系列的配置操作以定制其序列化和反序列化的行为。
static {
//所有字段都列入进行转换
// 设置序列化时包含的字段规则这里设置为JsonSerialize.Inclusion.ALWAYS表示所有字段都列入进行转换即无论字段值是否为null都会包含在序列化生成的JSON字符串中。
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
//取消默认转换timestamp形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
//统一时间的格式
// 取消默认将日期类型转换为时间戳timestamp形式的行为这样在序列化日期对象时会按照后续配置的日期格式进行转换而不是转换为时间戳格式。
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// 忽略在序列化空的Java Bean即没有属性值的对象转JSON字符串时可能出现的错误使得即使对象为空也能尝试进行序列化操作避免抛出异常。
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
// 统一设置时间的格式通过传入一个SimpleDateFormat对象并指定格式为DateTimeUtil类中定义的标准格式"yyyy-MM-dd HH:mm:ss"),使得在序列化和反序列化日期对象时,都按照这个标准格式进行处理。
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
//忽略json存在属性但是java对象不存在属性的错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
// 忽略在反序列化JSON字符串时如果JSON中存在的属性但对应的Java对象不存在该属性的这种错误情况使得即使JSON数据和Java对象结构不完全匹配也能尽量进行反序列化操作避免因属性不匹配而抛出异常。
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
*
* @param obj
* @param <T>
* @return
* JavaJSON
* nullnullString使ObjectMapperJSONIOnull
* @param obj JavaJavaJacksonJacksonGetterSetter
* @param <T> 使
* @return JSONnullnull
*/
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便
* nullnullString使ObjectMapperJSONIOnull
* @param obj JavaJavaJackson
* @param <T>
* @return JSONnullnull
*/
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
* JSONJava
* JSONJavaclazznullnullString使ObjectMapperJSONJavaIOnull
* @param str JSONJacksonJava
* @param clazz JavaClass便ObjectMapperJSONJava
* @param <T> Java使
* @return JavaJSONnullnull
*/
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
* JSONJavaTypeReference
* JSONTypeReferencenullnullTypeReferenceString使ObjectMapperTypeReferenceJSONJavaIOnull
* @param str JSONJackson
* @param typeReference TypeReferenceJava使ObjectMapper
* @param <T> Java
* @return JavaJSONTypeReferencenullnull
*/
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
* collectionClasselementClassesClassJavaType使ObjectMapperJSONJava
* IOnull
* @param str JSONJackson
* @param collectionClass ClassList.classSet.class
* @param elementClasses ClassList<String>List.classString.class
* @param <T> Java
* @return JavaIOnull
*/
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;
}
}
}
}

@ -12,22 +12,29 @@ import redis.clients.jedis.JedisPool;
* @Date 2019/1/1 15:03
* @CONTACT 317758022@qq.com
* @DESC
* CommonCacheUtilRedis
* JedisPoolWrapperJedisPoolJedisRedis使@Slf4jRedisSnailmallException便
* Spring@Component便使11
*/
@Component
@Slf4j
public class CommonCacheUtil {
// 自动注入JedisPoolWrapper对象用于获取JedisPool连接池以此来建立与Redis服务器的连接并获取Jedis客户端实例从而进行后续的缓存操作。
@Autowired
private JedisPoolWrapper jedisPoolWrapper;
/**
* key
* Redis
* JedisPoolWrapperJedisPoolJedisRedis0Jedis.select(0)使JedissetkeyvalueRedis
* SnailmallExceptionRedis
* @param key Redis
* @param value RedisRedis
*/
public void cache(String key, String value) {
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
Jedis.set(key, value);
@ -40,13 +47,17 @@ public class CommonCacheUtil {
}
/**
* key
* Rediskey
* JedisPoolWrapperJedisPoolJedisRedis0使Jedisgetvalue
* SnailmallExceptionRedis
* @param key RedisRedisnull
* @return Redisnull
*/
public String getCacheValue(String key) {
String value = null;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
value = Jedis.get(key);
@ -60,13 +71,19 @@ public class CommonCacheUtil {
}
/**
* key
* Redissetnx
* JedisPoolJedis0使setnx01使expiresetnx
* SnailmallExceptionRedis
* @param key Redissetnx
* @param value RedisRedis
* @param expire RedisRedis
* @return setnx10
*/
public long cacheNxExpire(String key, String value, int expire) {
long result = 0;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
result = jedis.setnx(key, value);
@ -82,11 +99,13 @@ public class CommonCacheUtil {
}
/**
* key
* Rediskey
* JedisPoolJedis0使JedisdelSnailmallExceptionRedis
* @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 +117,4 @@ public class CommonCacheUtil {
}
}
}
}
}

@ -12,31 +12,58 @@ import javax.annotation.PostConstruct;
* @Author swg.
* @Date 2019/1/1 15:00
* @CONTACT 317758022@qq.com
* @DESC redisredishash
* @DESC JedisPoolWrapperJedisPoolJedisRedis
* RedisRedisHash
* Spring@Component使JedisPool使@Slf4j便
*/
@Component
@Slf4j
public class JedisPoolWrapper {
// 自动注入Parameters对象该对象应该包含了Redis相关的配置参数例如Redis服务器的主机地址、端口号以及连接池的一些配置参数最大连接数、最大空闲连接数、最大等待时间等用于后续初始化JedisPool连接池。
@Autowired
private Parameters parameters;
// 用于存储创建好的JedisPool对象初始化为null在init方法中会根据配置参数进行实例化后续通过getJedisPool方法向外提供这个连接池对象供其他代码获取并使用来与Redis服务器建立连接。
private JedisPool jedisPool = null;
/**
* @PostConstruct
* initParametersRedisJedisPoolJedisPoolConfigRedisJedisPool
* 便
*/
@PostConstruct
public void init(){
public void init() {
try {
// 创建一个JedisPoolConfig对象用于配置JedisPool连接池的各种属性比如连接池的最大连接数、最大空闲连接数、最大等待时间等以控制连接池的行为和性能。
JedisPoolConfig config = new JedisPoolConfig();
// 从Parameters对象中获取Redis连接池的最大连接数配置参数并设置到JedisPoolConfig对象中用于限制连接池中总共可以创建的最大连接数量。
config.setMaxTotal(parameters.getRedisMaxTotal());
// 从Parameters对象中获取Redis连接池的最大空闲连接数配置参数并设置到JedisPoolConfig对象中用于限制连接池中处于空闲状态的最大连接数量。
config.setMaxIdle(parameters.getRedisMaxIdle());
// 从Parameters对象中获取获取Redis连接时的最大等待时间单位为毫秒配置参数并设置到JedisPoolConfig对象中用于指定当连接池中的连接都被占用时获取连接最多等待的时间超过这个时间则会抛出异常。
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx");
// 使用配置好的JedisPoolConfig对象以及从Parameters对象中获取的Redis服务器的主机地址parameters.getRedisHost()、端口号parameters.getRedisPort()等信息创建JedisPool对象
// 这里的2000表示连接超时时间单位为毫秒"xxx"表示连接Redis服务器的密码实际应用中应替换为真实的密码通过这个构造方法实例化JedisPool连接池完成初始化工作。
jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx");
// 记录初始化Redis连接池成功的日志信息便于在查看日志时确认连接池是否正常初始化完成。
log.info("【初始化redis连接池成功】");
}catch (Exception e){
log.error("【初始化redis连接池失败】",e);
} catch (Exception e) {
// 如果在初始化过程中出现异常记录初始化Redis连接池失败的错误日志信息并将异常对象e传递进去方便查看详细的异常堆栈信息用于排查初始化失败的原因。
log.error("【初始化redis连接池失败】", e);
}
}
/**
* JedisPool使便JedisRedis
* @return JedisPoolinitjedisPoolnullnull
*/
public JedisPool getJedisPool() {
return jedisPool;
}
}
}

@ -9,25 +9,39 @@ import org.springframework.stereotype.Component;
* @Date 2019/1/1 14:27
* @CONTACT 317758022@qq.com
* @DESC
* Parameters使Spring`@Value``application.properties``application.yml`
* Spring`@Component`便使
* 使Lombok`@Data`GetterSetterToStringEqualsAndHashCode便使
*/
@Component
@Data
public class Parameters {
/*****redis config start*******/
// 使用`@Value("${redis.host}")`注解从配置文件中读取`redis.host`属性对应的值,并将其注入到`redisHost`属性中用于存储Redis服务器的主机地址。
@Value("${redis.host}")
private String redisHost;
// 同样通过`@Value("${redis.port}")`注解从配置文件获取`redis.port`属性的值,注入到`redisPort`属性用于存储Redis服务器的端口号。
@Value("${redis.port}")
private int redisPort;
// 此处注解可能存在书写错误,按照语义推测,应该是`@Value("${redis.max-total}")`用于获取Redis连接池的最大连接数配置参数注入到`redisMaxTotal`属性中。
@Value("${redis.max-idle}")
private int redisMaxTotal;
// 按照正确的语义,`@Value("${redis.max-idle}")`应该是用于获取Redis连接池的最大空闲连接数配置参数注入到`redisMaxIdle`属性,这里的注解与语义上的属性对应可能弄反了,实际使用时需留意修正。
@Value("${redis.max-total}")
private int redisMaxIdle;
// 使用`@Value("${redis.max-wait-millis}")`注解从配置文件读取Redis连接时最大等待时间单位为毫秒的配置参数并注入到`redisMaxWaitMillis`属性中,用于控制获取连接的等待时长限制。
@Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis;
/*****redis config end*******/
/*****curator config start*******/
// 通过`@Value("${zk.host}")`注解从配置文件获取`zk.host`属性对应的值,注入到`zkHost`属性中用于存储Zookeeper服务器的主机地址可能用于与Zookeeper相关的操作配置比如分布式相关的协调等场景
@Value("${zk.host}")
private String zkHost;
/*****curator config end*******/
}
}

@ -10,14 +10,28 @@ import org.springframework.web.bind.annotation.RequestParam;
* @Date 2019/1/5 14:57
* @CONTACT 317758022@qq.com
* @DESC
* ProductClientSpring Cloud OpenFeign"product-service"
* 使@FeignClient"product-service"使@RequestMapping@RequestParamHTTP便
*/
@FeignClient("product-service")
public interface ProductClient {
/**
* "product-service""/product/detail.do"
* @RequestParam("productId")"productId"IntegerIDServerResponse
* @param productId Integer
* @return ServerResponse"product-service"
*/
@RequestMapping("/product/detail.do")
ServerResponse getProductDetail(@RequestParam("productId") Integer productId);
ServerResponse getProductDetail(@RequestParam("productId") Integer productId) ;
/**
* "product-service""/product/queryProduct.do"
* @RequestParam("productId")"productId"IntegerIDServerResponse
* @param productId Integer
* @return ServerResponse"product-service"
*/
@RequestMapping("/product/queryProduct.do")
ServerResponse queryProduct(@RequestParam("productId") Integer productId);
}
}

@ -5,38 +5,58 @@ package com.njupt.swg.common.constants;
* @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com
* @DESC
* Constants便使
* Redis
*/
public class Constants {
/**自定义状态码 start**/
// 表示请求成功的状态码通常在服务端成功处理请求并返回正常结果时使用符合HTTP状态码中200表示成功的语义方便在项目中统一表示成功的响应情况。
public static final int RESP_STATUS_OK = 200;
// 表示未授权用户未登录或者权限不足等情况的状态码对应HTTP状态码中的401用于在需要进行权限校验的场景下当用户没有相应权限访问资源时返回该状态码告知客户端需要进行认证授权操作。
public static final int RESP_STATUS_NOAUTH = 401;
// 表示服务器内部错误的状态码等同于HTTP状态码中的500当服务端在处理请求过程中出现了未预期的错误例如代码运行时异常等情况时返回这个状态码给客户端表示服务端出现了问题需要进一步排查和修复。
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
// 表示请求的语法错误或者参数等不符合要求的状态码对应HTTP状态码中的400常用于客户端发送的请求格式不正确、缺少必要参数等情况提示客户端检查并修正请求内容。
public static final int RESP_STATUS_BADREQUEST = 400;
/**自定义状态码 end**/
public interface Cart{
int CHECKED = 1;//即购物车选中状态
int UN_CHECKED = 0;//购物车中未选中状态
// 以下是一个内部接口Cart用于定义与购物车相关的常量将购物车相关的常量集中在这个内部接口中进行管理使得代码结构更清晰方便对购物车相关逻辑中使用的常量进行统一维护。
public interface Cart {
// 表示购物车中商品处于选中状态的常量值为1在处理购物车中商品的选中与否逻辑时可以通过这个常量来判断和设置商品的选中状态。
int CHECKED = 1;
// 表示购物车中商品处于未选中状态的常量值为0与CHECKED常量相对应用于区分商品在购物车中的不同选中情况。
int UN_CHECKED = 0;
// 表示在购物车中对商品数量限制操作失败时的提示信息常量,其值为"LIMIT_NUM_FAIL",可用于在业务逻辑中当商品数量限制相关操作(比如添加商品数量超过限制等情况)失败时,返回给客户端相应的提示信息。
String LIMIT_NUM_FAIL = "LIMIT_NUM_FAIL";
// 表示在购物车中对商品数量限制操作成功时的提示信息常量,值为"LIMIT_NUM_SUCCESS",用于在商品数量限制相关操作成功后,向客户端反馈相应的成功提示消息。
String LIMIT_NUM_SUCCESS = "LIMIT_NUM_SUCCESS";
}
// 内部接口Product用于定义与产品状态相关的常量将产品不同状态对应的常量集中在此处管理便于在涉及产品状态判断、更新等业务逻辑中统一使用和维护这些常量。
/** 产品的状态 **/
public interface Product{
public interface Product {
// 表示产品处于上架、可销售状态的常量值为1在业务逻辑中可以通过这个常量来判断产品是否可供用户购买等情况。
int PRODUCT_ON = 1;
// 表示产品处于下架状态的常量值为2用于标识产品暂时不可销售例如库存不足、商品调整等原因导致下架时使用该常量来表示其状态。
int PRODUCT_OFF = 2;
// 表示产品已被删除的状态常量值为3当产品从系统中彻底删除后通过这个常量来体现其在业务层面的最终状态方便在数据查询、过滤等操作中基于这个状态进行相关处理。
int PRODUCT_DELETED = 3;
}
/***redis product stock**/
// 用于定义在Redis中存储产品库存相关数据时的键key的前缀字符串常量方便在Redis操作中统一管理和区分不同产品的库存相关数据后续实际的键可以在此前缀基础上添加具体的产品标识等信息来构成完整的键。
public static final String PRODUCT_TOKEN_STOCK_PREFIX = "product__stock_";
// 用于定义在Redis中存储产品相关数据除库存外可能还有其他产品相关属性等情况时的键key的前缀字符串常量同样起到统一管理和区分不同产品数据在Redis中的存储的作用便于根据这个前缀构建完整的Redis键来进行数据的读写操作。
public static final String PRODUCT_TOKEN_PREFIX = "product__";
}
}

@ -1,6 +1,5 @@
package com.njupt.swg.common.exception;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ServerResponse;
import lombok.extern.slf4j.Slf4j;
@ -12,22 +11,35 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @Author swg.
* @Date 2019/1/1 13:21
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ExceptionHandlerAdviceSpring
* 使@ControllerAdvice@ResponseBody使JSON便使@Slf4j便
*/
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionHandlerAdvice {
/**
* 使@ExceptionHandler(Exception.class)Exception
* ServerResponseServerResponsecreateByErrorCodeMessage使Constants.RESP_STATUS_INTERNAL_ERROR"系统异常,请稍后再试"
* @param e Exception
* @return ServerResponse
*/
@ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试");
public ServerResponse handleException(Exception e) {
log.error(e.getMessage(), e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试");
}
/**
* 使@ExceptionHandler(SnailmallException.class)SnailmallException
* SnailmallExceptionServerResponseServerResponsecreateByErrorCodeMessage使SnailmallExceptione.getExceptionStatus()e.getMessage()
* @param e SnailmallException
* @return ServerResponseSnailmallException
*/
@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());
}
}
}

@ -8,18 +8,32 @@ import lombok.Getter;
* @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com
* @DESC
* SnailmallExceptionJavaRuntimeException便
* 使Lombok@GetterexceptionStatusGetter便使
*/
@Getter
public class SnailmallException extends RuntimeException{
public class SnailmallException extends RuntimeException {
// 用于存储该异常对应的状态码默认初始化为ResponseEnum中定义的通用错误状态码ERROR的状态码可以在构造方法中根据具体业务需求重新赋值以此来区分不同类型的业务异常情况便于在统一处理异常时进行针对性的响应。
private int exceptionStatus = ResponseEnum.ERROR.getCode();
public SnailmallException(String msg){
/**
* msgSnailmallExceptionRuntimeExceptionmsg使ResponseEnum.ERROR.getCode()
*
* @param msg 使
*/
public SnailmallException(String msg) {
super(msg);
}
public SnailmallException(int code,String msg){
/**
* codemsgSnailmallExceptionRuntimeExceptionmsgcodeexceptionStatus
* 便
* @param code ResponseEnum
* @param msg 使
*/
public SnailmallException(int code, String msg) {
super(msg);
exceptionStatus = code;
}
}
}

@ -6,20 +6,38 @@ import lombok.Getter;
* @Author swg.
* @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ResponseEnum使便使
* 使Lombok@Gettercodedesc便
*/
@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;
}
}
}

@ -10,68 +10,158 @@ import java.io.Serializable;
* @Author swg.
* @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ServerResponse使便
* Serializable便
* Lombok@Getter@JsonSerializeJSONnull
*/
@Getter
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ServerResponse<T> implements Serializable {
// 用于存储返回结果的状态码通过不同的状态码来标识请求处理的结果情况例如成功、失败以及不同类型的错误等方便客户端根据状态码来判断后续的操作逻辑一般会与项目中定义的状态码枚举如ResponseEnum相对应。
private int status;
// 用于存储返回结果的提示消息,是对状态码含义的文字描述,更直观地向客户端传达请求处理的相关情况,比如操作成功的提示语或者出现错误时的具体原因说明等内容,有助于客户端更好地向用户展示相应的信息。
private String msg;
// 用于存储具体的业务数据其类型为泛型参数T意味着可以根据不同的业务场景返回不同类型的数据例如可以是用户信息对象、商品列表、订单详情等各种与业务相关的实体数据灵活性较高能够满足多样化的业务需求。
private T data;
public ServerResponse(){}
// 默认的无参构造方法主要用于在一些特定情况下如反序列化等创建对象实例虽然当前类中暂时没有复杂的初始化逻辑依赖于这个构造方法但遵循Java Bean规范保留了它。
public ServerResponse() {}
private ServerResponse(int status){
/**
* ServerResponse使
*
* @param status status
*/
private ServerResponse(int status) {
this.status = status;
}
private ServerResponse(int status,String msg){
/**
* ServerResponse
* 使便
* @param status status
* @param msg msg
*/
private ServerResponse(int status, String msg) {
this.status = status;
this.msg = msg;
}
private ServerResponse(int status,T data){
/**
* ServerResponse
* 使
* @param status status
* @param data Tdata
*/
private ServerResponse(int status, T data) {
this.status = status;
this.data = data;
}
private ServerResponse(int status,String msg,T data){
/**
* ServerResponse
*
* @param status status
* @param msg msg
* @param data Tdata
*/
private ServerResponse(int status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
/**
* 使@JsonIgnoreJSON
* ServerResponseResponseEnumSUCCESS便
* @return truefalse
*/
@JsonIgnore
public boolean isSuccess(){
public boolean isSuccess() {
return this.status == ResponseEnum.SUCCESS.getCode();
}
/**
*
* 便ServerResponse使
* ServerResponse使ResponseEnum
*
* @param <T>
* @return ServerResponse
*/
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);
}
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);
public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
}
/**
*
* ServerResponse使ResponseEnum
* 使
* @param <T>
* @param message ServerResponsemsg
* @return ServerResponse
*/
public static <T>ServerResponse<T> createByError(){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc());
public static <T> ServerResponse<T> createBySuccessMessage(String message) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message);
}
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
/**
* ServerResponse使ResponseEnum
* 便
* @param <T> dataServerResponsedata
* @param data T
* @return ServerResponse
*/
public static <T> ServerResponse<T> createBySuccess(T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), data);
}
public static <T>ServerResponse<T> createByErrorCodeMessage(int code,String msg){
return new ServerResponse<>(code,msg);
/**
* ServerResponse使ResponseEnum
* 使
* @param <T> dataServerResponsedata
* @param message ServerResponsemsg
* @param data T
* @return ServerResponse
*/
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
}
/**
* 便ServerResponse便使
* ServerResponse使ResponseEnum
*
* @param <T>
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByError() {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
}
/**
* ServerResponse使ResponseEnum
* msg便
* @param <T>
* @param msg ServerResponsemsg
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
}
/**
* ServerResponse
* codemsg使便
* @param <T>
* @param code ServerResponsestatus
* @param msg ServerResponsemsg
* @return ServerResponse
*/
public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
return new ServerResponse<>(code, msg);
}
}

@ -6,37 +6,67 @@ import java.math.BigDecimal;
* @Author swg.
* @Date 2019/1/5 15:20
* @CONTACT 317758022@qq.com
* @DESC BigDecimal
* @DESC BigDecimalUtildoubleBigDecimal
* 便使
*/
public class BigDecimalUtil {
private BigDecimalUtil(){
// 将构造方法私有化这样外部就不能通过new关键字来创建这个类的实例因为这个类作为工具类只提供静态方法供使用不需要实例化对象来调用方法从而保证了工具类使用方式的规范性。
private BigDecimalUtil() {
}
public static BigDecimal add(double v1, double v2){
/**
* double
* doublev1v2使BigDecimalb1b2BigDecimaladdBigDecimal
* @param v1 double
* @param v2 doublev1
* @return BigDecimalv1v2
*/
public static BigDecimal add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2);
}
public static BigDecimal sub(double v1,double v2){
/**
* double
* doublev1v2BigDecimalb1b2BigDecimalsubtract
* @param v1 double使
* @param v2 doublev1
* @return BigDecimalv1v2
*/
public static BigDecimal sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2);
}
public static BigDecimal mul(double v1,double v2){
/**
* double
* doublev1v2BigDecimalb1b2BigDecimalmultiply
* @param v1 double
* @param v2 doublev1
* @return BigDecimalv1v2
*/
public static BigDecimal mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2);
}
public static BigDecimal div(double v1,double v2){
/**
* double2
* doublev1v2BigDecimalb1b2BigDecimaldivide2ROUND_HALF_UP
* @param v1 double使
* @param v2 double00v12
* @return BigDecimalv1v22
*/
public static BigDecimal div(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);//四舍五入,保留2位小数
return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP); //四舍五入,保留2位小数
//除不尽的情况
}
}
}

@ -8,67 +8,107 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* cookie
* @Author
* @Date
* @CONTACT
* @DESC CookieUtilCookieCookie便Cookie便
*/
@Slf4j
public class CookieUtil {
// 定义了Cookie的域名限定了该Cookie在哪个域名下能够被识别和使用这里设置为"oursnail.cn"意味着只有当访问该域名下的网页时浏览器才会发送对应的Cookie到服务器端以此来确保Cookie的作用范围符合项目要求。
private final static String COOKIE_DOMAIN = "oursnail.cn";
private final static String COOKIE_NAME = "snailmall_login_token";
// 定义了登录相关Cookie的名称在整个项目中通过这个固定的名称来识别和操作对应的登录Cookie这里将其命名为"snailmall_login_token"方便统一管理与登录认证相关的Cookie操作。
private final static String COOKIE_NAME = "snailmall_login_token";
/**
* cookie
* @param response
* @param token
* Cookie
* Cookie访Cookie使Cookie便访Cookie
* @param response HttpServletResponseCookieCookie
* @param token CookieCookie
*/
public static void writeLoginToken(HttpServletResponse response,String token){
Cookie ck = new Cookie(COOKIE_NAME,token);
public static void writeLoginToken(HttpServletResponse response, String token) {
// 创建一个新的Cookie对象其名称为预先定义的登录Cookie名称COOKIE_NAME值为传入的token参数以此构建一个符合项目要求的登录Cookie实例。
Cookie ck = new Cookie(COOKIE_NAME, token);
// 设置Cookie的域名属性使其与预先定义的域名COOKIE_DOMAIN一致这样浏览器就能根据该域名来判断何时发送这个Cookie到服务器确保只有访问指定域名的页面时才会携带该Cookie保障了Cookie的作用范围准确性。
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//设值在根目录
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的路径为根目录"/"这意味着该Cookie在整个域名下的所有页面路径中都有效无论用户在该域名下访问哪个具体页面浏览器都会自动携带这个Cookie发送给服务器方便在项目的各个页面中都能基于这个Cookie进行相关的登录验证等操作。
ck.setPath("/");
// 将Cookie的HttpOnly属性设置为true这样可以防止通过JavaScript等脚本语言在客户端对该Cookie进行访问和操作有效地避免了脚本攻击带来的安全风险提高了Cookie存储用户信息的安全性。
ck.setHttpOnly(true);
// 设置Cookie的最大存活时间这里设置为一年换算成秒即60秒 * 60分钟 * 24小时 * 365天表示该Cookie在客户端浏览器上能够保存的时长超过这个时间后浏览器会自动删除该Cookie。如果设置为 -1 则表示永久有效若不设置该属性maxAge不设置Cookie就只会保存在内存中不会写入硬盘且只在当前页面有效关闭页面后就会失效。
ck.setMaxAge(60 * 60 * 24 * 365);
// 记录要写入的Cookie的名称和值的日志信息方便在查看日志时了解具体的Cookie写入情况对于调试或者跟踪Cookie相关操作具有重要作用比如排查是否正确写入了期望的登录Cookie等问题。
log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue());
// 将设置好的Cookie添加到响应对象中浏览器在接收到服务器的响应时就会根据响应中的这个设置来保存相应的Cookie信息从而完成登录Cookie的写入操作。
response.addCookie(ck);
}
/**
* cookie
* @param request
* @return
* HTTPCookie
* CookieCookieCookieCOOKIE_NAMECookieCookieCookienull
* @param request HttpServletRequestCookie便
* @return CookieCookienull
*/
public static String readLoginToken(HttpServletRequest request){
public static String readLoginToken(HttpServletRequest request) {
// 获取请求中的所有Cookie数组如果请求中没有携带任何Cookie则返回null否则得到一个包含所有Cookie的数组用于后续遍历查找登录Cookie的操作。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks){
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if (cks!= null) {
for (Cookie ck : cks) {
// 记录每个Cookie的名称和值的日志信息有助于在查看日志时了解请求中具体携带了哪些Cookie方便排查与Cookie相关的问题比如确认是否正确接收到了期望的登录Cookie等情况。
log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 通过比较当前Cookie的名称与预先定义的登录Cookie名称COOKIE_NAME是否相等来判断该Cookie是否为我们要找的登录Cookie如果相等则表示找到了对应的登录Cookie。
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 记录找到的登录Cookie的名称和值的详细日志信息方便后续查看具体找到了哪个登录Cookie及其对应的值然后返回该Cookie的值用于后续的业务逻辑处理比如验证用户身份等操作。
log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
return ck.getValue();
}
}
}
return null;
}
/**
*
* @param request
* @param response
* Cookie
* CookieCookieCOOKIE_NAMECookie0CookieCookieCookie
* @param request HttpServletRequestCookie便Cookie
* @param response HttpServletResponseCookieCookieCookie
*/
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
// 获取请求中的所有Cookie数组如果请求中没有携带Cookie则返回null否则得到包含所有Cookie的数组用于后续遍历查找要删除的登录Cookie的操作。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks) {
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
if (cks!= null) {
for (Cookie ck : cks) {
// 通过比较当前Cookie的名称与预先定义的登录Cookie名称COOKIE_NAME是否相等来判断该Cookie是否为我们要删除的登录Cookie如果相等则表示找到了对应的登录Cookie接下来进行删除操作。
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 设置要删除的Cookie的域名属性使其与预先定义的域名COOKIE_DOMAIN一致确保能够准确地删除对应的登录Cookie避免误删其他同名但不同域名下的Cookie情况发生。
ck.setDomain(COOKIE_DOMAIN);
// 设置Cookie的路径为根目录"/"与之前写入该Cookie时设置的路径保持一致这样才能准确地找到并删除对应的登录Cookie确保删除操作的准确性。
ck.setPath("/");
ck.setMaxAge(0);//0表示消除此cookie
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的最大存活时间为0表示让浏览器立即删除这个Cookie从而实现从客户端清除该登录Cookie的目的完成注销时的Cookie删除操作。
ck.setMaxAge(0);
// 记录要删除的Cookie的名称和值的日志信息方便在查看日志时了解具体的Cookie删除情况有助于排查与Cookie删除相关的问题比如确认是否正确地向浏览器发送了删除登录Cookie的指令等情况。
log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 将设置好要删除状态的Cookie添加到响应对象中浏览器在接收到服务器的这个响应后就会根据设置删除对应的Cookie从而完成从客户端删除登录Cookie的操作。
response.addCookie(ck);
return;
}
}
}
}
}
}

@ -4,65 +4,99 @@ import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @DESC
* @DESC DateTimeUtilJoda-TimeJavaDate便使
*/
public class DateTimeUtil {
//joda-time
//str->Date
//Date->str
// 引入Joda-Time库来进行时间相关的操作相比Java原生的日期时间处理方式Joda-Time提供了更方便、灵活且不易出错的接口和功能适用于各种复杂的时间处理场景。
// str->Date 表示可以将表示时间的字符串转换为Date对象Date->str表示能把Date对象转换为对应的表示时间的字符串以下定义了一些相关的方法来实现这些转换功能。
// 定义了一个表示标准时间格式的常量,格式为"yyyy-MM-dd HH:mm:ss",符合常见的日期时间展示格式,在没有特别指定其他格式的情况下,很多时间转换操作会默认使用这个格式,保证时间表示的规范性和统一性。
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static Date strToDate(String dateTimeStr, String formatStr){
/**
* Date
* formatStrDateTimeFormatter使dateTimeStrDateTimeDateTimeJavaDateDate
* @param dateTimeStr formatStrformatStr"yyyy-MM-dd"dateTimeStr
* @param formatStr "yyyy-MM-dd HH:mm:ss""yyyy-MM-dd"DateTimeFormatter
* @return DateJoda-Time
*/
public static Date strToDate(String dateTimeStr, String formatStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date,String formatStr){
if(date == null){
/**
* Date
* DatenullnullDatenullDateDateTimeformatStrDate
* @param date Datenull
* @param formatStr Date"yyyy-MM-dd"--
* @return Datenull
*/
public static String dateToStr(Date date, String formatStr) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(formatStr);
}
//固定好格式
public static Date strToDate(String dateTimeStr){
/**
* "yyyy-MM-dd HH:mm:ss"Date
* STANDARD_FORMATDateTimeFormatter使DateTimeDate便
* @param dateTimeStr "yyyy-MM-dd HH:mm:ss""2024-12-18 10:20:30"
* @return DateJoda-Time
*/
public static Date strToDate(String dateTimeStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date){
if(date == null){
/**
* Date"yyyy-MM-dd HH:mm:ss"
* DatenullDateDateTimeSTANDARD_FORMATDate便
* @param date Datenull
* @return Datenull
*/
public static String dateToStr(Date date) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(STANDARD_FORMAT);
}
//Date -> 时间戳
/**
* Date19701100:00:00 UTC
* DatenullnullSimpleDateFormat"yyyy-MM-dd HH:mm:ss"DateDateDate使
* @param date Datenullnull
* @return Datenullnull19701100:00:00 UTC
* @throws ParseException 使SimpleDateFormat使try-catch
*/
public static Long dateToChuo(Date date) throws ParseException {
if(date == null){
if (date == null) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.parse(String.valueOf(date)).getTime();
}
/**
* SimpleDateFormatDateDate
* maindateToChuo
* @param args 使使
* @throws ParseException 使SimpleDateFormatmain使try-catch
*/
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
String time="1970-01-06 11:45:55";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = "1970-01-06 11:45:55";
Date date = format.parse(time);
System.out.print("Format To times:"+date.getTime());
System.out.print("Format To times:" + date.getTime());
}
}
}

@ -8,119 +8,134 @@ import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import java.io.IOException;
import java.text.SimpleDateFormat;
/**
* jackson
* @Author
* @Date
* @CONTACT
* @DESC JsonUtilJackson
* JacksonObjectMapper使JavaJSONJSONJava便
*/
@Slf4j
public class JsonUtil {
// 创建一个ObjectMapper对象它是Jackson库中核心的对象负责实际执行序列化和反序列化的具体操作后续通过配置它的各种属性来定制序列化和反序列化的行为规则。
private static ObjectMapper objectMapper = new ObjectMapper();
// 静态代码块在类加载时执行用于对ObjectMapper对象进行一系列的配置操作使得它符合项目在序列化和反序列化方面的特定要求。
static {
//所有字段都列入进行转换
// 设置序列化时包含的字段规则这里指定为JsonSerialize.Inclusion.ALWAYS表示在将Java对象序列化为JSON字符串时无论对象的字段值是否为null都会把所有字段都包含进转换后的JSON字符串中确保完整地表示对象的结构和数据。
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
//取消默认转换timestamp形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
//统一时间的格式
// 取消默认将日期类型转换为时间戳timestamp形式的行为。通常情况下Jackson默认会把日期对象转换为时间戳进行序列化通过设置这个配置为false就可以按照后续指定的日期格式进行序列化更符合常见的日期展示需求。
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// 忽略在序列化空的Java Bean即没有属性值或者属性值都为null的对象转JSON字符串时可能出现的错误。这样即使对象为空也能尝试进行序列化操作避免因为空对象而抛出异常使得序列化过程更加健壮。
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
// 统一设置时间的格式通过传入一个SimpleDateFormat对象并指定格式为DateTimeUtil类中定义的标准格式通常是"yyyy-MM-dd HH:mm:ss"这种常见的日期时间格式),使得在序列化和反序列化涉及日期类型的对象时,都按照这个统一的格式进行处理,便于数据的一致性和可读性。
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
//忽略json存在属性但是java对象不存在属性的错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
// 忽略在反序列化JSON字符串时如果JSON中存在的属性但对应的Java对象不存在该属性的这种错误情况。这样即使JSON数据和Java对象结构不完全匹配例如JSON有多余的字段也能尽量进行反序列化操作避免因属性不匹配而直接抛出异常增强了反序列化的兼容性。
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
*
* @param obj
* @param <T>
* @return
* JavaJSON
* nullnullStringJSON使ObjectMapperJSONIOnull
* @param obj JavaJacksonJavaGetterSetter<T>
* @param <T> 使Java
* @return JSONnullnullJSONJSON
*/
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便
* 使JSONnullnullString使ObjectMapperJSONIOnull
* @param obj JavaJacksonJava<T>JSON
* @param <T> 使JSON便使
* @return JSONnullnullJSON
*/
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
* JSONJava
* JSONJavaclazznullnullString使ObjectMapperJSONJavaIOnull
* @param str JSONJacksonJavaJSON
* @param clazz JavaClass便ObjectMapperJSONJava<T>
* @param <T> Java使JSONJava便JSON
* @return JavaJSONnullnullJava
*/
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
* JSONJavaTypeReference
* JSONTypeReferencenullnullTypeReferenceString使ObjectMapperTypeReferenceJSONJavaIOnull
* @param str JSONJacksonJSONJSON
* @param typeReference TypeReferenceJavaList<String>Map<Integer, User>使ObjectMapper<T>Java
* @param <T> Java使JSONJava
* @return JavaJSONTypeReferencenullnullJava
*/
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
* collectionClasselementClassesClassJavaType使ObjectMapperJSONJava
* List<User>JSONList.classUser.classIOnull
* @param str JSONJacksonJSONJSON
* @param collectionClass ClassList.classSet.classMap.class<T>
* @param elementClasses ClassList<User>List.classUser.class便ObjectMapper
* @param <T> Java使便使
* @return JavaIOnullJava
*/
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,9 +3,19 @@ package com.njupt.swg.common.utils;
import java.security.MessageDigest;
/**
* MD5
* @Author
* @Date
* @CONTACT
* @DESC MD5UtilMD5JavaMessageDigestMD5便UTF-8MD5
*/
public class MD5Util {
/**
*
* byteToHexStringMD5便使
* @param b MD5MessageDigestdigestMD5
* @return [10, 20, 30]"0a141e"
*/
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
@ -14,6 +24,12 @@ public class MD5Util {
return resultSb.toString();
}
/**
*
* Java256161644hexDigitsbyteArrayToHexString
* @param b -128127Java10"0a"
* @return 2byteArrayToHexString
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
@ -24,11 +40,11 @@ public class MD5Util {
}
/**
* MD5
*
* @param origin
* @param charsetname
* @return
* MD5使null
* MessageDigest使MD5MessageDigestdigestbyteArrayToHexStringnullMD5
* @param origin MD5MD5
* @param charsetname "utf-8""gbk"null使使UTF-8
* @return MD5MessageDigestnull使
*/
private static String MD5Encode(String origin, String charsetname) {
String resultString = null;
@ -44,16 +60,26 @@ public class MD5Util {
return resultString.toUpperCase();
}
/**
* 便UTF-8MD5
* MD5Encode"utf-8"使UTF-8MD5便MD5Encode便使UTF-8MD5
* @param origin MD5UTF-8
* @return UTF-8MD5null
*/
public static String MD5EncodeUtf8(String origin) {
//这里可以加盐
return MD5Encode(origin, "utf-8");
}
/**
* MD5EncodeUtf8"123456"MD5便MD5
* @param args 使使
*/
public static void main(String[] args) {
System.out.println(MD5EncodeUtf8("123456"));
}
// 预先定义的十六进制数字字符数组用于在将字节转换为十六进制字符串时根据字节对应的十六进制数字索引获取相应的字符表示其中包含了0到9以及a到f的十六进制数字字符按照顺序排列方便在byteToHexString等方法中进行十六进制转换操作。
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}
}

@ -12,35 +12,58 @@ import java.util.Properties;
* @Date 2018/1/10 14:56
* @DESC
* @CONTACT 317758022@qq.com
* PropertiesUtilparameter.properties便便
*/
@Slf4j
public class PropertiesUtil {
// 用于存储从配置文件中读取的属性信息是一个Properties对象它以键值对的形式保存配置文件中的各项配置内容后续通过键来获取对应的值实现对配置信息的访问和使用。
private static Properties props;
// 静态代码块在类加载时执行主要用于加载配置文件parameter.properties到Properties对象中以便后续可以获取其中的配置属性值。
static {
// 指定要加载的配置文件的名称,这里固定为"parameter.properties",意味着该工具类默认会从类路径下查找这个名称的配置文件进行加载操作,如果实际配置文件名称不同或者路径有变化,需要相应地修改此处。
String fileName = "parameter.properties";
// 创建一个新的Properties对象用于后续存放从配置文件中读取的键值对信息它提供了加载配置文件以及获取属性值等相关方法是Java中处理配置文件属性的常用类。
props = new Properties();
try {
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
// 通过类加载器获取指定配置文件fileName的输入流然后使用InputStreamReader将其包装为字符流并指定字符编码为"UTF-8"最后调用Properties对象的load方法将配置文件的内容加载到props对象中完成配置文件的读取操作。
// 如果在这个过程中出现IO异常例如配置文件不存在、无法正确读取等情况则会捕获该异常并通过日志记录错误信息方便后续查看和排查配置文件读取失败的原因。
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName), "UTF-8"));
} catch (IOException e) {
log.error("配置文件读取异常",e);
log.error("配置文件读取异常", e);
}
}
public static String getProperty(String key){
/**
*
* Propertiespropsnullnull便使
* @param key null
* @return null
*/
public static String getProperty(String key) {
String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){
if (StringUtils.isBlank(value)) {
return null;
}
return value.trim();
}
public static String getProperty(String key,String defaultValue){
/**
* getProperty(String key)
* PropertiespropsnulldefaultValuevaluevalue使便使
* @param key 使
* @param defaultValue "DEBUG"
* @return
*/
public static String getProperty(String key, String defaultValue) {
String value = props.getProperty(key.trim());
if(StringUtils.isBlank(value)){
if (StringUtils.isBlank(value)) {
value = defaultValue;
}
return value.trim();
}
}
}

@ -10,7 +10,6 @@ import com.njupt.swg.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
/**
@ -18,23 +17,40 @@ import javax.servlet.http.HttpServletRequest;
* @Date 2019/1/5 16:19
* @CONTACT 317758022@qq.com
* @DESC
* BaseController
* CommonCacheUtilCookieUtilJsonUtilCookieJSONUser
*/
@Slf4j
public class BaseController {
// 通过Spring的依赖注入机制自动注入CommonCacheUtil对象这个对象通常用于和缓存进行交互比如从缓存中获取存储的用户相关数据等操作方便在后续获取当前用户信息时使用缓存来提高效率、减少数据库查询等操作。
@Autowired
private CommonCacheUtil commonCacheUtil;
User getCurrentUser(HttpServletRequest httpServletRequest){
/**
* HTTP
* CookieUtilHttpServletRequestCookienullSnailmallException
* 使CommonCacheUtilJSONJSONnullResponseEnum.NEED_LOGIN.getCode()ResponseEnum.NEED_LOGIN.getDesc()SnailmallException
* JSONJsonUtilJSONUserUser
* @param httpServletRequest HTTPCookie
* @return UserBaseController使
* @throws SnailmallException
*/
User getCurrentUser(HttpServletRequest httpServletRequest) {
// 从请求中读取登录令牌Cookie中的值用于后续判断用户是否登录以及从缓存中获取对应的用户信息若返回为空则表示没有找到登录相关的Cookie即用户未登录。
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)){
if (StringUtils.isEmpty(loginToken)) {
throw new SnailmallException("用户未登陆,无法获取当前用户信息");
}
// 根据获取到的登录令牌从缓存中获取对应的用户信息的JSON字符串表示形式缓存中通常会预先存储用户登录成功后放入的相关用户信息方便后续快速获取若返回为null则表示缓存中不存在该用户信息可能是登录状态失效等原因导致。
String userJsonStr = commonCacheUtil.getCacheValue(loginToken);
if(userJsonStr == null){
throw new SnailmallException(ResponseEnum.NEED_LOGIN.getCode(),ResponseEnum.NEED_LOGIN.getDesc());
if (userJsonStr == null) {
throw new SnailmallException(ResponseEnum.NEED_LOGIN.getCode(), ResponseEnum.NEED_LOGIN.getDesc());
}
User user = JsonUtil.Str2Obj(userJsonStr,User.class);
// 利用JsonUtil工具类将从缓存中获取到的用户信息的JSON字符串反序列化为User对象使得可以在后续业务逻辑中直接使用User对象的属性和方法进行相关操作比如获取用户权限、展示用户详情等操作完成获取当前用户信息的关键步骤。
User user = JsonUtil.Str2Obj(userJsonStr, User.class);
return user;
}
}
}

@ -1,19 +1,19 @@
package com.njupt.swg.controller;
import com.njupt.swg.cache.CommonCacheUtil;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ResponseEnum;
import com.njupt.swg.common.resp.ServerResponse;
import com.njupt.swg.common.utils.JsonUtil;
import com.njupt.swg.entity.User;
import com.njupt.swg.service.ICartService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import com.njupt.swg.cache.CommonCacheUtil;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ResponseEnum;
import com.njupt.swg.common.resp.ServerResponse;
import com.njupt.swg.common.utils.JsonUtil;
import com.njupt.swg.entity.User;
import com.njupt.swg.service.ICartService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
* @Author swg.
@ -23,168 +23,277 @@ import java.util.Enumeration;
*/
@RestController
@RequestMapping("/cart")
public class CartController extends BaseController{
// CartController类继承自BaseController类这意味着它可以继承BaseController中定义的通用方法和属性
// 例如获取当前用户信息的相关逻辑等,主要负责处理购物车模块相关的各种业务请求,是购物车功能在后端的具体控制器实现。
public class CartController extends BaseController {
// 通过Spring框架的依赖注入功能自动将实现了ICartService接口的实例注入到这个属性中。
// ICartService接口应该定义了一系列用于处理购物车业务逻辑的方法例如添加商品、更新商品数量等操作对应的方法
// 在本类的各个请求处理方法中会调用该服务层接口的方法来完成具体的业务功能。
@Autowired
private ICartService cartService;
// 同样通过Spring的依赖注入机制注入CommonCacheUtil实例。
// CommonCacheUtil通常用于与缓存系统进行交互比如从缓存中获取用户相关的数据等操作
// 在某些购物车相关业务处理中(例如需要获取缓存中的用户信息来确定购物车归属等情况)会起到重要作用。
@Autowired
private CommonCacheUtil commonCacheUtil;
/**
* 1.
* "limitQuantity"
* LIMIT_NUM_FAIL LIMIT_NUM_SUCCESS
* 1.
* HTTP
* "limitQuantity"
* LIMIT_NUM_FAILLIMIT_NUM_SUCCESS便
* BaseControllergetCurrentUser
* user.getId()productIdcountcartServiceadd
* cartServiceServerResponse
*
* @param httpServletRequest HTTP
* productIdcountCookie
* @param productId Integer
* cartServiceadd
* @param count
*
* @return ServerResponse
* LIMIT_NUM_SUCCESS
* LIMIT_NUM_FAIL
*/
@RequestMapping("add.do")
public ServerResponse add(HttpServletRequest httpServletRequest, Integer productId, Integer count){
public ServerResponse add(HttpServletRequest httpServletRequest, Integer productId, Integer count) {
User user = getCurrentUser(httpServletRequest);
return cartService.add(user.getId(),productId,count);
return cartService.add(user.getId(), productId, count);
}
/**
* 2.
* "limitQuantity"
* LIMIT_NUM_FAIL LIMIT_NUM_SUCCESS
* 2.
* HTTP
* "limitQuantity"
* LIMIT_NUM_FAILLIMIT_NUM_SUCCESS便
* user.getId()productIdcountcartServiceupdate
* cartServiceServerResponse
*
* @param httpServletRequest HTTP
* @param productId
* cartServiceproductIdcount
* @param count
*
* @return ServerResponse
* LIMIT_NUM_SUCCESS
* LIMIT_NUM_FAIL
*/
@RequestMapping("update.do")
public ServerResponse update(HttpServletRequest httpServletRequest,Integer productId,Integer count){
public ServerResponse update(HttpServletRequest httpServletRequest, Integer productId, Integer count) {
User user = getCurrentUser(httpServletRequest);
return cartService.update(user.getId(),productId,count);
return cartService.update(user.getId(), productId, count);
}
/**
* 3.
* 3.
* HTTP
* user.getId()productIdscartServicedelete
* productIds
* cartServiceproductIdsServerResponse
*
* @param httpServletRequest HTTPproductIds
* @param productIds String
* ID"123"ID"123,456,789"cartService
* @return ServerResponse
*
*/
@RequestMapping("delete_product.do")
public ServerResponse delete_product(HttpServletRequest httpServletRequest,String productIds){
public ServerResponse delete_product(HttpServletRequest httpServletRequest, String productIds) {
User user = getCurrentUser(httpServletRequest);
return cartService.delete(user.getId(),productIds);
return cartService.delete(user.getId(), productIds);
}
/**
* 4.List
* ,2
* 4. List
* HTTP
* user.getId()cartServicelist
* cartServicelistServerResponse
* 2便
*
* @param httpServletRequest HTTP
* @return ServerResponse
* 2便
*/
@RequestMapping("list.do")
public ServerResponse list(HttpServletRequest httpServletRequest){
public ServerResponse list(HttpServletRequest httpServletRequest) {
User user = getCurrentUser(httpServletRequest);
return cartService.list(user.getId());
}
/**
* 5.
*/
/**
* 5.
* HTTP
* BaseController`getCurrentUser``httpServletRequest`
*
* `user.getId()``Constants.Cart.CHECKED``null``cartService``selectOrUnSelect`
* `cartService``ServerResponse`
*
* @param httpServletRequest HTTPCookie
* @return `ServerResponse`便
*/
@RequestMapping("select_all.do")
public ServerResponse select_all(HttpServletRequest httpServletRequest){
public ServerResponse select_all(HttpServletRequest httpServletRequest) {
User user = getCurrentUser(httpServletRequest);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED,null);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED, null);
}
/**
* 6.
* 6.
* HTTP
* `getCurrentUser``httpServletRequest`
* `user.getId()``Constants.Cart.UN_CHECKED``null``cartService``selectOrUnSelect`
* `cartService``ServerResponse`
*
* @param httpServletRequest HTTP
* @return `ServerResponse`便
*/
@RequestMapping("un_select_all.do")
public ServerResponse un_select_all(HttpServletRequest httpServletRequest){
public ServerResponse un_select_all(HttpServletRequest httpServletRequest) {
User user = getCurrentUser(httpServletRequest);
return cartService.selectOrUnSelect(user.getId(),Constants.Cart.UN_CHECKED,null);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.UN_CHECKED, null);
}
/**
* 7.
* 7.
* HTTP
* `getCurrentUser``httpServletRequest`
* `user.getId()``Constants.Cart.CHECKED``productId``cartService``selectOrUnSelect`
* `cartService``ServerResponse`
*
* @param httpServletRequest HTTP`productId`
* @param productId `cartService`
* @return `ServerResponse`便
*/
@RequestMapping("select.do")
public ServerResponse select(HttpServletRequest httpServletRequest,Integer productId){
public ServerResponse select(HttpServletRequest httpServletRequest, Integer productId) {
User user = getCurrentUser(httpServletRequest);
return cartService.selectOrUnSelect(user.getId(),Constants.Cart.CHECKED,productId);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.CHECKED, productId);
}
/**
* 8.
* 8.
* HTTP
* `getCurrentUser``httpServletRequest`
* `user.getId()``Constants.Cart.UN_CHECKED``productId``cartService``selectOrUnSelect`
* `cartService``productId``ServerResponse`
*
* @param httpServletRequest HTTP`productId`
* @param productId `cartService`
* @return `ServerResponse`便
*/
@RequestMapping("un_select.do")
public ServerResponse un_select(HttpServletRequest httpServletRequest,Integer productId){
public ServerResponse un_select(HttpServletRequest httpServletRequest, Integer productId) {
User user = getCurrentUser(httpServletRequest);
return cartService.selectOrUnSelect(user.getId(),Constants.Cart.UN_CHECKED,productId);
return cartService.selectOrUnSelect(user.getId(), Constants.Cart.UN_CHECKED, productId);
}
/**
* 9.
* 9.
* HTTP
* `getCurrentUser``httpServletRequest`
* `user.getId()``cartService``get_cart_product_count`
* `cartService``get_cart_product_count``ServerResponse<Integer>`
* `ServerResponse<Integer>``<Integer>``ServerResponse`便
*
* @param httpServletRequest HTTP
* @return `ServerResponse<Integer>`
*/
@RequestMapping("get_cart_product_count.do")
public ServerResponse<Integer> get_cart_product_count(HttpServletRequest httpServletRequest){
public ServerResponse<Integer> get_cart_product_count(HttpServletRequest httpServletRequest) {
User user = getCurrentUser(httpServletRequest);
return cartService.get_cart_product_count(user.getId());
}
/**
* feignuserId
* feignuserId
* FeignHTTP
* `User``null`
* `httpServletRequest``headerNames``null`
* "snailmall_login_token"`value``StringUtils.isBlank(value)``true``ServerResponse`
* `commonCacheUtil``getCacheValue`JSON`userJsonStr`JSON`null``ServerResponse`
* JSON使`JsonUtil``Str2Obj``User``user`
* `user``null``user.getId()``cartService``list``ServerResponse``user``null``ServerResponse`
*
* @param httpServletRequest HTTPFeign
* @return `ServerResponse`
*/
@RequestMapping("getCartList.do")
public ServerResponse getCartList(HttpServletRequest httpServletRequest){
public ServerResponse getCartList(HttpServletRequest httpServletRequest) {
User user = null;
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
if (headerNames != null) {
if (headerNames!= null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
if(name.equalsIgnoreCase("snailmall_login_token")){
if (name.equalsIgnoreCase("snailmall_login_token")) {
String value = httpServletRequest.getHeader(name);
if(StringUtils.isBlank(value)){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (StringUtils.isBlank(value)) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
String userJsonStr = commonCacheUtil.getCacheValue(value);
if(userJsonStr == null){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (userJsonStr == null) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
user = JsonUtil.Str2Obj(userJsonStr,User.class);
user = JsonUtil.Str2Obj(userJsonStr, User.class);
}
}
}
if (user == null){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (user == null) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
return cartService.list(user.getId());
}
/**
* feign
* feign
* FeignHTTP
* `User``null`
* `headerNames``null`"snailmall_login_token"`value`
* `ServerResponse`
* `commonCacheUtil`JSON`userJsonStr`JSON`null``ServerResponse`
* JSON使`JsonUtil``Str2Obj``User``user`
* `user``null``user.getId()``cartService``removeCart``ServerResponse``user``null``ServerResponse`
*
* @param httpServletRequest HTTPFeign
* @return `ServerResponse`
*/
@RequestMapping("removeCart.do")
public ServerResponse removeCart(HttpServletRequest httpServletRequest){
public ServerResponse removeCart(HttpServletRequest httpServletRequest) {
User user = null;
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
if (headerNames != null) {
if (headerNames!= null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
if(name.equalsIgnoreCase("snailmall_login_token")){
if (name.equalsIgnoreCase("snailmall_login_token")) {
String value = httpServletRequest.getHeader(name);
if(StringUtils.isBlank(value)){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (StringUtils.isBlank(value)) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
String userJsonStr = commonCacheUtil.getCacheValue(value);
if(userJsonStr == null){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (userJsonStr == null) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
user = JsonUtil.Str2Obj(userJsonStr,User.class);
user = JsonUtil.Str2Obj(userJsonStr, User.class);
}
}
}
if (user == null){
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(),"用户未登陆,无法获取当前用户信息");
if (user == null) {
return ServerResponse.createByErrorCodeMessage(ResponseEnum.NEED_LOGIN.getCode(), "用户未登陆,无法获取当前用户信息");
}
return cartService.removeCart(user.getId());
}
}
}

@ -3,34 +3,64 @@ package com.njupt.swg.dao;
import com.njupt.swg.entity.Cart;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// @Mapper注解用于标识这个接口是MyBatis的Mapper接口MyBatis框架会自动扫描并生成该接口的代理实现类
// 从而可以与数据库进行交互执行对应的SQL操作这个接口主要定义了针对购物车Cart相关数据的数据库操作方法。
@Mapper
public interface CartMapper {
// 根据购物车记录的主键通常是一个唯一标识该记录的整数型ID删除对应的购物车记录返回值为受影响的行数
// 如果成功删除一条记录返回值通常为1如果没有匹配的记录可删除则返回0。
int deleteByPrimaryKey(Integer id);
// 向数据库中插入一条完整的购物车记录Cart对象这个方法会将Cart对象中的所有属性对应的字段值插入到数据库表中
// 返回值同样为受影响的行数若插入成功一般返回1表示成功插入了一条记录。
int insert(Cart record);
// 插入购物车记录但允许只插入Cart对象中部分非空的属性对应的字段值到数据库表中
// 相比insert方法更加灵活当Cart对象某些属性为null时只会插入那些非null属性对应的字段返回值为受影响的行数。
int insertSelective(Cart record);
// 根据购物车记录的主键id从数据库中查询并返回对应的购物车记录以Cart对象形式返回
// 如果找不到对应的记录则返回null。
Cart selectByPrimaryKey(Integer id);
// 根据购物车记录的主键id来有选择性地更新购物车记录只会更新Cart对象中那些非null属性对应的数据库字段值
// 返回值为受影响的行数用于指示实际更新了几条记录若没有任何字段值被更新比如传入的Cart对象属性都是null或者和数据库中原有值相同则返回0。
int updateByPrimaryKeySelective(Cart record);
// 根据购物车记录的主键id更新购物车记录会将Cart对象中的所有属性对应的字段值更新到数据库表中对应的记录里
// 不管属性值是否为null返回值为受影响的行数用来表示实际更新了几条记录。
int updateByPrimaryKey(Cart record);
// 根据用户的IDuserId和商品的IDproductId联合查询购物车中对应的记录
// 通过@Param注解明确指定了参数的名称方便在对应的SQL语句中使用这些参数进行准确的条件查询
// 返回值是匹配条件的购物车记录以Cart对象形式返回若找不到符合条件的记录则返回null。
Cart selectByUserIdProductId(@Param("userId") Integer userId, @Param("productId") Integer productId);
// 根据用户的IDuserId查询该用户购物车中的所有记录返回一个包含多个Cart对象的List集合
// 如果该用户没有购物车记录则返回一个空的List集合。
List<Cart> selectCartByUserId(Integer userId);
// 根据用户的IDuserId查询该用户购物车中已选中商品的状态数量具体含义可能根据业务逻辑而定比如返回已选中商品的记录条数等
// 返回一个整数类型的结果,例如返回已选中商品的数量或者代表选中状态的某种统计数值等。
int selectCartCheckedStatusByUserId(Integer userId);
// 根据用户的IDuserId以及要删除商品的ID列表productIdList从购物车中删除对应的商品记录
// 通过@Param注解分别指定了参数名称便于在对应的SQL语句中准确使用这些参数进行批量删除操作
// 返回值为受影响的行数,代表实际删除了几条记录。
int deleteByProductIds(@Param("userId") Integer userId, @Param("productIdList") List<String> productIdList);
// 根据用户的IDuserId、商品的选中状态checked可能是用整数表示选中或未选中比如1表示选中0表示未选中等具体由业务定义以及商品的IDproductId
// 来设置商品在购物车中的选中或未选中状态此方法通常用于更新购物车中某个商品的选中情况无返回值void类型
// 对应的SQL操作会根据传入的参数对数据库表中的相应字段进行更新操作。
void selectOrUnSelectProduct(@Param("userId") Integer userId, @Param("checked") int checked, @Param("productId") Integer productId);
// 根据用户的IDuserId查询该用户购物车中商品的总数量返回一个整数类型的结果用于统计该用户购物车中一共有多少件商品。
int selectCartProductCount(Integer userId);
// 根据用户的IDuserId查询该用户购物车中所有已选中商品的记录返回一个包含多个已选中Cart对象的List集合
// 方便后续对已选中商品进行相关业务处理比如计算总价等操作若没有已选中商品则返回一个空的List集合。
List<Cart> selectCheckedCartByUserId(Integer userId);
}

@ -4,26 +4,44 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
// 使用Lombok的 @Data注解会自动为这个类生成常用的方法比如Getter、Setter方法
// 以及toString、hashCode、equals等方法简化了代码编写让实体类的属性访问和操作更加便捷。
@Data
// 使用 @AllArgsConstructor注解Lombok会为这个类自动生成一个包含所有参数的构造函数
// 方便在创建对象时一次性传入所有属性的值进行初始化。
@AllArgsConstructor
// 使用 @NoArgsConstructor注解Lombok会为这个类自动生成一个无参构造函数
// 这在很多框架(如一些序列化、反序列化的场景或者通过反射创建对象的情况)中是必要的,确保类有默认的构造方式。
@NoArgsConstructor
public class Cart {
// 购物车记录的唯一标识符,通常在数据库中作为主键使用,用于区分不同的购物车记录,类型为整数类型。
private Integer id;
// 关联的用户的唯一标识符,用于表明这个购物车记录属于哪个用户,通过这个字段可以将购物车与具体的用户关联起来,方便查询某个用户的购物车相关信息,类型为整数类型。
private Integer userId;
// 购物车中商品的唯一标识符用于确定购物车中存放的是哪个具体商品方便在业务逻辑中根据商品ID查找商品详情、库存等信息类型为整数类型。
private Integer productId;
// 商品在购物车中的数量代表用户添加该商品到购物车的数量情况例如用户添加了3件某商品到购物车这个字段的值就会是3类型为整数类型。
private Integer quantity;
// 用于表示商品在购物车中的选中状态具体取值含义可能由业务逻辑定义比如可以用1表示选中0表示未选中等方便在购物车相关操作如全选、取消全选、单独选中某个商品等操作中标记商品的状态类型为整数类型。
private Integer checked;
// 使用 @JsonFormat注解来指定日期类型字段的序列化格式这里将createTime字段在序列化为JSON字符串时
// 按照指定的格式("yyyy-MM-dd HH:mm:ss.SSS",即年-月-日 时:分:秒.毫秒)进行格式化,方便在前后端数据交互或者数据存储时,日期格式能够统一且符合预期的展示需求。
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date createTime;
// 与createTime类似也是使用 @JsonFormat注解来指定日期类型字段的序列化格式
// updateTime字段在序列化为JSON字符串时按照"yyyy-MM-dd HH:mm:ss.SSS"格式进行格式化,
// 通常用于记录购物车记录最后一次更新的时间,方便跟踪数据的变化情况以及进行一些基于时间的业务逻辑处理(如判断数据是否过期等情况)。
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private Date updateTime;
}

@ -3,35 +3,70 @@ package com.njupt.swg.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
// 使用Lombok的 @Data注解它会自动帮我们生成这个类的一系列常用方法包括所有属性的Getter和Setter方法
// 以及重写的toString方法、hashCode方法和equals方法等。这样可以减少大量样板代码的编写让代码看起来更简洁
// 同时也方便在其他地方对类的属性进行访问和操作。
@Data
// @AllArgsConstructor注解指示Lombok为这个类生成一个包含所有参数的构造函数。
// 这意味着我们可以通过传入所有属性的值来便捷地创建一个Product类的实例例如
// Product product = new Product(1, 2, "商品名称", "商品副标题", "主图路径", "子图路径列表", "商品详情", new BigDecimal("9.99"), 100, 1, new Date(), new Date());
@AllArgsConstructor
// @NoArgsConstructor注解让Lombok为这个类生成一个无参构造函数。
// 在很多情况下,比如通过反射创建对象、一些序列化和反序列化的场景中,无参构造函数是必不可少的,
// 确保类有默认的创建实例的方式。
@NoArgsConstructor
public class Product {
// 商品的唯一标识符,在整个系统中用于区分不同的商品,通常会作为数据库表中的主键字段,类型为整数类型,
// 其他业务逻辑或者数据库操作往往会基于这个ID来对特定的商品进行查询、更新、删除等操作。
private Integer id;
// 商品所属的分类的标识符,用于将商品归类到不同的类别下,方便进行分类查询、展示等操作,比如按照不同的品类展示商品列表,类型为整数类型,
// 通过这个字段可以关联到对应的商品分类信息,了解商品所属的大类别或者小类别等情况。
private Integer categoryId;
// 商品的名称,用于展示给用户,让用户直观地知道商品是什么,是一个字符串类型的属性,例如"华为P50手机"等,
// 在商品列表展示、详情页展示等场景中会用到这个字段来呈现商品的基本信息。
private String name;
// 商品的副标题,通常可以用来补充说明商品的一些特点、卖点或者优惠信息等内容,也是字符串类型,
// 比如"华为P50手机超感知徕卡四摄限时优惠",可以进一步吸引用户关注该商品。
private String subtitle;
// 商品的主图片的路径或者标识,一般用于在前端页面展示商品的主要图片,字符串类型,
// 这个路径可以是相对路径或者绝对路径,指向存储商品主图的位置(可能是服务器本地存储、云存储等),方便前端根据这个路径来加载并展示图片。
private String mainImage;
// 商品的子图片的路径或者标识列表,以字符串形式存储,通常用特定的分隔符(如逗号等)将多个子图片的路径分隔开,
// 用于展示商品的多角度、多细节图片,让用户更全面地了解商品外观、功能等方面的情况,方便前端加载并展示商品的多张图片。
private String subImages;
// 商品的详细描述信息,包含了商品的各种详细参数、功能介绍、使用说明等内容,是一个比较长的字符串类型,
// 在商品详情页中会完整地展示这些信息,帮助用户深入了解商品的具体情况,以便做出购买决策。
private String detail;
// 商品的价格使用BigDecimal类型来精确表示金额避免使用浮点数类型如double、float带来的精度丢失问题
// 例如可以表示为new BigDecimal("9.99"),准确地存储商品的售价信息,在购物车计算总价、下单结算等涉及金额计算的业务场景中会频繁使用这个字段。
private BigDecimal price;
// 商品的库存数量,用于记录当前商品还有多少件可供销售,整数类型,
// 在添加商品到购物车、下单扣减库存等业务操作中,会根据这个字段的值来判断操作是否可行,比如库存不足时不能继续添加商品到购物车等情况。
private Integer stock;
// 商品的状态标识具体含义由业务逻辑定义可能用不同的整数值来表示不同的状态比如1表示上架、0表示下架等
// 用于控制商品是否在前端展示、是否可购买等情况,方便后台管理人员对商品的售卖状态进行管理和操作。
private Integer status;
// 商品的创建时间记录商品信息首次被录入系统的时间点使用Date类型来准确表示时间
// 在数据统计、历史记录查询等业务场景中可能会用到这个字段,例如查看某个时间段内新上架的商品等情况。
private Date createTime;
// 商品的更新时间,每当商品的相关信息(如名称、价格、库存等属性发生变化时)被修改后,会更新这个时间字段,
// 同样是Date类型可用于跟踪商品信息的变更情况比如查看商品最近一次的价格调整时间等业务需求。
private Date updateTime;
}

@ -4,7 +4,6 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
@ -12,32 +11,52 @@ import java.util.Date;
* @Author swg.
* @Date 2018/12/31 21:01
* @CONTACT 317758022@qq.com
* @DESC
* @DESC
* 便
*/
@Data
// 使用Lombok的 @Data注解会自动为这个类生成常用的方法包括所有属性的Getter和Setter方法以及重写的toString、hashCode和equals方法等减少了手动编写这些样板代码的工作量使代码更加简洁便于对类中属性进行操作和使用。
@NoArgsConstructor
// 通过 @NoArgsConstructor注解Lombok会为这个类生成一个无参构造函数。在很多场景下比如通过反射创建对象、进行序列化和反序列化操作时无参构造函数是必要的确保类有默认的创建实例的方式。
@AllArgsConstructor
// @AllArgsConstructor注解则指示Lombok生成一个包含所有参数的构造函数方便在创建对象时一次性传入所有属性的值进行初始化例如
// User user = new User(1, "testUser", "123456", "test@example.com", "13812345678", "你的爱好是什么?", "看书", 1, new Date(), new Date());
@ToString
// @ToString注解会让Lombok自动重写toString方法使得在打印对象或者将对象转换为字符串表示形式时能够直观地展示对象的各个属性值方便调试和查看对象的状态。
// 实现Serializable接口表示这个类的对象可以被序列化和反序列化这在很多场景下是很重要的比如将用户对象存储到文件、在网络中传输用户对象等情况确保对象能够以字节流的形式进行持久化和传递并且在反序列化后能正确还原对象状态。
public class User implements Serializable {
// 用户的唯一标识符通常作为数据库表中的主键字段来区分不同的用户在整个系统中其他与用户相关的业务操作如查询、更新、删除用户信息等大多会基于这个ID来进行类型为整数类型。
private Integer id;
// 用户登录使用的用户名,是一个字符串类型的属性,要求在系统中具有唯一性(一般会有相应的业务逻辑和数据库约束来保证),用于用户在登录界面输入,以标识自己的身份,例如"admin"、"user123"等。
private String username;
// 用户登录的密码同样为字符串类型存储用户设置的密码信息出于安全考虑在实际应用中通常会对密码进行加密存储而不是以明文形式保存例如使用MD5、SHA等加密算法对密码进行处理后再存储到数据库中。
private String password;
// 用户的电子邮箱地址,字符串类型,可用于用户注册验证、找回密码等功能,例如"test@example.com",通过向该邮箱发送验证链接或者重置密码的相关信息来完成相应的操作,同时也方便系统与用户进行一些重要通知的邮件沟通。
private String email;
// 用户的手机号码,字符串类型,常用于短信验证码验证、手机号登录等功能场景,例如"13812345678",在如今的很多应用中,手机号已经成为一种常用且重要的用户身份验证和联系的方式。
private String phone;
// 密保问题,字符串类型,是用户在设置账号安全相关信息时所填写的用于找回密码等情况的验证问题,例如"你的爱好是什么?",当用户忘记密码时,可以通过回答正确的密保问题来重置密码,增加账号的安全性。
private String question;
// 密保问题的答案与question属性相对应用户填写的用于验证密保问题的正确答案也是字符串类型例如"看书",只有回答正确这个答案,才能进行后续的密码重置等安全相关操作。
private String answer;
//角色0-管理员,1-普通用户
// 用户的角色标识整数类型通过不同的整数值来区分用户在系统中的不同角色在这里定义了0表示管理员角色1表示普通用户角色不同角色在系统中通常具有不同的权限例如管理员可能具有管理所有用户、系统配置等高级权限而普通用户只能进行与自身相关的常规操作如查看购物车、下单等
private Integer role;
// 用户账号的创建时间使用Date类型准确记录用户首次在系统中注册账号的时间点在数据统计如统计每日新增用户数量等、历史记录查询查看某个时间段内注册的用户等等业务场景中会用到这个字段。
private Date createTime;
// 用户账号信息的最后更新时间每当用户修改了自己的用户名、密码、邮箱等相关信息后会更新这个时间字段同样为Date类型方便跟踪用户信息的变更情况例如查看用户最近一次修改密码的时间等。
private Date updateTime;
}

@ -9,31 +9,51 @@ import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Author swg.
* @Date 2019/1/7 13:23
* @CONTACT 317758022@qq.com
* @DESC
* @DESC MessageReceiverRabbitMQ
*
*/
@Component
// 使用 @Component注解将这个类标记为Spring的一个组件这样Spring容器会自动扫描并管理这个类的实例使其可以参与依赖注入等Spring相关的功能。
@Slf4j
// 通过 @Slf4j注解使用Lombok的功能自动为这个类添加一个名为log的日志记录器方便在类中记录各种运行时的日志信息有助于调试、监控和追踪代码的执行情况。
public class MessageReceiver {
// 通过Spring的依赖注入机制自动注入ICartService接口的实现类对象用于调用购物车相关的业务逻辑方法
// 在这里具体用于执行清除购物车的操作依赖注入使得代码的耦合性更低方便替换不同的购物车服务实现类并且符合Spring的面向接口编程的理念。
@Autowired
private ICartService cartService;
/**
* @RabbitListener"cart-queue"
*
*
* @param message ID
* userId
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue("cart-queue"),
exchange = @Exchange("cart-exchange")
))
public void proess(String message){
log.info("接收到的消息为:{}",message);
public void proess(String message) {
// 使用日志记录器记录接收到的消息内容,方便在运行时查看接收到的消息具体是什么,有助于调试和排查问题,例如查看消息是否符合预期格式等情况。
log.info("接收到的消息为:{}", message);
// 将接收到的消息预期是一个字符串形式的整数转换为实际的整数类型userId这里假设发送过来的消息内容就是有效的用户ID字符串表示形式
// 如果消息格式不符合要求可能会抛出NumberFormatException异常在实际应用中可能需要更完善的错误处理机制来应对这种情况。
Integer userId = Integer.parseInt(message);
log.info("【MQ解析数据,前者为userId,后者为productId{}】",userId);
//清除购物车
// 再次使用日志记录器记录解析后的用户ID信息便于在日志中清晰地看到处理的用户相关数据方便后续查看消息处理的具体情况以及追踪问题比如查看是否处理了正确的用户的购物车清除操作等。
log.info("【MQ解析数据,前者为userId,后者为productId{}】", userId);
// 调用ICartService的removeCart方法传入解析得到的用户IDuserId执行清除对应用户购物车的业务操作
// 这个操作可能涉及到数据库中购物车相关记录的删除等具体的持久层操作具体由ICartService的实现类中对应的方法逻辑来决定。
cartService.removeCart(userId);
}
}
}

@ -31,211 +31,452 @@ import java.util.List;
*/
@Service
@Slf4j
public class CartServiceImpl implements ICartService{
// CartServiceImpl类实现了ICartService接口意味着它需要实现该接口中定义的所有方法
// 这个类主要负责处理购物车相关的具体业务逻辑,例如添加商品到购物车、更新购物车中商品数量等操作,
// 通过调用其他组件如CartMapper、ProductClient、CommonCacheUtil等来协同完成业务功能并将结果以ServerResponse的形式返回给上层调用者如控制层
public class CartServiceImpl implements ICartService {
// 通过Spring的依赖注入机制自动注入CartMapper接口的实现类对象。
// CartMapper用于与数据库进行交互执行如查询、插入、更新、删除购物车相关记录等数据库操作
// 在这里的各个购物车业务方法中会频繁调用它来完成持久化层面的工作,实现业务逻辑与数据访问的分离。
@Autowired
private CartMapper cartMapper;
// 同样通过依赖注入注入ProductClient对象。
// ProductClient通常是用于和外部的商品服务进行通信的客户端比如在购物车业务中需要获取商品的详细信息是否下架、库存情况等
// 就会通过这个客户端去调用商品服务提供的接口来获取相应数据,实现不同微服务之间的协作。
@Autowired
private ProductClient productClient;
// 注入CommonCacheUtil对象用于与缓存系统进行交互操作。
// 在处理购物车业务时,可能会频繁用到商品相关信息,通过缓存可以减少重复查询数据库的次数,提高系统性能。
// 例如先从缓存中查找商品信息,如果缓存中不存在再去查询数据库,并将查询到的数据存入缓存供后续使用。
@Autowired
private CommonCacheUtil commonCacheUtil;
/**
* ICartService
*
* @param userId null
* SnailmallException
* @param productId null
* ServerResponse
* @param count nullnull
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse add(Integer userId, Integer productId, Integer count) {
//1.校验参数
if(userId == null){
// 1. 校验参数
// 首先检查用户ID是否为null如果为null意味着用户没有登录这不符合添加商品到购物车的业务要求所以抛出SnailmallException异常
// 异常信息为"用户未登陆",后续在合适的地方(比如控制层)可以捕获这个异常并返回相应提示给客户端,告知用户需要先登录才能进行添加操作。
if (userId == null) {
throw new SnailmallException("用户未登陆");
}
if(productId == null || count == null){
// 接着检查商品ID和商品数量是否为null如果这两个参数中有任何一个为null说明传入的参数不符合要求无法准确执行添加商品的操作
// 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示参数不正确的响应对象给客户端提示用户检查并修正传入的参数。
if (productId == null || count == null) {
return ServerResponse.createByErrorMessage("参数不正确");
}
//2.校验商品
String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+productId);
// 2. 校验商品
// 尝试从缓存中获取商品信息缓存的键是通过Constants.PRODUCT_TOKEN_PREFIX加上商品ID拼接而成的
// 这样可以根据不同的商品ID来准确地在缓存中查找对应的商品缓存数据提高获取商品信息的效率避免频繁访问数据库。
String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + productId);
Product product = null;
if(productStr == null){
// 如果从缓存中获取到的商品信息字符串为null说明缓存中不存在该商品的相关信息此时需要通过ProductClient去远程调用商品服务来查询商品详情。
if (productStr == null) {
// 调用ProductClient的queryProduct方法传入商品ID作为参数向商品服务发起查询请求获取包含商品信息的ServerResponse对象。
ServerResponse response = productClient.queryProduct(productId);
// 从ServerResponse对象中获取其包含的商品数据部分可能是一个Object类型的对象具体内容由商品服务返回的数据结构决定
Object object = response.getData();
// 使用JsonUtil工具类的obj2String方法将获取到的商品数据对象转换为字符串形式方便后续进行反序列化操作。
String objStr = JsonUtil.obj2String(object);
product = (Product) JsonUtil.Str2Obj(objStr,Product.class);
}else {
product = (Product) JsonUtil.Str2Obj(productStr,Product.class);
// 再通过JsonUtil的Str2Obj方法将字符串形式的商品信息反序列化为Product对象以便后续在业务逻辑中使用商品的各个属性进行相关判断和操作。
product = (Product) JsonUtil.Str2Obj(objStr, Product.class);
} else {
// 如果缓存中存在商品信息字符串则直接使用JsonUtil的Str2Obj方法将其反序列化为Product对象避免了再次远程查询商品服务的开销。
product = (Product) JsonUtil.Str2Obj(productStr, Product.class);
}
if(product == null){
// 如果经过上述步骤获取到的商品对象为null说明商品不存在可能是商品ID有误或者商品已经被彻底删除等原因
// 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示商品不存在的响应对象给客户端告知用户无法添加不存在的商品到购物车。
if (product == null) {
return ServerResponse.createByErrorMessage("商品不存在");
}
if(!product.getStatus().equals(Constants.Product.PRODUCT_ON)){
// 检查商品的状态是否为Constants.Product.PRODUCT_ON这个常量应该是在业务中定义好的表示商品处于正常可售卖状态的标识
// 如果商品状态不等于这个正常可售卖状态标识,说明商品可能已经下架或者被删除了,不应该再添加到购物车中,
// 所以返回一个表示商品下架或者删除的错误提示响应对象给客户端。
if (!product.getStatus().equals(Constants.Product.PRODUCT_ON)) {
return ServerResponse.createByErrorMessage("商品下架或者删除");
}
//3.根据商品或者购物车,购物车存在则增加商品数量即可,不存在则创建新的购物车,一个用户对应一个购物车
Cart cart = cartMapper.selectByUserIdProductId(userId,productId);
if (cart == null){
// 3. 根据商品或者购物车,购物车存在则增加商品数量即可,不存在则创建新的购物车,一个用户对应一个购物车
// 通过CartMapper的selectByUserIdProductId方法根据传入的用户ID和商品ID去数据库中查询购物车中是否已经存在该商品的记录
// 如果不存在即查询返回的Cart对象为null则表示该用户的购物车中还没有添加过这个商品需要创建一个新的购物车记录。
Cart cart = cartMapper.selectByUserIdProductId(userId, productId);
if (cart == null) {
// 创建一个新的Cart对象用于表示要插入到数据库中的购物车记录信息。
Cart cartItem = new Cart();
// 设置新购物车记录的用户ID关联到对应的用户表示这个购物车是属于哪个用户的。
cartItem.setUserId(userId);
// 设置商品ID明确购物车中存放的是哪个商品。
cartItem.setProductId(productId);
// 设置商品数量,即此次要添加到购物车的商品数量。
cartItem.setQuantity(count);
// 设置商品的选中状态这里设置为Constants.Cart.CHECKED应该是在业务中定义好的表示选中的常量表示默认添加到购物车后商品是选中状态具体选中含义可根据业务逻辑确定比如是否参与总价计算等。
cartItem.setChecked(Constants.Cart.CHECKED);
// 通过CartMapper的insert方法将新创建的购物车记录插入到数据库中该方法会返回受影响的行数正常插入成功应该返回1
// 如果返回值为0表示插入操作失败可能是数据库出现问题或者其他原因导致插入不成功此时返回一个表示添加购物车失败的错误提示响应对象给客户端。
int resultCount = cartMapper.insert(cartItem);
if(resultCount == 0){
if (resultCount == 0) {
return ServerResponse.createByErrorMessage("添加购物车失败");
}
}else {
cart.setQuantity(cart.getQuantity()+count);
} else {
// 如果购物车中已经存在该商品的记录,说明之前已经添加过这个商品到购物车了,此时只需要更新商品的数量即可,
// 将原有的商品数量加上此次要添加的数量更新Cart对象中的quantity属性。
cart.setQuantity(cart.getQuantity() + count);
// 通过CartMapper的updateByPrimaryKeySelective方法根据更新后的Cart对象去更新数据库中对应的购物车记录
// 这个方法会根据Cart对象中不为null的属性来更新数据库相应字段返回受影响的行数若返回0则表示更新失败同样返回添加购物车失败的提示响应给客户端。
int resultCount = cartMapper.updateByPrimaryKeySelective(cart);
if(resultCount == 0){
if (resultCount == 0) {
return ServerResponse.createByErrorMessage("添加购物车失败");
}
}
//构建购物车信息,返回给前端,并且要检查库存
CartVo cartVo = getCartVoLimit(userId,true);
// 构建购物车信息,返回给前端,并且要检查库存
// 调用getCartVoLimit方法来构建包含购物车详细信息的CartVo对象这个方法内部会进一步处理购物车中商品的各种信息
// 比如获取商品的详细信息(可能再次从缓存或远程查询商品服务获取更详细数据)、判断库存情况、计算购物车商品的总价等操作,
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户添加商品到购物车操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = getCartVoLimit(userId, true);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId null
*
* @param productId null
* ServerResponseID
* @param count nullnull
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse update(Integer userId, Integer productId, Integer count) {
if(productId == null || count == null){
// 首先对传入的参数进行校验如果商品ID或者商品数量为null说明参数不符合要求无法进行更新操作
// 此时返回一个通过ServerResponse.createByErrorMessage方法创建的表示参数错误的响应对象给客户端提示用户修正传入的参数。
if (productId == null || count == null) {
return ServerResponse.createByErrorMessage("参数错误");
}
Cart cart = cartMapper.selectByUserIdProductId(userId,productId);
if(cart == null){
// 通过CartMapper的selectByUserIdProductId方法根据传入的用户ID和商品ID去数据库中查询购物车中是否存在对应的商品记录
// 如果不存在即查询返回的Cart对象为null说明要更新数量的商品在购物车中不存在无法进行更新操作
// 此时返回一个表示购物车不存在的错误提示响应对象给客户端,告知用户无法进行更新。
Cart cart = cartMapper.selectByUserIdProductId(userId, productId);
if (cart == null) {
return ServerResponse.createByErrorMessage("购物车不存在");
}
// 如果找到了对应的购物车商品记录将Cart对象中的quantity属性设置为新传入的商品数量值准备更新数据库中的对应记录。
cart.setQuantity(count);
// 通过CartMapper的updateByPrimaryKeySelective方法根据更新后的Cart对象去更新数据库中对应的购物车记录
// 该方法会根据Cart对象中不为null的属性来更新数据库相应字段返回受影响的行数若返回0则表示更新失败返回一个表示更新购物车失败的错误提示响应给客户端。
int updateCount = cartMapper.updateByPrimaryKeySelective(cart);
if(updateCount == 0){
if (updateCount == 0) {
return ServerResponse.createByErrorMessage("更新购物车失败");
}
CartVo cartVo = this.getCartVoLimit(userId,true);
// 调用getCartVoLimit方法构建包含更新后购物车详细信息的CartVo对象这个方法会进一步处理购物车商品的相关信息如获取商品详情、判断库存、计算总价等
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户更新购物车商品数量操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = this.getCartVoLimit(userId, true);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId null
*
* @param productIds "1,2,3"ID123
* ID
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse delete(Integer userId, String productIds) {
// 使用Google Guava库的Splitter工具类按照逗号","将传入的商品ID字符串productIds分割成一个字符串列表
// 这样就能方便地获取到要删除的各个商品的ID以便后续批量操作数据库中对应的购物车商品记录。
List<String> productIdList = Splitter.on(",").splitToList(productIds);
if(CollectionUtils.isEmpty(productIdList)){
// 通过Apache Commons Collections的CollectionUtils工具类检查分割后的商品ID列表是否为空
// 如果为空说明传入的参数不符合要求无法准确执行删除操作此时返回一个表示参数错误的ServerResponse对象给客户端提示用户修正传入的参数。
if (CollectionUtils.isEmpty(productIdList)) {
return ServerResponse.createByErrorMessage("参数错误");
}
int rowCount = cartMapper.deleteByProductIds(userId,productIdList);
if(rowCount == 0){
// 调用CartMapper的deleteByProductIds方法根据用户ID和要删除的商品ID列表从数据库中删除对应的购物车商品记录
// 该方法会返回受影响的行数即实际删除的记录条数正常情况下如果成功删除了对应的商品记录返回值应该大于0。
int rowCount = cartMapper.deleteByProductIds(userId, productIdList);
// 如果返回值为0表示没有实际删除任何记录可能是因为要删除的商品已经不存在于购物车中了
// 此时返回一个表示商品已经不存在于购物车中请勿重复删除的ServerResponse对象给客户端告知用户当前操作情况。
if (rowCount == 0) {
return ServerResponse.createByErrorMessage("此商品已经不存在于购物车中,请勿重复删除");
}
CartVo cartVo = this.getCartVoLimit(userId,false);
// 调用getCartVoLimit方法构建包含删除商品后购物车详细信息的CartVo对象这个方法会进一步处理购物车商品的相关信息如获取剩余商品详情、计算总价等
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户删除购物车商品操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = this.getCartVoLimit(userId, false);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse list(Integer userId) {
CartVo cartVo = this.getCartVoLimit(userId,false);
// 直接调用getCartVoLimit方法构建包含购物车详细信息的CartVo对象这个方法内部会处理购物车商品的相关信息如获取商品详情、判断库存、计算总价等
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户查询购物车列表操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = this.getCartVoLimit(userId, false);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId
* @param checked Constants.Cart.CHECKEDConstants.Cart.UN_CHECKED
* @param productId null
* @return ServerResponse
* CartVo
* 便
*/
@Override
public ServerResponse selectOrUnSelect(Integer userId, int checked, Integer productId) {
cartMapper.selectOrUnSelectProduct(userId,checked,productId);
CartVo cartVo = this.getCartVoLimit(userId,false);
// 调用CartMapper的selectOrUnSelectProduct方法根据用户ID、要设置的选中状态以及商品ID如果不为null则针对特定商品为null则针对所有商品具体看业务逻辑实现来更新数据库中购物车商品的选中状态记录
// 此方法无返回值它直接执行数据库更新操作将对应的购物车商品记录的选中状态字段更新为传入的checked值。
cartMapper.selectOrUnSelectProduct(userId, checked, productId);
// 调用getCartVoLimit方法构建包含更新后购物车详细信息的CartVo对象这个方法会进一步处理购物车商品的相关信息如获取商品详情、判断库存、计算总价等
// 最后返回一个表示操作成功且包含构建好的CartVo对象的ServerResponse对象给客户端告知用户设置商品选中状态操作成功并将购物车的详细信息返回给前端展示给用户。
CartVo cartVo = this.getCartVoLimit(userId, false);
return ServerResponse.createBySuccess(cartVo);
}
/**
* ICartService
*
* @param userId null0
* @return ServerResponse<Integer>
* ServerResponse
* 便
*/
@Override
public ServerResponse<Integer> get_cart_product_count(Integer userId) {
if(userId == null){
// 首先判断用户ID是否为null如果为null按照业务逻辑直接返回一个表示操作成功且数据部分为0的ServerResponse对象
// 意味着如果用户未登录或者传入的用户ID无效等情况默认购物车商品数量为0告知调用者当前情况。
if (userId == null) {
return ServerResponse.createBySuccess(0);
}
// 调用CartMapper的selectCartProductCount方法传入用户ID作为参数去查询数据库中对应用户购物车中商品的总数量
// 然后返回一个表示操作成功且数据部分为查询到的商品数量的ServerResponse<Integer>对象给客户端,方便调用者获取并使用这个数量信息。
return ServerResponse.createBySuccess(cartMapper.selectCartProductCount(userId));
}
/**
* ICartService
*
* @param userId
* @return ServerResponse
* "清除购物车成功"便
*/
@Override
public ServerResponse removeCart(Integer userId) {
// 首先通过CartMapper的selectCartByUserId方法根据用户ID去查询数据库中该用户购物车中的所有商品记录返回一个包含多个Cart对象的列表代表购物车中的所有商品信息。
List<Cart> cartList = cartMapper.selectCartByUserId(userId);
for(Cart cart:cartList){
// 遍历查询到的购物车商品记录列表对于每一条购物车记录调用CartMapper的deleteByPrimaryKey方法根据购物车记录的主键ID从数据库中删除对应的记录
// 这样就实现了逐个删除购物车中的所有商品记录,达到清空购物车的目的。
for (Cart cart : cartList) {
cartMapper.deleteByPrimaryKey(cart.getId());
}
// 返回一个表示操作成功且包含提示消息"清除购物车成功"的ServerResponse对象给客户端告知用户购物车已成功清空方便调用者如控制层将这个消息返回给前端展示给用户。
return ServerResponse.createBySuccessMessage("清除购物车成功");
}
/**
*
* @param userId
* @return
* CartVo
*
* @param userId
* @param isJudgeStock
* truefalse
* @return CartVoCartProductVo
* 便使
*/
private CartVo getCartVoLimit(Integer userId,boolean isJudgeStock) {
private CartVo getCartVoLimit(Integer userId, boolean isJudgeStock) {
// 创建一个新的CartVo对象用于组装最终要返回的购物车详细信息这个对象将包含购物车的各种关键信息如商品列表、总价、全选状态等内容。
CartVo cartVo = new CartVo();
// 创建一个空的CartProductVo列表用于存放购物车中各个商品的详细信息对象后续会遍历购物车记录将每个商品的详细信息封装成CartProductVo对象后添加到这个列表中。
List<CartProductVo> cartProductVoList = Lists.newArrayList();
// 通过CartMapper的selectCartByUserId方法根据传入的用户ID去查询数据库中该用户购物车中的所有商品记录返回一个包含多个Cart对象的列表代表购物车中的所有商品信息。
List<Cart> cartList = cartMapper.selectCartByUserId(userId);
// 初始化购物车总价为0使用BigDecimal类型来精确表示金额避免浮点数运算的精度问题后续会根据购物车中选中商品的价格和数量来累加计算总价。
BigDecimal cartTotalPrice = new BigDecimal("0");
if(CollectionUtils.isNotEmpty(cartList)){
//1.遍历购物车一条购物车记录对应一个商品这些购物车共同对应到一个用户userId
for(Cart cart:cartList){
// 判断查询到的购物车商品记录列表是否不为空,如果不为空,说明该用户购物车中有商品,需要进一步处理这些商品的详细信息,进行购物车信息的构建。
if (CollectionUtils.isNotEmpty(cartList)) {
// 1. 遍历购物车一条购物车记录对应一个商品这些购物车共同对应到一个用户userId
// 开始遍历购物车商品记录列表对于每一条购物车记录代表一个商品在购物车中的信息进行如下操作构建对应的CartProductVo对象来封装商品详细信息并添加到购物车商品列表中。
for (Cart cart : cartList) {
CartProductVo cartProductVo = new CartProductVo();
// 设置CartProductVo对象的ID一般可以使用购物车记录的主键ID作为其唯一标识方便后续在一些业务逻辑中对特定商品信息进行定位和操作。
cartProductVo.setId(cart.getId());
// 设置CartProductVo对象的用户ID关联到对应的用户确保商品信息所属的用户明确与传入的参数userId对应。
cartProductVo.setUserId(cart.getUserId());
// 设置CartProductVo对象的商品ID明确这个商品信息对应的具体商品与购物车记录中的商品ID保持一致。
cartProductVo.setProductId(cart.getProductId());
//2.从redis中获取商品获取不到则feign获取并且重置进redis中
String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX+cart.getProductId());
// 2. 从redis中获取商品获取不到则feign获取并且重置进redis中
// 尝试从缓存这里假设使用Redis缓存通过CommonCacheUtil工具类操作中获取商品信息缓存的键是通过Constants.PRODUCT_TOKEN_PREFIX加上购物车记录中商品的ID拼接而成的
// 这样可以根据不同的商品ID准确地在缓存中查找对应的商品缓存数据提高获取商品信息的效率避免频繁访问数据库。
String productStr = commonCacheUtil.getCacheValue(Constants.PRODUCT_TOKEN_PREFIX + cart.getProductId());
Product product = null;
if(productStr == null){
// 如果从缓存中获取到的商品信息字符串为null说明缓存中不存在该商品的相关信息此时需要通过ProductClient去远程调用商品服务来查询商品详情。
if (productStr == null) {
ServerResponse response = productClient.queryProduct(cart.getProductId());
Object object = response.getData();
String objStr = JsonUtil.obj2String(object);
product = (Product) JsonUtil.Str2Obj(objStr,Product.class);
}else {
product = (Product) JsonUtil.Str2Obj(productStr,Product.class);
product = (Product) JsonUtil.Str2Obj(objStr, Product.class);
} else {
// 如果缓存中存在商品信息字符串则直接使用JsonUtil的Str2Obj方法将其反序列化为Product对象避免了再次远程查询商品服务的开销。
product = (Product) JsonUtil.Str2Obj(productStr, Product.class);
}
if(product != null){
// 如果成功获取到了商品对象即product不为null说明获取到了商品的详细信息接下来可以设置CartProductVo对象中与商品相关的各种属性值。
if (product!= null) {
// 设置CartProductVo对象的商品主图片路径从获取到的商品对象中获取主图片路径属性值方便前端根据这个路径展示商品的主图片。
cartProductVo.setProductMainImage(product.getMainImage());
// 设置CartProductVo对象的商品名称从商品对象中获取商品名称属性值用于在前端展示商品的名称信息。
cartProductVo.setProductName(product.getName());
// 设置CartProductVo对象的商品副标题从商品对象中获取副标题属性值可在前端展示商品的补充说明信息如卖点、优惠等相关内容。
cartProductVo.setProductSubtitle(product.getSubtitle());
// 设置CartProductVo对象的商品状态从商品对象中获取商品状态属性值用于前端展示商品是否可购买等状态信息例如是否下架等情况
cartProductVo.setProductStatus(product.getStatus());
// 设置CartProductVo对象的商品价格从商品对象中获取商品价格属性值用于后续计算商品总价等操作注意这里价格使用BigDecimal类型保证精度。
cartProductVo.setProductPrice(product.getPrice());
// 设置CartProductVo对象的商品库存从商品对象中获取商品库存属性值用于判断库存是否足够等相关业务逻辑处理。
cartProductVo.setProductStock(product.getStock());
//3.判断这个商品的库存,有些接口不需要再去判断库存了所以根据传进来的isJudgeStock这个boolean参数来决定是否判断库存
// 3. 判断这个商品的库存有些接口不需要再去判断库存了所以根据传进来的isJudgeStock这个boolean参数来决定是否判断库存
int buyLimitCount = 0;
if (isJudgeStock){
if(product.getStock() > cart.getQuantity()){
//4.库存是够的
if (isJudgeStock) {
// 如果需要判断库存即isJudgeStock为true则比较商品的库存数量和购物车中该商品的数量判断库存是否足够。
if (product.getStock() > cart.getQuantity()) {
// 4. 库存是够的
// 如果商品库存大于购物车中该商品的数量,说明库存充足,购买数量可以按照购物车中记录的数量来确定,将购买数量设置为购物车中的商品数量。
buyLimitCount = cart.getQuantity();
// 设置CartProductVo对象的限制数量标识为Constants.Cart.LIMIT_NUM_SUCCESS应该是业务中定义好的表示数量限制成功即库存足够的
// 在构建购物车信息的方法中,针对每个购物车商品记录,根据库存情况设置相关属性,并计算商品总价等信息,以下是继续添加注释后的详细内容。
// 如果库存足够设置CartProductVo对象的限制数量标识为表示库存足够的常量值意味着当前商品在购物车中的数量是可购买的数量没有受到库存限制。
cartProductVo.setLimitQuantity(Constants.Cart.LIMIT_NUM_SUCCESS);
}else {
//5.库存不够了,则返回当前最大库存
} else {
// 5. 库存不够了,则返回当前最大库存
// 当商品库存小于或等于购物车中该商品的数量时,说明库存不足,此时将购买数量设置为商品当前的库存数量,即用户最多只能购买库存剩余的数量。
buyLimitCount = product.getStock();
// 设置CartProductVo对象的限制数量标识为Constants.Cart.LIMIT_NUM_FAIL应该是业务中定义好的表示数量限制失败即库存不足的标识
// 用于前端展示等场景告知用户该商品库存不足,可能无法按照购物车中原本设置的数量购买。
cartProductVo.setLimitQuantity(Constants.Cart.LIMIT_NUM_FAIL);
// 创建一个新的Cart对象用于更新购物车中该商品的记录因为库存不足需要将购物车中记录的商品数量更新为实际可购买的库存数量。
Cart cartItem = new Cart();
// 设置新Cart对象的ID为当前购物车商品记录的ID确保更新的是正确的购物车商品记录。
cartItem.setId(cart.getId());
// 设置新Cart对象的商品数量为前面计算得到的可购买的库存数量buyLimitCount准备更新数据库中的购物车记录。
cartItem.setQuantity(buyLimitCount);
// 通过CartMapper的updateByPrimaryKeySelective方法根据更新后的Cart对象去更新数据库中对应的购物车记录
// 这个方法会根据Cart对象中不为null的属性来更新数据库相应字段以确保购物车中的商品数量与实际库存情况相符。
cartMapper.updateByPrimaryKeySelective(cartItem);
}
}else {
} else {
// 如果不需要判断库存即isJudgeStock为false则直接将购买数量设置为购物车中原本记录的商品数量不考虑库存是否足够的情况可能适用于某些特定业务场景比如仅查看购物车信息而不涉及购买操作时。
buyLimitCount = cart.getQuantity();
}
//6.购买的数量已经是确定的了,下面就可以直接计算价格了
// 6. 购买的数量已经是确定的了,下面就可以直接计算价格了
// 设置CartProductVo对象的商品数量为前面确定好的购买数量buyLimitCount这个数量将用于后续计算该商品在购物车中的总价。
cartProductVo.setQuantity(buyLimitCount);
cartProductVo.setProductTotalPrice(BigDecimalUtil.mul(product.getPrice().doubleValue(),buyLimitCount));
// 使用BigDecimalUtil工具类的mul方法应该是自定义的用于BigDecimal类型乘法运算的工具方法确保精度
// 传入商品价格的double值通过调用product.getPrice().doubleValue()获取和购买数量buyLimitCount计算得到该商品在购物车中的总价
// 并设置到CartProductVo对象的ProductTotalPrice属性中方便后续汇总购物车总价以及展示给用户查看商品的价格明细。
cartProductVo.setProductTotalPrice(BigDecimalUtil.mul(product.getPrice().doubleValue(), buyLimitCount));
// 设置CartProductVo对象的商品选中状态从购物车记录中获取商品的选中状态cart.getChecked()并赋值给CartProductVo对象
// 用于前端展示商品是否被选中以及参与购物车总价计算等相关业务逻辑(例如只有选中的商品总价才会累加到购物车总价中)。
cartProductVo.setProductChecked(cart.getChecked());
}
//7.选中的,就加入到总价中
if(cart.getChecked() == Constants.Cart.CHECKED){
cartTotalPrice = BigDecimalUtil.add(cartTotalPrice.doubleValue(),cartProductVo.getProductTotalPrice().doubleValue());
// 7. 选中的,就加入到总价中
// 判断购物车记录中该商品的选中状态是否为Constants.Cart.CHECKED表示已选中如果是则将该商品的总价累加到购物车总价cartTotalPrice中。
// 通过BigDecimalUtil工具类的add方法同样是自定义的用于BigDecimal类型加法运算的工具方法保证精度
// 传入当前购物车总价的double值cartTotalPrice.doubleValue()和该商品的总价cartProductVo.getProductTotalPrice().doubleValue())进行累加计算,
// 更新购物车总价,以得到所有选中商品的总价信息。
if (cart.getChecked() == Constants.Cart.CHECKED) {
cartTotalPrice = BigDecimalUtil.add(cartTotalPrice.doubleValue(), cartProductVo.getProductTotalPrice().doubleValue());
}
// 将构建好的包含该商品详细信息的CartProductVo对象添加到购物车商品列表cartProductVoList完成一个商品信息的处理后续继续遍历其他购物车商品记录进行同样的操作。
cartProductVoList.add(cartProductVo);
}
}
// 设置CartVo对象的购物车总价属性CartTotalPrice为前面计算得到的购物车总价cartTotalPrice方便将购物车总价信息传递给上层调用者如控制层展示给用户查看整个购物车的商品总价情况。
cartVo.setCartTotalPrice(cartTotalPrice);
// 设置CartVo对象的购物车商品列表属性CartProductVoList为前面构建好的包含所有商品详细信息的列表cartProductVoList
// 这样在前端展示购物车信息时,可以遍历这个列表展示每个商品的各项信息(如名称、价格、数量、选中状态等)。
cartVo.setCartProductVoList(cartProductVoList);
// 调用getAllCheckedStatus方法下面定义的用于判断购物车中商品是否全选的方法传入用户ID作为参数获取购物车中商品的全选状态是否所有商品都被选中
// 并设置到CartVo对象的AllChecked属性中方便前端展示购物车全选按钮的状态以及进行全选、取消全选等相关业务逻辑操作。
cartVo.setAllChecked(this.getAllCheckedStatus(userId));
cartVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.oursnail.cn/"));
log.info("购物车列表内容为:{}",cartVo);
// 通过PropertiesUtil工具类的getProperty方法应该是用于读取配置文件中属性值的工具方法尝试获取配置文件中名为"ftp.server.http.prefix"的属性值,
// 如果获取不到,则使用默认值"http://img.oursnail.cn/"作为图片主机地址,这个地址通常用于前端展示购物车中商品图片时拼接图片的具体路径,
// 例如商品主图片路径可能是相对路径通过拼接这个图片主机地址可以得到完整的可访问的图片URL方便前端正确展示商品图片。
cartVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix", "http://img.oursnail.cn/"));
// 使用日志记录器记录购物车列表的详细内容,方便在开发、调试以及运行过程中查看购物车信息的构建情况,排查可能出现的问题,例如购物车总价计算错误、商品信息缺失等情况。
log.info("购物车列表内容为:{}", cartVo);
// 最后返回构建好的包含完整购物车详细信息的CartVo对象供上层调用者如在其他业务方法中使用例如将其包装在ServerResponse对象中返回给控制层进而展示给前端页面。
return cartVo;
}
/**
* 0-1-
* 0 - 1 -
*
* 0false
* true
*
* @param userId
* @return truefalse
*/
private Boolean getAllCheckedStatus(Integer userId) {
if(userId == null){
// 首先判断用户ID是否为null如果为null按照业务逻辑直接返回false表示无法判断全选状态或者默认不是全选状态可根据实际业务含义调整
// 例如在未登录或者传入无效用户ID的情况下不认为购物车是全选状态。
if (userId == null) {
return false;
}
// 调用CartMapper的selectCartCheckedStatusByUserId方法传入用户ID作为参数去查询数据库中该用户购物车中已选中商品的状态数量
// 这里返回值的具体含义可能根据业务逻辑而定例如返回的是已选中商品的记录条数等只要返回值不为0就表示有商品被选中。
return cartMapper.selectCartCheckedStatusByUserId(userId) == 0;
}
}
}

@ -3,7 +3,6 @@ package com.njupt.swg.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
@ -11,24 +10,69 @@ import java.math.BigDecimal;
* @Author swg.
* @Date 2019/1/5 15:18
* @CONTACT 317758022@qq.com
* @DESC
* @DESC CartProductVoValue Object VO
* 便
* 使VO
* 使Serializable便使
*/
@NoArgsConstructor
// 通过 @NoArgsConstructor注解Lombok会为这个类自动生成一个无参构造函数。在很多情况下比如通过反射创建对象、进行序列化和反序列化操作时无参构造函数是必要的确保类有默认的创建实例的方式。
@AllArgsConstructor
// @AllArgsConstructor注解则指示Lombok生成一个包含所有参数的构造函数方便在创建对象时一次性传入所有属性的值进行初始化例如
// CartProductVo cartProductVo = new CartProductVo(1, 1001, 2001, 2, "华为手机", "高性能智能手机", "http://img/phone.jpg", new BigDecimal("3999"), 1, new BigDecimal("7998"), 10, 1, "库存充足");
@Data
// 使用Lombok的 @Data注解会自动为这个类生成常用的方法包括所有属性的Getter和Setter方法以及重写的toString、hashCode和equals方法等减少了手动编写这些样板代码的工作量使代码更加简洁便于对类中属性进行操作和使用。
public class CartProductVo implements Serializable {
// 购物车商品记录的唯一标识符,通常与数据库中购物车表内对应商品记录的主键相关联,用于在系统中唯一标识这条购物车商品记录,方便进行查询、更新、删除等操作,类型为整数类型。
private Integer id;
// 用户的唯一标识符关联到具体是哪个用户的购物车中的商品通过这个字段可以明确该商品所属的用户与系统中用户模块的用户ID相对应类型为整数类型。
private Integer userId;
// 商品的唯一标识符用于确定购物车中存放的是哪个具体商品可通过这个ID去查询商品的详细信息如商品详情、库存等在整个商品管理和购物流程中用于区分不同的商品类型为整数类型。
private Integer productId;
private Integer quantity;//购物车中此商品的数量
// 购物车中此商品的数量代表用户添加该商品到购物车的数量情况例如用户添加了3件某商品到购物车这个字段的值就会是3类型为整数类型
// 在购物车相关业务逻辑中,这个数量会参与到总价计算、库存判断等操作中。
private Integer quantity;
// 商品的名称,用于展示给用户,让用户直观地知道商品是什么,是一个字符串类型的属性,例如"华为P50手机"等,
// 在购物车列表展示、商品详情页展示等场景中会用到这个字段来呈现商品的基本信息。
private String productName;
// 商品的副标题,通常可以用来补充说明商品的一些特点、卖点或者优惠信息等内容,也是字符串类型,
// 比如"华为P50手机超感知徕卡四摄限时优惠",可以进一步吸引用户关注该商品,在前端展示购物车商品时丰富商品的展示信息。
private String productSubtitle;
// 商品的主图片的路径或者标识,一般用于在前端页面展示商品的主要图片,字符串类型,
// 这个路径可以是相对路径或者绝对路径,指向存储商品主图的位置(可能是服务器本地存储、云存储等),方便前端根据这个路径来加载并展示图片,使购物车中的商品展示更加直观形象。
private String productMainImage;
// 商品的价格使用BigDecimal类型来精确表示金额避免使用浮点数类型如double、float带来的精度丢失问题
// 例如可以表示为new BigDecimal("9.99"),准确地存储商品的售价信息,在购物车计算总价、下单结算等涉及金额计算的业务场景中会频繁使用这个字段。
private BigDecimal productPrice;
// 商品的状态标识具体含义由业务逻辑定义可能用不同的整数值来表示不同的状态比如1表示上架、0表示下架等
// 用于控制商品是否在前端展示、是否可购买等情况,方便后台管理人员对商品的售卖状态进行管理和操作,在购物车中展示商品时也需要根据这个状态来决定是否显示商品等操作。
private Integer productStatus;
// 商品在购物车中的总价通过商品的单价productPrice乘以购物车中该商品的数量quantity计算得出同样使用BigDecimal类型保证金额计算的精度
// 用于在购物车中展示每个商品的总价情况,方便用户清楚了解购买该商品所需的费用,以及参与整个购物车总价的汇总计算等业务操作。
private BigDecimal productTotalPrice;
// 商品的库存数量,用于记录当前商品还有多少件可供销售,整数类型,
// 在购物车相关业务中,如添加商品到购物车、更新商品数量等操作时,会根据这个字段的值来判断操作是否可行,比如库存不足时不能继续添加商品到购物车或者需要调整购物车中商品数量等情况,同时在前端展示时也可以告知用户商品的库存剩余情况。
private Integer productStock;
private Integer productChecked;//此商品是否勾选
private String limitQuantity;//限制数量的一个返回结果
}
// 此商品是否勾选用于表示商品在购物车中的选中状态具体取值含义可能由业务逻辑定义比如可以用1表示选中0表示未选中等方便在购物车相关操作如全选、取消全选、单独选中某个商品等操作中标记商品的状态类型为整数类型
// 在计算购物车总价时,通常只会将勾选状态的商品总价进行累加,并且前端展示购物车时也会根据这个状态来展示商品是否被选中的视觉效果(如打勾图标等)。
private Integer productChecked;
// 限制数量的一个返回结果,这个字段的具体含义可能根据业务逻辑来定,比如在判断商品库存与购物车中商品数量关系后,根据库存情况返回相应的提示信息,
// 像"库存充足"、"库存不足仅剩X件"等类似表示数量限制情况的内容,以字符串形式存储,方便前端展示给用户,让用户了解商品数量相关的限制情况。
private String limitQuantity;
}

@ -1,7 +1,6 @@
package com.njupt.swg.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@ -9,12 +8,29 @@ import java.util.List;
* @Author swg.
* @Date 2019/1/5 15:18
* @CONTACT 317758022@qq.com
* @DESC
* @DESC CartVoValue ObjectVO
* 便
* 使VO使
*/
@Data
// 使用Lombok的 @Data注解会自动为这个类生成常用的方法包括所有属性的Getter和Setter方法以及重写的toString方法等这样可以减少手动编写这些样板代码的工作量使代码更加简洁便于对类中属性进行操作和使用。
public class CartVo {
// 购物车中商品信息列表是一个包含CartProductVo对象的List集合每个CartProductVo对象代表购物车中的一个商品的详细信息如商品名称、价格、数量、选中状态等
// 通过这个列表可以完整地展示购物车中所有商品的各项具体情况,方便前端遍历并展示购物车中的商品列表,以及在后端进行基于购物车商品列表的各种业务逻辑处理(如计算总价、判断全选状态等)。
private List<CartProductVo> cartProductVoList;
// 购物车的总价使用BigDecimal类型来精确表示金额避免浮点数运算带来的精度丢失问题这个总价是通过计算购物车中所有已选中商品的价格总和得到的
// 它准确地反映了用户当前选择购买的商品的总费用情况,在购物车页面展示以及下单结算等业务场景中是一个非常关键的信息,方便用户清楚知晓购买这些商品需要支付的金额总数。
private BigDecimal cartTotalPrice;
private Boolean allChecked;//是否已经都勾选
// 是否已经都勾选是一个布尔类型的属性用于表示购物车中的所有商品是否都处于被选中的状态其取值为true表示购物车中的商品全部都被勾选了
// false则表示存在至少一个商品未被勾选这个属性方便前端展示购物车全选按钮的状态如全选按钮是否应该被勾选等视觉效果
// 同时在后端进行一些批量操作(如批量删除选中商品、批量结算等)时也可以根据这个属性来判断是否符合操作条件。
private Boolean allChecked;
// 图片主机地址,是一个字符串类型的属性,通常用于存放服务器上存储商品图片的主机地址或者前缀路径,例如"http://img.oursnail.cn/"
// 在前端展示购物车中商品的图片时需要将商品图片的相对路径与这个图片主机地址进行拼接从而形成完整的可访问的图片URL以正确地加载并展示商品的图片信息使购物车中的商品展示更加直观形象。
private String imageHost;
}
}

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

@ -11,70 +11,111 @@ import redis.clients.jedis.JedisPool;
* @Author swg.
* @Date 2019/1/1 15:03
* @CONTACT 317758022@qq.com
* @DESC
* @DESC CommonCacheUtilRedis
*
* 便Redis访
* SnailmallException便
*/
@Component
// 使用 @Component注解将这个类标记为Spring的一个组件使得Spring容器能够自动扫描并管理这个类的实例这样在其他需要使用的地方就可以通过依赖注入的方式来获取该类的实例进行缓存操作。
@Slf4j
// 通过 @Slf4j注解利用Lombok的功能自动为这个类添加一个名为log的日志记录器用于记录在与Redis缓存交互过程中出现的各种错误信息等日志内容方便后续排查问题、监控系统运行状态等。
public class CommonCacheUtil {
// 通过Spring的依赖注入机制自动注入JedisPoolWrapper对象JedisPoolWrapper应该是对JedisPoolJedis连接池对象用于管理Jedis客户端与Redis服务器的连接进行包装的一个类
// 在这里注入它的目的是为了获取JedisPool实例进而通过JedisPool获取Jedis客户端对象来与Redis进行交互操作实现缓存相关的功能这种方式有助于更好地管理和配置Jedis连接资源。
@Autowired
private JedisPoolWrapper jedisPoolWrapper;
/**
* key
* keyRedis
*
* @param key IDID便
* @param value JSON
*/
public void cache(String key, String value) {
try {
// 通过JedisPoolWrapper获取JedisPool对象它是管理Jedis客户端连接的连接池如果获取到的连接池不为null说明可以正常获取到与Redis服务器的连接资源进而进行后续操作。
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
// 从JedisPool中获取一个Jedis客户端对象Jedis客户端用于实际与Redis服务器进行交互这里使用了Java 7的try-with-resources语句
// 可以确保在使用完Jedis客户端后自动关闭资源释放连接避免资源泄露提高资源利用效率和系统的稳定性。
try (Jedis Jedis = pool.getResource()) {
// 选择Redis的数据库编号这里选择0号数据库通常Redis可以配置多个数据库默认从0开始编号根据实际业务需求和项目配置可能会选择不同的数据库来区分不同类型的数据缓存等情况不过这里固定选择了0号数据库进行操作。
Jedis.select(0);
// 使用Jedis客户端的set方法将指定的键key和值value存储到Redis缓存中实现缓存数据的功能如果键已经存在则会覆盖原有的值这是一种简单的设置缓存数据的操作方式。
Jedis.set(key, value);
}
}
} catch (Exception e) {
// 如果在缓存数据过程中出现任何异常使用日志记录器记录错误信息方便后续查看具体的异常情况排查是网络问题、Redis服务器问题还是其他原因导致的缓存失败这里记录的日志级别为ERROR表示出现了错误情况。
log.error("redis存值失败", e);
// 抛出SnailmallException异常异常信息为"redis报错",在调用这个方法的上层模块(如业务逻辑层的相关类)可以捕获这个异常,并根据业务需求进行相应的处理,比如返回错误提示给客户端或者进行重试等操作。
throw new SnailmallException("redis报错");
}
}
/**
* key
* keyvalueRedisnull
*
* @param key Redis
* @return null便
*/
public String getCacheValue(String key) {
String value = null;
try {
// 通过JedisPoolWrapper获取JedisPool对象用于后续获取Jedis客户端连接判断连接池是否可用若不为null则可以继续进行获取缓存值的操作。
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
// 从JedisPool中获取一个Jedis客户端对象并使用try-with-resources语句确保资源的正确释放然后通过这个客户端与Redis服务器进行交互操作。
try (Jedis Jedis = pool.getResource()) {
// 选择Redis的0号数据库与前面存储数据时选择的数据库保持一致确保在同一个数据库中进行数据的读写操作避免出现数据查找不一致等问题。
Jedis.select(0);
// 使用Jedis客户端的get方法根据传入的键key从Redis缓存中获取对应的值并将获取到的值赋给局部变量value若键不存在则返回null这是Redis获取缓存值的基本操作方式。
value = Jedis.get(key);
}
}
} catch (Exception e) {
// 如果在获取缓存值的过程中出现异常记录详细的错误日志方便后续排查问题确定是网络、服务器还是其他原因导致的获取失败日志级别为ERROR表示出现了错误情况。
log.error("redis获取指失败", e);
// 抛出SnailmallException异常告知调用者Redis操作出现报错上层模块可以捕获该异常并进行相应的业务处理比如返回默认值或者提示客户端缓存获取失败等情况。
throw new SnailmallException("redis报错");
}
return value;
}
/**
* key
* keyRedis
* setnx10
*
* @param key
* @param value
* @param expire Redis60
* @return 10setnx便
*/
public long cacheNxExpire(String key, String value, int expire) {
long result = 0;
try {
// 获取JedisPool对象用于获取Jedis客户端连接若连接池不为null则可进行后续与Redis服务器的交互操作。
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
// 从JedisPool中获取一个Jedis客户端对象并通过try-with-resources语句确保使用后自动关闭资源然后与Redis服务器进行操作。
try (Jedis jedis = pool.getResource()) {
// 选择Redis的0号数据库确保在正确的数据库中进行数据操作。
jedis.select(0);
// 使用Jedis客户端的setnx方法SET if Not eXists的缩写是一个原子操作尝试将指定的键key和值value存储到Redis缓存中
// 只有当键不存在时才会设置成功返回1如果键已经存在则设置失败返回0将这个结果赋值给result变量用于后续判断操作是否成功以及返回给调用者。
result = jedis.setnx(key, value);
// 如果前面的setnx操作成功即result为1则使用Jedis客户端的expire方法为刚刚设置的键key设置过期时间单位为秒通过传入的expire参数指定具体的过期时长确保缓存数据在指定时间后自动失效。
jedis.expire(key, expire);
}
}
} catch (Exception e) {
// 如果在设置带过期时间的缓存数据过程中出现异常记录详细的错误日志便于后续排查是哪个环节出现问题导致操作失败日志级别为ERROR表示出现错误情况。
log.error("redis塞值和设置缓存时间失败", e);
// 抛出SnailmallException异常告知调用者Redis操作出现报错上层模块捕获该异常后可以根据业务逻辑进行相应处理比如重新尝试设置或者提示客户端操作失败等情况。
throw new SnailmallException("redis报错");
}
@ -82,23 +123,25 @@ public class CommonCacheUtil {
}
/**
* key
* keyRedis
*
* @param key Redis
*/
public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
if (pool!= null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
// 使用Jedis客户端的del方法尝试从Redis缓存中删除指定的键key对应的缓存数据若键存在则会成功删除若不存在则不做任何操作不会抛出异常这是Redis删除缓存数据的基本操作方式。
jedis.del(key);
} catch (Exception e) {
// 如果在删除缓存数据过程中出现异常记录详细的错误日志方便后续排查是网络、Redis服务器还是其他原因导致的删除失败日志级别为ERROR表示出现错误情况。
log.error("从redis中删除失败", e);
// 抛出SnailmallException异常告知调用者Redis操作出现报错上层模块捕获该异常后可以根据业务逻辑进行相应处理比如提示客户端缓存删除失败等情况。
throw new SnailmallException("redis报错");
}
}
}
}
}
}

@ -13,30 +13,64 @@ import javax.annotation.PostConstruct;
* @Date 2019/1/1 15:00
* @CONTACT 317758022@qq.com
* @DESC redisredishash
* JedisPoolWrapperJedisPoolJedis
* JedisPoolConfigJedisPool便RedisJedisPoolJedisRedis
* 便
*/
@Component
// 使用 @Component注解将这个类标记为Spring的一个组件使得Spring容器能够自动扫描并管理这个类的实例这样其他需要使用JedisPool的地方就可以通过依赖注入等方式获取该类的实例进行操作。
@Slf4j
// 通过 @Slf4j注解利用Lombok的功能自动为这个类添加一个名为log的日志记录器用于记录在初始化JedisPool过程中出现的各种情况成功或失败的日志信息方便后续排查问题、监控系统运行状态等。
public class JedisPoolWrapper {
// 通过Spring的依赖注入机制自动注入Parameters对象Parameters类应该是用于存放Redis相关配置参数如最大连接数、最大空闲连接数、最大等待时间等的一个类
// 在这里注入它的目的是获取这些配置参数来正确配置JedisPool连接池使连接池的各项属性符合项目的实际需求和运行环境要求。
@Autowired
private Parameters parameters;
// 定义一个JedisPool类型的私有变量用于存放初始化后的JedisPool实例初始值设置为null在后续的init方法中会根据配置参数进行实例化操作
// 通过这种方式可以在类的其他方法如getJedisPool方法中返回这个实例供外部使用实现对JedisPool的封装和统一管理。
private JedisPool jedisPool = null;
/**
* @PostConstructJedisPool
* ParametersJedisPoolConfigJedisPool
* 便
*/
@PostConstruct
public void init(){
public void init() {
try {
// 创建一个JedisPoolConfig对象JedisPoolConfig用于配置JedisPool连接池的各种属性例如连接池的最大连接数、最大空闲连接数、最大等待时间等通过设置这些属性可以优化连接池的性能和资源利用情况。
JedisPoolConfig config = new JedisPoolConfig();
// 设置JedisPool连接池的最大连接数通过调用Parameters对象的getRedisMaxTotal方法获取配置的最大连接数参数值并设置到JedisPoolConfig对象中
// 这个参数决定了连接池中最多可以同时存在多少个Jedis客户端连接需要根据实际的Redis服务器性能、业务并发量等因素合理设置避免连接数过多导致服务器压力过大或过少影响业务处理效率。
config.setMaxTotal(parameters.getRedisMaxTotal());
// 设置JedisPool连接池的最大空闲连接数从Parameters对象中获取对应的配置参数值通过getRedisMaxIdle方法并设置到JedisPoolConfig对象中
// 最大空闲连接数表示连接池中最多可以空闲未被使用的Jedis客户端连接数量合理设置这个参数有助于提高连接资源的利用效率避免资源浪费。
config.setMaxIdle(parameters.getRedisMaxIdle());
// 设置JedisPool连接池的最大等待时间单位为毫秒通过Parameters对象的getRedisMaxWaitMillis方法获取配置参数值并设置到JedisPoolConfig对象中
// 当连接池中的连接资源耗尽时,如果有新的获取连接请求,请求线程会在这个最大等待时间内等待空闲连接出现,如果超过这个时间仍无空闲连接,则可能抛出异常,根据业务对响应时间的要求等因素合理设置这个参数。
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,"xxx");
// 使用配置好的JedisPoolConfig对象、Redis服务器的主机地址通过Parameters对象的getRedisHost方法获取、端口号通过getRedisPort方法获取以及连接超时时间这里设置为2000毫秒和密码这里示例中为"xxx",实际中应替换为真实密码)等信息,
// 创建一个JedisPool实例这个实例就是用于管理Jedis客户端与Redis服务器连接的连接池后续其他代码可以通过这个连接池获取Jedis客户端来与Redis进行交互操作。
jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 2000, "xxx");
// 使用日志记录器记录一条信息表示初始化redis连接池成功方便在运行时查看系统启动过程中连接池初始化的情况确认是否正常初始化对于监控系统状态有一定帮助。
log.info("【初始化redis连接池成功】");
}catch (Exception e){
log.error("【初始化redis连接池失败】",e);
} catch (Exception e) {
// 如果在初始化JedisPool连接池过程中出现异常使用日志记录器记录错误信息日志级别为ERROR表示出现了错误情况同时将异常对象e传入方便记录详细的异常堆栈信息便于后续排查问题确定是配置参数错误、网络问题还是其他原因导致的初始化失败。
log.error("【初始化redis连接池失败】", e);
}
}
/**
* JedisPool使RedisJedisRedis
* initJedisPooljedisPoolnullnull使
*
* @return JedisPoolJedisRedisnullJedisPool
*/
public JedisPool getJedisPool() {
return jedisPool;
}
}
}

@ -8,26 +8,49 @@ import org.springframework.stereotype.Component;
* @Author swg.
* @Date 2019/1/1 14:27
* @CONTACT 317758022@qq.com
* @DESC
* @DESC ParametersSpring @Valueapplication.propertiesapplication.yml
* 便使RedisZookeeper使
* 使Lombok @DataGetterSettertoString使便使
*/
@Component
// 使用 @Component注解将这个类标记为Spring的一个组件使得Spring容器能够自动扫描并管理这个类的实例并且能够识别类中的 @Value注解进行属性值的注入操作确保可以正确获取配置文件中的参数值。
@Data
// 通过 @Data注解Lombok会自动生成各属性的Getter和Setter方法等方便获取和设置类中的各个配置参数属性的值例如可以通过getRedisHost方法获取Redis服务器的主机地址通过setRedisHost方法设置新的主机地址等操作提高代码的简洁性和可维护性。
public class Parameters {
/*****redis config start*******/
// 使用 @Value注解从配置文件例如application.properties或application.yml等具体取决于项目配置中读取名为"redis.host"的配置项的值并将其注入到这个名为redisHost的字符串类型属性中
// 这个属性用于存储Redis服务器的主机地址例如"localhost"或者具体的IP地址等在初始化与Redis的连接时会用到这个地址来定位Redis服务器类型为字符串类型。
@Value("${redis.host}")
private String redisHost;
// 同样通过 @Value注解从配置文件中读取"redis.port"配置项的值并注入到这个整数类型的属性中该属性表示Redis服务器的端口号默认Redis端口号一般是6379
// 但可以根据实际部署情况进行配置修改在建立与Redis服务器的连接时需要指定正确的端口号类型为整数类型。
@Value("${redis.port}")
private int redisPort;
// 此处可能存在属性命名上的混淆,从 @Value注解的配置项"${redis.max-idle}"来看本意应该是获取Redis连接池的最大空闲连接数配置参数
// 但属性名却定义为redisMaxTotal可能是命名错误按常规理解应该是redisMaxIdle这个属性用于设置JedisPool如果使用Jedis连接Redis的话中允许的最大空闲连接数量合理设置可优化连接资源利用类型为整数类型。
@Value("${redis.max-idle}")
private int redisMaxTotal;
// 类似地,这里的 @Value注解从配置文件中读取"${redis.max-total}"配置项的值注入到该属性按常理应该是表示Redis连接池的最大连接数即连接池中最多能同时存在的Jedis客户端连接数量
// 不过属性名是redisMaxIdle可能是命名失误正常应类似redisMaxConnTotal之类更符合语义的名称在配置JedisPool时会依据这个参数来控制连接池规模类型为整数类型。
@Value("${redis.max-total}")
private int redisMaxIdle;
// 通过 @Value注解读取配置文件中"${redis.max-wait-millis}"配置项的值注入到这个整数类型属性中该属性表示当从Redis连接池中获取连接时如果连接池中没有空闲连接
// 请求线程最多等待的时间(单位为毫秒),根据业务的并发情况和对响应时间的要求等因素合理设置这个参数,避免长时间等待或因超时而出现异常,类型为整数类型。
@Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis;
/*****redis config end*******/
/*****curator config start*******/
// 使用 @Value注解从配置文件中读取名为"zk.host"的配置项的值并注入到这个字符串类型属性中该属性用于存储Zookeeper服务器的主机地址
// 在项目中如果涉及到使用Zookeeper进行服务注册与发现、分布式锁等相关功能时会通过这个地址去连接Zookeeper服务器类型为字符串类型。
@Value("${zk.host}")
private String zkHost;
/*****curator config end*******/
}
}

@ -4,43 +4,78 @@ package com.njupt.swg.common.constants;
* @Author swg.
* @Date 2019/1/1 13:19
* @CONTACT 317758022@qq.com
* @DESC
* @DESC Constants使
* 使使便
*/
public class Constants {
/**自定义状态码 start**/
/**
* start
*
* 使使
*/
// 表示操作成功的状态码通常在请求处理成功后返回给客户端对应HTTP状态码中的200表示请求已成功被服务器接收、理解、并接受按照业务逻辑正常处理完成类型为整数类型。
public static final int RESP_STATUS_OK = 200;
// 表示未授权的状态码对应HTTP状态码中的401意味着客户端尝试访问需要授权的资源但未提供有效的认证信息如未登录或者登录凭证过期等情况服务器拒绝该请求类型为整数类型。
public static final int RESP_STATUS_NOAUTH = 401;
// 表示服务器内部错误的状态码等同于HTTP状态码中的500说明在服务器端处理请求的过程中发生了意外的错误可能是代码逻辑问题、数据库连接异常等原因导致无法正常完成请求处理类型为整数类型。
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
// 表示请求参数错误的状态码类似HTTP状态码中的400意味着客户端发送的请求中包含了不符合要求的参数如格式错误、必填参数缺失等情况服务器无法按照这样的参数进行正确的业务处理类型为整数类型。
public static final int RESP_STATUS_BADREQUEST = 400;
/**
* end
*/
/**自定义状态码 end**/
/***redis user相关的key以这个打头**/
/**
* *redis userkey
* Rediskey便
* RedisID
*/
public static final String TOKEN_PREFIX = "user_";
/**
* redis
* RedisCacheExtimeREDIS_SESSION_EXTIMERedis
* 60 * 60 * 1030便
*/
public interface RedisCacheExtime{
int REDIS_SESSION_EXTIME = 60 * 60 * 10;//30分钟
public interface RedisCacheExtime {
int REDIS_SESSION_EXTIME = 60 * 60 * 10; // 30分钟
}
/** 用户注册判断重复的参数类型 start **/
/**
* start
* 使
* 使使使便
*/
// 表示用于判断用户注册时邮箱是否重复的参数类型标识,在进行邮箱重复性验证的代码逻辑中可以使用这个常量来明确操作的对象是邮箱,类型为字符串类型。
public static final String EMAIL = "email";
// 表示用于判断用户注册时用户名是否重复的参数类型标识,在验证用户名是否已被使用的业务逻辑中,通过这个常量来清晰地表明所处理的参数是用户名,类型为字符串类型。
public static final String USERNAME = "username";
/** 用户注册判断重复的参数类型 end **/
/**
* end
*/
/** 用户角色 **/
public interface Role{
int ROLE_CUSTOME = 0;//普通用户
int ROLE_ADMIN = 1;//管理员用户
/**
*
* Role
* 便使
*/
public interface Role {
// 表示普通用户的角色标识,通常普通用户具有基本的使用系统功能的权限,但不具备管理、配置等高级权限,在权限判断等代码中可以通过这个常量来识别用户是否为普通用户,类型为整数类型。
int ROLE_CUSTOME = 0; // 普通用户
// 表示管理员用户的角色标识,管理员用户一般拥有系统的高级权限,例如可以管理用户、配置系统参数等操作,在进行权限相关业务逻辑处理时,通过这个常量来区分是否为管理员角色,类型为整数类型。
int ROLE_ADMIN = 1; // 管理员用户
}
/**用户注册分布式锁路径***/
/**
*
* 使
*
*/
public static final String USER_REGISTER_DISTRIBUTE_LOCK_PATH = "/user_reg";
}
}

@ -1,6 +1,5 @@
package com.njupt.swg.common.exception;
import com.njupt.swg.common.constants.Constants;
import com.njupt.swg.common.resp.ServerResponse;
import lombok.extern.slf4j.Slf4j;
@ -13,21 +12,49 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @Date 2019/1/1 13:21
* @CONTACT 317758022@qq.com
* @DESC
* ExceptionHandlerAdviceSpring @ControllerAdvice @ExceptionHandler
*
*/
@ControllerAdvice
// @ControllerAdvice注解表明这个类是一个全局的异常处理类它可以对整个Spring MVC应用中多个Controller控制器里抛出的异常进行统一处理使得异常处理逻辑更加集中便于维护和管理而不需要在每个Controller中单独编写异常处理代码。
@ResponseBody
// @ResponseBody注解用于指示这个类中的方法返回的结果直接作为响应体写入到HTTP响应中而不是像常规的Controller方法那样去寻找视图进行渲染等操作这样可以方便地返回JSON格式或者其他格式的数据作为响应符合现在大多数前后端分离项目中直接返回数据给前端的需求。
@Slf4j
// 通过 @Slf4j注解利用Lombok的功能自动为这个类添加一个名为log的日志记录器用于记录在处理异常过程中出现的各种异常信息方便后续排查问题、分析系统出现异常的原因等确保对异常情况有详细的记录可供查看。
public class ExceptionHandlerAdvice {
/**
* Exception
* Exception
*
* @param e 便Exception
* @return ServerResponseConstants.RESP_STATUS_INTERNAL_ERROR便
*/
@ExceptionHandler(Exception.class)
public ServerResponse handleException(Exception e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR,"系统异常,请稍后再试");
public ServerResponse handleException(Exception e) {
// 使用日志记录器记录异常信息通过e.getMessage()获取异常的简要消息同时传入异常对象e本身这样日志中还会记录详细的异常堆栈信息方便后续排查问题确定是哪里出现了问题导致这个异常被抛出日志级别为ERROR表示出现了错误情况。
log.error(e.getMessage(), e);
// 通过ServerResponse的createByErrorCodeMessage静态方法创建一个表示错误响应的ServerResponse对象传入表示系统内部错误的状态码Constants.RESP_STATUS_INTERNAL_ERROR以及提示用户的错误消息"系统异常,请稍后再试"
// 返回这个对象给前端,告知前端系统出现了内部错误,让用户稍后再尝试操作,实现了对异常情况的统一响应处理,使前端能以统一的方式展示错误提示给用户。
return ServerResponse.createByErrorCodeMessage(Constants.RESP_STATUS_INTERNAL_ERROR, "系统异常,请稍后再试");
}
/**
* SnailmallExceptionSnailmallException
* 使
*
* @param e SnailmallException便SnailmallException
* @return ServerResponseSnailmallExceptionServerResponsecreateByErrorCodeMessagee.getExceptionStatus()e.getMessage()
*
*/
@ExceptionHandler(SnailmallException.class)
public ServerResponse handleException(SnailmallException e){
log.error(e.getMessage(),e);
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(),e.getMessage());
public ServerResponse handleException(SnailmallException e) {
// 使用日志记录器记录SnailmallException类型异常的详细信息包括异常消息和堆栈信息方便后续查看业务异常出现的原因进行业务逻辑的调整和优化等日志级别为ERROR表示出现了错误情况。
log.error(e.getMessage(), e);
// 根据自定义异常对象中携带的状态码和消息创建一个ServerResponse对象返回给前端使得前端可以根据返回的状态码和消息展示相应的提示给用户告知用户业务层面具体的异常情况实现了业务异常的统一处理和反馈机制。
return ServerResponse.createByErrorCodeMessage(e.getExceptionStatus(), e.getMessage());
}
}
}

@ -7,19 +7,40 @@ import lombok.Getter;
* @Author swg.
* @Date 2019/1/1 13:18
* @CONTACT 317758022@qq.com
* @DESC
* @DESC SnailmallExceptionJavaRuntimeException
* 便
* Java使
*/
@Getter
public class SnailmallException extends RuntimeException{
// 使用Lombok的 @Getter注解会自动为这个类的私有属性exceptionStatus生成对应的Getter方法这样在其他地方就可以方便地获取这个属性的值无需手动编写Getter方法使代码更加简洁便于操作类中的属性。
public class SnailmallException extends RuntimeException {
// 定义一个私有整型属性用于存储异常对应的状态码初始值设置为ResponseEnum.ERROR.getCode()这里ResponseEnum应该是一个枚举类用于定义各种响应状态相关的枚举值如错误码等
// 通过这种方式可以默认给自定义异常赋予一个通用的错误状态码,后续在构造函数中可以根据具体情况进行修改,类型为整数类型。
private int exceptionStatus = ResponseEnum.ERROR.getCode();
public SnailmallException(String msg){
/**
* SnailmallException
* RuntimeExceptionmsg便
* 使ResponseEnum.ERROR.getCode()
*
* @param msg 便
*/
public SnailmallException(String msg) {
super(msg);
}
public SnailmallException(int code,String msg){
/**
* SnailmallException
* RuntimeExceptionmsg
* codeexceptionStatus便便
*
* @param code 便
* @param msg msg
*/
public SnailmallException(int code, String msg) {
super(msg);
exceptionStatus = code;
}
}
}

@ -7,19 +7,34 @@ import lombok.Getter;
* @Date 2018/12/31 20:15
* @CONTACT 317758022@qq.com
* @DESC
* ResponseEnum
* codedesc便使使
*/
@Getter
// 使用Lombok的 @Getter注解会自动为这个枚举类型中的私有属性code和desc生成对应的Getter方法这样在其他地方就可以方便地获取这些属性的值无需手动编写Getter方法使代码更加简洁便于操作枚举中的属性值。
public enum ResponseEnum {
SUCCESS(0,"SUCCESS"),
ERROR(1,"ERROR"),
ILLEGAL_ARGUMENTS(2,"ILLEGAL_ARGUMENTS"),
NEED_LOGIN(10,"NEED_LOGIN");
// 定义一个名为SUCCESS的枚举常量表示操作成功的返回状态其对应的状态码为0描述信息为"SUCCESS"
// 在项目中当某个操作(如数据库查询、业务逻辑处理等)成功完成后,可以使用这个枚举常量来表示成功的结果,方便统一返回格式以及在其他代码中进行结果判断等操作。
SUCCESS(0, "SUCCESS"),
// 定义一个名为ERROR的枚举常量状态码为1描述信息为"ERROR",用于表示出现错误的返回状态,在业务逻辑中如果发生了异常或者不符合预期的情况,可以使用这个枚举常量来表示操作失败,向调用者传达出现错误的信息。
ERROR(1, "ERROR"),
// 定义一个名为ILLEGAL_ARGUMENTS的枚举常量状态码为2描述信息为"ILLEGAL_ARGUMENTS",通常用于表示传入的参数不符合要求(如参数格式错误、缺失必要参数等情况)的返回状态,
// 在进行参数校验的代码逻辑中,如果发现参数不合法,就可以使用这个枚举常量来返回相应的错误提示信息,告知调用者参数存在问题。
ILLEGAL_ARGUMENTS(2, "ILLEGAL_ARGUMENTS"),
// 定义一个名为NEED_LOGIN的枚举常量状态码为10描述信息为"NEED_LOGIN",主要用于表示需要用户登录才能进行后续操作的返回状态,
// 例如在访问某些需要权限的接口时,如果用户未登录,就可以返回这个枚举常量对应的状态信息,提示用户先进行登录操作,再尝试访问相应的资源。
NEED_LOGIN(10, "NEED_LOGIN");
// 定义一个私有整型属性用于存储枚举常量对应的状态码通过构造函数进行初始化赋值在其他地方可以通过Getter方法获取这个状态码用于在返回结果中传递具体的状态标识类型为整数类型。
private int code;
// 定义一个私有字符串属性,用于存储枚举常量对应的描述信息,同样通过构造函数初始化,方便在返回结果中向调用者展示更直观的操作结果描述内容,类型为字符串类型。
private String desc;
ResponseEnum(int code,String desc){
// 枚举类型的构造函数用于初始化每个枚举常量的状态码code和描述信息desc属性在定义枚举常量时会自动调用这个构造函数传入相应的值进行初始化确保每个枚举常量都有对应的状态码和描述内容便于后续使用。
ResponseEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
}

@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
@ -13,67 +12,102 @@ import java.io.Serializable;
* @Date 2018/12/31 20:11
* @CONTACT 317758022@qq.com
* @DESC
* ServerResponse便
* statusmsgdata便便
*/
@Getter
// 使用Lombok的 @Getter注解会自动为这个类中的私有属性status、msg、data生成对应的Getter方法使得在其他地方如调用接口的客户端代码可以方便地获取这些属性的值无需手动编写Getter方法代码更加简洁便于操作类中的属性。
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
// 这个注解用于配置Jackson序列化的行为这里指定了只序列化非空的属性也就是说在将ServerResponse对象转换为JSON格式例如返回给前端时如果属性值为null那么该属性就不会出现在最终的JSON数据中
// 这样可以减少不必要的数据传输使返回的JSON数据更加精简符合实际业务中通常不需要返回大量空值属性的需求。
public class ServerResponse<T> implements Serializable {
// 定义一个私有整型属性用于存储返回结果的状态码通过不同的状态码可以表示操作是成功还是失败以及其他各种不同的业务状态例如可以使用前面定义的ResponseEnum枚举中的状态码值类型为整数类型。
private int status;
// 定义一个私有字符串属性,用于存储返回结果的提示消息,这个消息可以是操作成功的提示、失败的错误信息或者其他相关的说明内容,方便客户端根据消息展示给用户相应的提示信息,类型为字符串类型。
private String msg;
// 定义一个泛型属性用于存储具体的业务数据类型根据实际业务场景而定可以是单个对象、列表、Map等各种数据结构通过泛型可以使这个返回封装类更加灵活适用于不同类型数据的返回类型为泛型类型<T>。
private T data;
public ServerResponse(){}
// 默认的无参构造函数用于创建一个初始状态的ServerResponse对象在某些情况下可能需要先创建对象再设置其各个属性的值提供了一种默认的创建方式不过在实际使用中更多是通过静态方法来创建具体状态的返回实例。
public ServerResponse() {
}
private ServerResponse(int status){
// 私有构造函数用于创建一个只传入状态码的ServerResponse对象主要在类内部的其他构造函数或者静态方法中调用方便根据给定的状态码初始化对象外部一般不直接使用这个构造函数而是通过提供的静态方法来创建返回实例参数为状态码status类型为整数类型。
private ServerResponse(int status) {
this.status = status;
}
private ServerResponse(int status,String msg){
// 私有构造函数用于创建一个传入状态码和提示消息的ServerResponse对象方便在需要指定特定状态码和相应提示消息的情况下初始化对象同样主要在类内部被调用参数为状态码status和提示消息msg分别为整数类型和字符串类型。
private ServerResponse(int status, String msg) {
this.status = status;
this.msg = msg;
}
private ServerResponse(int status,T data){
// 私有构造函数用于创建一个传入状态码和具体数据的ServerResponse对象适用于操作成功并需要返回具体业务数据的场景在类内部通过静态方法等方式调用这个构造函数来创建相应的返回实例参数为状态码status和数据data类型分别为整数类型和泛型类型<T>。
private ServerResponse(int status, T data) {
this.status = status;
this.data = data;
}
private ServerResponse(int status,String msg,T data){
// 私有构造函数用于创建一个传入状态码、提示消息和具体数据的ServerResponse对象是最完整的构造方式可用于各种不同的业务返回情况根据具体的状态、消息以及要返回的数据来初始化对象参数分别为状态码status、提示消息msg和数据data对应整数类型、字符串类型和泛型类型<T>。
private ServerResponse(int status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
/**
* @JsonIgnoreJSONJSON
* statusResponseEnum.SUCCESS.getCode()
* truefalse便JavaScript
*/
@JsonIgnore
public boolean isSuccess(){
public boolean isSuccess() {
return this.status == ResponseEnum.SUCCESS.getCode();
}
/**
*
* 便ServerResponse使使
*/
public static <T>ServerResponse<T> createBySuccess(){
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
// 创建一个表示操作成功且只包含默认成功提示消息从ResponseEnum.SUCCESS.getDesc()获取的ServerResponse对象适用于不需要额外返回具体数据的简单成功场景例如一些只需要告知客户端操作已成功完成的接口返回情况返回类型为泛型的ServerResponse<T>,其中<T>根据实际调用情况确定具体类型。
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对象传入的参数message就是要设置的具体提示消息内容适用于需要给客户端传递一些额外的成功相关说明信息的情况例如操作成功后提示用户某些注意事项等返回类型为泛型的ServerResponse<T>。
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对象传入的参数data就是要返回的具体业务数据适用于接口查询操作成功后返回查询到的数据给客户端等场景返回类型为泛型的ServerResponse<T><T>根据实际返回的数据类型确定。
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对象传入的参数message是提示消息data是具体业务数据综合了前面几种成功情况的特点适用于更复杂一些的成功返回场景返回类型为泛型的ServerResponse<T>。
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<>(ResponseEnum.SUCCESS.getCode(), message, data);
}
/**
*
* ServerResponse便使便
*/
public static <T>ServerResponse<T> createByError(){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),ResponseEnum.ERROR.getDesc());
}
public static <T>ServerResponse<T> createByErrorMessage(String msg){
return new ServerResponse<>(ResponseEnum.ERROR.getCode(),msg);
// 创建一个表示操作失败且只包含默认失败提示消息从ResponseEnum.ERROR.getDesc()获取的ServerResponse对象适用于一些通用的、不需要特别说明具体错误原因的失败场景返回类型为泛型的ServerResponse<T>。
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对象传入的参数msg就是要设置的具体错误提示内容适用于根据不同的业务错误情况返回相应的具体错误信息给客户端的场景返回类型为泛型的ServerResponse<T>。
public static <T> ServerResponse<T> createByErrorMessage(String msg) {
return new ServerResponse<>(ResponseEnum.ERROR.getCode(), msg);
}
}
// 创建一个表示操作失败且可以自定义状态码和错误提示消息的ServerResponse对象传入的参数code是要设置的具体状态码msg是错误提示消息适用于一些需要返回特定状态码来表示不同类型失败情况例如不同业务模块的不同错误类型对应不同状态码的场景返回类型为泛型的ServerResponse<T>。
public static <T> ServerResponse<T> createByErrorCodeMessage(int code, String msg) {
return new ServerResponse<>(code, msg);
}
}

@ -2,73 +2,105 @@ package com.njupt.swg.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* cookie
* CookieUtilCookieWebCookie
* Cookie访Cookie便Cookie
*/
@Slf4j
// 通过 @Slf4j注解利用Lombok的功能自动为这个类添加一个名为log的日志记录器用于记录在Cookie操作过程中出现的各种相关信息比如写入、读取、删除Cookie时的具体内容等方便后续排查问题、监控Cookie的使用情况等。
public class CookieUtil {
// 定义一个静态的字符串常量用于指定Cookie的域名这里设置为"oursnail.cn"意味着这个Cookie在该域名下及其子域名下都是有效的具体还取决于浏览器的同源策略等因素
// 在实际应用中要根据项目部署的域名情况进行准确设置确保Cookie能在正确的范围内被识别和使用类型为字符串类型。
private final static String COOKIE_DOMAIN = "oursnail.cn";
// 定义一个静态的字符串常量用于指定Cookie的名称这里设置为"snailmall_login_token"通过这个特定的名称可以在多个Cookie中准确地识别出用于存储用户登录凭证等相关信息的Cookie类型为字符串类型。
private final static String COOKIE_NAME = "snailmall_login_token";
/**
* cookie
* @param response
* @param token
* CookietokenCookie便
*
* @param response HttpServletResponseCookieHttpServletResponse
* @param token Cookie
*/
public static void writeLoginToken(HttpServletResponse response,String token){
Cookie ck = new Cookie(COOKIE_NAME,token);
public static void writeLoginToken(HttpServletResponse response, String token) {
// 创建一个新的Cookie对象使用前面定义的COOKIE_NAME作为Cookie的名称传入的token作为Cookie的值这样就构造好了一个要发送给客户端的Cookie实例。
Cookie ck = new Cookie(COOKIE_NAME, token);
// 设置Cookie的域名通过调用setDomain方法并传入COOKIE_DOMAIN常量使得这个Cookie在指定的域名"oursnail.cn"及其子域名)下有效,确保在相应的网站范围内可以被正确识别和使用。
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//设值在根目录
ck.setHttpOnly(true);//不允许通过脚本访问cookie,避免脚本攻击
ck.setMaxAge(60*60*24*365);//一年,-1表示永久,单位是秒maxage不设置的话cookie就不会写入硬盘只会写在内存只在当前页面有效
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的路径为根目录"/"意味着这个Cookie在整个网站的所有页面该域名下的所有路径都可以被访问到根据业务需求合理设置路径可以控制Cookie的有效访问范围例如设置为特定的子目录则只在该子目录下的页面有效等情况。
ck.setPath("/");
// 设置Cookie的HttpOnly属性为true这是一种安全机制设置后浏览器将不允许通过JavaScript等脚本语言来访问这个Cookie有效地避免了跨站脚本攻击XSS攻击中攻击者通过脚本获取用户登录凭证等敏感信息的风险提高了系统的安全性。
ck.setHttpOnly(true);
// 设置Cookie的最大生存时间有效期为一年通过将秒数换算60 * 60 * 24 * 365后设置给setMaxAge方法单位是秒这里表示这个Cookie从写入客户端开始在一年内都有效
// 如果设置为 -1 则表示永久有效不过实际中永久有效的情况要谨慎使用考虑到安全性和用户可能主动注销等因素若不设置这个属性即默认值Cookie就只会存在于浏览器内存中只在当前页面有效关闭页面后就会被清除根据业务需求合理设置有效期很重要。
ck.setMaxAge(60 * 60 * 24 * 365);
// 使用日志记录器记录要写入的Cookie的名称和值方便在运行时查看具体写入的Cookie信息例如排查是否写入了正确的登录凭证等情况日志级别根据实际情况可能为INFO等合适的级别用于记录操作相关的重要信息。
log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue());
// 通过HttpServletResponse对象的addCookie方法将构造好并设置好属性的Cookie添加到响应中发送给客户端浏览器使得浏览器能够接收到并存储这个Cookie完成Cookie的写入操作。
response.addCookie(ck);
}
/**
* cookie
* @param request
* @return
* CookieCookie
*
* @param request HttpServletRequestCookieHttpServletRequest
* @return CookieCookienull便
*/
public static String readLoginToken(HttpServletRequest request){
public static String readLoginToken(HttpServletRequest request) {
// 通过HttpServletRequest对象的getCookies方法获取客户端发送过来的所有Cookie返回一个Cookie数组如果请求中没有携带Cookie则返回null这里先判断是否获取到了Cookie数组。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks){
log.info("cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
if (cks!= null) {
// 遍历获取到的Cookie数组对每个Cookie进行检查查找名称与定义的COOKIE_NAME"snailmall_login_token"匹配的Cookie。
for (Cookie ck : cks) {
// 使用日志记录器记录每个Cookie的名称和值方便在运行时查看请求中携带的Cookie具体情况例如排查是否存在预期的登录凭证Cookie以及其值是否正确等情况日志级别可根据实际需求设置为INFO等合适级别。
log.info("cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 通过StringUtils的equals方法这里导入了Apache Commons Lang3的StringUtils类用于方便地进行字符串比较操作避免空指针等问题比较当前Cookie的名称和定义的COOKIE_NAME是否相等
// 如果相等说明找到了存储用户登录凭证的Cookie返回其值以便后续在服务端进行相应的用户身份验证等操作。
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
log.info("return cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
return ck.getValue();
}
}
}
// 如果遍历完所有Cookie都没有找到名称匹配的Cookie说明没有获取到有效的用户登录凭证信息返回null给调用者。
return null;
}
/**
*
* @param request
* @param response
* CookieCookie0Cookie
*
* @param request HttpServletRequestCookie便CookieHttpServletRequest
* @param response HttpServletResponseCookie0CookieCookieHttpServletResponse
*/
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
// 通过HttpServletRequest对象的getCookies方法获取客户端发送过来的所有Cookie返回一个Cookie数组如果请求中没有携带Cookie则返回null先判断是否获取到了Cookie数组。
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck:cks) {
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
if (cks!= null) {
// 遍历获取到的Cookie数组查找名称与定义的COOKIE_NAME"snailmall_login_token"匹配的Cookie找到后进行删除操作。
for (Cookie ck : cks) {
if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
// 设置要删除的Cookie的域名与写入Cookie时设置的域名保持一致通过COOKIE_DOMAIN常量指定确保删除的是正确域名下对应的Cookie。
ck.setDomain(COOKIE_DOMAIN);
// 设置Cookie的路径为根目录"/"同样要和写入时的路径设置一致保证准确删除对应的Cookie避免因路径不一致导致删除失败的情况。
ck.setPath("/");
ck.setMaxAge(0);//0表示消除此cookie
log.info("del cookieName:{},cookieBValue:{}",ck.getName(),ck.getValue());
// 设置Cookie的最大生存时间有效期为0表示这个Cookie立即失效浏览器接收到这样设置后的Cookie后会自动将其从本地存储中删除从而实现了删除登录凭证Cookie的目的。
ck.setMaxAge(0);
// 使用日志记录器记录要删除的Cookie的名称和值等信息方便在运行时查看具体的删除操作情况例如确认是否删除了正确的Cookie等日志级别可根据实际需求设置为INFO等合适级别。
log.info("del cookieName:{},cookieBValue:{}", ck.getName(), ck.getValue());
// 通过HttpServletResponse对象的addCookie方法将修改了属性主要是设置有效期为0后的Cookie添加到响应中发送给客户端让浏览器执行删除操作完成Cookie的删除过程之后遇到return语句直接结束方法执行。
response.addCookie(ck);
return;
}
}
}
}
}
}

@ -4,65 +4,115 @@ import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @DESC
* DateTimeUtilJoda-Time便
* `Date` `Date` `Date` 便
*/
public class DateTimeUtil {
//joda-time
//str->Date
//Date->str
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
// joda-time
// 以下是使用Joda-Time库进行时间处理相关的代码Joda-Time库提供了比Java标准库中 `java.util.Date` 和 `SimpleDateFormat` 更易用、功能更丰富且不易出错的日期时间处理方式,在很多项目中被广泛用于时间相关的操作。
// str->Date
// Date->str
// 定义一个静态的字符串常量,用于表示标准的日期时间格式,这里采用了 "yyyy-MM-dd HH:mm:ss" 的格式,即年-月-日 时:分:秒的形式,
// 在很多业务场景中,如数据库存储时间、接口传输时间数据等经常会使用这种通用的日期时间格式,方便统一数据格式以及进行不同系统间的交互和数据展示等操作,类型为字符串类型。
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static Date strToDate(String dateTimeStr, String formatStr){
/**
* `Date` 使Joda-Time `Date`
*
* @param dateTimeStr `formatStr` "2024-12-18 10:30:00"
* @param formatStr "yyyy-MM-dd HH:mm:ss"
* @return `Date` Joda-Time `Date` `Date`
*/
public static Date strToDate(String dateTimeStr, String formatStr) {
// 根据传入的日期时间格式字符串(`formatStr`)创建一个 `DateTimeFormatter` 对象,它用于定义如何解析输入的日期时间字符串,类似于 `SimpleDateFormat` 在Java标准库中的作用但功能更强大且更易于使用是Joda-Time库中进行日期时间格式化和解析的关键类之一。
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
// 使用创建好的 `DateTimeFormatter` 对象的 `parseDateTime` 方法,将输入的日期时间字符串(`dateTimeStr`)解析为一个 `DateTime` 对象,`DateTime` 是Joda-Time库中表示日期时间的核心类提供了丰富的日期时间操作方法。
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
// 通过 `DateTime` 对象的 `toDate` 方法,将 `DateTime` 对象转换为Java标准库中的 `Date` 对象,这样就完成了从指定格式字符串到 `Date` 对象的转换方便在Java代码中后续使用标准的 `Date` 相关操作方法进行处理。
return dateTime.toDate();
}
public static String dateToStr(Date date,String formatStr){
if(date == null){
/**
* `Date` Joda-Time `Date`
*
* @param date `Date` `Date`
* @param formatStr "yyyy-MM-dd" --
* @return `date` `null` `StringUtils.EMPTY` 便
*/
public static String dateToStr(Date date, String formatStr) {
if (date == null) {
return StringUtils.EMPTY;
}
// 使用传入的 `Date` 对象创建一个 `DateTime` 对象以便利用Joda-Time库的功能进行格式化操作`DateTime` 类提供了更灵活方便的格式化方法来将日期时间转换为指定格式的字符串。
DateTime dateTime = new DateTime(date);
return dateTime.toString(formatStr);
}
//固定好格式
public static Date strToDate(String dateTimeStr){
/**
* `Date` 使`STANDARD_FORMAT`
* "yyyy-MM-dd HH:mm:ss" 便使
*
* @param dateTimeStr "yyyy-MM-dd HH:mm:ss" "2024-12-18 10:30:00"
* @return `Date` Joda-Time `Date`
*/
public static Date strToDate(String dateTimeStr) {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
return dateTime.toDate();
}
public static String dateToStr(Date date){
if(date == null){
/**
* `Date` "yyyy-MM-dd HH:mm:ss" `Date` 使
* `Date` `null`便
*
* @param date `Date` `Date`
* @return "yyyy-MM-dd HH:mm:ss" `date` `null` `StringUtils.EMPTY`
*/
public static String dateToStr(Date date) {
if (date == null) {
return StringUtils.EMPTY;
}
DateTime dateTime = new DateTime(date);
return dateTime.toString(STANDARD_FORMAT);
}
//Date -> 时间戳
/**
* `Date` 19701100:00:00 UTC使Java `SimpleDateFormat`
* `Date` 使 `ParseException`
*
* @param date `Date` `Date`
* @return 19701100:00:00 UTC `Date` `date` `null` `null`便 `Long`
* @throws ParseException 使 `SimpleDateFormat`
*/
public static Long dateToChuo(Date date) throws ParseException {
if(date == null){
if (date == null) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
// 创建一个 `SimpleDateFormat` 对象,指定日期时间格式为标准的 "yyyy-MM-dd HH:mm:ss",用于将 `Date` 对象格式化为对应的字符串形式,以便后续解析为时间戳。
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 使用 `SimpleDateFormat` 对象的 `parse` 方法,先将 `Date` 对象转换为字符串(通过 `String.valueOf(date)` 获取 `Date` 对象对应的字符串表示),再将这个字符串解析为对应的日期时间,然后通过 `getTime` 方法获取从1970年1月1日00:00:00 UTC到这个日期时间的毫秒数即时间戳完成转换操作。
return format.parse(String.valueOf(date)).getTime();
}
/**
* `Date`
*
*
* @param args 使
* @throws ParseException `dateToChuo` `ParseException`
*/
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
String time="1970-01-06 11:45:55";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = "1970-01-06 11:45:55";
Date date = format.parse(time);
System.out.print("Format To times:"+date.getTime());
System.out.print("Format To times:" + date.getTime());
}
}
}

@ -8,119 +8,168 @@ import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import java.io.IOException;
import java.text.SimpleDateFormat;
/**
* jackson
* JacksonJavaJSONJSONJSON
* JacksonObjectMapperJSON
*/
@Slf4j
// 使用 @Slf4j注解通过Lombok库自动为该类生成一个名为log的日志记录器方便在序列化和反序列化出现问题时记录相关信息便于后续排查错误和监控数据处理情况。
public class JsonUtil {
// 创建一个静态的ObjectMapper对象ObjectMapper是Jackson库中核心的类负责处理JSON数据的序列化和反序列化工作在这里作为整个工具类中进行JSON操作的基础实例在类加载时就进行初始化后续所有的序列化和反序列化方法都会使用到它。
private static ObjectMapper objectMapper = new ObjectMapper();
static {
//所有字段都列入进行转换
// 以下是在静态代码块中对ObjectMapper对象进行的一系列配置操作这些配置会影响整个工具类中使用该ObjectMapper进行JSON处理的行为。
// 所有字段都列入进行转换
// 设置序列化时包含所有字段即不管字段的值是否为null在将Java对象转换为JSON字符串时都会包含这些字段通过JsonSerialize.Inclusion.ALWAYS配置来实现
// 这样可以确保JSON数据完整地反映Java对象的结构避免因某些字段为null而被默认忽略的情况如果有业务需求要求完整展示对象结构的话
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
//取消默认转换timestamp形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
//忽略空bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
//统一时间的格式
// 取消默认转换timestamp形式
// 配置序列化时不将日期类型的数据转换为时间戳形式默认情况下Jackson可能会将Date类型转换为时间戳而是按照后续设置的日期格式进行转换
// 通过将SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS设置为false来实现这样可以使得日期在JSON中的表示更符合常见的日期字符串格式便于阅读和理解也便于与其他系统进行数据交互时的日期格式统一。
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// 忽略空bean转json的错误
// 配置在将一个空的Java对象没有任何属性或者属性值都为null的对象通常称为空Bean转换为JSON字符串时忽略可能出现的错误
// 将SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS设置为false避免因为空对象转换问题导致整个序列化过程失败提高了程序的健壮性特别是在某些业务场景下可能会出现临时的空对象需要转换为JSON的情况。
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
// 统一时间的格式
// 设置日期格式通过创建一个SimpleDateFormat对象并指定日期格式为DateTimeUtil.STANDARD_FORMAT假设DateTimeUtil类中定义了通用的日期时间格式比如"yyyy-MM-dd HH:mm:ss"
// 然后将这个SimpleDateFormat对象设置给ObjectMapper使得在序列化和反序列化涉及日期类型数据时都按照这个统一的格式进行处理保证了日期数据在JSON中的格式一致性方便在不同模块间传递和解析时间相关的数据。
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
//忽略json存在属性但是java对象不存在属性的错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
// 忽略json存在属性但是java对象不存在属性的错误
// 配置在反序列化将JSON字符串转换为Java对象忽略JSON数据中存在但Java对象中不存在对应属性的情况
// 将DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES设置为false避免因为JSON数据结构和Java对象结构不完全匹配而导致反序列化失败
// 这在处理来自不同数据源的JSON数据或者JSON数据结构发生变化但Java对象结构尚未完全同步更新的场景中非常有用提高了反序列化的兼容性和容错性。
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
*
* @param obj
* @param <T>
* @return
* JavaJSONnullnull
* IOExceptionJSONnull
* 便JSON便
*
* @param obj JavaJava<T>
* @param <T> JSONJavaJava<T>
* @return JSONnullnull便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);
// 判断传入的对象是否本身就是字符串类型如果是则直接返回该字符串不需要进行JSON序列化操作
// 否则使用ObjectMapper的writeValueAsString方法将对象转换为JSON字符串并返回。
return obj instanceof String? (String) obj : objectMapper.writeValueAsString(obj);
} catch (IOException e) {
log.warn("parse object to string error",e);
// 如果在序列化过程中出现IOException例如对象中有无法序列化的属性、循环引用等问题使用日志记录器记录警告信息
// 包含简单的错误描述以及异常对象本身方便后续排查问题同时返回null表示序列化失败日志级别为WARN表示出现了可继续运行但可能需要关注的情况。
log.warn("parse object to string error", e);
return null;
}
}
/**
* 便
* @param obj
* @param <T>
* @return
* obj2StringJavaJSONJSON
* 便JSONnullnull
*
* @param obj Java<T>Javaobj2String
* @param <T> JSONJavaobj2String<T>
* @return JSONnullnullJSON
*/
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);
// 判断传入的对象是否为字符串类型如果是则直接返回该字符串否则使用ObjectMapper的writerWithDefaultPrettyPrinter方法获取一个能够生成美化格式JSON字符串的写入器
// 再通过这个写入器的writeValueAsString方法将对象转换为美化后的JSON字符串并返回。
return obj instanceof String? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (IOException e) {
log.warn("parse object to string error",e);
log.warn("parse object to string error", e);
return null;
}
}
/**
*
* @param str
* @param clazz
* @param <T>
* @return
* JSONJavaJSONUser
* Javanullnull
* IOExceptionJSONnull
*
* @param str JSONJacksonJSONJava
* @param clazz JavaUser.classJacksonJSONJavaClass<T>Java<T>
* @param <T> Javaclazz便JSONJava使<T>
* @return Javanullnull<T>clazzJava便
*/
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);
// 判断传入的clazz参数指定的类是否就是String类如果是表示要将JSON字符串直接转换为String类型的对象其实就是返回原字符串本身
// 否则使用ObjectMapper的readValue方法根据传入的JSON字符串str和指定的类类型clazz将JSON数据解析为对应的Java对象并返回。
return clazz.equals(String.class)? (T) str : objectMapper.readValue(str, clazz);
} catch (IOException e) {
log.warn("parse string to obj error",e);
log.warn("parse string to obj error", e);
return null;
}
}
/**
*
* @param str
* @param typeReference
* @param <T>
* @return
* 使TypeReference
* JSONJavaTypeReferencenullnullnull
*
* @param str JSONJackson
* @param typeReference TypeReferenceTypeReference使
* new TypeReference<List<User>>() {}JSONList<User>TypeReference
* @param <T> JavatypeReference便JSON<T>
* @return JavaTypeReferencenullnull<T>typeReferenceJava便
*/
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));
// 判断传入的typeReference指定的类型是否就是String类如果是表示要将JSON字符串直接作为String类型返回其实就是原字符串本身
// 否则使用ObjectMapper的readValue方法根据传入的JSON字符串str和typeReference指定的复杂对象类型将JSON数据解析为对应的Java对象并返回。
return (T) (typeReference.getType().equals(String.class)? str : objectMapper.readValue(str, typeReference));
} catch (IOException e) {
log.warn("parse string to obj error",e);
log.warn("parse string to obj error", e);
return null;
}
}
/**
*
* @param str
* @param collectionClass
* @param elementClasses
* @param <T>
* @return
* ObjectMapperJavaType
* JSONnull
*
* @param str JSONJackson
* @param collectionClass List.classSet.classClass<?>?
* @param elementClasses List<User>User.classClass<?>便Class<?>...
* @param <T> JavacollectionClasselementClasses便JSON<T>
* @return Javanull<T>Java便
*/
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;
}
}
}
}

@ -4,56 +4,102 @@ import java.security.MessageDigest;
/**
* MD5
* MD5UtilMD5MD5
* UTF-8便
*/
public class MD5Util {
/**
*
*
* @param b MD5MessageDigestdigestbyte[]
* @return
*/
private static String byteArrayToHexString(byte b[]) {
// 创建一个StringBuffer对象用于高效地拼接字符串避免频繁创建新的字符串对象提高性能后续将逐个字节转换后的十六进制字符依次添加到这个StringBuffer中。
StringBuffer resultSb = new StringBuffer();
// 遍历传入的字节数组对每个字节调用byteToHexString方法进行十六进制转换并将转换后的结果添加到StringBuffer中。
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
// 将StringBuffer中的内容转换为字符串并返回这个字符串就是字节数组对应的十六进制表示形式。
return resultSb.toString();
}
/**
* 44
*
* @param b -128 127 byte
* @return 210 "0a"
*/
private static String byteToHexString(byte b) {
// 将传入的字节转换为整数类型方便后续进行位运算等操作由于字节类型在Java中是有符号的其范围是 -128 到 127所以转换后的整数可能是负数。
int n = b;
// 如果转换后的整数是负数通过加上256将其转换为正数范围0 到 255这是因为十六进制表示时是基于无符号数的范围进行处理的确保后续计算能正确对应十六进制值。
if (n < 0)
n += 256;
// 获取字节转换后的整数的高4位相当于除以16取整用于对应十六进制的高位数字例如对于字节值为20十六进制为 14这里得到的就是1。
int d1 = n / 16;
// 获取字节转换后的整数的低4位相当于除以16取余用于对应十六进制的低位数字对于上面的例子这里得到的就是4。
int d2 = n % 16;
// 通过查找预定义的十六进制字符数组hexDigits获取对应高4位和低4位的十六进制字符并将它们拼接起来返回例如对于上面的例子会返回 "14",也就是十六进制表示形式。
return hexDigits[d1] + hexDigits[d2];
}
/**
* MD5
* MD5MD5使MD5
* MD5 `null`
*
* @param origin
* @param charsetname
* @return
* @param origin MD5
* @param charsetname 使 `null` 使
* @return MD5 `null`
*/
private static String MD5Encode(String origin, String charsetname) {
// 先初始化一个变量,用于存储最终的加密结果字符串,初始值设置为原始字符串本身,后续会根据加密操作进行更新。
String resultString = null;
try {
resultString = new String(origin);
// 获取MD5算法的MessageDigest实例用于执行MD5加密操作通过传入算法名称 "MD5" 调用MessageDigest的getInstance方法来获取如果不支持指定的算法会抛出异常。
MessageDigest md = MessageDigest.getInstance("MD5");
// 判断传入的字符编码名称是否为 `null` 或者空字符串如果是则使用默认编码方式将原始字符串转换为字节数组后进行MD5加密否则使用指定的字符编码将原始字符串转换为字节数组再进行加密操作。
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
} catch (Exception exception) {
// 当前代码只是简单地捕获了可能出现的异常例如获取MD5算法实例失败、字符编码不支持等异常情况但没有做进一步的处理在实际应用中应该根据具体业务需求完善异常处理逻辑比如记录日志、返回特定的错误提示等。
}
// 将最终得到的加密结果字符串转换为大写形式并返回MD5加密结果通常以十六进制字符串表示大写形式方便统一格式展示以及后续的比较等操作例如与存储在数据库中的加密后的密码进行比对验证等情况
return resultString.toUpperCase();
}
/**
* 使UTF-8MD5MD5Encode "utf-8"UTF-8MD5
* MD5
*
* @param origin MD5
* @return UTF-8MD5
*/
public static String MD5EncodeUtf8(String origin) {
//这里可以加盐
// 这里可以加盐例如可以在origin字符串前后添加特定的盐值字符串后再进行加密不过当前只是简单调用MD5Encode方法进行常规的MD5加密没有实际添加盐值操作。
return MD5Encode(origin, "utf-8");
}
/**
* MD5 "123456" MD5
*
*
* @param args 使
*/
public static void main(String[] args) {
System.out.println(MD5EncodeUtf8("123456"));
}
/**
* 0 "0"10 "a"
* 便MD5
*/
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}
}

@ -13,22 +13,43 @@ import org.springframework.stereotype.Component;
* @Date 2019/1/1 17:57
* @CONTACT 317758022@qq.com
* @DESC zkcurator
* ZkClientApache CuratorZooKeeper `Parameters` ZooKeeper
* CuratorFrameworkFactory便ZooKeeperZooKeeper
*/
@Component
// 使用Spring的@Component注解将这个类标记为一个组件意味着Spring容器在扫描到这个类时会将其纳入管理范围能够进行依赖注入等操作方便在项目的其他地方使用这个类创建的ZooKeeper客户端实例。
public class ZkClient {
@Autowired
// 通过Spring的 @Autowired注解进行依赖注入将 `Parameters` 类的实例注入到当前类中,`Parameters` 类应该是用于存储项目相关配置参数的在这里主要是获取ZooKeeper服务器的连接字符串等配置信息以便后续构建ZooKeeper客户端时使用。
private Parameters parameters;
/**
* @BeanSpringBeanSpringCuratorFrameworkBean
* BeanZooKeeper使ZooKeeperBeanZooKeeper
*
* @return CuratorFrameworkZooKeeperZooKeeperCuratorFramework
*/
@Bean
public CuratorFramework getZkClient(){
public CuratorFramework getZkClient() {
CuratorFrameworkFactory.Builder builder= CuratorFrameworkFactory.builder()
// 使用CuratorFrameworkFactory的builder方法创建一个构建器对象用于构建CuratorFramework类型的ZooKeeper客户端实例通过链式调用的方式可以方便地设置各种客户端的配置属性。
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
// 通过调用connectString方法设置ZooKeeper服务器的连接字符串这个连接字符串的值从注入的 `Parameters` 类实例中获取(通过调用 `parameters.getZkHost()` 方法),
// 连接字符串通常格式类似 "ip1:port1,ip2:port2,..."用于指定要连接的ZooKeeper服务器节点的地址和端口信息确保客户端能够准确连接到ZooKeeper集群。
.connectString(parameters.getZkHost())
// 设置连接超时时间为3000毫秒3秒通过调用connectionTimeoutMs方法进行设置意味着客户端在尝试连接ZooKeeper服务器时如果在3秒内无法建立连接将会判定连接超时并可能触发相应的异常处理或者重试逻辑
// 根据业务需求合理设置连接超时时间很重要,避免长时间等待连接导致程序响应缓慢等问题。
.connectionTimeoutMs(3000)
// 设置重试策略,这里使用了 `RetryNTimes` 重试策略它表示客户端在连接ZooKeeper服务器出现失败时会进行最多5次重试每次重试间隔10毫秒通过创建 `RetryNTimes` 实例并传入重试次数和重试间隔时间参数来配置,
// 合理的重试策略有助于提高客户端在面对网络波动等临时连接问题时的健壮性确保能够成功连接到ZooKeeper服务器并进行后续操作。
.retryPolicy(new RetryNTimes(5, 10));
// 通过构建器对象的build方法构建出最终的CuratorFramework类型的ZooKeeper客户端实例此时客户端实例已经根据前面设置的配置属性进行了相应的初始化但还未启动。
CuratorFramework framework = builder.build();
// 调用客户端实例的start方法启动ZooKeeper客户端启动后客户端就可以与ZooKeeper服务器进行通信例如发送请求获取节点数据、创建或删除节点等操作完成整个客户端的创建和启动流程最后返回这个启动好的客户端实例。
framework.start();
return framework;
}
}
}
Loading…
Cancel
Save