Compare commits

...

3 Commits

Author SHA1 Message Date
陈萱 2cb4616fa2 Merge remote-tracking branch 'refs/remotes/origin/feature/cx' into gsh_branch
9 months ago
pb4nq52pf 8054e530c9 accept pr2
9 months ago
pb4nq52pf b869e6fe81 accept pr
9 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;
}
}

@ -70,6 +70,66 @@ import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* @Author swg.
* @Date 2019/1/3 19:13
* @CONTACT 317758022@qq.com
* @DESC
*/
@Service
@Slf4j
public class FileServiceImpl implements IFileService{
@Override
public String upload(MultipartFile file, String path) {
String fileName = file.getOriginalFilename();
//扩展名
//abc.jpg
String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);
String uploadFileName = UUID.randomUUID().toString()+"."+fileExtensionName;
log.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}",fileName,path,uploadFileName);
File fileDir = new File(path);
if(!fileDir.exists()){
fileDir.setWritable(true);
fileDir.mkdirs();
}
log.info("【文件上传路径为:{}】",fileDir);
File targetFile = new File(path,uploadFileName);
try {
file.transferTo(targetFile);
//文件已经上传成功了
log.info("【文件上传本地服务器成功】");
FtpUtil.uploadFile(Lists.newArrayList(targetFile));
//已经上传到ftp服务器上
log.info("【文件上传到文件服务器成功】");
targetFile.delete();
log.info("【删除本地文件】");
} catch (IOException e) {
log.error("上传文件异常",e);
return null;
}
//A:abc.jpg
//B:abc.jpg
return targetFile.getName();
}
}
package com.njupt.swg.service;
import com.google.common.collect.Lists;
import com.njupt.swg.common.utils.FtpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* @Author swg.
* @Date 2019/1/3 19:13
@ -172,6 +232,123 @@ import java.util.UUID;
// 使用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);
// 从原始文件名中提取文件的扩展名,通过查找文件名中最后一个 "." 出现的位置,然后取其后面的字符串部分,得到文件的扩展名,
// 例如对于文件名 "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()) {
fileDir.setWritable(true);
fileDir.mkdirs();
}
// 根据传入的文件上传路径创建一个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);
// 如果在文件上传过程包括本地保存或者上传到FTP服务器等操作中出现IOException异常使用日志记录器记录详细的错误信息包括异常堆栈信息
// 方便后续查看日志来排查具体是哪个环节出现了I/O相关的问题例如是文件传输失败、FTP连接异常还是其他文件操作异常等情况同时返回null表示文件上传失败。
return null;
}
//A:abc.jpg
//B:abc.jpg
return targetFile.getName();
// 返回上传后文件在服务器上对应的文件名这里是经过重命名后的新文件名即之前生成的uploadFileName
// 这个文件名可以在其他地方如数据库中记录、返回给前端用于构建文件访问链接等使用用于标识和访问已经上传到FTP服务器上的文件完成文件上传操作并返回相应的文件名结果。
}
}
package com.njupt.swg.service;
import com.google.common.collect.Lists;
import com.njupt.swg.common.utils.FtpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
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
// 使用Lombok的 @Slf4j注解自动生成名为log的SLF4J日志记录器用于在文件上传的各个步骤中记录关键信息如文件名称、路径等以及出现异常情况时记录详细的错误信息
// 方便后续查看日志来了解文件上传的执行情况排查可能出现的问题例如文件上传失败的原因、是否成功传输到FTP服务器等情况。
public class FileServiceImpl implements IFileService {
/**

@ -15,6 +15,19 @@ package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author swg.
* @Date 2019/1/3 19:12
* @CONTACT 317758022@qq.com
* @DESC
*/
public interface IFileService {
String upload(MultipartFile file, String path);
}
package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author swg.
* @Date 2019/1/3 19:12
@ -40,6 +53,38 @@ 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);
}
package com.njupt.swg.service;
import org.springframework.web.multipart.MultipartFile;
/**
* IFileService
* 使

@ -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