This commit is contained in:
2025-12-18 16:48:45 +08:00
parent b97f0da746
commit 41cbe2bd54
80 changed files with 5434 additions and 351 deletions

View File

@@ -24,6 +24,10 @@
<groupId>org.xyzh.common</groupId>
<artifactId>common-auth</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-exception</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId>

View File

@@ -7,7 +7,7 @@ import org.springframework.stereotype.Component;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
import org.xyzh.common.utils.IDUtils;
import org.xyzh.common.utils.id.IdUtil;
import javax.crypto.SecretKey;
import java.util.Date;
@@ -55,7 +55,7 @@ public class JwtTokenUtil {
return Jwts.builder()
.setClaims(claims)
.setSubject(user.getUserId())
.setId(IDUtils.generateID()) // 使用IDUtils生成JWT ID
.setId(IdUtil.generateID()) // 使用IdUtil生成JWT ID
.setIssuedAt(new Date())
.setExpiration(generateExpirationDate())
.signWith(getSigningKey())

View File

@@ -0,0 +1,116 @@
package org.xyzh.common.auth.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.redis.service.RedisService;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
/**
* @description 登录用户工具类从Redis获取当前登录用户信息
* @filename LoginUtil.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Component
public class LoginUtil {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
private static final String LOGIN_TOKEN_PREFIX = "login:token:";
@Autowired
private RedisService redisService;
private static LoginUtil instance;
@PostConstruct
public void init() {
instance = this;
}
/**
* 获取当前登录用户信息
*/
public static LoginDomain getCurrentLogin() {
String token = getToken();
if (!StringUtils.hasText(token)) {
return null;
}
try {
String cacheKey = LOGIN_TOKEN_PREFIX + token;
Object obj = instance.redisService.get(cacheKey);
if (obj instanceof LoginDomain) {
return (LoginDomain) obj;
}
} catch (Exception e) {
// 忽略异常
}
return null;
}
/**
* 获取当前用户ID
*/
public static String getCurrentUserId() {
LoginDomain login = getCurrentLogin();
if (login != null && login.getUser() != null) {
return login.getUser().getUserId();
}
return null;
}
/**
* 获取当前用户名
*/
public static String getCurrentUserName() {
LoginDomain login = getCurrentLogin();
if (login != null && login.getUserInfo() != null) {
return login.getUserInfo().getUsername();
}
return null;
}
/**
* 判断用户是否已登录
*/
public static boolean isLoggedIn() {
return getCurrentLogin() != null;
}
/**
* 从请求头获取Token
*/
public static String getToken() {
HttpServletRequest request = getRequest();
if (request == null) {
return null;
}
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) {
return authHeader.substring(BEARER_PREFIX.length());
}
return null;
}
/**
* 获取当前请求
*/
private static HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
return attributes.getRequest();
}
return null;
}
}

View File

@@ -22,6 +22,9 @@ public class PageDomain<T> implements Serializable {
* @since 2025-11-02
*/
private List<T> dataList;
public PageDomain() {
}
public PageDomain(PageParam pageParam, List<T> dataList) {
if (pageParam == null) {

View File

@@ -73,4 +73,11 @@ public class PageParam implements Serializable {
}
this.offset = (this.page - 1) * this.pageSize;
}
public void setTotal(int total){
this.total = total;
if (this.pageSize > 0) {
this.totalPages = (int) Math.ceil((double) total / pageSize);
}
}
}

View File

@@ -0,0 +1,27 @@
package org.xyzh.common.dto.sys;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "来客信息")
public class TbGuestDTO extends BaseDTO{
private static final long serialVersionUID = 1L;
@Schema(description = "来客ID")
private String userId;
@Schema(description = "姓名")
private String name;
@Schema(description = "电话")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "微信ID")
private String wechatId;
}

View File

@@ -24,9 +24,15 @@ public class BaseVO implements Serializable {
@Schema(description = "创建人")
private String creator;
@Schema(description = "创建人名称")
private String creatorName;
@Schema(description = "更新人")
private String updater;
@Schema(description = "更新人名称")
private String updaterName;
@Schema(description = "部门路径")
private String deptPath;

View File

@@ -0,0 +1,46 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<groupId>org.xyzh.common</groupId>
<artifactId>common-exception</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,36 @@
package org.xyzh.common.exception;
import lombok.Getter;
/**
* @description 业务异常
* @filename BusinessException.java
* @author yslg
* @copyright yslg
* @since 2025-12-17
*/
@Getter
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final Integer code;
private final String message;
public BusinessException(String message) {
super(message);
this.code = 500;
this.message = message;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
this.code = 500;
this.message = message;
}
}

View File

@@ -0,0 +1,124 @@
package org.xyzh.common.exception.handler;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
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.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.exception.BusinessException;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @description 全局异常处理器
* @filename GlobalExceptionHandler.java
* @author yslg
* @copyright yslg
* @since 2025-12-17
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return ResultDomain.failure(e.getCode(), e.getMessage());
}
/**
* 参数校验异常 - @RequestBody 参数校验失败
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
log.warn("参数校验失败: {}", message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 参数校验异常 - @Validated 校验失败(方法参数直接校验)
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleConstraintViolationException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
String message = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "));
log.warn("参数校验失败: {}", message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 参数绑定异常
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleBindException(BindException e) {
String message = e.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
log.warn("参数绑定失败: {}", message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 缺少请求参数
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
String message = "缺少必要参数: " + e.getParameterName();
log.warn(message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 缺少请求Partmultipart/form-data
*/
@ExceptionHandler(MissingServletRequestPartException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleMissingServletRequestPartException(MissingServletRequestPartException e) {
String message = "缺少必要参数: " + e.getRequestPartName();
log.warn(message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 文件上传大小超限
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.warn("文件上传大小超限: {}", e.getMessage());
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), "上传文件大小超过限制");
}
/**
* 其他未捕获异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleException(Exception e) {
log.error("系统异常: ", e);
return ResultDomain.failure(HttpStatus.INTERNAL_SERVER_ERROR.value(), "系统异常,请联系管理员");
}
}

View File

@@ -1,23 +0,0 @@
package org.xyzh.common.utils;
import java.util.UUID;
/**
* @description IDUtils.java文件描述
* @filename IDUtils.java
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
public class IDUtils {
/**
* @description 生成UUID
* @return UUID
* @author yslg
* @since 2025-11-02
*/
public static String generateID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}

View File

@@ -0,0 +1,46 @@
package org.xyzh.common.utils.id;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
/**
* @description ID生成工具类
* @filename IdUtil.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
public class IdUtil {
private static final AtomicLong SEQUENCE = new AtomicLong(0);
/**
* 生成流水号UUID格式无横线
*/
public static String getOptsn() {
return "optsn"+UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 生成雪花ID简化实现使用时间戳+序列号)
*/
public static String getSnowflakeId() {
long timestamp = System.currentTimeMillis();
long seq = SEQUENCE.incrementAndGet() % 10000;
return String.valueOf(timestamp) + String.format("%04d", seq);
}
/**
* 生成UUID
*/
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 生成ID
*/
public static String generateID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}

View File

@@ -1,8 +1,13 @@
package org.xyzh.common.utils.validation;
import org.xyzh.common.utils.validation.method.InSetValidateMethod;
import org.xyzh.common.utils.validation.method.MinFieldsValidateMethod;
import org.xyzh.common.utils.validation.method.ObjectValidateMethod;
import org.xyzh.common.utils.validation.method.ValidateMethod;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@@ -37,10 +42,10 @@ public class ValidationUtils {
for (ValidationParam param : validationParams) {
try {
Object fieldValue = getFieldValue(obj, param.getFieldName());
validateField(param, fieldValue, result);
validateField(param, fieldValue, obj, result);
} catch (Exception e) {
result.addError(param.getFieldLabel() + "字段获取失败: " + e.getMessage());
}
}
}
return result;
@@ -52,7 +57,7 @@ public class ValidationUtils {
* @param validationParams 校验参数列表
* @return ValidationResult 校验结果
*/
public static ValidationResult validateMap(Map<String, Object> map, List<ValidationParam> validationParams) {
public static <V> ValidationResult validateMap(Map<String, V> map, List<ValidationParam> validationParams) {
ValidationResult result = new ValidationResult();
if (map == null) {
@@ -66,7 +71,7 @@ public class ValidationUtils {
for (ValidationParam param : validationParams) {
Object fieldValue = map.get(param.getFieldName());
validateField(param, fieldValue, result);
validateField(param, fieldValue, map, result);
}
return result;
@@ -76,9 +81,10 @@ public class ValidationUtils {
* @description 校验单个字段
* @param param 校验参数
* @param fieldValue 字段值
* @param obj 原始对象
* @param result 校验结果
*/
private static void validateField(ValidationParam param, Object fieldValue, ValidationResult result) {
private static void validateField(ValidationParam param, Object fieldValue, Object obj, ValidationResult result) {
String fieldLabel = param.getFieldLabel();
// 1. 必填校验
@@ -176,10 +182,11 @@ public class ValidationUtils {
}
}
// 8. 使用ValidateMethod校验直接传入实例,保留兼容性
// 8. 使用ValidateMethod校验直接传入实例
if (param.getValidateMethod() != null) {
try {
if (!param.getValidateMethod().validate(fieldValue)) {
Object validateTarget = (param.getValidateMethod() instanceof ObjectValidateMethod) ? obj : fieldValue;
if (!param.getValidateMethod().validate(validateTarget)) {
String errorMsg = param.getValidateMethod().getErrorMessage();
if (errorMsg != null && !errorMsg.isEmpty()) {
result.addError(errorMsg);
@@ -194,13 +201,27 @@ public class ValidationUtils {
}
/**
* @description 获取对象字段值支持getter方法直接访问)
* @description 获取对象字段值支持getter方法直接访问和嵌套路径
* @param obj 对象
* @param fieldName 字段名
* @param fieldName 字段名,支持嵌套路径如"pageParam.page"
* @return 字段值
* @throws Exception 异常
*/
private static Object getFieldValue(Object obj, String fieldName) throws Exception {
if (obj == null) {
return null;
}
// 支持嵌套路径,如"pageParam.page"
if (fieldName.contains(".")) {
String[] parts = fieldName.split("\\.", 2);
Object nestedObj = getFieldValue(obj, parts[0]);
if (nestedObj == null) {
return null;
}
return getFieldValue(nestedObj, parts[1]);
}
if (obj instanceof Map) {
return ((Map<?, ?>) obj).get(fieldName);
}
@@ -317,5 +338,46 @@ public class ValidationUtils {
.patternDesc("请输入有效的手机号码")
.build();
}
}
/**
* @description 创建"至少填一个"校验参数
* @param fieldNames 字段名列表
* @param fieldLabels 字段标签列表
* @return ValidationParam
*/
public static ValidationParam atLeastOne(List<String> fieldNames, List<String> fieldLabels) {
return minFields(fieldNames, fieldLabels, 1);
}
/**
* @description 创建"至少填几个"校验参数
* @param fieldNames 字段名列表
* @param fieldLabels 字段标签列表
* @param minCount 至少填写的数量
* @return ValidationParam
*/
public static ValidationParam minFields(List<String> fieldNames, List<String> fieldLabels, int minCount) {
return ValidationParam.builder()
.fieldName("_minFields")
.fieldLabel(String.join("", fieldLabels))
.validateMethod(new MinFieldsValidateMethod(fieldNames, fieldLabels, minCount))
.build();
}
/**
* @description 创建N选1校验参数值必须是集合中的一个
* @param fieldName 字段名
* @param fieldLabel 字段标签
* @param required 是否必填
* @param allowedValues 允许的值集合
* @return ValidationParam
*/
public static ValidationParam inSet(String fieldName, String fieldLabel, Boolean required, Collection<?> allowedValues) {
return ValidationParam.builder()
.fieldName(fieldName)
.fieldLabel(fieldLabel)
.required(required)
.validateMethod(new InSetValidateMethod(fieldLabel, allowedValues))
.build();
}
}

View File

@@ -0,0 +1,68 @@
package org.xyzh.common.utils.validation.method;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @description N选1校验方法值必须是指定集合中的一个
* @filename InSetValidateMethod.java
* @author yslg
* @copyright xyzh
* @since 2025-12-18
*/
public class InSetValidateMethod implements ValidateMethod {
private final Set<Object> allowedValues;
private final String errorMessage;
/**
* @description 构造函数
* @param fieldLabel 字段标签
* @param allowedValues 允许的值集合
*/
public InSetValidateMethod(String fieldLabel, Collection<?> allowedValues) {
this.allowedValues = allowedValues.stream().map(v -> (Object) v).collect(Collectors.toSet());
this.errorMessage = fieldLabel + "必须是以下值之一: " + formatValues(allowedValues);
}
/**
* @description 构造函数(自定义错误信息)
* @param fieldLabel 字段标签
* @param allowedValues 允许的值集合
* @param customErrorMessage 自定义错误信息
*/
public InSetValidateMethod(String fieldLabel, Collection<?> allowedValues, String customErrorMessage) {
this.allowedValues = allowedValues.stream().map(v -> (Object) v).collect(Collectors.toSet());
this.errorMessage = customErrorMessage;
}
@Override
public Boolean validate(Object value) {
if (value == null) {
return false;
}
return allowedValues.contains(value);
}
@Override
public String getErrorMessage() {
return errorMessage;
}
@Override
public String getName() {
return "N选1校验";
}
/**
* @description 格式化允许值列表
* @param values 值集合
* @return 格式化后的字符串
*/
private String formatValues(Collection<?> values) {
return values.stream()
.map(v -> v instanceof String ? "\"" + v + "\"" : String.valueOf(v))
.collect(Collectors.joining(", ", "[", "]"));
}
}

View File

@@ -0,0 +1,98 @@
package org.xyzh.common.utils.validation.method;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
/**
* @description 至少填几个字段校验方法
* @filename MinFieldsValidateMethod.java
* @author yslg
* @copyright yslg
* @since 2025-12-18
*/
public class MinFieldsValidateMethod implements ObjectValidateMethod {
private final List<String> fieldNames;
private final int minCount;
private final String errorMessage;
public MinFieldsValidateMethod(List<String> fieldNames, List<String> fieldLabels) {
this(fieldNames, fieldLabels, 1);
}
public MinFieldsValidateMethod(List<String> fieldNames, List<String> fieldLabels, int minCount) {
this.fieldNames = fieldNames;
this.minCount = minCount;
this.errorMessage = String.join("", fieldLabels) + "至少填写" + minCount + "";
}
@Override
public Boolean validate(Object obj) {
if (obj == null || fieldNames == null || fieldNames.isEmpty()) {
return false;
}
int count = 0;
for (String fieldName : fieldNames) {
try {
Object fieldValue = getFieldValue(obj, fieldName);
if (fieldValue != null) {
if (fieldValue instanceof String) {
if (!((String) fieldValue).trim().isEmpty()) {
count++;
}
} else {
count++;
}
}
} catch (Exception e) {
// 字段不存在,忽略
}
}
return count >= minCount;
}
private Object getFieldValue(Object obj, String fieldName) throws Exception {
if (obj instanceof Map) {
return ((Map<?, ?>) obj).get(fieldName);
}
Class<?> clazz = obj.getClass();
// 尝试getter方法
try {
String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method getter = clazz.getMethod(getterName);
return getter.invoke(obj);
} catch (NoSuchMethodException e) {
// 尝试直接访问字段
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ex) {
// 尝试父类
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
Field field = superClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}
throw ex;
}
}
}
@Override
public String getErrorMessage() {
return errorMessage;
}
@Override
public String getName() {
return "至少填" + minCount + "个校验";
}
}

View File

@@ -0,0 +1,11 @@
package org.xyzh.common.utils.validation.method;
/**
* @description 对象级校验方法接口,表示对整个对象进行校验
* @filename ObjectValidateMethod.java
* @author yslg
* @copyright yslg
* @since 2025-12-18
*/
public interface ObjectValidateMethod extends ValidateMethod {
}

View File

@@ -20,6 +20,7 @@
<module>common-redis</module>
<module>common-utils</module>
<module>common-all</module>
<module>common-exception</module>
</modules>
<properties>
@@ -59,6 +60,11 @@
<artifactId>common-utils</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-exception</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>