feat: 重构项目结构并新增微信小程序认证模块
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.youlai.boot.common.annotation;
|
||||
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
@@ -16,34 +17,31 @@ import java.lang.annotation.*;
|
||||
public @interface Log {
|
||||
|
||||
/**
|
||||
* 日志描述
|
||||
* 模块
|
||||
*
|
||||
* @return 日志描述
|
||||
* @return 模块
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 日志模块
|
||||
*
|
||||
* @return 日志模块
|
||||
*/
|
||||
|
||||
LogModuleEnum module();
|
||||
|
||||
/**
|
||||
* 是否记录请求参数
|
||||
* 操作类型
|
||||
*
|
||||
* @return 是否记录请求参数
|
||||
* @return 操作类型
|
||||
*/
|
||||
boolean params() default true;
|
||||
ActionTypeEnum value();
|
||||
|
||||
/**
|
||||
* 是否记录响应结果
|
||||
* <br/>
|
||||
* 响应结果默认不记录,避免日志过大
|
||||
* @return 是否记录响应结果
|
||||
* 操作标题(可选,默认使用枚举描述)
|
||||
*
|
||||
* @return 标题
|
||||
*/
|
||||
boolean result() default false;
|
||||
String title() default "";
|
||||
|
||||
/**
|
||||
* 自定义日志内容(可选,用于记录操作细节)
|
||||
*
|
||||
* @return 日志内容
|
||||
*/
|
||||
String content() default "";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.youlai.boot.common.annotation;
|
||||
|
||||
import com.youlai.boot.core.validator.FieldValidator;
|
||||
import com.youlai.boot.common.validator.FieldValidator;
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
|
||||
|
||||
136
src/main/java/com/youlai/boot/common/aspect/LogAspect.java
Normal file
136
src/main/java/com/youlai/boot/common/aspect/LogAspect.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package com.youlai.boot.common.aspect;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.model.entity.SysLog;
|
||||
import com.youlai.boot.system.service.LogService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日志切面
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LogAspect {
|
||||
|
||||
private final LogService logService;
|
||||
|
||||
/**
|
||||
* 日志注解切点
|
||||
*/
|
||||
@Pointcut("@annotation(logAnnotation)")
|
||||
public void logPointCut(Log logAnnotation) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 环绕通知:记录操作日志
|
||||
*/
|
||||
@Around(value = "logPointCut(logAnnotation)", argNames = "pjp,logAnnotation")
|
||||
public Object around(ProceedingJoinPoint pjp, Log logAnnotation) throws Throwable {
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
Object result = null;
|
||||
Exception exception = null;
|
||||
|
||||
try {
|
||||
result = pjp.proceed();
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
} finally {
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
saveLogAsync(logAnnotation, executionTime, exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步保存日志
|
||||
*/
|
||||
@Async
|
||||
public void saveLogAsync(Log logAnnotation, long executionTime, Exception exception) {
|
||||
try {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
|
||||
// 解析 User-Agent
|
||||
String userAgentStr = request.getHeader("User-Agent");
|
||||
UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
|
||||
|
||||
// 解析 IP 地区
|
||||
String ip = IPUtils.getIpAddr(request);
|
||||
String region = IPUtils.getRegion(ip);
|
||||
String province = null;
|
||||
String city = null;
|
||||
if (StrUtil.isNotBlank(region)) {
|
||||
String[] parts = region.split("\\|");
|
||||
if (parts.length >= 3) {
|
||||
province = StrUtil.blankToDefault(parts[2], null);
|
||||
city = StrUtil.blankToDefault(parts[3], null);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前用户信息
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
String username = SecurityUtils.getUsername();
|
||||
|
||||
// 构建日志实体
|
||||
LogModuleEnum module = logAnnotation.module();
|
||||
ActionTypeEnum actionType = logAnnotation.value();
|
||||
String title = StrUtil.blankToDefault(logAnnotation.title(),
|
||||
module.getLabel() + "-" + actionType.getLabel());
|
||||
String content = logAnnotation.content();
|
||||
|
||||
SysLog logEntity = new SysLog();
|
||||
logEntity.setModule(module);
|
||||
logEntity.setActionType(actionType);
|
||||
logEntity.setTitle(title);
|
||||
logEntity.setContent(content);
|
||||
logEntity.setOperatorId(userId);
|
||||
logEntity.setOperatorName(username);
|
||||
logEntity.setRequestUri(request.getRequestURI());
|
||||
logEntity.setRequestMethod(request.getMethod());
|
||||
logEntity.setIp(ip);
|
||||
logEntity.setProvince(province);
|
||||
logEntity.setCity(city);
|
||||
logEntity.setDevice(userAgent.getOs().getName());
|
||||
logEntity.setOs(userAgent.getOs().getName());
|
||||
logEntity.setBrowser(userAgent.getBrowser().getName());
|
||||
logEntity.setStatus(exception == null ? 1 : 0);
|
||||
logEntity.setErrorMsg(exception != null ? exception.getMessage() : null);
|
||||
logEntity.setExecutionTime((int) executionTime);
|
||||
logEntity.setCreateTime(LocalDateTime.now());
|
||||
|
||||
logService.save(logEntity);
|
||||
} catch (Exception e) {
|
||||
log.error("保存操作日志异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.youlai.boot.common.aspect;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.annotation.RepeatSubmit;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 防重复提交切面
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class RepeatSubmitAspect {
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* 防重复提交切点
|
||||
*/
|
||||
@Pointcut("@annotation(repeatSubmit)")
|
||||
public void repeatSubmitPointCut(RepeatSubmit repeatSubmit) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 环绕通知:处理防重复提交逻辑
|
||||
*/
|
||||
@Around(value = "repeatSubmitPointCut(repeatSubmit)", argNames = "pjp,repeatSubmit")
|
||||
public Object handleRepeatSubmit(ProceedingJoinPoint pjp, RepeatSubmit repeatSubmit) throws Throwable {
|
||||
String lockKey = buildLockKey();
|
||||
|
||||
int expire = repeatSubmit.expire();
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
|
||||
boolean locked = lock.tryLock(0, expire, TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
throw new BusinessException(ResultCode.DUPLICATE_SUBMISSION);
|
||||
}
|
||||
return pjp.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成防重复提交锁的 key
|
||||
* @return 锁的 key
|
||||
*/
|
||||
private String buildLockKey() {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
// 用户唯一标识
|
||||
String userIdentifier = getUserIdentifier(request);
|
||||
// 请求唯一标识 = 请求方法 + 请求路径 + 请求参数(严谨的做法)
|
||||
String requestIdentifier = StrUtil.join(":", request.getMethod(), request.getRequestURI());
|
||||
return StrUtil.format(RedisConstants.Lock.RESUBMIT, userIdentifier, requestIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户唯一标识
|
||||
* 1. 从请求头中获取 Token,使用 SHA-256 加密 Token 作为用户唯一标识
|
||||
* 2. 如果 Token 为空,使用 IP 作为用户唯一标识
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return 用户唯一标识
|
||||
*/
|
||||
private String getUserIdentifier(HttpServletRequest request) {
|
||||
// 用户身份唯一标识
|
||||
String userIdentifier;
|
||||
// 从请求头中获取 Token
|
||||
String tokenHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (StrUtil.isNotBlank(tokenHeader) && tokenHeader.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX)) {
|
||||
String rawToken = tokenHeader.substring(SecurityConstants.BEARER_TOKEN_PREFIX.length()); // 去掉 Bearer 后的 Token
|
||||
userIdentifier = DigestUtil.sha256Hex(rawToken); // 使用 SHA-256 加密 Token 作为用户唯一标识
|
||||
} else {
|
||||
userIdentifier = IPUtils.getIpAddr(request); // 使用 IP 作为用户唯一标识
|
||||
}
|
||||
return userIdentifier;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.youlai.boot.common.constant;
|
||||
|
||||
/**
|
||||
* 系统常量
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface SystemConstants {
|
||||
|
||||
/**
|
||||
* 根节点ID
|
||||
*/
|
||||
Long ROOT_NODE_ID = 0L;
|
||||
|
||||
/**
|
||||
* 系统默认密码
|
||||
*/
|
||||
String DEFAULT_PASSWORD = "123456";
|
||||
|
||||
/**
|
||||
* 超级管理员角色编码
|
||||
*/
|
||||
String ROOT_ROLE_CODE = "ROOT";
|
||||
|
||||
|
||||
/**
|
||||
* 系统配置 IP的QPS限流的KEY
|
||||
*/
|
||||
String SYSTEM_CONFIG_IP_QPS_LIMIT_KEY = "IP_QPS_THRESHOLD_LIMIT";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 操作类型枚举
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Schema(enumAsRef = true)
|
||||
@Getter
|
||||
public enum ActionTypeEnum implements IBaseEnum<Integer> {
|
||||
|
||||
LOGIN(1, "登录"),
|
||||
LOGOUT(2, "登出"),
|
||||
INSERT(3, "新增"),
|
||||
UPDATE(4, "修改"),
|
||||
DELETE(5, "删除"),
|
||||
GRANT(6, "授权"),
|
||||
EXPORT(7, "导出"),
|
||||
IMPORT(8, "导入"),
|
||||
UPLOAD(9, "上传"),
|
||||
DOWNLOAD(10, "下载"),
|
||||
CHANGE_PASSWORD(11, "修改密码"),
|
||||
RESET_PASSWORD(12, "重置密码"),
|
||||
ENABLE(13, "启用"),
|
||||
DISABLE(14, "禁用"),
|
||||
LIST(15, "查询列表"),
|
||||
OTHER(99, "其他");
|
||||
|
||||
@EnumValue
|
||||
private final Integer value;
|
||||
|
||||
@JsonValue
|
||||
private final String label;
|
||||
|
||||
ActionTypeEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 数据权限枚举
|
||||
* <p>
|
||||
* 多角色数据权限合并策略:取并集(OR),即用户能看到所有角色权限范围内的数据。
|
||||
* 如果任一角色是 ALL,则直接跳过数据权限过滤。
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Getter
|
||||
public enum DataScopeEnum implements IBaseEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 所有数据权限 - 最高权限,可查看所有数据
|
||||
*/
|
||||
ALL(1, "所有数据"),
|
||||
|
||||
/**
|
||||
* 部门及子部门数据 - 可查看本部门及其下属所有部门的数据
|
||||
*/
|
||||
DEPT_AND_SUB(2, "部门及子部门数据"),
|
||||
|
||||
/**
|
||||
* 本部门数据 - 仅可查看本部门的数据
|
||||
*/
|
||||
DEPT(3, "本部门数据"),
|
||||
|
||||
/**
|
||||
* 本人数据 - 仅可查看自己的数据
|
||||
*/
|
||||
SELF(4, "本人数据"),
|
||||
|
||||
/**
|
||||
* 自定义部门数据 - 可查看指定部门的数据
|
||||
* <p>
|
||||
* 需要配合 sys_role_dept 表使用,存储角色可访问的部门ID列表
|
||||
*/
|
||||
CUSTOM(5, "自定义部门数据");
|
||||
|
||||
private final Integer value;
|
||||
|
||||
private final String label;
|
||||
|
||||
DataScopeEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为全部数据权限
|
||||
*
|
||||
* @param value 数据权限值
|
||||
* @return 是否为全部数据权限
|
||||
*/
|
||||
public static boolean isAll(Integer value) {
|
||||
return ALL.getValue().equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据值获取枚举
|
||||
*
|
||||
* @param value 数据权限值
|
||||
* @return 枚举对象,未找到则返回 null
|
||||
*/
|
||||
public static DataScopeEnum getByValue(Integer value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
for (DataScopeEnum dataScope : values()) {
|
||||
if (dataScope.getValue().equals(value)) {
|
||||
return dataScope;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -12,22 +14,39 @@ import lombok.Getter;
|
||||
*/
|
||||
@Schema(enumAsRef = true)
|
||||
@Getter
|
||||
public enum LogModuleEnum {
|
||||
public enum LogModuleEnum implements IBaseEnum<Integer> {
|
||||
|
||||
EXCEPTION("异常"),
|
||||
LOGIN("登录"),
|
||||
USER("用户"),
|
||||
DEPT("部门"),
|
||||
ROLE("角色"),
|
||||
MENU("菜单"),
|
||||
DICT("字典"),
|
||||
SETTING("系统配置"),
|
||||
OTHER("其他");
|
||||
LOGIN(1, "登录"),
|
||||
USER(2, "用户管理"),
|
||||
ROLE(3, "角色管理"),
|
||||
DEPT(4, "部门管理"),
|
||||
MENU(5, "菜单管理"),
|
||||
DICT(6, "字典管理"),
|
||||
CONFIG(7, "系统配置"),
|
||||
FILE(8, "文件管理"),
|
||||
NOTICE(9, "通知公告"),
|
||||
LOG(10, "日志管理"),
|
||||
CODEGEN(11, "代码生成"),
|
||||
OTHER(99, "其他");
|
||||
|
||||
@EnumValue
|
||||
private final Integer value;
|
||||
|
||||
@JsonValue
|
||||
private final String moduleName;
|
||||
private final String label;
|
||||
|
||||
LogModuleEnum(String moduleName) {
|
||||
this.moduleName = moduleName;
|
||||
LogModuleEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RequestMethodEnum {
|
||||
/**
|
||||
* 搜寻 @AnonymousGetMapping
|
||||
*/
|
||||
GET("GET"),
|
||||
|
||||
/**
|
||||
* 搜寻 @AnonymousPostMapping
|
||||
*/
|
||||
POST("POST"),
|
||||
|
||||
/**
|
||||
* 搜寻 @AnonymousPutMapping
|
||||
*/
|
||||
PUT("PUT"),
|
||||
|
||||
/**
|
||||
* 搜寻 @AnonymousPatchMapping
|
||||
*/
|
||||
PATCH("PATCH"),
|
||||
|
||||
/**
|
||||
* 搜寻 @AnonymousDeleteMapping
|
||||
*/
|
||||
DELETE("DELETE"),
|
||||
|
||||
/**
|
||||
* 否则就是所有 Request 接口都放行
|
||||
*/
|
||||
ALL("All");
|
||||
|
||||
/**
|
||||
* Request 类型
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
public static RequestMethodEnum find(String type) {
|
||||
for (RequestMethodEnum value : RequestMethodEnum.values()) {
|
||||
if (value.getType().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return ALL;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 状态枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/10/14
|
||||
*/
|
||||
@Getter
|
||||
public enum StatusEnum implements IBaseEnum<Integer> {
|
||||
|
||||
ENABLE(1, "启用"),
|
||||
DISABLE (0, "禁用");
|
||||
|
||||
private final Integer value;
|
||||
|
||||
|
||||
private final String label;
|
||||
|
||||
StatusEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.youlai.boot.common.exception;
|
||||
|
||||
import com.youlai.boot.common.result.IResultCode;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.helpers.MessageFormatter;
|
||||
|
||||
/**
|
||||
* 自定义业务异常
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2022/7/31
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
public IResultCode resultCode;
|
||||
|
||||
public BusinessException(IResultCode errorCode) {
|
||||
super(errorCode.getMsg());
|
||||
this.resultCode = errorCode;
|
||||
}
|
||||
|
||||
|
||||
public BusinessException(IResultCode errorCode,String message) {
|
||||
super(message);
|
||||
this.resultCode = errorCode;
|
||||
}
|
||||
|
||||
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BusinessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BusinessException(String message, Object... args) {
|
||||
super(formatMessage(message, args));
|
||||
}
|
||||
|
||||
private static String formatMessage(String message, Object... args) {
|
||||
return MessageFormatter.arrayFormat(message, args).getMessage();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.youlai.boot.common.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* 键值对
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2024/5/25
|
||||
*/
|
||||
@Schema(description = "键值对")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class KeyValue {
|
||||
|
||||
public KeyValue(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Schema(description = "选项的值")
|
||||
private String key;
|
||||
|
||||
@Schema(description = "选项的标签")
|
||||
private String value;
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.youlai.boot.common.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 下拉选项对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/1/22
|
||||
*/
|
||||
@Schema(description ="下拉选项对象")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class Option<T> {
|
||||
|
||||
public Option(T value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public Option(T value, String label, List<Option<T>> children) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.children= children;
|
||||
}
|
||||
|
||||
public Option(T value, String label, String tag) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.tag= tag;
|
||||
}
|
||||
|
||||
|
||||
@Schema(description="选项的值")
|
||||
private T value;
|
||||
|
||||
@Schema(description="选项的标签")
|
||||
private String label;
|
||||
|
||||
@Schema(description = "标签类型")
|
||||
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
|
||||
private String tag;
|
||||
|
||||
@Schema(description="子选项列表")
|
||||
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
|
||||
private List<Option<T>> children;
|
||||
|
||||
}
|
||||
43
src/main/java/com/youlai/boot/common/result/ExcelResult.java
Normal file
43
src/main/java/com/youlai/boot/common/result/ExcelResult.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Excel导出响应结构体
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2025/1/14 11:46:08
|
||||
*/
|
||||
@Data
|
||||
public class ExcelResult {
|
||||
|
||||
/**
|
||||
* 响应码,来确定是否导入成功
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 有效条数
|
||||
*/
|
||||
private Integer validCount;
|
||||
|
||||
/**
|
||||
* 无效条数
|
||||
*/
|
||||
private Integer invalidCount;
|
||||
|
||||
/**
|
||||
* 错误提示信息
|
||||
*/
|
||||
private List<String> messageList;
|
||||
|
||||
public ExcelResult() {
|
||||
this.code = ResultCode.SUCCESS.getCode();
|
||||
this.validCount = 0;
|
||||
this.invalidCount = 0;
|
||||
this.messageList = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/youlai/boot/common/result/IResultCode.java
Normal file
15
src/main/java/com/youlai/boot/common/result/IResultCode.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
/**
|
||||
* 响应码接口
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 1.0.0
|
||||
**/
|
||||
public interface IResultCode {
|
||||
|
||||
String getCode();
|
||||
|
||||
String getMsg();
|
||||
|
||||
}
|
||||
71
src/main/java/com/youlai/boot/common/result/PageResult.java
Normal file
71
src/main/java/com/youlai/boot/common/result/PageResult.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页响应结构体
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2022/2/18
|
||||
*/
|
||||
@Data
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
private String code;
|
||||
|
||||
private String msg;
|
||||
|
||||
private PageData<T> data;
|
||||
|
||||
/**
|
||||
* 构建分页结果(MyBatis-Plus {@link IPage})。
|
||||
*
|
||||
* <p>data 为当前页记录列表;page 提供分页元信息。</p>
|
||||
*/
|
||||
public static <T> PageResult<T> success(IPage<T> page) {
|
||||
PageResult<T> result = new PageResult<>();
|
||||
result.setCode(ResultCode.SUCCESS.getCode());
|
||||
result.setMsg(ResultCode.SUCCESS.getMsg());
|
||||
|
||||
List<T> records =
|
||||
(page == null || page.getRecords() == null)
|
||||
? Collections.emptyList()
|
||||
: page.getRecords();
|
||||
PageData<T> pageData = new PageData<>();
|
||||
pageData.setList(records);
|
||||
pageData.setTotal(page != null ? page.getTotal() : 0L);
|
||||
result.setData(pageData);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建列表结果(无分页)。
|
||||
*
|
||||
* <p>page 置为 null,用于与分页返回区分。</p>
|
||||
*/
|
||||
public static <T> PageResult<T> success(List<T> list) {
|
||||
PageResult<T> result = new PageResult<>();
|
||||
result.setCode(ResultCode.SUCCESS.getCode());
|
||||
result.setMsg(ResultCode.SUCCESS.getMsg());
|
||||
PageData<T> pageData = new PageData<>();
|
||||
pageData.setList(list != null ? list : Collections.emptyList());
|
||||
pageData.setTotal(0L);
|
||||
result.setData(pageData);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PageData<T> {
|
||||
|
||||
private List<T> list;
|
||||
|
||||
private long total;
|
||||
}
|
||||
|
||||
}
|
||||
118
src/main/java/com/youlai/boot/common/result/ResponseWriter.java
Normal file
118
src/main/java/com/youlai/boot/common/result/ResponseWriter.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 响应写入器
|
||||
* <p>
|
||||
* 用于在过滤器、Security处理器等无法使用 @RestControllerAdvice 的场景中统一写入HTTP响应。
|
||||
* 支持写入成功响应和错误响应。
|
||||
* 此类为工具类,所有方法均为静态方法,禁止实例化。
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
public final class ResponseWriter {
|
||||
|
||||
/**
|
||||
* 私有构造函数,防止实例化
|
||||
*/
|
||||
private ResponseWriter() {
|
||||
throw new UnsupportedOperationException("工具类不允许实例化");
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入成功响应
|
||||
*
|
||||
* @param response HttpServletResponse
|
||||
* @param data 响应数据(可选)
|
||||
*/
|
||||
public static void writeSuccess(HttpServletResponse response, Object data) {
|
||||
writeResult(response, Result.success(data), HttpStatus.OK.value());
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入成功响应(无数据)
|
||||
*
|
||||
* @param response HttpServletResponse
|
||||
*/
|
||||
public static void writeSuccess(HttpServletResponse response) {
|
||||
writeSuccess(response, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入错误响应
|
||||
*
|
||||
* @param response HttpServletResponse
|
||||
* @param resultCode 响应结果码
|
||||
*/
|
||||
public static void writeError(HttpServletResponse response, ResultCode resultCode) {
|
||||
writeError(response, resultCode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入错误响应(带自定义消息)
|
||||
*
|
||||
* @param response HttpServletResponse
|
||||
* @param resultCode 响应结果码
|
||||
* @param message 自定义消息(可选,为 null 时使用 resultCode 的默认消息)
|
||||
*/
|
||||
public static void writeError(HttpServletResponse response, ResultCode resultCode, String message) {
|
||||
Result<?> result = message == null
|
||||
? Result.failed(resultCode)
|
||||
: Result.failed(resultCode, message);
|
||||
|
||||
int httpStatus = mapHttpStatus(resultCode);
|
||||
writeResult(response, result, httpStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入响应结果(通用方法)
|
||||
*
|
||||
* @param response HttpServletResponse
|
||||
* @param result 响应结果对象
|
||||
* @param httpStatus HTTP状态码
|
||||
*/
|
||||
private static void writeResult(HttpServletResponse response, Result<?> result, int httpStatus) {
|
||||
try {
|
||||
// 设置HTTP状态码
|
||||
response.setStatus(httpStatus);
|
||||
|
||||
// 设置响应编码和内容类型
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
// 写入响应
|
||||
JakartaServletUtil.write(response,
|
||||
JSONUtil.toJsonStr(result),
|
||||
MediaType.APPLICATION_JSON_VALUE
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("写入响应时发生未知异常: httpStatus={}, result={}", httpStatus, result, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据业务结果码映射HTTP状态码
|
||||
*
|
||||
* @param resultCode 业务结果码
|
||||
* @return HTTP状态码
|
||||
*/
|
||||
private static int mapHttpStatus(ResultCode resultCode) {
|
||||
return switch (resultCode) {
|
||||
case ACCESS_UNAUTHORIZED,
|
||||
ACCESS_TOKEN_INVALID,
|
||||
REFRESH_TOKEN_INVALID -> HttpStatus.UNAUTHORIZED.value();
|
||||
default -> HttpStatus.BAD_REQUEST.value();
|
||||
};
|
||||
}
|
||||
}
|
||||
79
src/main/java/com/youlai/boot/common/result/Result.java
Normal file
79
src/main/java/com/youlai/boot/common/result/Result.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结构体
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2022/1/30
|
||||
**/
|
||||
@Data
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private String code;
|
||||
|
||||
private T data;
|
||||
|
||||
private String msg;
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
return success(null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(ResultCode.SUCCESS.getCode());
|
||||
result.setMsg(ResultCode.SUCCESS.getMsg());
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed() {
|
||||
return result(ResultCode.SYSTEM_ERROR.getCode(), ResultCode.SYSTEM_ERROR.getMsg(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(String msg) {
|
||||
return result(ResultCode.SYSTEM_ERROR.getCode(), msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> judge(boolean status) {
|
||||
if (status) {
|
||||
return success();
|
||||
} else {
|
||||
return failed();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(IResultCode resultCode) {
|
||||
return result(resultCode.getCode(), resultCode.getMsg(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(IResultCode resultCode, String msg) {
|
||||
return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(IResultCode resultCode, T data) {
|
||||
return result(resultCode.getCode(), resultCode.getMsg(), data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(IResultCode resultCode, String msg, T data) {
|
||||
return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), data);
|
||||
}
|
||||
|
||||
private static <T> Result<T> result(IResultCode resultCode, T data) {
|
||||
return result(resultCode.getCode(), resultCode.getMsg(), data);
|
||||
}
|
||||
|
||||
private static <T> Result<T> result(String code, String msg, T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(code);
|
||||
result.setData(data);
|
||||
result.setMsg(msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
155
src/main/java/com/youlai/boot/common/result/ResultCode.java
Normal file
155
src/main/java/com/youlai/boot/common/result/ResultCode.java
Normal file
@@ -0,0 +1,155 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 响应码枚举
|
||||
* <p>
|
||||
* 参考《阿里巴巴 Java 开发手册》错误码设计建议:
|
||||
* 00000 表示成功。
|
||||
* A**** 表示用户端错误(如参数错误、认证失败等)。
|
||||
* B**** 表示当前系统执行出错(如系统超时等)。
|
||||
* C**** 表示调用第三方服务出错(如中间件、数据库等外部依赖)。
|
||||
* <p>
|
||||
* 错误码位数与号段说明:
|
||||
* - 错误码为字符串类型,共 5 位:错误产生来源(A/B/C) + 四位数字编号。
|
||||
* - 四位数字编号范围 0001~9999,大类之间建议按步长 100 预留号段(如 A0200、A0300、A0400)。
|
||||
* - 错误码后三位编号与 HTTP 状态码无关。
|
||||
* <p>
|
||||
* 说明:
|
||||
* - 本项目仅保留实际使用的错误码,并在 A/B/C 各保留少量示例,避免枚举无限膨胀。
|
||||
* - 如需扩展业务错误码,建议在对应宏观分类下按场景划分号段并保持全局唯一。
|
||||
* <p>
|
||||
* 附表(节选):错误码列表(示例/项目使用项)
|
||||
* <pre>
|
||||
* | 错误码 | 中文描述 | 说明 |
|
||||
* |-------|----------------------|------------------|
|
||||
* | 00000 | 成功 | 正常执行后的返回 |
|
||||
* | A0001 | 用户端错误 | 一级宏观错误码 |
|
||||
* | A0100 | 用户注册错误 | 二级宏观错误码 |
|
||||
* | A0101 | 用户未同意隐私协议 | 二级宏观错误码 |
|
||||
* | A0200 | 用户登录异常 | 二级宏观错误码 |
|
||||
* | A0201 | 用户账户不存在 | 二级宏观错误码 |
|
||||
* | A0202 | 用户账户被冻结 | 二级宏观错误码 |
|
||||
* | A0230 | 访问令牌无效或已过期 | 令牌校验失败 |
|
||||
* | A0241 | 用户验证码尝试次数超限 | 二级宏观错误码 |
|
||||
* | A0300 | 访问权限异常 | 二级宏观错误码 |
|
||||
* | A0301 | 访问未授权 | 二级宏观错误码 |
|
||||
* | A0400 | 用户请求参数错误 | 二级宏观错误码 |
|
||||
* | A0410 | 请求必填参数为空 | 二级宏观错误码 |
|
||||
* | A0500 | 用户请求服务异常 | 二级宏观错误码 |
|
||||
* | A0502 | 请求并发数超出限制 | 二级宏观错误码 |
|
||||
* | A0506 | 请勿重复提交 | 二级宏观错误码 |
|
||||
* | B0001 | 系统执行出错 | 一级宏观错误码 |
|
||||
* | B0100 | 系统执行超时 | 二级宏观错误码 |
|
||||
* | C0001 | 调用第三方服务出错 | 一级宏观错误码 |
|
||||
* | C0113 | 接口不存在 | 二级宏观错误码 |
|
||||
* | C0300 | 数据库服务出错 | 二级宏观错误码 |
|
||||
* </pre>
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2020/6/23
|
||||
**/
|
||||
public enum ResultCode implements IResultCode, Serializable {
|
||||
|
||||
SUCCESS("00000", "成功"),
|
||||
|
||||
/** 一级宏观错误码:用户端错误(由客户端输入/认证/权限/请求方式等引起,需客户端配合修正) */
|
||||
USER_ERROR("A0001", "用户端错误"),
|
||||
|
||||
|
||||
/** 二级宏观错误码:用户端具体错误(按号段细分,便于定位是注册/登录/令牌/参数/防重等问题) */
|
||||
|
||||
/** A01xx:用户注册错误 */
|
||||
USER_REGISTRATION_ERROR("A0100", "用户注册错误"),
|
||||
USER_NOT_AGREE_PRIVACY_AGREEMENT("A0101", "用户未同意隐私协议"),
|
||||
|
||||
/** A013x:校验码输入错误 */
|
||||
VERIFICATION_CODE_INPUT_ERROR("A0130", "校验码输入错误"),
|
||||
|
||||
/** A02xx:用户登录异常 */
|
||||
USER_LOGIN_EXCEPTION("A0200", "用户登录异常"),
|
||||
ACCOUNT_NOT_FOUND("A0201", "用户账户不存在"),
|
||||
ACCOUNT_FROZEN("A0202", "用户账户被冻结"),
|
||||
USER_PASSWORD_ERROR("A0210", "用户名或密码错误"),
|
||||
/** A023x:令牌无效或已过期 */
|
||||
ACCESS_TOKEN_INVALID("A0230", "访问令牌无效或已过期"),
|
||||
REFRESH_TOKEN_INVALID("A0231", "刷新令牌无效或已过期"),
|
||||
/** A024x:验证码错误 */
|
||||
USER_VERIFICATION_CODE_ERROR("A0240", "验证码错误"),
|
||||
USER_VERIFICATION_CODE_ATTEMPT_LIMIT_EXCEEDED("A0241", "用户验证码尝试次数超限"),
|
||||
USER_VERIFICATION_CODE_EXPIRED("A0242", "用户验证码过期"),
|
||||
|
||||
/** A03xx:访问权限异常 */
|
||||
ACCESS_PERMISSION_EXCEPTION("A0300", "访问权限异常"),
|
||||
ACCESS_UNAUTHORIZED("A0301", "访问未授权"),
|
||||
|
||||
/** A04xx:用户请求参数错误 */
|
||||
USER_REQUEST_PARAMETER_ERROR("A0400", "用户请求参数错误"),
|
||||
INVALID_USER_INPUT("A0402", "无效的用户输入"),
|
||||
REQUEST_REQUIRED_PARAMETER_IS_EMPTY("A0410", "请求必填参数为空"),
|
||||
PARAMETER_FORMAT_MISMATCH("A0421", "参数格式不匹配"),
|
||||
|
||||
/** A05xx:用户请求服务异常 */
|
||||
USER_REQUEST_SERVICE_EXCEPTION("A0500", "用户请求服务异常"),
|
||||
REQUEST_CONCURRENCY_LIMIT_EXCEEDED("A0502", "请求并发数超出限制"),
|
||||
DUPLICATE_SUBMISSION("A0506", "请勿重复提交"),
|
||||
|
||||
/** A07xx:文件处理异常 */
|
||||
UPLOAD_FILE_EXCEPTION("A0700", "上传文件异常"),
|
||||
DELETE_FILE_EXCEPTION("A0710", "删除文件异常"),
|
||||
|
||||
/** 一级宏观错误码:系统端错误(服务端内部异常/超时/不可用等,需后端排查修复) */
|
||||
SYSTEM_ERROR("B0001", "系统执行出错"),
|
||||
|
||||
/** 二级宏观错误码:系统端具体错误(按号段细分,便于定位超时/限流/资源耗尽等) */
|
||||
SYSTEM_EXECUTION_TIMEOUT("B0100", "系统执行超时"),
|
||||
|
||||
/** 一级宏观错误码:第三方服务错误(外部依赖/中间件/数据库等引起,需检查依赖健康与配置) */
|
||||
THIRD_PARTY_SERVICE_ERROR("C0001", "调用第三方服务出错"),
|
||||
|
||||
/** 二级宏观错误码:第三方服务具体错误(按号段细分,便于定位是接口不存在/数据库异常等) */
|
||||
INTERFACE_NOT_EXIST("C0113", "接口不存在"),
|
||||
DATABASE_SERVICE_ERROR("C0300", "数据库服务出错"),
|
||||
DATABASE_EXECUTION_SYNTAX_ERROR("C0313", "数据库执行语法错误"),
|
||||
INTEGRITY_CONSTRAINT_VIOLATION("C0342", "违反了完整性约束"),
|
||||
DATABASE_ACCESS_DENIED("C0351", "演示环境已禁用数据库写入功能,请本地部署修改数据库链接或开启Mock模式进行体验");
|
||||
|
||||
private final String code;
|
||||
|
||||
private final String msg;
|
||||
|
||||
ResultCode(String code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"\"code\":\"" + code + '\"' +
|
||||
", \"msg\":\"" + msg + '\"' +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
public static ResultCode getValue(String code) {
|
||||
for (ResultCode value : values()) {
|
||||
if (value.getCode().equals(code)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return SYSTEM_ERROR; // 默认系统执行错误
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
package com.youlai.boot.common.util;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 日期工具类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.4.2
|
||||
*/
|
||||
public class DateUtils {
|
||||
|
||||
/**
|
||||
* 区间日期格式化为数据库日期格式
|
||||
* <p>
|
||||
* eg:2021-01-01 → 2021-01-01 00:00:00
|
||||
*
|
||||
* @param obj 要处理的对象
|
||||
* @param startTimeFieldName 起始时间字段名
|
||||
* @param endTimeFieldName 结束时间字段名
|
||||
*/
|
||||
public static void toDatabaseFormat(Object obj, String startTimeFieldName, String endTimeFieldName) {
|
||||
Field startTimeField = ReflectUtil.getField(obj.getClass(), startTimeFieldName);
|
||||
Field endTimeField = ReflectUtil.getField(obj.getClass(), endTimeFieldName);
|
||||
|
||||
if (startTimeField != null) {
|
||||
processDateTimeField(obj, startTimeField, startTimeFieldName, "yyyy-MM-dd 00:00:00");
|
||||
}
|
||||
|
||||
if (endTimeField != null) {
|
||||
processDateTimeField(obj, endTimeField, endTimeFieldName, "yyyy-MM-dd 23:59:59");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日期字段
|
||||
*
|
||||
* @param obj 要处理的对象
|
||||
* @param field 字段
|
||||
* @param fieldName 字段名
|
||||
* @param targetPattern 目标数据库日期格式
|
||||
*/
|
||||
private static void processDateTimeField(Object obj, Field field, String fieldName, String targetPattern) {
|
||||
Object fieldValue = ReflectUtil.getFieldValue(obj, fieldName);
|
||||
if (fieldValue != null) {
|
||||
// 得到原始的日期格式
|
||||
String pattern = field.isAnnotationPresent(DateTimeFormat.class) ? field.getAnnotation(DateTimeFormat.class).pattern() : "yyyy-MM-dd";
|
||||
// 转换为日期对象
|
||||
DateTime dateTime = DateUtil.parse(StrUtil.toString(fieldValue), pattern);
|
||||
// 转换为目标数据库日期格式
|
||||
ReflectUtil.setFieldValue(obj, fieldName, dateTime.toString(targetPattern));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.youlai.boot.common.validator;
|
||||
|
||||
import com.youlai.boot.common.annotation.ValidField;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 字段校验器
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2024/11/18
|
||||
*/
|
||||
public class FieldValidator implements ConstraintValidator<ValidField, String> {
|
||||
|
||||
private String[] allowedValues;
|
||||
|
||||
@Override
|
||||
public void initialize(ValidField constraintAnnotation) {
|
||||
this.allowedValues = constraintAnnotation.allowedValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
return Arrays.asList(allowedValues).contains(value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user