chore(assets): 参赛提交规范红线修复(ASCII 化 + 相对路径)
按《参赛成果物提交规范·赛道一》§6 红线: - samples/ 目录改名 sample/(git mv,保留历史) - 10 个中日文样本文件 + docs 参赛手册 PDF 重命名为 ASCII (requirements_*/template_*/rules_*/contestant-handbook.pdf) - tests/test_zh_template.py 硬编码绝对路径 D:\00_project\Genesis 改为相对路径 - 全局更新 21 个活动文件引用;历史日志/审查文档不改(追加说明记录) 全量 pytest 431 passed / 99.15%
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.stock</groupId>
|
||||
<artifactId>stock-trade-system</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.stock</groupId>
|
||||
<artifactId>trade-common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>trade-common</name>
|
||||
<description>trade-common</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger Annotations -->
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>2.2.20</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JJWT (Java JWT) -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringDoc OpenAPI UI -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security Core -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>5.7.11</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security Config -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>5.7.11</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
<version>5.7.11</version>
|
||||
</dependency>
|
||||
|
||||
<!-- PageHelper -->
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||
<version>1.4.7</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.stock.common.dal.dataobject;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础 DO 类,所有模块的 DO 类都应继承此类
|
||||
*
|
||||
* @author TraeAI
|
||||
*/
|
||||
@Data
|
||||
public abstract class BaseDO implements Serializable {
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 最后更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 创建者,例如:userId
|
||||
* TODO: 考虑从上下文中自动获取创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String creator;
|
||||
|
||||
/**
|
||||
* 更新者,例如:userId
|
||||
* TODO: 考虑从上下文中自动获取更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updater;
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
private Boolean deleted;
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.stock.common.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 通用返回结果
|
||||
*
|
||||
* @param <T> 数据泛型
|
||||
*/
|
||||
@Data
|
||||
@Validated
|
||||
public class CommonResult<T> implements Serializable {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 返回数据
|
||||
*/
|
||||
private T data;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
public static <T> CommonResult<T> success(T data) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.setCode(200); // 默认成功码为200
|
||||
result.setData(data);
|
||||
result.setMsg("成功");
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(Integer code, String message) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.setCode(code);
|
||||
result.setMsg(message);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 可根据需要添加更多静态方法,例如处理特定错误码等
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.stock.trading.common.api;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 通用API返回对象
|
||||
* @param <T>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class CommonResult<T> {
|
||||
private long code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
protected CommonResult(long code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
* @param data 获取的数据
|
||||
*/
|
||||
public static <T> CommonResult<T> success(T data) {
|
||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
* @param data 获取的数据
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> success(T data, String message) {
|
||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param errorCode 错误码
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(IErrorCode errorCode) {
|
||||
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param errorCode 错误码
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(IErrorCode errorCode, String message) {
|
||||
return new CommonResult<T>(errorCode.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(String message) {
|
||||
return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> failed() {
|
||||
return failed(ResultCode.FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数验证失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> validateFailed() {
|
||||
return failed(ResultCode.VALIDATE_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数验证失败返回结果
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> validateFailed(String message) {
|
||||
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未登录返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> unauthorized(T data) {
|
||||
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> forbidden(T data) {
|
||||
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.stock.trading.common.api;
|
||||
|
||||
/**
|
||||
* 封装API的错误码
|
||||
*/
|
||||
public interface IErrorCode {
|
||||
long getCode();
|
||||
|
||||
String getMessage();
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.stock.trading.common.api;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页数据封装类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class PageResult<T> {
|
||||
private Long pageNum;
|
||||
private Long pageSize;
|
||||
private Long totalPage;
|
||||
private Long total;
|
||||
private List<T> list;
|
||||
|
||||
/**
|
||||
* 将MyBatisPageHelper分页后的list转为分页信息
|
||||
*/
|
||||
public static <T> PageResult<T> restPage(List<T> list) {
|
||||
PageResult<T> result = new PageResult<T>();
|
||||
//PageInfo<T> pageInfo = new PageInfo<T>(list);
|
||||
//result.setTotalPage(pageInfo.getPages());
|
||||
//result.setPageNum(pageInfo.getPageNum());
|
||||
//result.setPageSize(pageInfo.getPageSize());
|
||||
//result.setTotal(pageInfo.getTotal());
|
||||
//result.setList(pageInfo.getList());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.stock.trading.common.api;
|
||||
|
||||
/**
|
||||
* 常用API返回对象状态码
|
||||
*/
|
||||
public enum ResultCode implements IErrorCode {
|
||||
SUCCESS(200, "操作成功"),
|
||||
FAILED(500, "操作失败"),
|
||||
VALIDATE_FAILED(404, "参数检验失败"),
|
||||
UNAUTHORIZED(401, "暂未登录或token已经过期"),
|
||||
FORBIDDEN(403, "没有相关权限");
|
||||
private long code;
|
||||
private String message;
|
||||
|
||||
private ResultCode(long code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public long getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.stock.trading.common.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 基础实体类,包含创建时间和更新时间
|
||||
*/
|
||||
@Data
|
||||
public abstract class BaseEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.stock.trading.common.exception;
|
||||
|
||||
import com.stock.trading.common.api.IErrorCode;
|
||||
|
||||
/**
|
||||
* 自定义API异常
|
||||
*/
|
||||
public class ApiException extends RuntimeException {
|
||||
private IErrorCode errorCode;
|
||||
|
||||
public ApiException(IErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public ApiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ApiException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ApiException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public IErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.stock.trading.common.exception;
|
||||
|
||||
import com.stock.trading.common.api.CommonResult;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理自定义异常
|
||||
*/
|
||||
@ResponseBody
|
||||
@ExceptionHandler(value = ApiException.class)
|
||||
public CommonResult<String> handle(ApiException e) {
|
||||
if (e.getErrorCode() != null) {
|
||||
return CommonResult.failed(e.getErrorCode());
|
||||
}
|
||||
return CommonResult.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.stock.trading.common.util;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 日期工具类
|
||||
*/
|
||||
public class DateUtil {
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
public static String formatDateTime(Date date) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
public static String formatDate(Date date) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
*/
|
||||
public static String formatTime(Date date) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
|
||||
return sdf.format(date);
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package com.stock.trading.common.util;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JwtToken生成工具类
|
||||
* JWT token的格式:header.payload.signature
|
||||
* header的格式(算法、token的类型):
|
||||
* {
|
||||
* "alg": "HS512",
|
||||
* "typ": "JWT"
|
||||
* }
|
||||
* payload的格式(用户名、创建时间、过期时间):
|
||||
* {
|
||||
* "sub": "wang",
|
||||
* "created": 1489079981398,
|
||||
* "exp": 1489689981
|
||||
* }
|
||||
* signature的生成算法:
|
||||
* HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
|
||||
*/
|
||||
public class JwtTokenUtil {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
|
||||
private static final String CLAIM_KEY_USERNAME = "sub";
|
||||
private static final String CLAIM_KEY_CREATED = "created";
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
@Value("${jwt.expiration}")
|
||||
private Long expiration;
|
||||
@Value("${jwt.tokenHead}")
|
||||
private String tokenHead;
|
||||
|
||||
/**
|
||||
* 根据负责生成JWT的token
|
||||
*/
|
||||
private String generateToken(Map<String, Object> claims) {
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setExpiration(generateExpirationDate())
|
||||
.signWith(SignatureAlgorithm.HS512, secret)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取JWT中的负载
|
||||
*/
|
||||
private Claims getClaimsFromToken(String token) {
|
||||
Claims claims = null;
|
||||
try {
|
||||
claims = Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("JWT格式验证失败: {}", token);
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成token的过期时间
|
||||
*/
|
||||
private Date generateExpirationDate() {
|
||||
return new Date(System.currentTimeMillis() + expiration * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取登录用户名
|
||||
*/
|
||||
public String getUserNameFromToken(String token) {
|
||||
String username;
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
username = claims.getSubject();
|
||||
} catch (Exception e) {
|
||||
username = null;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证token是否还有效
|
||||
*
|
||||
* @param token 客户端传入的token
|
||||
* @param userDetails 从数据库中查询出来的用户信息
|
||||
*/
|
||||
public boolean validateToken(String token, UserDetails userDetails) {
|
||||
String username = getUserNameFromToken(token);
|
||||
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断token是否已经失效
|
||||
*/
|
||||
private boolean isTokenExpired(String token) {
|
||||
Date expiredDate = getExpiredDateFromToken(token);
|
||||
return expiredDate.before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取过期时间
|
||||
*/
|
||||
private Date getExpiredDateFromToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
return claims.getExpiration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户信息生成token
|
||||
*/
|
||||
public String generateToken(UserDetails userDetails) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
|
||||
claims.put(CLAIM_KEY_CREATED, new Date());
|
||||
return generateToken(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断token是否可以被刷新
|
||||
*/
|
||||
public boolean canRefresh(String token) {
|
||||
return !isTokenExpired(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
public String refreshToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
claims.put(CLAIM_KEY_CREATED, new Date());
|
||||
return generateToken(claims);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.trade.common.api;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页数据封装类
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class CommonPage<T> {
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
private Integer totalPage;
|
||||
private Long total;
|
||||
private List<T> list;
|
||||
|
||||
/**
|
||||
* 将PageHelper分页后的list转为分页信息
|
||||
*/
|
||||
public static <T> CommonPage<T> restPage(List<T> list) {
|
||||
CommonPage<T> result = new CommonPage<>();
|
||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<T> pageInfo = page;
|
||||
result.setTotalPage((int)pageInfo.getPages());
|
||||
result.setPageNum((int)pageInfo.getCurrent());
|
||||
result.setPageSize((int)pageInfo.getSize());
|
||||
result.setTotal(pageInfo.getTotal());
|
||||
result.setList(pageInfo.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将MyBatis Plus分页后的Page转为分页信息
|
||||
*/
|
||||
public static <T> CommonPage<T> restPage(com.baomidou.mybatisplus.extension.plugins.pagination.Page<T> page) {
|
||||
CommonPage<T> result = new CommonPage<>();
|
||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<T> pageInfo = page;
|
||||
result.setTotalPage((int)pageInfo.getPages());
|
||||
result.setPageNum((int)pageInfo.getCurrent());
|
||||
result.setPageSize((int)pageInfo.getSize());
|
||||
result.setTotal(pageInfo.getTotal());
|
||||
result.setList(pageInfo.getRecords());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package com.trade.common.api;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 通用返回对象
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class CommonResult<T> {
|
||||
private long code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
protected CommonResult() {
|
||||
}
|
||||
|
||||
protected CommonResult(long code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
* @param data 获取的数据
|
||||
*/
|
||||
public static <T> CommonResult<T> success(T data) {
|
||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
* @param data 获取的数据
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> success(T data, String message) {
|
||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param errorCode 错误码
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(IErrorCode errorCode) {
|
||||
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param errorCode 错误码
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(IErrorCode errorCode, String message) {
|
||||
return new CommonResult<T>(errorCode.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(String message) {
|
||||
return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> failed() {
|
||||
return failed(ResultCode.FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数验证失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> validateFailed() {
|
||||
return failed(ResultCode.VALIDATE_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数验证失败返回结果
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> validateFailed(String message) {
|
||||
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未登录返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> unauthorized(T data) {
|
||||
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> forbidden(T data) {
|
||||
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.trade.common.api;
|
||||
|
||||
/**
|
||||
* 封装API的错误码
|
||||
* @author Trade Team
|
||||
*/
|
||||
public interface IErrorCode {
|
||||
long getCode();
|
||||
|
||||
String getMessage();
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.trade.common.api;
|
||||
|
||||
/**
|
||||
* 枚举常用API操作码
|
||||
* @author Trade Team
|
||||
*/
|
||||
public enum ResultCode implements IErrorCode {
|
||||
SUCCESS(200, "操作成功"),
|
||||
FAILED(500, "操作失败"),
|
||||
VALIDATE_FAILED(404, "参数检验失败"),
|
||||
UNAUTHORIZED(401, "暂未登录或token已经过期"),
|
||||
FORBIDDEN(403, "没有相关权限");
|
||||
private long code;
|
||||
private String message;
|
||||
|
||||
private ResultCode(long code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.trade.common.config;
|
||||
|
||||
import io.swagger.v3.oas.models.ExternalDocumentation;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springdoc.core.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Swagger基础配置
|
||||
* @author Trade Team
|
||||
*/
|
||||
public abstract class BaseSwaggerConfig {
|
||||
|
||||
/**
|
||||
* 自定义Swagger配置
|
||||
*/
|
||||
public abstract SwaggerProperties swaggerProperties();
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi publicApi() {
|
||||
SwaggerProperties swaggerProperties = swaggerProperties();
|
||||
return GroupedOpenApi.builder()
|
||||
.group(swaggerProperties.getGroup())
|
||||
.pathsToMatch(swaggerProperties.getApiBasePackage())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OpenAPI springShopOpenAPI() {
|
||||
SwaggerProperties swaggerProperties = swaggerProperties();
|
||||
return new OpenAPI()
|
||||
.info(new Info().title(swaggerProperties.getTitle())
|
||||
.description(swaggerProperties.getDescription())
|
||||
.version(swaggerProperties.getVersion())
|
||||
.license(new License().name(swaggerProperties.getLicense()).url(swaggerProperties.getLicenseUrl())))
|
||||
.externalDocs(new ExternalDocumentation()
|
||||
.description(swaggerProperties.getDocDescription())
|
||||
.url(swaggerProperties.getDocUrl()))
|
||||
.components(new io.swagger.v3.oas.models.Components()
|
||||
.addSecuritySchemes("BearerAuth",
|
||||
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")));
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.trade.common.config;
|
||||
|
||||
import com.trade.common.exception.ApiException;
|
||||
import com.trade.common.response.CommonResult;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
* @author Trade Team
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理自定义API异常
|
||||
* @param e API异常
|
||||
* @return 统一结果封装
|
||||
*/
|
||||
@ResponseBody
|
||||
@ExceptionHandler(value = ApiException.class)
|
||||
public CommonResult handle(ApiException e) {
|
||||
if (e.getErrorCode() != null) {
|
||||
return CommonResult.failed(e.getErrorCode());
|
||||
}
|
||||
return CommonResult.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.trade.common.config;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis配置类
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Configuration
|
||||
@MapperScan({"com.trade.common.mapper"})
|
||||
public class MyBatisConfig {
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.trade.common.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* <p>MyBatis Plus 配置类</p>
|
||||
*
|
||||
* @author creator
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启事务管理
|
||||
@MapperScan("com.trade.*.mapper") // 扫描 Mapper 接口,根据实际模块调整或在各模块单独配置
|
||||
public class MyBatisPlusConfig {
|
||||
|
||||
/**
|
||||
* 配置 MyBatis Plus 拦截器
|
||||
*
|
||||
* @return MybatisPlusInterceptor 实例
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
|
||||
// 1. 添加分页插件
|
||||
// DbType 用于指定数据库类型,MySQL为例
|
||||
// optimizeJoin 是否优化left join连接查询,默认为false,建议保持false以保证结果正确性
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
|
||||
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
|
||||
// paginationInnerInterceptor.setOverflow(false);
|
||||
// 设置最大单页限制数量,默认 500 条,-1 不受限制
|
||||
// paginationInnerInterceptor.setMaxLimit(500L);
|
||||
interceptor.addInnerInterceptor(paginationInnerInterceptor);
|
||||
|
||||
// 2. 添加乐观锁插件 (如果需要)
|
||||
// 当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
|
||||
// 需要在实体类的字段上加上 @Version 注解
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
|
||||
// 3. 防止全表更新与删除插件 (如果需要, 注意在生产环境谨慎使用,因为它会阻止没有 WHERE 条件的 UPDATE 和 DELETE 操作)
|
||||
// interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
// 如果使用了 MyBatis Plus 的逻辑删除功能,可以在这里配置全局的逻辑删除字段等
|
||||
// 例如,在 application.yml/properties 中配置:
|
||||
// mybatis-plus.global-config.db-config.logic-delete-field=deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
|
||||
// mybatis-plus.global-config.db-config.logic-delete-value=1 # 逻辑已删除值(默认为 1)
|
||||
// mybatis-plus.global-config.db-config.logic-not-delete-value=0 # 逻辑未删除值(默认为 0)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.trade.common.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis配置类
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
serializer.setObjectMapper(om);
|
||||
|
||||
// String序列化配置
|
||||
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
|
||||
|
||||
// key采用String的序列化方式
|
||||
redisTemplate.setKeySerializer(stringRedisSerializer);
|
||||
// hash的key也采用String的序列化方式
|
||||
redisTemplate.setHashKeySerializer(stringRedisSerializer);
|
||||
// value序列化方式采用jackson
|
||||
redisTemplate.setValueSerializer(serializer);
|
||||
// hash的value序列化方式采用jackson
|
||||
redisTemplate.setHashValueSerializer(serializer);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.trade.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
/**
|
||||
* Spring Security配置
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
httpSecurity.csrf()
|
||||
.disable()
|
||||
.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/login", "/register", "/swagger-ui/**", "/v3/api-docs/**")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.trade.common.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springdoc.core.GroupedOpenApi;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* <p>SpringDoc OpenAPI 配置类</p>
|
||||
* <p>用于生成API接口文档</p>
|
||||
*
|
||||
* @author creator
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class SpringDocConfig {
|
||||
|
||||
@Value("${spring.application.name:default-service}")
|
||||
private String applicationName;
|
||||
|
||||
/**
|
||||
* 配置 OpenAPI 基本信息
|
||||
*
|
||||
* @return OpenAPI 实例
|
||||
*/
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI(
|
||||
@Value("${springdoc.version:1.0.0}") String appVersion,
|
||||
@Value("${springdoc.title:API Documentation}") String title,
|
||||
@Value("${springdoc.description:API documentation for the service}") String description,
|
||||
@Value("${springdoc.contact.name:API Support}") String contactName,
|
||||
@Value("${springdoc.contact.email:[email protected]}") String contactEmail,
|
||||
@Value("${springdoc.contact.url:https://example.com}") String contactUrl,
|
||||
@Value("${springdoc.license.name:Apache 2.0}") String licenseName,
|
||||
@Value("${springdoc.license.url:https://www.apache.org/licenses/LICENSE-2.0.html}") String licenseUrl) {
|
||||
|
||||
// 优化:将 applicationName 用于 title 和 description (如果它们是默认值)
|
||||
String effectiveTitle = title.equals("API Documentation") ? applicationName + " API Documentation" : title;
|
||||
String effectiveDescription = description.equals("API documentation for the service") ?
|
||||
"API documentation for the " + applicationName + " service." : description;
|
||||
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title(effectiveTitle)
|
||||
.version(appVersion)
|
||||
.description(effectiveDescription)
|
||||
.contact(new Contact()
|
||||
.name(contactName)
|
||||
.email(contactEmail)
|
||||
.url(contactUrl))
|
||||
.license(new License()
|
||||
.name(licenseName)
|
||||
.url(licenseUrl)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个默认的 API 分组
|
||||
* <p>
|
||||
* 可以根据需要创建多个 GroupedOpenApi Bean 来对 API 进行分组展示。
|
||||
* 例如,可以按模块、按版本或按访问权限进行分组。
|
||||
* </p>
|
||||
*
|
||||
* @return GroupedOpenApi 实例
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi defaultApiGroup() {
|
||||
// 优化:使用 applicationName 作为 group 名称,使其更具辨识度
|
||||
String groupName = applicationName.replace("-service", "") + "-apis";
|
||||
return GroupedOpenApi.builder()
|
||||
.group(groupName) // API 分组的名称
|
||||
.packagesToScan("com.trade."+ applicationName.replace("trade-","") + ".controller") // 指定扫描的包路径,需要根据模块名动态调整
|
||||
// .pathsToMatch("/api/**") // 可以通过路径匹配来包含特定的API
|
||||
// .displayName(applicationName + " APIs") // 分组的显示名称
|
||||
.build();
|
||||
}
|
||||
|
||||
// 示例:为特定模块创建API分组 (如果需要)
|
||||
// @Bean
|
||||
// public GroupedOpenApi userApiGroup() {
|
||||
// return GroupedOpenApi.builder()
|
||||
// .group("user-management")
|
||||
// .packagesToScan("com.trade.user.controller") // 假设用户模块的controller在此包下
|
||||
// .pathsToMatch("/user/**")
|
||||
// .displayName("User Management APIs")
|
||||
// .build();
|
||||
// }
|
||||
|
||||
// @Bean
|
||||
// public GroupedOpenApi productApiGroup() {
|
||||
// return GroupedOpenApi.builder()
|
||||
// .group("product-catalog")
|
||||
// .packagesToScan("com.trade.product.controller") // 假设产品模块的controller在此包下
|
||||
// .pathsToMatch("/product/**")
|
||||
// .displayName("Product Catalog APIs")
|
||||
// .build();
|
||||
// }
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.trade.common.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Swagger属性配置
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@ConfigurationProperties(prefix = "swagger")
|
||||
public class SwaggerProperties {
|
||||
/**
|
||||
* API文档生成基础路径
|
||||
*/
|
||||
private String apiBasePackage;
|
||||
/**
|
||||
* 是否开启Swagger
|
||||
*/
|
||||
private boolean enable;
|
||||
/**
|
||||
* 页面标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 页面描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
private String version;
|
||||
/**
|
||||
* 许可证名称
|
||||
*/
|
||||
private String license;
|
||||
/**
|
||||
* 许可证URL
|
||||
*/
|
||||
private String licenseUrl;
|
||||
/**
|
||||
* 外部文档地址
|
||||
*/
|
||||
private String docUrl;
|
||||
/**
|
||||
* 外部文档描述
|
||||
*/
|
||||
private String docDescription;
|
||||
/**
|
||||
* 分组名称
|
||||
*/
|
||||
private String group;
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.trade.common.constant;
|
||||
|
||||
/**
|
||||
* <p>通用常量类</p>
|
||||
*
|
||||
* @author creator
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public final class CommonConstant {
|
||||
|
||||
private CommonConstant() {
|
||||
// 私有构造函数,防止实例化
|
||||
}
|
||||
|
||||
// --------------------------- 符号常量 ---------------------------
|
||||
public static final String EMPTY_STRING = "";
|
||||
public static final String SPACE = " ";
|
||||
public static final String COMMA = ",";
|
||||
public static final String PERIOD = ".";
|
||||
public static final String COLON = ":";
|
||||
public static final String SEMICOLON = ";";
|
||||
public static final String HYPHEN = "-";
|
||||
public static final String UNDERSCORE = "_";
|
||||
public static final String SLASH = "/";
|
||||
public static final String BACKSLASH = "\\";
|
||||
public static final String PIPE = "|";
|
||||
|
||||
// --------------------------- HTTP 相关常量 ---------------------------
|
||||
/**
|
||||
* HTTP 请求头:认证 (Authorization)
|
||||
*/
|
||||
public static final String HTTP_HEADER_AUTHORIZATION = "Authorization";
|
||||
/**
|
||||
* HTTP 请求头:Bearer Token 前缀
|
||||
*/
|
||||
public static final String HTTP_HEADER_BEARER_PREFIX = "Bearer ";
|
||||
/**
|
||||
* HTTP 请求头:内容类型 (Content-Type)
|
||||
*/
|
||||
public static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
|
||||
/**
|
||||
* HTTP 内容类型:JSON
|
||||
*/
|
||||
public static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8";
|
||||
/**
|
||||
* HTTP 内容类型:表单
|
||||
*/
|
||||
public static final String CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8";
|
||||
/**
|
||||
* HTTP 内容类型:文件上传
|
||||
*/
|
||||
public static final String CONTENT_TYPE_MULTIPART_FORM_DATA = "multipart/form-data";
|
||||
|
||||
// --------------------------- 编码常量 ---------------------------
|
||||
public static final String CHARSET_UTF8 = "UTF-8";
|
||||
public static final String CHARSET_GBK = "GBK";
|
||||
|
||||
// --------------------------- 日期时间格式常量 ---------------------------
|
||||
public static final String DATETIME_FORMAT_DEFAULT = "yyyy-MM-dd HH:mm:ss";
|
||||
public static final String DATE_FORMAT_DEFAULT = "yyyy-MM-dd";
|
||||
public static final String TIME_FORMAT_DEFAULT = "HH:mm:ss";
|
||||
public static final String DATETIME_FORMAT_NO_SEPARATOR = "yyyyMMddHHmmss";
|
||||
public static final String DATE_FORMAT_NO_SEPARATOR = "yyyyMMdd";
|
||||
|
||||
// --------------------------- 缓存相关常量 ---------------------------
|
||||
/**
|
||||
* 缓存键分隔符
|
||||
*/
|
||||
public static final String CACHE_KEY_SEPARATOR = "::";
|
||||
/**
|
||||
* 用户信息缓存键前缀
|
||||
*/
|
||||
public static final String CACHE_USER_PREFIX = "user";
|
||||
/**
|
||||
* Token 缓存键前缀
|
||||
*/
|
||||
public static final String CACHE_TOKEN_PREFIX = "token";
|
||||
|
||||
// --------------------------- 逻辑删除状态 ---------------------------
|
||||
/**
|
||||
* 逻辑未删除
|
||||
*/
|
||||
public static final Integer LOGIC_NOT_DELETED = 0;
|
||||
/**
|
||||
* 逻辑已删除
|
||||
*/
|
||||
public static final Integer LOGIC_DELETED = 1;
|
||||
|
||||
// --------------------------- 通用状态 (启用/禁用) ---------------------------
|
||||
/**
|
||||
* 状态:启用
|
||||
*/
|
||||
public static final Integer STATUS_ENABLED = 1;
|
||||
/**
|
||||
* 状态:禁用
|
||||
*/
|
||||
public static final Integer STATUS_DISABLED = 0;
|
||||
|
||||
// --------------------------- 默认值常量 ---------------------------
|
||||
/**
|
||||
* 默认分页大小
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_SIZE = 10;
|
||||
/**
|
||||
* 默认当前页码
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_NUM = 1;
|
||||
|
||||
// --------------------------- 其他常量 ---------------------------
|
||||
/**
|
||||
* 默认的超级管理员用户ID (示例)
|
||||
*/
|
||||
public static final Long SUPER_ADMIN_ID = 1L;
|
||||
/**
|
||||
* 默认的超级管理员角色代码 (示例)
|
||||
*/
|
||||
public static final String ROLE_SUPER_ADMIN = "ROLE_SUPER_ADMIN";
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.trade.common.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class UserDto implements UserDetails {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String password;
|
||||
private Integer status;
|
||||
private List<String> permissionList;
|
||||
|
||||
public UserDto(Long id, String username, String password, Integer status, List<String> permissionList) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.status = status;
|
||||
this.permissionList = permissionList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return permissionList.stream()
|
||||
.filter(permission -> permission!=null)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return status == 1;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.trade.common.exception;
|
||||
|
||||
/**
|
||||
* 自定义API异常
|
||||
* @author Trade Team
|
||||
*/
|
||||
import com.trade.common.api.IErrorCode;
|
||||
|
||||
public class ApiException extends RuntimeException {
|
||||
private IErrorCode errorCode;
|
||||
|
||||
public ApiException(IErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public ApiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ApiException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ApiException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public IErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.trade.common.exception;
|
||||
|
||||
import com.trade.common.api.IErrorCode;
|
||||
|
||||
/**
|
||||
* 断言处理类,用于抛出各种API异常
|
||||
* @author Trade Team
|
||||
*/
|
||||
public class Asserts {
|
||||
public static void fail(String message) {
|
||||
throw new ApiException(message);
|
||||
}
|
||||
|
||||
public static void fail(IErrorCode errorCode) {
|
||||
throw new ApiException(errorCode);
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.trade.common.exception;
|
||||
|
||||
import com.trade.common.vo.ResultCodeEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* <p>自定义业务异常类</p>
|
||||
*
|
||||
* @author creator
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private final int code;
|
||||
|
||||
/**
|
||||
* 构造器,使用自定义消息
|
||||
*
|
||||
* @param message 异常消息
|
||||
*/
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = ResultCodeEnum.OPERATION_FAILED.getCode(); // 默认为操作失败
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器,使用自定义错误码和消息
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param message 异常消息
|
||||
*/
|
||||
public BusinessException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器,使用 ResultCodeEnum 定义的错误
|
||||
*
|
||||
* @param resultCodeEnum 结果代码枚举
|
||||
*/
|
||||
public BusinessException(ResultCodeEnum resultCodeEnum) {
|
||||
super(resultCodeEnum.getMessage());
|
||||
this.code = resultCodeEnum.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器,使用 ResultCodeEnum 定义的错误,并附加额外信息
|
||||
*
|
||||
* @param resultCodeEnum 结果代码枚举
|
||||
* @param detailMessage 详细错误信息,将追加到枚举定义的消息后
|
||||
*/
|
||||
public BusinessException(ResultCodeEnum resultCodeEnum, String detailMessage) {
|
||||
super(resultCodeEnum.getMessage() + ": " + detailMessage);
|
||||
this.code = resultCodeEnum.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器,包装另一个异常
|
||||
*
|
||||
* @param message 异常消息
|
||||
* @param cause 原始异常
|
||||
*/
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = ResultCodeEnum.INTERNAL_SERVER_ERROR.getCode(); // 默认为内部服务器错误
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器,使用自定义错误码、消息并包装另一个异常
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param message 异常消息
|
||||
* @param cause 原始异常
|
||||
*/
|
||||
public BusinessException(int code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器,使用 ResultCodeEnum 并包装另一个异常
|
||||
*
|
||||
* @param resultCodeEnum 结果代码枚举
|
||||
* @param cause 原始异常
|
||||
*/
|
||||
public BusinessException(ResultCodeEnum resultCodeEnum, Throwable cause) {
|
||||
super(resultCodeEnum.getMessage(), cause);
|
||||
this.code = resultCodeEnum.getCode();
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.trade.common.exception;
|
||||
|
||||
/**
|
||||
* 封装API的错误码
|
||||
* @author Trade Team
|
||||
*/
|
||||
public interface ErrorCode {
|
||||
long getCode();
|
||||
String getMessage();
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package com.trade.common.exception;
|
||||
|
||||
import com.trade.common.vo.ResultCodeEnum;
|
||||
import com.trade.common.vo.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>全局异常处理器</p>
|
||||
* <p>捕获controller层抛出的异常,并返回统一的JSON格式响应</p>
|
||||
*
|
||||
* @author creator
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理自定义业务异常 BusinessException
|
||||
*
|
||||
* @param e BusinessException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.OK) // 通常业务异常返回200,通过code和message区分
|
||||
public Result<?> handleBusinessException(BusinessException e) {
|
||||
log.error("业务异常: {}", e.getMessage(), e);
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验异常 (Query Param / Path Variable)
|
||||
*
|
||||
* @param e ConstraintViolationException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<?> handleConstraintViolationException(ConstraintViolationException e) {
|
||||
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
|
||||
String message = violations.stream()
|
||||
.map(violation -> String.format("%s: %s", getFieldName(violation), violation.getMessage()))
|
||||
.collect(Collectors.joining("; "));
|
||||
log.warn("参数校验失败 (ConstraintViolationException): {}", message);
|
||||
return Result.error(ResultCodeEnum.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
private String getFieldName(ConstraintViolation<?> violation) {
|
||||
String propertyPath = violation.getPropertyPath().toString();
|
||||
// propertyPath 通常是 "methodName.argName.fieldName" 或 "fieldName"
|
||||
// 我们尝试获取最后一个点之后的部分作为字段名
|
||||
int lastDotIndex = propertyPath.lastIndexOf('.');
|
||||
return (lastDotIndex == -1) ? propertyPath : propertyPath.substring(lastDotIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验异常 (RequestBody @Valid)
|
||||
*
|
||||
* @param e MethodArgumentNotValidException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
|
||||
String message = fieldErrors.stream()
|
||||
.map(error -> String.format("%s: %s", error.getField(), error.getDefaultMessage()))
|
||||
.collect(Collectors.joining("; "));
|
||||
log.warn("参数校验失败 (MethodArgumentNotValidException): {}", message);
|
||||
return Result.error(ResultCodeEnum.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数绑定异常 (form-data/x-www-form-urlencoded @Valid)
|
||||
*
|
||||
* @param e BindException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<?> handleBindException(BindException e) {
|
||||
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
|
||||
String message = fieldErrors.stream()
|
||||
.map(error -> String.format("%s: %s", error.getField(), error.getDefaultMessage()))
|
||||
.collect(Collectors.joining("; "));
|
||||
log.warn("参数绑定失败 (BindException): {}", message);
|
||||
return Result.error(ResultCodeEnum.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理缺少请求参数异常
|
||||
*
|
||||
* @param e MissingServletRequestParameterException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
|
||||
String message = String.format("缺少必要的请求参数: %s (类型: %s)", e.getParameterName(), e.getParameterType());
|
||||
log.warn(message);
|
||||
return Result.error(ResultCodeEnum.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数类型不匹配异常
|
||||
*
|
||||
* @param e MethodArgumentTypeMismatchException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
|
||||
String message = String.format("参数类型不匹配: 参数 '%s' 需要类型 '%s', 但提供了值 '%s'",
|
||||
e.getName(), e.getRequiredType() != null ? e.getRequiredType().getSimpleName() : "N/A", e.getValue());
|
||||
log.warn(message, e);
|
||||
return Result.error(ResultCodeEnum.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理HTTP请求方法不支持异常
|
||||
*
|
||||
* @param e HttpRequestMethodNotSupportedException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
public Result<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
|
||||
String message = String.format("不支持的请求方法: %s. 支持的方法有: %s", e.getMethod(), e.getSupportedHttpMethods());
|
||||
log.warn(message);
|
||||
return Result.error(ResultCodeEnum.FORBIDDEN, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理404 NoHandlerFoundException
|
||||
* 需要在 application.properties 中配置 spring.mvc.throw-exception-if-no-handler-found=true
|
||||
* 和 spring.web.resources.add-mappings=false (如果使用了静态资源映射)
|
||||
*
|
||||
* @param e NoHandlerFoundException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public Result<?> handleNoHandlerFoundException(NoHandlerFoundException e) {
|
||||
String message = String.format("接口 [%s %s] 不存在", e.getHttpMethod(), e.getRequestURL());
|
||||
log.warn(message, e);
|
||||
return Result.error(ResultCodeEnum.NOT_FOUND, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理请求体不可读异常
|
||||
*
|
||||
* @param e HttpMessageNotReadableException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
|
||||
log.warn("请求体不可读或JSON格式错误: {}", e.getMessage());
|
||||
return Result.error(ResultCodeEnum.BAD_REQUEST, "请求体不可读或JSON格式错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理其他所有未捕获的运行时异常
|
||||
*
|
||||
* @param e RuntimeException 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<?> handleRuntimeException(RuntimeException e) {
|
||||
log.error("服务器发生运行时异常: {}", e.getMessage(), e);
|
||||
return Result.error(ResultCodeEnum.INTERNAL_SERVER_ERROR, "服务器发生运行时异常,请联系管理员");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理其他所有未捕获的顶级异常 Exception
|
||||
*
|
||||
* @param e Exception 实例
|
||||
* @return Result 封装的错误响应
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<?> handleException(Exception e) {
|
||||
log.error("服务器发生未知异常: {}", e.getMessage(), e);
|
||||
return Result.error(ResultCodeEnum.INTERNAL_SERVER_ERROR, "服务器发生未知异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package com.trade.common.response;
|
||||
|
||||
import com.trade.common.api.IErrorCode;
|
||||
|
||||
/**
|
||||
* 通用返回对象
|
||||
* @author Trade Team
|
||||
*/
|
||||
public class CommonResult<T> {
|
||||
private long code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
protected CommonResult() {
|
||||
}
|
||||
|
||||
protected CommonResult(long code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
* @param data 获取的数据
|
||||
*/
|
||||
public static <T> CommonResult<T> success(T data) {
|
||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
* @param data 获取的数据
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> success(T data, String message) {
|
||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param errorCode 错误码
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(IErrorCode errorCode) {
|
||||
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param errorCode 错误码
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(IErrorCode errorCode, String message) {
|
||||
return new CommonResult<T>(errorCode.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(String message) {
|
||||
return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> failed() {
|
||||
return failed(ResultCode.FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数验证失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> validateFailed() {
|
||||
return failed(ResultCode.VALIDATE_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数验证失败返回结果
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> validateFailed(String message) {
|
||||
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未登录返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> unauthorized(T data) {
|
||||
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> forbidden(T data) {
|
||||
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
|
||||
}
|
||||
|
||||
public long getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(long code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.trade.common.response;
|
||||
|
||||
import com.trade.common.api.IErrorCode;
|
||||
|
||||
import com.trade.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* 枚举常用API操作码
|
||||
* @author Trade Team
|
||||
*/
|
||||
public enum ResultCode implements IErrorCode {
|
||||
SUCCESS(200, "操作成功"),
|
||||
FAILED(500, "操作失败"),
|
||||
VALIDATE_FAILED(404, "参数检验失败"),
|
||||
UNAUTHORIZED(401, "暂未登录或token已经过期"),
|
||||
FORBIDDEN(403, "没有相关权限");
|
||||
private long code;
|
||||
private String message;
|
||||
|
||||
private ResultCode(long code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package com.trade.common.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* redis操作Service,
|
||||
* 对象和数组都以json形式进行存储
|
||||
* @author Trade Team
|
||||
*/
|
||||
public interface RedisService {
|
||||
/**
|
||||
* 存储数据
|
||||
*/
|
||||
void set(String key, Object value);
|
||||
|
||||
/**
|
||||
* 存储数据并设置过期时间
|
||||
*/
|
||||
void set(String key, Object value, long expire);
|
||||
|
||||
/**
|
||||
* 获取数据
|
||||
*/
|
||||
Object get(String key);
|
||||
|
||||
/**
|
||||
* 设置过期时间
|
||||
*/
|
||||
boolean expire(String key, long expire);
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
*/
|
||||
void del(String key);
|
||||
|
||||
/**
|
||||
* 批量删除数据
|
||||
*/
|
||||
void del(List<String> keys);
|
||||
|
||||
/**
|
||||
* 批量删除key
|
||||
*/
|
||||
Long delByPrefix(String prefix);
|
||||
|
||||
/**
|
||||
* 对某个key的value进行递增操作
|
||||
*/
|
||||
Long increment(String key, Long delta);
|
||||
|
||||
/**
|
||||
* 对某个key的value进行递减操作
|
||||
*/
|
||||
Long decrement(String key, Long delta);
|
||||
|
||||
/**
|
||||
* 获取所有key
|
||||
*/
|
||||
Set<String> keys(String pattern);
|
||||
|
||||
/**
|
||||
* 判断key是否存在
|
||||
*/
|
||||
Boolean hasKey(String key);
|
||||
|
||||
/**
|
||||
* 将数据放入redis的list中
|
||||
*/
|
||||
Long lPush(String key, Object value);
|
||||
|
||||
/**
|
||||
* 将数据放入redis的list中并设置过期时间
|
||||
*/
|
||||
Long lPush(String key, Object value, long expire);
|
||||
|
||||
/**
|
||||
* 从redis的list中获取数据
|
||||
*/
|
||||
Object lPop(String key);
|
||||
|
||||
/**
|
||||
* 将数据放入redis的set中
|
||||
*/
|
||||
Long sAdd(String key, Object... values);
|
||||
|
||||
/**
|
||||
* 将数据放入redis的set中并设置过期时间
|
||||
*/
|
||||
Long sAdd(String key, long expire, Object... values);
|
||||
|
||||
/**
|
||||
* 从redis的set中获取数据
|
||||
*/
|
||||
Set<Object> sMembers(String key);
|
||||
|
||||
/**
|
||||
* 将数据放入redis的hash中
|
||||
*/
|
||||
void hPut(String key, String hashKey, Object value);
|
||||
|
||||
/**
|
||||
* 将数据放入redis的hash中并设置过期时间
|
||||
*/
|
||||
void hPut(String key, String hashKey, Object value, long expire);
|
||||
|
||||
/**
|
||||
* 从redis的hash中获取数据
|
||||
*/
|
||||
Object hGet(String key, String hashKey);
|
||||
|
||||
/**
|
||||
* 将map放入redis的hash中
|
||||
*/
|
||||
void hPutAll(String key, Map<String, Object> map);
|
||||
|
||||
/**
|
||||
* 将map放入redis的hash中并设置过期时间
|
||||
*/
|
||||
void hPutAll(String key, Map<String, Object> map, long expire);
|
||||
|
||||
/**
|
||||
* 从redis的hash中获取所有数据
|
||||
*/
|
||||
Map<String, Object> hGetAll(String key);
|
||||
|
||||
/**
|
||||
* 删除redis的hash中的数据
|
||||
*/
|
||||
void hDel(String key, Object... hashKeys);
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package com.trade.common.service.impl;
|
||||
|
||||
import com.trade.common.service.RedisService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* redis操作Service的实现类
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Service
|
||||
public class RedisServiceImpl implements RedisService {
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
public void set(String key, Object value) {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String key, Object value, long expire) {
|
||||
redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String key) {
|
||||
return redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expire(String key, long expire) {
|
||||
return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void del(String key) {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void del(List<String> keys) {
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long delByPrefix(String prefix) {
|
||||
Set<String> keys = redisTemplate.keys(prefix + "*");
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
return redisTemplate.delete(keys);
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long increment(String key, Long delta) {
|
||||
return redisTemplate.opsForValue().increment(key, delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long decrement(String key, Long delta) {
|
||||
return redisTemplate.opsForValue().decrement(key, delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keys(String pattern) {
|
||||
return redisTemplate.keys(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean hasKey(String key) {
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long lPush(String key, Object value) {
|
||||
return redisTemplate.opsForList().leftPush(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long lPush(String key, Object value, long expire) {
|
||||
Long result = redisTemplate.opsForList().leftPush(key, value);
|
||||
expire(key, expire);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object lPop(String key) {
|
||||
return redisTemplate.opsForList().leftPop(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long sAdd(String key, Object... values) {
|
||||
return redisTemplate.opsForSet().add(key, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long sAdd(String key, long expire, Object... values) {
|
||||
Long result = redisTemplate.opsForSet().add(key, values);
|
||||
expire(key, expire);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Object> sMembers(String key) {
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hPut(String key, String hashKey, Object value) {
|
||||
redisTemplate.opsForHash().put(key, hashKey, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hPut(String key, String hashKey, Object value, long expire) {
|
||||
redisTemplate.opsForHash().put(key, hashKey, value);
|
||||
expire(key, expire);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object hGet(String key, String hashKey) {
|
||||
return redisTemplate.opsForHash().get(key, hashKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hPutAll(String key, Map<String, Object> map) {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hPutAll(String key, Map<String, Object> map, long expire) {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
expire(key, expire);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> hGetAll(String key) {
|
||||
return (Map<String, Object>) (Map) redisTemplate.opsForHash().entries(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hDel(String key, Object... hashKeys) {
|
||||
redisTemplate.opsForHash().delete(key, hashKeys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.trade.common.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 日期工具类
|
||||
* @author Trade Team
|
||||
*/
|
||||
public class DateUtil {
|
||||
|
||||
/**
|
||||
* 将LocalDateTime格式化为指定字符串
|
||||
* @param dateTime LocalDateTime对象
|
||||
* @param pattern 格式模式,如 "yyyy-MM-dd HH:mm:ss"
|
||||
* @return 格式化后的日期字符串
|
||||
*/
|
||||
public static String formatLocalDateTime(LocalDateTime dateTime, String pattern) {
|
||||
if (dateTime == null || pattern == null || pattern.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return dateTime.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前LocalDateTime格式化为默认字符串 "yyyy-MM-dd HH:mm:ss"
|
||||
* @param dateTime LocalDateTime对象
|
||||
* @return 格式化后的日期字符串
|
||||
*/
|
||||
public static String formatLocalDateTime(LocalDateTime dateTime) {
|
||||
return formatLocalDateTime(dateTime, "yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串解析为LocalDateTime对象
|
||||
* @param dateString 日期字符串
|
||||
* @param pattern 格式模式,如 "yyyy-MM-dd HH:mm:ss"
|
||||
* @return 解析后的LocalDateTime对象
|
||||
*/
|
||||
public static LocalDateTime parseLocalDateTime(String dateString, String pattern) {
|
||||
if (dateString == null || dateString.isEmpty() || pattern == null || pattern.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return LocalDateTime.parse(dateString, formatter);
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package com.trade.common.util;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JwtToken生成工具类
|
||||
* JWT token的格式:header.payload.signature
|
||||
* header的格式(算法、token的类型):
|
||||
* {
|
||||
* "alg": "HS512",
|
||||
* "typ": "JWT"
|
||||
* }
|
||||
* payload的格式(用户名、创建时间、生成时间):
|
||||
* {
|
||||
* "sub":"wang",
|
||||
* "created":1489079981398
|
||||
* }
|
||||
* signature的生成算法:
|
||||
* HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
|
||||
* @author Trade Team
|
||||
*/
|
||||
public class JwtTokenUtil {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
|
||||
private static final String CLAIM_KEY_USERNAME = "sub";
|
||||
private static final String CLAIM_KEY_CREATED = "created";
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
@Value("${jwt.expiration}")
|
||||
private Long expiration;
|
||||
@Value("${jwt.tokenHead}")
|
||||
private String tokenHead;
|
||||
|
||||
/**
|
||||
* 根据负责生成JWT的token
|
||||
*/
|
||||
private String generateToken(Map<String, Object> claims) {
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setExpiration(generateExpirationDate())
|
||||
.signWith(SignatureAlgorithm.HS512, secret)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取JWT中的负载
|
||||
*/
|
||||
private Claims getClaimsFromToken(String token) {
|
||||
Claims claims = null;
|
||||
try {
|
||||
claims = Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("JWT格式验证失败: {}", token);
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成token的过期时间
|
||||
*/
|
||||
private Date generateExpirationDate() {
|
||||
return new Date(System.currentTimeMillis() + expiration * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取登录用户名
|
||||
*/
|
||||
public String getUserNameFromToken(String token) {
|
||||
String username;
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
username = claims.getSubject();
|
||||
} catch (Exception e) {
|
||||
username = null;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证token是否还有效
|
||||
*
|
||||
* @param token 客户端传入的token
|
||||
* @param userDetails 从数据库中查询出来的用户信息
|
||||
*/
|
||||
public boolean validateToken(String token, UserDetails userDetails) {
|
||||
String username = getUserNameFromToken(token);
|
||||
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断token是否已经失效
|
||||
*/
|
||||
private boolean isTokenExpired(String token) {
|
||||
Date expiredDate = getExpiredDateFromToken(token);
|
||||
return expiredDate.before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取过期时间
|
||||
*/
|
||||
private Date getExpiredDateFromToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
return claims.getExpiration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户信息生成token
|
||||
*/
|
||||
public String generateToken(UserDetails userDetails) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
|
||||
claims.put(CLAIM_KEY_CREATED, new Date());
|
||||
return generateToken(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断token是否可以被刷新
|
||||
*/
|
||||
public boolean canRefresh(String token) {
|
||||
return !isTokenExpired(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
public String refreshToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
claims.put(CLAIM_KEY_CREATED, new Date());
|
||||
return generateToken(claims);
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
package com.trade.common.util;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* <p>JWT (JSON Web Token) 工具类</p>
|
||||
* <p>用于生成、解析和验证JWT</p>
|
||||
*
|
||||
* @author creator
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Slf4j
|
||||
@Component // 使其可以被Spring管理,方便注入配置
|
||||
public class JwtUtils {
|
||||
|
||||
/**
|
||||
* JWT 密钥,从配置文件读取,必须足够复杂以保证安全
|
||||
* 建议长度至少为256位 (32个ASCII字符)
|
||||
*/
|
||||
@Value("${jwt.secret:defaultSecretKey_must_be_at_least_32_characters_long_for_HS256}")
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* JWT 过期时间(毫秒),从配置文件读取,默认为1小时
|
||||
*/
|
||||
@Value("${jwt.expiration:3600000}")
|
||||
private long expiration;
|
||||
|
||||
private SecretKey secretKey;
|
||||
|
||||
/**
|
||||
* 初始化密钥
|
||||
*/
|
||||
private SecretKey getSecretKey() {
|
||||
if (secretKey == null) {
|
||||
// 优化:确保密钥长度符合HS256要求,如果不足则进行提示或使用默认安全密钥
|
||||
if (!StringUtils.hasText(secret) || secret.length() < 32) {
|
||||
log.warn("JWT secret key is not configured or too short (must be at least 32 chars for HS256). Using a default secure key. THIS IS NOT RECOMMENDED FOR PRODUCTION!");
|
||||
// Keys.secretKeyFor(SignatureAlgorithm.HS256) 会生成一个安全的随机密钥
|
||||
this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
|
||||
} else {
|
||||
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
|
||||
}
|
||||
}
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成JWT Token
|
||||
*
|
||||
* @param subject 主题,通常是用户ID或其他唯一标识
|
||||
* @param claims 自定义声明 (payload)
|
||||
* @return 生成的JWT字符串
|
||||
*/
|
||||
public String generateToken(String subject, Map<String, Object> claims) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expiration);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims) // 设置自定义声明
|
||||
.setSubject(subject) // 设置主题
|
||||
.setId(UUID.randomUUID().toString()) // JWT的唯一身份标识
|
||||
.setIssuedAt(now) // 设置签发时间
|
||||
.setExpiration(expiryDate) // 设置过期时间
|
||||
.signWith(getSecretKey(), SignatureAlgorithm.HS256) // 设置签名算法和密钥
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成JWT Token,仅包含 subject
|
||||
*
|
||||
* @param subject 主题,通常是用户ID或其他唯一标识
|
||||
* @return 生成的JWT字符串
|
||||
*/
|
||||
public String generateToken(String subject) {
|
||||
return generateToken(subject, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中获取 Claims (Payload)
|
||||
*
|
||||
* @param token JWT字符串
|
||||
* @return Claims 对象
|
||||
* @throws ExpiredJwtException 如果JWT已过期
|
||||
* @throws UnsupportedJwtException 如果JWT格式不受支持
|
||||
* @throws MalformedJwtException 如果JWT格式错误
|
||||
* @throws SignatureException 如果签名验证失败
|
||||
* @throws IllegalArgumentException 如果token为空或无效
|
||||
*/
|
||||
private Claims getClaimsFromToken(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
throw new IllegalArgumentException("JWT token cannot be null or empty.");
|
||||
}
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSecretKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中获取主题 (Subject)
|
||||
*
|
||||
* @param token JWT字符串
|
||||
* @return 主题
|
||||
*/
|
||||
public String getSubjectFromToken(String token) {
|
||||
try {
|
||||
return getClaimsFromToken(token).getSubject();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to get subject from token: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中获取指定的 Claim 值
|
||||
*
|
||||
* @param token JWT字符串
|
||||
* @param claimKey Claim的键
|
||||
* @param type Claim值的类型
|
||||
* @param <T> Claim值的泛型
|
||||
* @return Claim值,如果不存在或类型不匹配则返回null
|
||||
*/
|
||||
public <T> T getClaimFromToken(String token, String claimKey, Class<T> type) {
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
return claims.get(claimKey, type);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to get claim '{}' from token: {}", claimKey, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT Token是否有效
|
||||
*
|
||||
* @param token JWT字符串
|
||||
* @return 如果token有效返回true,否则返回false
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Jwts.parserBuilder().setSigningKey(getSecretKey()).build().parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (SignatureException ex) {
|
||||
log.error("Invalid JWT signature: {}", ex.getMessage());
|
||||
} catch (MalformedJwtException ex) {
|
||||
log.error("Invalid JWT token: {}", ex.getMessage());
|
||||
} catch (ExpiredJwtException ex) {
|
||||
log.warn("Expired JWT token: {}", ex.getMessage());
|
||||
} catch (UnsupportedJwtException ex) {
|
||||
log.error("Unsupported JWT token: {}", ex.getMessage());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.error("JWT claims string is empty: {}", ex.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断JWT Token是否已过期
|
||||
*
|
||||
* @param token JWT字符串
|
||||
* @return 如果已过期返回true,否则返回false。如果token无效也返回true。
|
||||
*/
|
||||
public boolean isTokenExpired(String token) {
|
||||
try {
|
||||
Date expirationDate = getClaimsFromToken(token).getExpiration();
|
||||
return expirationDate.before(new Date());
|
||||
} catch (ExpiredJwtException e) {
|
||||
return true; // 明确已过期
|
||||
} catch (Exception e) {
|
||||
return true; // 其他解析错误,视为无效或已过期
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新JWT Token的过期时间
|
||||
* <p>
|
||||
* 注意:这实际上是重新签发了一个新的Token,包含了原有Token的claims和subject。
|
||||
* </p>
|
||||
*
|
||||
* @param token 旧的JWT字符串
|
||||
* @return 新的JWT字符串,如果旧token无效则返回null
|
||||
*/
|
||||
public String refreshToken(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
// 检查是否允许刷新 (例如,可以在claims中设置一个特定的刷新标记或时间窗口)
|
||||
// 此处简单实现为只要未过期即可刷新
|
||||
if (claims.getExpiration().after(new Date())) {
|
||||
return generateToken(claims.getSubject(), claims);
|
||||
}
|
||||
} catch (ExpiredJwtException ex) {
|
||||
log.warn("Cannot refresh an already expired JWT token: {}", ex.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("Error refreshing JWT token: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JWT的过期时间
|
||||
*
|
||||
* @return 过期时间(毫秒)
|
||||
*/
|
||||
public long getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.trade.common.util;
|
||||
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 请求工具类
|
||||
* @author Trade Team
|
||||
*/
|
||||
public class RequestUtil {
|
||||
|
||||
/**
|
||||
* 获取当前请求的HttpServletRequest对象
|
||||
* @return HttpServletRequest对象,如果不在请求上下文中则返回null
|
||||
*/
|
||||
public static HttpServletRequest getCurrentRequest() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return null;
|
||||
}
|
||||
return attributes.getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的IP地址
|
||||
* @return IP地址字符串
|
||||
*/
|
||||
public static String getRequestIp() {
|
||||
HttpServletRequest request = getCurrentRequest();
|
||||
if (request == null) {
|
||||
return "";
|
||||
}
|
||||
String ip = request.getHeader("x-forwarded-for");
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的User-Agent
|
||||
* @return User-Agent字符串
|
||||
*/
|
||||
public static String getUserAgent() {
|
||||
HttpServletRequest request = getCurrentRequest();
|
||||
if (request == null) {
|
||||
return "";
|
||||
}
|
||||
return request.getHeader("User-Agent");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的Referer
|
||||
* @return Referer字符串
|
||||
*/
|
||||
public static String getReferer() {
|
||||
HttpServletRequest request = getCurrentRequest();
|
||||
if (request == null) {
|
||||
return "";
|
||||
}
|
||||
return request.getHeader("Referer");
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.trade.common.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Spring工具类,用于获取Spring上下文中的Bean
|
||||
* @author Trade Team
|
||||
*/
|
||||
@Component
|
||||
public class SpringUtil implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
if (SpringUtil.applicationContext == null) {
|
||||
SpringUtil.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取applicationContext
|
||||
* @return ApplicationContext
|
||||
*/
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过name获取 Bean.
|
||||
* @param name Bean的名称
|
||||
* @return Object
|
||||
*/
|
||||
public static Object getBean(String name) {
|
||||
return getApplicationContext().getBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过class获取Bean.
|
||||
* @param clazz Bean的类型
|
||||
* @param <T> 泛型
|
||||
* @return T
|
||||
*/
|
||||
public static <T> T getBean(Class<T> clazz) {
|
||||
return getApplicationContext().getBean(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过name,以及Clazz返回指定的Bean
|
||||
* @param name Bean的名称
|
||||
* @param clazz Bean的类型
|
||||
* @param <T> 泛型
|
||||
* @return T
|
||||
*/
|
||||
public static <T> T getBean(String name, Class<T> clazz) {
|
||||
return getApplicationContext().getBean(name, clazz);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.trade.common.util;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 参数校验工具类
|
||||
* @author Trade Team
|
||||
*/
|
||||
public class ValidationUtil {
|
||||
|
||||
private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
|
||||
/**
|
||||
* 校验对象
|
||||
* @param obj 待校验对象
|
||||
* @param <T> 对象类型
|
||||
* @throws IllegalArgumentException 如果校验失败,则抛出此异常
|
||||
*/
|
||||
public static <T> void validate(T obj) {
|
||||
Set<ConstraintViolation<T>> violations = VALIDATOR.validate(obj);
|
||||
if (!violations.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (ConstraintViolation<T> violation : violations) {
|
||||
sb.append(violation.getMessage()).append(";");
|
||||
}
|
||||
throw new IllegalArgumentException(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验对象的指定属性
|
||||
* @param obj 待校验对象
|
||||
* @param propertyName 待校验属性名
|
||||
* @param <T> 对象类型
|
||||
* @throws IllegalArgumentException 如果校验失败,则抛出此异常
|
||||
*/
|
||||
public static <T> void validateProperty(T obj, String propertyName) {
|
||||
Set<ConstraintViolation<T>> violations = VALIDATOR.validateProperty(obj, propertyName);
|
||||
if (!violations.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (ConstraintViolation<T> violation : violations) {
|
||||
sb.append(violation.getMessage()).append(";");
|
||||
}
|
||||
throw new IllegalArgumentException(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.trade.common.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>通用API接口返回结果封装类</p>
|
||||
*
|
||||
* @author creator
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "通用API接口返回结果")
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 成功标志
|
||||
*/
|
||||
@Schema(description = "成功标志", example = "true")
|
||||
private boolean success = true;
|
||||
|
||||
/**
|
||||
* 返回处理消息
|
||||
*/
|
||||
@Schema(description = "返回处理消息", example = "操作成功!")
|
||||
private String message = "操作成功!";
|
||||
|
||||
/**
|
||||
* 返回代码
|
||||
*/
|
||||
@Schema(description = "返回代码", example = "200")
|
||||
private Integer code = 200;
|
||||
|
||||
/**
|
||||
* 返回数据对象 data
|
||||
*/
|
||||
@Schema(description = "返回数据对象")
|
||||
private T result;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
@Schema(description = "时间戳", example = "1609459200000")
|
||||
private long timestamp = System.currentTimeMillis();
|
||||
|
||||
public Result() {
|
||||
}
|
||||
|
||||
public Result(boolean success, String message, Integer code) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok() {
|
||||
Result<T> r = new Result<>();
|
||||
r.setSuccess(true);
|
||||
r.setCode(ResultCodeEnum.SUCCESS.getCode());
|
||||
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(String msg) {
|
||||
Result<T> r = new Result<>();
|
||||
r.setSuccess(true);
|
||||
r.setCode(ResultCodeEnum.SUCCESS.getCode());
|
||||
r.setMessage(msg);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(T data) {
|
||||
Result<T> r = new Result<>();
|
||||
r.setSuccess(true);
|
||||
r.setCode(ResultCodeEnum.SUCCESS.getCode());
|
||||
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
|
||||
r.setResult(data);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(String msg, T data) {
|
||||
Result<T> r = new Result<>();
|
||||
r.setSuccess(true);
|
||||
r.setCode(ResultCodeEnum.SUCCESS.getCode());
|
||||
r.setMessage(msg);
|
||||
r.setResult(data);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(String msg) {
|
||||
return error(ResultCodeEnum.INTERNAL_SERVER_ERROR.getCode(), msg);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(Integer code, String msg) {
|
||||
Result<T> r = new Result<>();
|
||||
r.setSuccess(false);
|
||||
r.setCode(code);
|
||||
r.setMessage(msg);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(ResultCodeEnum resultCodeEnum) {
|
||||
Result<T> r = new Result<>();
|
||||
r.setSuccess(false);
|
||||
r.setCode(resultCodeEnum.getCode());
|
||||
r.setMessage(resultCodeEnum.getMessage());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(ResultCodeEnum resultCodeEnum, String msg) {
|
||||
Result<T> r = new Result<>();
|
||||
r.setSuccess(false);
|
||||
r.setCode(resultCodeEnum.getCode());
|
||||
r.setMessage(msg); // Use custom message
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 无权限访问返回结果
|
||||
*/
|
||||
public static <T> Result<T> noauth(String msg) {
|
||||
return error(ResultCodeEnum.UNAUTHORIZED.getCode(), msg);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.trade.common.vo;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* <p>结果代码枚举</p>
|
||||
*
|
||||
* @author creator
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Getter
|
||||
public enum ResultCodeEnum {
|
||||
|
||||
SUCCESS(200, "操作成功"),
|
||||
OPERATION_FAILED(5001, "操作失败"),
|
||||
BAD_REQUEST(400, "请求参数错误"),
|
||||
UNAUTHORIZED(401, "未经授权"),
|
||||
FORBIDDEN(403, "访问被拒绝"),
|
||||
NOT_FOUND(404, "资源不存在"),
|
||||
INTERNAL_SERVER_ERROR(500, "服务器内部错误"),
|
||||
SERVICE_UNAVAILABLE(503, "服务不可用");
|
||||
|
||||
private final Integer code;
|
||||
private final String message;
|
||||
|
||||
ResultCodeEnum(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user