temp
This commit is contained in:
45
urbanLifelineServ/common/common-all/pom.xml
Normal file
45
urbanLifelineServ/common/common-all/pom.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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-all</artifactId>
|
||||
<version>${urban-lifeline.version}</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-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-dto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-utils</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
123
urbanLifelineServ/common/common-auth/pom.xml
Normal file
123
urbanLifelineServ/common/common-auth/pom.xml
Normal file
@@ -0,0 +1,123 @@
|
||||
<?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-auth</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Common Core dependency -->
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-utils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-dto</artifactId>
|
||||
</dependency>
|
||||
<!-- JWT Dependencies -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<!-- 排除默认的logback依赖 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<!-- 排除默认的logback依赖 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Log4j2 日志依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
<!-- JWT 依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 配置处理 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||
<!-- 排除默认的logback依赖 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.xyzh.common.auth.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @description HttpLogin.java文件描述 HTTP登录注解
|
||||
* @filename HttpLogin.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface HttpLogin {
|
||||
|
||||
/**
|
||||
* @description 是否必需,默认为true
|
||||
* @return boolean
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
boolean required() default true;
|
||||
|
||||
/**
|
||||
* @description 当token无效时的错误消息
|
||||
* @return String
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
String message() default "用户未登录或登录已过期";
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.xyzh.common.auth.annotation.resovler;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.xyzh.common.auth.annotation.HttpLogin;
|
||||
import org.xyzh.common.auth.token.TokenParser;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
import org.xyzh.common.utils.NonUtils;
|
||||
import org.xyzh.redis.service.RedisService;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @description HttpLoginArgumentResolver.java文件描述 HTTP登录参数解析器
|
||||
* @filename HttpLoginArgumentResolver.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Component
|
||||
public class HttpLoginArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final TokenParser tokenParser;
|
||||
private final RedisService redisService;
|
||||
|
||||
/**
|
||||
* 使用构造器注入,依赖TokenParser接口而非具体实现
|
||||
* 这样避免了与auth模块的直接依赖,解决循环依赖问题
|
||||
*/
|
||||
public HttpLoginArgumentResolver(TokenParser tokenParser,
|
||||
RedisService redisService) {
|
||||
this.tokenParser = tokenParser;
|
||||
this.redisService = redisService;
|
||||
}
|
||||
|
||||
private static final String TOKEN_PREFIX = "Bearer ";
|
||||
private static final String REDIS_LOGIN_PREFIX = "login:token:";
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(HttpLogin.class)
|
||||
&& LoginDomain.class.isAssignableFrom(parameter.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
HttpLogin httpLogin = parameter.getParameterAnnotation(HttpLogin.class);
|
||||
if (httpLogin == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 从请求头中获取token
|
||||
String token = extractTokenFromRequest(webRequest);
|
||||
|
||||
if (NonUtils.isEmpty(token)) {
|
||||
if (httpLogin.required()) {
|
||||
throw new IllegalArgumentException(httpLogin.message());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证token格式和有效性
|
||||
if (!tokenParser.validateToken(token, tokenParser.getUserIdFromToken(token))) {
|
||||
if (httpLogin.required()) {
|
||||
throw new IllegalArgumentException(httpLogin.message());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 从Redis中获取LoginDomain
|
||||
String userId = tokenParser.getUserIdFromToken(token);
|
||||
String redisKey = REDIS_LOGIN_PREFIX + userId;
|
||||
LoginDomain loginDomain = (LoginDomain) redisService.get(redisKey);
|
||||
|
||||
if (loginDomain == null) {
|
||||
if (httpLogin.required()) {
|
||||
throw new IllegalArgumentException(httpLogin.message());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新token信息
|
||||
loginDomain.setToken(token);
|
||||
return loginDomain;
|
||||
|
||||
} catch (Exception e) {
|
||||
if (httpLogin.required()) {
|
||||
throw new IllegalArgumentException(httpLogin.message());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从请求中提取token
|
||||
* @param webRequest 请求对象
|
||||
* @return String token
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String extractTokenFromRequest(NativeWebRequest webRequest) {
|
||||
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 优先从Authorization头获取
|
||||
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (NonUtils.isNotEmpty(authHeader) && authHeader.startsWith(TOKEN_PREFIX)) {
|
||||
return authHeader.substring(TOKEN_PREFIX.length());
|
||||
}
|
||||
|
||||
// 从请求参数中获取token
|
||||
String token = request.getParameter("token");
|
||||
if (NonUtils.isNotEmpty(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
// 从请求头中获取token
|
||||
token = request.getHeader("token");
|
||||
if (NonUtils.isNotEmpty(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package org.xyzh.common.auth.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 认证配置属性类
|
||||
* 用于配置认证相关的属性,包括白名单路径
|
||||
*
|
||||
* @author yslg
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "urban-lifeline.auth")
|
||||
public class AuthProperties {
|
||||
|
||||
/**
|
||||
* 是否启用认证过滤器
|
||||
* 默认启用
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 登录接口路径
|
||||
* 支持不同服务自定义登录地址
|
||||
*/
|
||||
private String loginPath = "/urban-lifeline/auth/login";
|
||||
|
||||
/**
|
||||
* 登出接口路径
|
||||
*/
|
||||
private String logoutPath = "/urban-lifeline/auth/logout";
|
||||
|
||||
/**
|
||||
* 验证码获取接口路径
|
||||
*/
|
||||
private String captchaPath = "/urban-lifeline/auth/captcha";
|
||||
|
||||
/**
|
||||
* 刷新 Token 接口路径
|
||||
*/
|
||||
private String refreshPath = "/urban-lifeline/auth/refresh";
|
||||
|
||||
/**
|
||||
* 通用白名单路径列表(非认证接口)
|
||||
* 例如:Swagger、静态资源、健康检查等
|
||||
*/
|
||||
private List<String> whitelist = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Token 请求头名称
|
||||
* 默认: Authorization
|
||||
*/
|
||||
private String tokenHeader = "Authorization";
|
||||
|
||||
/**
|
||||
* Token 前缀
|
||||
* 默认: Bearer
|
||||
*/
|
||||
private String tokenPrefix = "Bearer ";
|
||||
|
||||
public AuthProperties() {
|
||||
// 默认通用白名单:Swagger 及静态资源等
|
||||
whitelist.add("/swagger-ui/**");
|
||||
whitelist.add("/swagger-ui.html");
|
||||
whitelist.add("/v3/api-docs/**");
|
||||
whitelist.add("/webjars/**");
|
||||
whitelist.add("/favicon.ico");
|
||||
whitelist.add("/error");
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getLoginPath() {
|
||||
return loginPath;
|
||||
}
|
||||
|
||||
public void setLoginPath(String loginPath) {
|
||||
this.loginPath = loginPath;
|
||||
}
|
||||
|
||||
public String getLogoutPath() {
|
||||
return logoutPath;
|
||||
}
|
||||
|
||||
public void setLogoutPath(String logoutPath) {
|
||||
this.logoutPath = logoutPath;
|
||||
}
|
||||
|
||||
public String getCaptchaPath() {
|
||||
return captchaPath;
|
||||
}
|
||||
|
||||
public void setCaptchaPath(String captchaPath) {
|
||||
this.captchaPath = captchaPath;
|
||||
}
|
||||
|
||||
public String getRefreshPath() {
|
||||
return refreshPath;
|
||||
}
|
||||
|
||||
public void setRefreshPath(String refreshPath) {
|
||||
this.refreshPath = refreshPath;
|
||||
}
|
||||
|
||||
public List<String> getWhitelist() {
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
public void setWhitelist(List<String> whitelist) {
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证相关接口路径集合(login / logout / captcha / refresh)
|
||||
* 供 SecurityConfig 和 JwtAuthenticationFilter 统一放行
|
||||
*/
|
||||
public List<String> getAuthPaths() {
|
||||
List<String> authPaths = new ArrayList<>();
|
||||
if (StringUtils.hasText(loginPath)) {
|
||||
authPaths.add(loginPath);
|
||||
}
|
||||
if (StringUtils.hasText(logoutPath)) {
|
||||
authPaths.add(logoutPath);
|
||||
}
|
||||
if (StringUtils.hasText(captchaPath)) {
|
||||
authPaths.add(captchaPath);
|
||||
}
|
||||
if (StringUtils.hasText(refreshPath)) {
|
||||
authPaths.add(refreshPath);
|
||||
}
|
||||
return authPaths;
|
||||
}
|
||||
|
||||
public String getTokenHeader() {
|
||||
return tokenHeader;
|
||||
}
|
||||
|
||||
public void setTokenHeader(String tokenHeader) {
|
||||
this.tokenHeader = tokenHeader;
|
||||
}
|
||||
|
||||
public String getTokenPrefix() {
|
||||
return tokenPrefix;
|
||||
}
|
||||
|
||||
public void setTokenPrefix(String tokenPrefix) {
|
||||
this.tokenPrefix = tokenPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.xyzh.common.auth.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.xyzh.common.auth.filter.JwtAuthenticationFilter;
|
||||
import org.xyzh.common.auth.token.TokenParser;
|
||||
import org.xyzh.redis.service.RedisService;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationFilter jwtAuthenticationFilter(TokenParser tokenParser,
|
||||
AuthProperties authProperties,
|
||||
RedisService redisService) {
|
||||
return new JwtAuthenticationFilter(tokenParser, authProperties, redisService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http,
|
||||
AuthProperties authProperties,
|
||||
JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.formLogin(form -> form.disable())
|
||||
.httpBasic(basic -> basic.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(authz -> {
|
||||
// 认证接口放行(login / logout / captcha / refresh)
|
||||
if (authProperties.getAuthPaths() != null) {
|
||||
authProperties.getAuthPaths().forEach(path -> authz.requestMatchers(path).permitAll());
|
||||
}
|
||||
|
||||
// 通用白名单放行(Swagger、静态资源等)
|
||||
if (authProperties.getWhitelist() != null) {
|
||||
authProperties.getWhitelist().forEach(path -> authz.requestMatchers(path).permitAll());
|
||||
}
|
||||
|
||||
authz
|
||||
.requestMatchers("/error", "/favicon.ico").permitAll()
|
||||
.anyRequest().authenticated();
|
||||
})
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.xyzh.common.auth.config;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.xyzh.common.auth.annotation.resovler.HttpLoginArgumentResolver;
|
||||
|
||||
/**
|
||||
* @description WebMvcConfig.java文件描述 WebMVC配置类
|
||||
* @filename WebMvcConfig.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
private final HandlerMethodArgumentResolver httpLoginArgumentResolver;
|
||||
|
||||
/**
|
||||
* 使用构造器注入
|
||||
* 通过接口抽象解决了循环依赖问题,不再需要@Lazy注解
|
||||
*/
|
||||
public WebMvcConfig(HttpLoginArgumentResolver httpLoginArgumentResolver) {
|
||||
this.httpLoginArgumentResolver = httpLoginArgumentResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
||||
resolvers.add(httpLoginArgumentResolver);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.xyzh.common.auth.contants;
|
||||
|
||||
/**
|
||||
* @description 认证相关常量类
|
||||
* @filename AuthContants.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-09
|
||||
*/
|
||||
public class AuthContants {
|
||||
|
||||
/**
|
||||
* 用户ID请求属性键
|
||||
*/
|
||||
public static final String USER_ID_ATTRIBUTE = "userId";
|
||||
|
||||
/**
|
||||
* 用户名请求属性键
|
||||
*/
|
||||
public static final String USERNAME_ATTRIBUTE = "username";
|
||||
|
||||
/**
|
||||
* Token请求属性键
|
||||
*/
|
||||
public static final String TOKEN_ATTRIBUTE = "token";
|
||||
|
||||
/**
|
||||
* JWT Claims 中的用户名键
|
||||
*/
|
||||
public static final String CLAIMS_USERNAME_KEY = "username";
|
||||
|
||||
/**
|
||||
* JWT Claims 中的用户ID键
|
||||
*/
|
||||
public static final String CLAIMS_USER_ID_KEY = "userId";
|
||||
|
||||
/**
|
||||
* 私有构造函数,防止实例化
|
||||
*/
|
||||
private AuthContants() {
|
||||
throw new UnsupportedOperationException("常量类不允许实例化");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package org.xyzh.common.auth.filter;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.xyzh.common.auth.config.AuthProperties;
|
||||
import org.xyzh.common.auth.contants.AuthContants;
|
||||
import org.xyzh.common.auth.token.TokenParser;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
|
||||
import org.xyzh.redis.service.RedisService;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @description JWT认证过滤器,用于检测用户请求是否登录,支持白名单配置
|
||||
* @filename JwtAuthenticationFilter.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-09
|
||||
*/
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
|
||||
|
||||
private final TokenParser tokenParser;
|
||||
private final AuthProperties authProperties;
|
||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
private final RedisService redisService;
|
||||
private static final String REDIS_LOGIN_PREFIX = "login:token:";
|
||||
|
||||
public JwtAuthenticationFilter(TokenParser tokenParser, AuthProperties authProperties) {
|
||||
this.tokenParser = tokenParser;
|
||||
this.authProperties = authProperties;
|
||||
this.redisService = null; // 占位,使用另一个构造函数注入
|
||||
}
|
||||
|
||||
public JwtAuthenticationFilter(TokenParser tokenParser, AuthProperties authProperties, RedisService redisService) {
|
||||
this.tokenParser = tokenParser;
|
||||
this.authProperties = authProperties;
|
||||
this.redisService = redisService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// 如果认证过滤器未启用,直接放行
|
||||
if (!authProperties.isEnabled()) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String requestPath = request.getRequestURI();
|
||||
if (requestPath == null) {
|
||||
requestPath = "";
|
||||
}
|
||||
|
||||
// 去掉 context-path,仅保留业务路径用于白名单匹配
|
||||
String contextPath = request.getContextPath();
|
||||
if (contextPath != null && !contextPath.isEmpty() && requestPath.startsWith(contextPath)) {
|
||||
requestPath = requestPath.substring(contextPath.length());
|
||||
}
|
||||
|
||||
// 检查是否在白名单中
|
||||
if (isWhitelisted(requestPath)) {
|
||||
log.debug("请求路径在白名单中,跳过认证: {}", requestPath);
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从请求头获取Token
|
||||
String token = extractToken(request);
|
||||
|
||||
if (!StringUtils.hasText(token)) {
|
||||
log.warn("请求缺少Token: {}", requestPath);
|
||||
handleUnauthorized(response, "未提供认证令牌,请先登录");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证Token
|
||||
if (tokenParser.isTokenExpired(token)) {
|
||||
log.warn("Token已过期: {}", requestPath);
|
||||
handleUnauthorized(response, "认证令牌已过期,请重新登录");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户ID
|
||||
String userId = tokenParser.getUserIdFromToken(token);
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
log.warn("Token中未找到用户ID: {}", requestPath);
|
||||
handleUnauthorized(response, "认证令牌无效");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证Token有效性
|
||||
if (!tokenParser.validateToken(token, userId)) {
|
||||
log.warn("Token验证失败: userId={}, path={}", userId, requestPath);
|
||||
handleUnauthorized(response, "认证令牌验证失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 将用户信息放入请求属性中,供后续使用
|
||||
Claims claims = tokenParser.getAllClaimsFromToken(token);
|
||||
request.setAttribute(AuthContants.USER_ID_ATTRIBUTE, userId);
|
||||
request.setAttribute(AuthContants.USERNAME_ATTRIBUTE, claims.get(AuthContants.CLAIMS_USERNAME_KEY, String.class));
|
||||
request.setAttribute(AuthContants.TOKEN_ATTRIBUTE, token);
|
||||
|
||||
// 从Redis加载 LoginDomain,并将权限装配到 Spring Security 上下文
|
||||
if (redisService != null) {
|
||||
Object obj = redisService.get(REDIS_LOGIN_PREFIX + userId);
|
||||
if (obj instanceof LoginDomain loginDomain) {
|
||||
// 组装权限码 authorities(已存在)
|
||||
List<SimpleGrantedAuthority> permAuthorities = null;
|
||||
if (loginDomain.getUserPermissions() != null) {
|
||||
permAuthorities = loginDomain.getUserPermissions().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(TbSysPermissionDTO::getCode)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// 组装角色 authorities(关键:ROLE_ 前缀)
|
||||
List<SimpleGrantedAuthority> roleAuthorities = null;
|
||||
if (loginDomain.getUserRoles() != null) {
|
||||
roleAuthorities = loginDomain.getUserRoles().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(r -> r.getRoleId()) // 若有角色code/名称,可替换为对应字段
|
||||
.filter(StringUtils::hasText)
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// 合并权限与角色
|
||||
List<SimpleGrantedAuthority> authorities = Stream
|
||||
.concat(
|
||||
permAuthorities != null ? permAuthorities.stream() : Stream.empty(),
|
||||
roleAuthorities != null ? roleAuthorities.stream() : Stream.empty()
|
||||
)
|
||||
.toList();
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(loginDomain, null, authorities);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Token验证成功: userId={}, path={}", userId, requestPath);
|
||||
|
||||
// 继续执行过滤器链
|
||||
filterChain.doFilter(request, response);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Token解析或验证异常: path={}, error={}", requestPath, e.getMessage(), e);
|
||||
handleUnauthorized(response, "认证令牌解析失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路径是否在白名单中
|
||||
*/
|
||||
private boolean isWhitelisted(@NonNull String path) {
|
||||
// 1. 先检查认证相关接口(login / logout / captcha / refresh)
|
||||
if (authProperties.getAuthPaths() != null) {
|
||||
for (String pattern : authProperties.getAuthPaths()) {
|
||||
if (pattern != null && pathMatcher.match(pattern, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 再检查通用白名单
|
||||
if (authProperties.getWhitelist() != null) {
|
||||
for (String pattern : authProperties.getWhitelist()) {
|
||||
if (pattern != null && pathMatcher.match(pattern, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中提取Token
|
||||
*/
|
||||
private String extractToken(HttpServletRequest request) {
|
||||
String header = request.getHeader(authProperties.getTokenHeader());
|
||||
|
||||
if (!StringUtils.hasText(header)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 支持 Bearer 前缀
|
||||
String prefix = authProperties.getTokenPrefix();
|
||||
if (StringUtils.hasText(prefix) && header.startsWith(prefix)) {
|
||||
return header.substring(prefix.length()).trim();
|
||||
}
|
||||
|
||||
// 也支持直接传递Token(不带前缀)
|
||||
return header.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理未授权响应
|
||||
*/
|
||||
private void handleUnauthorized(HttpServletResponse response, String message) throws IOException {
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
|
||||
ResultDomain<Object> result = ResultDomain.failure(HttpStatus.UNAUTHORIZED.value(), message);
|
||||
String json = JSON.toJSONString(result);
|
||||
|
||||
response.getWriter().write(json);
|
||||
response.getWriter().flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.xyzh.common.auth.token;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xyzh.common.auth.utils.JwtTokenUtil;
|
||||
|
||||
/**
|
||||
* @description JwtTokenParser.java文件描述 JWT令牌解析器实现类
|
||||
* @filename JwtTokenParser.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
@Component
|
||||
public class JwtTokenParser implements TokenParser {
|
||||
|
||||
private final JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
/**
|
||||
* Spring会自动注入,无需@Autowired注解
|
||||
*/
|
||||
public JwtTokenParser(JwtTokenUtil jwtTokenUtil) {
|
||||
this.jwtTokenUtil = jwtTokenUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserIdFromToken(String token) {
|
||||
return jwtTokenUtil.getUserIdFromToken(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Claims getAllClaimsFromToken(String token) {
|
||||
return jwtTokenUtil.getAllClaimsFromToken(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateToken(String token, String userId) {
|
||||
return jwtTokenUtil.validateToken(token, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTokenExpired(String token) {
|
||||
return jwtTokenUtil.isTokenExpired(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.xyzh.common.auth.token;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
|
||||
/**
|
||||
* @description TokenParser.java文件描述 令牌解析器接口
|
||||
* @filename TokenParser.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public interface TokenParser {
|
||||
|
||||
/**
|
||||
* @description 从令牌中获取用户ID
|
||||
* @param token 令牌
|
||||
* @return String 用户ID
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
String getUserIdFromToken(String token);
|
||||
|
||||
/**
|
||||
* @description 从令牌中获取所有声明信息
|
||||
* @param token 令牌
|
||||
* @return Claims 所有声明信息
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
Claims getAllClaimsFromToken(String token);
|
||||
|
||||
/**
|
||||
* @description 验证令牌
|
||||
* @param token 令牌
|
||||
* @param userId 用户ID
|
||||
* @return boolean 是否有效
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
boolean validateToken(String token, String userId);
|
||||
|
||||
/**
|
||||
* @description 检查令牌是否过期
|
||||
* @param token 令牌
|
||||
* @return boolean 是否过期
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
boolean isTokenExpired(String token);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package org.xyzh.common.auth.utils;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description JwtTokenUtil.java文件描述 JWT工具类
|
||||
* @filename JwtTokenUtil.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
@Component
|
||||
public class JwtTokenUtil {
|
||||
|
||||
@Value("${urban-lifeline.auth.jwt-secret:schoolNewsDefaultSecretKeyForJWT2025}")
|
||||
private String secret;
|
||||
|
||||
@Value("${urban-lifeline.auth.jwt-expiration:86400}")
|
||||
private Long expiration;
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
return Keys.hmacShaKeyFor(secret.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 生成JWT令牌
|
||||
* @param loginDomain 登录域对象
|
||||
* @return String JWT令牌
|
||||
* @author yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
public String generateToken(LoginDomain loginDomain) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
TbSysUserDTO user = loginDomain.getUser();
|
||||
|
||||
claims.put("userId", user.getUserId());
|
||||
claims.put("username", user.getUsername());
|
||||
claims.put("email", user.getEmail());
|
||||
claims.put("loginType", loginDomain.getLoginType());
|
||||
claims.put("ipAddress", loginDomain.getIpAddress());
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(user.getUserId())
|
||||
.setId(IDUtils.generateID()) // 使用IDUtils生成JWT ID
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(generateExpirationDate())
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从令牌中获取用户ID
|
||||
* @param token JWT令牌
|
||||
* @return String 用户ID
|
||||
* @author yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
public String getUserIdFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从令牌中获取过期时间
|
||||
* @param token JWT令牌
|
||||
* @return Date 过期时间
|
||||
* @author yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
public Date getExpirationDateFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从令牌中获取指定信息
|
||||
* @param token JWT令牌
|
||||
* @param claimsResolver 信息解析器
|
||||
* @return T 指定信息
|
||||
* @author yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
public <T> T getClaimFromToken(String token, java.util.function.Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从令牌中获取所有信息
|
||||
* @param token JWT令牌
|
||||
* @return Claims 所有信息
|
||||
* @author yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
public Claims getAllClaimsFromToken(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 验证令牌
|
||||
* @param token JWT令牌
|
||||
* @param userId 用户ID
|
||||
* @return boolean 是否有效
|
||||
* @author yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
public boolean validateToken(String token, String userId) {
|
||||
try {
|
||||
final String tokenUserId = getUserIdFromToken(token);
|
||||
return (userId.equals(tokenUserId) && !isTokenExpired(token));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检查令牌是否过期
|
||||
* @param token JWT令牌
|
||||
* @return boolean 是否过期
|
||||
* @author yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
public boolean isTokenExpired(String token) {
|
||||
final Date expiration = getExpirationDateFromToken(token);
|
||||
return expiration.before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 生成过期时间
|
||||
* @return Date 过期时间
|
||||
* @author yslg
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
private Date generateExpirationDate() {
|
||||
return new Date(System.currentTimeMillis() + expiration * 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# 认证配置示例文件
|
||||
# 将此配置添加到各服务的 application.yml 中
|
||||
|
||||
urban-lifeline:
|
||||
auth:
|
||||
enabled: true
|
||||
|
||||
# 认证接口:可以按服务自定义
|
||||
login-path: /urban-lifeline/auth/login
|
||||
logout-path: /urban-lifeline/auth/logout
|
||||
captcha-path: /urban-lifeline/auth/captcha
|
||||
refresh-path: /urban-lifeline/auth/refresh
|
||||
|
||||
# 通用白名单(非认证接口)
|
||||
whitelist:
|
||||
# Swagger/OpenAPI 文档相关(建议不带 context-path)
|
||||
- /swagger-ui/**
|
||||
- /swagger-ui.html
|
||||
- /v3/api-docs/**
|
||||
- /webjars/**
|
||||
|
||||
# 静态资源
|
||||
- /favicon.ico
|
||||
- /error
|
||||
|
||||
# 健康检查
|
||||
- /actuator/health
|
||||
- /actuator/info
|
||||
|
||||
# 其他需要放行的路径
|
||||
# - /public/**
|
||||
# - /api/public/**
|
||||
|
||||
29
urbanLifelineServ/common/common-core/pom.xml
Normal file
29
urbanLifelineServ/common/common-core/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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-core</artifactId>
|
||||
<version>${urban-lifeline.version}</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-dto</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.xyzh.common.core.constant;
|
||||
|
||||
|
||||
/**
|
||||
* @description Constants.java文件描述 常量类
|
||||
* @filename Constants.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
/**
|
||||
* @description 令牌前缀
|
||||
* @filename Constants.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
/**
|
||||
* @description JSON_WHITELIST_STR JSON白名单
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static final String JSON_WHITELIST_STR = "org.xyzh";
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.xyzh.common.core.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysUserRoleDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysDeptDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysViewDTO;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 登录域
|
||||
* @filename 登录域
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
public class LoginDomain implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private TbSysUserDTO user;
|
||||
|
||||
private TbSysUserInfoDTO userInfo;
|
||||
|
||||
private List<TbSysUserRoleDTO> userRoles;
|
||||
|
||||
private List<TbSysDeptDTO> userDepts;
|
||||
|
||||
private List<TbSysPermissionDTO> userPermissions;
|
||||
|
||||
private List<TbSysViewDTO> userViews;
|
||||
|
||||
private String token;
|
||||
|
||||
private Date tokenExpireTime;
|
||||
|
||||
private String loginTime;
|
||||
|
||||
private String ipAddress;
|
||||
|
||||
private String loginType;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.xyzh.common.core.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 登录参数
|
||||
* @filename 登录参数
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
public class LoginParam implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* 密码
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 邮箱
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String email;
|
||||
/**
|
||||
* 手机号
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String phone;
|
||||
/**
|
||||
* 微信ID
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String wechatId;
|
||||
|
||||
/**
|
||||
* 验证码类型
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String captchaType;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String captcha;
|
||||
/**
|
||||
* 验证码ID
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String captchaId;
|
||||
/**
|
||||
* 登录类型
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private String loginType;
|
||||
/**
|
||||
* 是否记住我
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private Boolean rememberMe;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.xyzh.common.core.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.xyzh.common.core.page.PageDomain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 结果域 通用返回结果
|
||||
* @filename 结果域
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
public class ResultDomain<T> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer code;
|
||||
private boolean success;
|
||||
private String message;
|
||||
private T data;
|
||||
private List<T> dataList;
|
||||
private PageDomain<T> pageDomain;
|
||||
|
||||
public ResultDomain(){
|
||||
}
|
||||
|
||||
public ResultDomain(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.success = false;
|
||||
}
|
||||
|
||||
public ResultDomain(int code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.success = false;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ResultDomain(int code, String message, List<T> dataList) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.success = false;
|
||||
this.dataList = dataList;
|
||||
}
|
||||
|
||||
public ResultDomain(int code, String message, PageDomain<T> pageDomain) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.success = false;
|
||||
this.pageDomain = pageDomain;
|
||||
}
|
||||
|
||||
// 静态工厂方法 - 推荐使用(简洁、清晰)
|
||||
public static <R> ResultDomain<R> success(String message) {
|
||||
ResultDomain<R> result = new ResultDomain<>();
|
||||
result.success = true;
|
||||
result.message = message;
|
||||
result.code = HttpStatus.OK.value();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <R> ResultDomain<R> success(String message, R data) {
|
||||
ResultDomain<R> result = new ResultDomain<>();
|
||||
result.success = true;
|
||||
result.message = message;
|
||||
result.data = data;
|
||||
result.code = HttpStatus.OK.value();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <R> ResultDomain<R> success(String message, List<R> dataList) {
|
||||
ResultDomain<R> result = new ResultDomain<>();
|
||||
result.success = true;
|
||||
result.message = message;
|
||||
result.dataList = dataList;
|
||||
result.code = HttpStatus.OK.value();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <R> ResultDomain<R> success(String message, PageDomain<R> pageDomain) {
|
||||
ResultDomain<R> result = new ResultDomain<>();
|
||||
result.success = true;
|
||||
result.message = message;
|
||||
result.pageDomain = pageDomain;
|
||||
result.code = HttpStatus.OK.value();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <R> ResultDomain<R> failure(String message) {
|
||||
ResultDomain<R> result = new ResultDomain<>();
|
||||
result.success = false;
|
||||
result.message = message;
|
||||
result.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <R> ResultDomain<R> failure(int code, String message) {
|
||||
ResultDomain<R> result = new ResultDomain<>();
|
||||
result.success = false;
|
||||
result.message = message;
|
||||
result.code = code;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.xyzh.common.core.enums;
|
||||
|
||||
/**
|
||||
* @description 验证码类型
|
||||
* @filename CaptchaType.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-03
|
||||
*/
|
||||
public enum CaptchaType {
|
||||
|
||||
EMAIL(1, "EMAIL", "邮箱验证码"),
|
||||
SMS(2, "SMS", "短信验证码"),
|
||||
IMAGE(3, "IMAGE", "图形验证码");
|
||||
|
||||
/**
|
||||
* 验证码类型编码
|
||||
*/
|
||||
private int code;
|
||||
|
||||
/**
|
||||
* 验证码类型名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 验证码类型描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
private CaptchaType(int code, String name, String description) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据名称获取验证码类型
|
||||
* @param name 验证码类型名称
|
||||
* @return 验证码类型
|
||||
* @author yslg
|
||||
* @since 2025-11-03
|
||||
*/
|
||||
public static CaptchaType fromName(String name) {
|
||||
for (CaptchaType type : CaptchaType.values()) {
|
||||
if (type.getName().equalsIgnoreCase(name)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.xyzh.common.core.page;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PageDomain<T> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 分页参数
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private PageParam pageParam;
|
||||
|
||||
/**
|
||||
* 数据列表
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private List<T> dataList;
|
||||
|
||||
public PageDomain(PageParam pageParam, List<T> dataList) {
|
||||
if (pageParam == null) {
|
||||
throw new IllegalArgumentException("分页参数不能为空");
|
||||
}
|
||||
|
||||
this.pageParam = pageParam;
|
||||
this.dataList = dataList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.xyzh.common.core.page;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @description 分页参数
|
||||
* @filename 分页参数
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class PageParam implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private int page;
|
||||
/**
|
||||
* 每页条数
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private int pageSize;
|
||||
|
||||
private int total;
|
||||
/**
|
||||
* 总页数
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
private int totalPages;
|
||||
|
||||
private int offset;
|
||||
|
||||
public PageParam(int page, int pageSize) {
|
||||
if (page <= 0) {
|
||||
throw new IllegalArgumentException("页码必须大于0");
|
||||
}
|
||||
if (pageSize <= 0) {
|
||||
throw new IllegalArgumentException("每页条数必须大于0");
|
||||
}
|
||||
this.page = page;
|
||||
this.pageSize = pageSize;
|
||||
this.offset = (page - 1) * pageSize;
|
||||
}
|
||||
|
||||
public void setPage(int page){
|
||||
if (page <= 0) {
|
||||
throw new IllegalArgumentException("页码必须大于0");
|
||||
}
|
||||
this.page = page;
|
||||
if (this.pageSize <= 0) {
|
||||
this.pageSize = 10;
|
||||
}
|
||||
this.offset = (page - 1) * this.pageSize;
|
||||
}
|
||||
|
||||
public void setPageSize(int pageSize){
|
||||
if (pageSize <= 0) {
|
||||
throw new IllegalArgumentException("每页条数必须大于0");
|
||||
}
|
||||
this.pageSize = pageSize;
|
||||
if (this.page <= 0) {
|
||||
this.page = 1;
|
||||
}
|
||||
this.offset = (this.page - 1) * this.pageSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.xyzh.common.core.page;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PageRequest<T> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private PageParam pageParam;
|
||||
private T filter;
|
||||
}
|
||||
35
urbanLifelineServ/common/common-dto/pom.xml
Normal file
35
urbanLifelineServ/common/common-dto/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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-dto</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Existing dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.xyzh.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "基础数据传输对象")
|
||||
public class BaseDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "操作流水号")
|
||||
private String optsn;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "更新人")
|
||||
private String updater;
|
||||
|
||||
@Schema(description = "部门路径")
|
||||
private String deptPath;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "创建时间", format = "date-time")
|
||||
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "更新时间", format = "date-time")
|
||||
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description = "删除时间", format = "date-time")
|
||||
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date deleteTime;
|
||||
|
||||
@Schema(description = "是否已删除", defaultValue = "false")
|
||||
private Boolean deleted = false;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
/**
|
||||
* @description 系统访问控制列表DTO
|
||||
* @filename TbSysAclDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统访问控制列表DTO")
|
||||
public class TbSysAclDTO extends BaseDTO {
|
||||
@Schema(description = "权限ID")
|
||||
private String aclId;
|
||||
|
||||
@Schema(description = "对象类型:article/file/course/...")
|
||||
private String objectType;
|
||||
|
||||
@Schema(description = "对象ID")
|
||||
private String objectId;
|
||||
|
||||
@Schema(description = "主体类型:user/dept/role")
|
||||
private String principalType;
|
||||
|
||||
@Schema(description = "主体ID")
|
||||
private String principalId;
|
||||
|
||||
@Schema(description = "当主体为role且限定到某部门时的部门ID(支持“某部门的某角色”)")
|
||||
private String principalDeptId;
|
||||
|
||||
@Schema(description = "权限位:1读 2写 4执行")
|
||||
private Integer permission;
|
||||
|
||||
@Schema(description = "允许或显式拒绝", defaultValue = "true")
|
||||
private Boolean allow = true;
|
||||
|
||||
@Schema(description = "是否包含子级(对dept/role生效)", defaultValue = "false")
|
||||
private Boolean includeDescendants = false;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
/**
|
||||
* @description ACL 策略表:定义对象类型的层级可见/可编辑规则
|
||||
* @filename 系统权限策略DTO
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统访问控制策略DTO")
|
||||
public class TbSysAclPolicyDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "策略ID")
|
||||
private String policyId;
|
||||
|
||||
@Schema(description = "策略名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "对象类型:article/file/course/..")
|
||||
private String objectType;
|
||||
|
||||
@Schema(description = "编辑层级规则:parent_only/parent_or_same_admin/owner_only/none")
|
||||
private String editHierarchyRule;
|
||||
|
||||
@Schema(description = "可见层级规则 children_all/children_specified/none")
|
||||
private String viewHierarchyRule;
|
||||
|
||||
@Schema(description = "默认权限(无显式ACL时应用)", defaultValue = "0")
|
||||
private Integer defaultPermission=0;
|
||||
|
||||
@Schema(description = "默认是否允许", defaultValue = "true")
|
||||
private boolean defaultAllow=true;
|
||||
|
||||
@Schema(description = "是否默认应用到子级", defaultValue = "true")
|
||||
private boolean applyToChildren=true;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
/**
|
||||
* @description 系统配置DTO
|
||||
* @filename TbSysConfigDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统配置DTO")
|
||||
public class TbSysConfigDTO extends BaseDTO {
|
||||
// optsn、creator、updater、deptPath、remark、createTime、updateTime、deleteTime、deleted 继承自BaseDTO
|
||||
|
||||
@Schema(description = "配置ID")
|
||||
private String configId;
|
||||
|
||||
@Schema(description = "配置键")
|
||||
private String key;
|
||||
|
||||
@Schema(description = "配置名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "配置值")
|
||||
private String value;
|
||||
|
||||
@Schema(description = "数据类型(String, Integer, Boolean, Float, Double)")
|
||||
private String configType;
|
||||
|
||||
@Schema(description = "配置渲染类型(select, input, textarea, checkbox, radio, switch)")
|
||||
private String renderType;
|
||||
|
||||
@Schema(description = "配置描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "正则表达式校验规则(JSON)")
|
||||
private JSONObject re;
|
||||
|
||||
@Schema(description = "可选项(JSON),render_type为select、checkbox、radio时使用")
|
||||
private JSONObject options;
|
||||
|
||||
@Schema(description = "配置组")
|
||||
private String group;
|
||||
|
||||
@Schema(description = "模块id")
|
||||
private String moduleId;
|
||||
|
||||
@Schema(description = "配置顺序")
|
||||
private Integer orderNum;
|
||||
|
||||
@Schema(description = "配置状态 0:启用 1:禁用", defaultValue = "0")
|
||||
private Integer status = 0;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
/**
|
||||
* @description 系统部门DTO
|
||||
* @filename TbSysDeptDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统部门DTO")
|
||||
public class TbSysDeptDTO extends BaseDTO {
|
||||
@Schema(description = "部门ID")
|
||||
private String deptId;
|
||||
|
||||
@Schema(description = "部门名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "父级部门ID")
|
||||
private String parentId;
|
||||
|
||||
@Schema(description = "部门描述")
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
/**
|
||||
* @description 系统部门角色关系DTO
|
||||
* @filename TbSysDeptRoleDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统部门角色关系DTO")
|
||||
public class TbSysDeptRoleDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "部门ID")
|
||||
private String deptId;
|
||||
|
||||
@Schema(description = "角色ID")
|
||||
private String roleId;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统模块DTO
|
||||
* @filename TbSysModuleDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统模块DTO")
|
||||
public class TbSysModuleDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "模块ID")
|
||||
private String moduleId;
|
||||
|
||||
@Schema(description = "模块名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "模块描述")
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统权限DTO
|
||||
* @filename TbSysPermissionDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统权限DTO")
|
||||
public class TbSysPermissionDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "权限ID")
|
||||
private String permissionId;
|
||||
|
||||
@Schema(description = "权限名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "权限代码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "权限描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "模块ID")
|
||||
private String moduleId;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统角色DTO
|
||||
* @filename TbSysRoleDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统角色DTO")
|
||||
public class TbSysRoleDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "角色ID")
|
||||
private String roleId;
|
||||
|
||||
@Schema(description = "角色名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "角色描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "角色作用域 global 全局角色, dept 部门角色")
|
||||
private String scope;
|
||||
|
||||
@Schema(description = "所属部门ID")
|
||||
private String ownerDeptId;
|
||||
|
||||
@Schema(description = "角色状态 true 有效, false 无效")
|
||||
private boolean status;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统角色权限关系DTO
|
||||
* @filename TbSysRolePermissionDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统角色权限关系DTO")
|
||||
public class TbSysRolePermissionDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "角色ID")
|
||||
private String roleId;
|
||||
|
||||
@Schema(description = "权限ID")
|
||||
private String permissionId;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统用户DTO
|
||||
* @filename TbSysUserDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统用户DTO")
|
||||
public class TbSysUserDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "微信ID")
|
||||
private String wechatId;
|
||||
|
||||
@Schema(description = "用户状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "用户类型")
|
||||
private String userType;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统用户信息DTO
|
||||
* @filename TbSysUserInfoDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统用户信息DTO")
|
||||
public class TbSysUserInfoDTO extends BaseDTO {
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "性别")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "姓")
|
||||
private String familyName;
|
||||
|
||||
@Schema(description = "名")
|
||||
private String givenName;
|
||||
|
||||
@Schema(description = "全名")
|
||||
private String fullName;
|
||||
|
||||
@Schema(description = "等级")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
private String idCard;
|
||||
|
||||
@Schema(description = "地址")
|
||||
private String address;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统用户角色关系DTO
|
||||
* @filename TbSysUserRoleDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统用户角色关系DTO")
|
||||
public class TbSysUserRoleDTO extends BaseDTO {
|
||||
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "角色ID")
|
||||
private String roleId;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统视图DTO
|
||||
* @filename TbSysViewDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统视图DTO")
|
||||
public class TbSysViewDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "视图ID")
|
||||
private String viewId;
|
||||
|
||||
@Schema(description = "视图名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "父视图ID")
|
||||
private String parentId;
|
||||
|
||||
@Schema(description = "URL")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "组件")
|
||||
private String component;
|
||||
|
||||
@Schema(description = "图标")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "类型")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "布局")
|
||||
private String layout;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer orderNum;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.xyzh.common.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @description 系统视图权限关系DTO
|
||||
* @filename TbSysViewPermissionDTO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "系统视图权限关系DTO")
|
||||
public class TbSysViewPermissionDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "视图ID")
|
||||
private String viewId;
|
||||
|
||||
@Schema(description = "权限ID")
|
||||
private String permissionId;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.xyzh.common.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 基础视图对象
|
||||
* @filename BaseVO.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-05
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "基础视图对象")
|
||||
public class BaseVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "操作流水号")
|
||||
private String optsn;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "更新人")
|
||||
private String updater;
|
||||
|
||||
@Schema(description = "部门路径")
|
||||
private String deptPath;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "创建时间", format = "date-time")
|
||||
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "更新时间", format = "date-time")
|
||||
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description = "删除时间", format = "date-time")
|
||||
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date deleteTime;
|
||||
|
||||
@Schema(description = "是否已删除", defaultValue = "false")
|
||||
private Boolean deleted = false;
|
||||
}
|
||||
|
||||
67
urbanLifelineServ/common/common-redis/pom.xml
Normal file
67
urbanLifelineServ/common/common-redis/pom.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?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-redis</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Redis Starter -->
|
||||
<!-- Redis 依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
<!-- 排除默认的logback依赖 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Data Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- FastJSON2 for JSON serialization -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Common Core dependency -->
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Context for @Component annotation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Auto Configuration -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.xyzh.redis.config;
|
||||
|
||||
import org.xyzh.common.core.constant.Constants;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONReader;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.alibaba.fastjson2.filter.Filter;
|
||||
|
||||
/**
|
||||
* @description FastJson2JsonRedisSerializer.java文件描述
|
||||
* @filename FastJson2JsonRedisSerializer.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
|
||||
{
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
|
||||
|
||||
private Class<T> clazz;
|
||||
|
||||
public FastJson2JsonRedisSerializer(Class<T> clazz)
|
||||
{
|
||||
super();
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(T t) throws SerializationException
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(byte[] bytes) throws SerializationException
|
||||
{
|
||||
if (bytes == null || bytes.length <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String str = new String(bytes, DEFAULT_CHARSET);
|
||||
|
||||
return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.xyzh.redis.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
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.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* @description RedisConfig.java文件描述 Redis配置
|
||||
* @filename RedisConfig.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
@AutoConfigureBefore(RedisAutoConfiguration.class)
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings(value = { "unchecked", "rawtypes" })
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory)
|
||||
{
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
FastJson2JsonRedisSerializer<Object> serializer = new FastJson2JsonRedisSerializer<>(Object.class);
|
||||
|
||||
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
|
||||
// Hash的key也采用StringRedisSerializer的序列化方式
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
package org.xyzh.redis.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @description RedisService.java Redis工具服务类,封装常用Redis操作
|
||||
* @filename RedisService.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@SuppressWarnings(value = { "unchecked", "rawtypes" })
|
||||
@Component
|
||||
public class RedisService {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* @description 设置key-value
|
||||
* @param key String 键
|
||||
* @param value Object 值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void set(String key, Object value) {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置key-value并指定过期时间
|
||||
* @param key String 键
|
||||
* @param value Object 值
|
||||
* @param timeout long 过期时间
|
||||
* @param unit TimeUnit 时间单位
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void set(String key, Object value, long timeout, TimeUnit unit) {
|
||||
redisTemplate.opsForValue().set(key, value, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取key对应的value
|
||||
* @param key String 键
|
||||
* @return Object 值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Object get(String key) {
|
||||
return redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除key
|
||||
* @param key String 键
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void delete(String key) {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 批量删除key
|
||||
* @param keys Collection<String> 键集合
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void delete(Collection<String> keys) {
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断key是否存在
|
||||
* @param key String 键
|
||||
* @return boolean 是否存在
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public boolean hasKey(String key) {
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置key过期时间(秒)
|
||||
* @param key String 键
|
||||
* @param timeout long 过期秒数
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void setExpire(String key, long timeout) {
|
||||
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置key过期时间(自定义单位)
|
||||
* @param key String 键
|
||||
* @param timeout long 过期时间
|
||||
* @param unit TimeUnit 时间单位
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void setExpire(String key, long timeout, TimeUnit unit) {
|
||||
redisTemplate.expire(key, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取key剩余过期时间(秒)
|
||||
* @param key String 键
|
||||
* @return long 剩余秒数
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public long getExpire(String key) {
|
||||
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 原子递增
|
||||
* @param key String 键
|
||||
* @param delta long 增量
|
||||
* @return long 递增后值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public long incr(String key, long delta) {
|
||||
Long result = redisTemplate.opsForValue().increment(key, delta);
|
||||
return result != null ? result : 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 原子递减
|
||||
* @param key String 键
|
||||
* @param delta long 减量
|
||||
* @return long 递减后值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public long decr(String key, long delta) {
|
||||
Long result = redisTemplate.opsForValue().increment(key, -delta);
|
||||
return result != null ? result : 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Hash操作-put
|
||||
* @param key String 键
|
||||
* @param hashKey String 哈希键
|
||||
* @param value Object 值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void hSet(String key, String hashKey, Object value) {
|
||||
redisTemplate.opsForHash().put(key, hashKey, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Hash操作-get
|
||||
* @param key String 键
|
||||
* @param hashKey String 哈希键
|
||||
* @return Object 值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Object hGet(String key, String hashKey) {
|
||||
return redisTemplate.opsForHash().get(key, hashKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Hash操作-获取所有
|
||||
* @param key String 键
|
||||
* @return Map<Object, Object> 哈希所有键值对
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Map<Object, Object> hGetAll(String key) {
|
||||
return redisTemplate.opsForHash().entries(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Hash操作-删除
|
||||
* @param key String 键
|
||||
* @param hashKeys String[] 哈希键数组
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void hDelete(String key, String... hashKeys) {
|
||||
redisTemplate.opsForHash().delete(key, (Object[]) hashKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description List操作-左入队
|
||||
* @param key String 键
|
||||
* @param value Object 值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void lPush(String key, Object value) {
|
||||
redisTemplate.opsForList().leftPush(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description List操作-右入队
|
||||
* @param key String 键
|
||||
* @param value Object 值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void rPush(String key, Object value) {
|
||||
redisTemplate.opsForList().rightPush(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description List操作-左出队
|
||||
* @param key String 键
|
||||
* @return Object 出队值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Object lPop(String key) {
|
||||
return redisTemplate.opsForList().leftPop(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description List操作-右出队
|
||||
* @param key String 键
|
||||
* @return Object 出队值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Object rPop(String key) {
|
||||
return redisTemplate.opsForList().rightPop(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description List操作-获取区间元素
|
||||
* @param key String 键
|
||||
* @param start long 起始索引
|
||||
* @param end long 结束索引
|
||||
* @return List<Object> 元素列表
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public List<Object> lRange(String key, long start, long end) {
|
||||
return redisTemplate.opsForList().range(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Set操作-添加元素
|
||||
* @param key String 键
|
||||
* @param values Object[] 元素数组
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void sAdd(String key, Object... values) {
|
||||
redisTemplate.opsForSet().add(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Set操作-移除元素
|
||||
* @param key String 键
|
||||
* @param values Object[] 元素数组
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void sRemove(String key, Object... values) {
|
||||
redisTemplate.opsForSet().remove(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Set操作-获取所有元素
|
||||
* @param key String 键
|
||||
* @return Set<Object> 元素集合
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Set<Object> sMembers(String key) {
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description ZSet操作-添加元素
|
||||
* @param key String 键
|
||||
* @param value Object 元素
|
||||
* @param score double 分数
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void zAdd(String key, Object value, double score) {
|
||||
redisTemplate.opsForZSet().add(key, value, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description ZSet操作-移除元素
|
||||
* @param key String 键
|
||||
* @param values Object[] 元素数组
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void zRemove(String key, Object... values) {
|
||||
redisTemplate.opsForZSet().remove(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description ZSet操作-按分数区间获取元素
|
||||
* @param key String 键
|
||||
* @param min double 最小分数
|
||||
* @param max double 最大分数
|
||||
* @return Set<Object> 元素集合
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Set<Object> zRangeByScore(String key, double min, double max) {
|
||||
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description ZSet操作-获取全部元素
|
||||
* @param key String 键
|
||||
* @param start long 起始索引
|
||||
* @param end long 结束索引
|
||||
* @return Set<Object> 元素集合
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Set<Object> zRange(String key, long start, long end) {
|
||||
return redisTemplate.opsForZSet().range(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 发布消息(Pub/Sub)
|
||||
* @param channel String 通道
|
||||
* @param message Object 消息内容
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void publish(String channel, Object message) {
|
||||
redisTemplate.convertAndSend(channel, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 执行Redis原生命令
|
||||
* @param action RedisCallback<?> 回调命令
|
||||
* @return Object 执行结果
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Object execute(RedisCallback<?> action) {
|
||||
return redisTemplate.execute(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有key(慎用)
|
||||
* @param pattern String 匹配模式
|
||||
* @return Set<String> key集合
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public Set<String> keys(String pattern) {
|
||||
Set<String> keys = redisTemplate.keys(pattern);
|
||||
Set<String> stringKeys = new HashSet<>();
|
||||
if (keys != null) {
|
||||
for (String key : keys) {
|
||||
stringKeys.add(key);
|
||||
}
|
||||
}
|
||||
return stringKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 清空当前数据库(慎用)
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public void flushDb() {
|
||||
redisTemplate.execute((RedisCallback<Void>) connection -> { connection.flushDb(); return null; });
|
||||
}
|
||||
|
||||
}
|
||||
34
urbanLifelineServ/common/common-utils/pom.xml
Normal file
34
urbanLifelineServ/common/common-utils/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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-utils</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Apache POI for Excel -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,23 @@
|
||||
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("-", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @description NonUtils.java文件描述 空值判断工具类
|
||||
* @filename NonUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class NonUtils {
|
||||
|
||||
private NonUtils() {
|
||||
throw new UnsupportedOperationException("工具类不能被实例化");
|
||||
}
|
||||
|
||||
// ======================== 基础null判断 ========================
|
||||
|
||||
/**
|
||||
* 判断对象是否为null
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象为null,false-对象不为null
|
||||
*/
|
||||
public static boolean isNull(Object obj) {
|
||||
return obj == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否不为null
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象不为null,false-对象为null
|
||||
*/
|
||||
public static boolean isNotNull(Object obj) {
|
||||
return obj != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象是否都为null
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-所有对象都为null,false-至少有一个对象不为null
|
||||
*/
|
||||
public static boolean isAllNull(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return true;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isNotNull(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象是否都不为null
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-所有对象都不为null,false-至少有一个对象为null
|
||||
*/
|
||||
public static boolean isAllNotNull(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isNull(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象中是否存在null
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-存在null对象,false-不存在null对象
|
||||
*/
|
||||
public static boolean hasNull(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return true;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isNull(obj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ======================== 空值判断(包含null、空字符串、空集合等) ========================
|
||||
|
||||
/**
|
||||
* 判断对象是否为空
|
||||
* - null -> true
|
||||
* - "" -> true
|
||||
* - " " -> true (仅包含空白字符)
|
||||
* - 空集合 -> true
|
||||
* - 空数组 -> true
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象为空,false-对象不为空
|
||||
*/
|
||||
public static boolean isEmpty(Object obj) {
|
||||
if (isNull(obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 字符串判断
|
||||
if (obj instanceof CharSequence) {
|
||||
return ((CharSequence) obj).length() == 0 || obj.toString().trim().isEmpty();
|
||||
}
|
||||
|
||||
// 集合判断
|
||||
if (obj instanceof Collection) {
|
||||
return ((Collection<?>) obj).isEmpty();
|
||||
}
|
||||
|
||||
// Map判断
|
||||
if (obj instanceof Map) {
|
||||
return ((Map<?, ?>) obj).isEmpty();
|
||||
}
|
||||
|
||||
// 数组判断
|
||||
if (obj.getClass().isArray()) {
|
||||
return Array.getLength(obj) == 0;
|
||||
}
|
||||
|
||||
// Optional判断
|
||||
if (obj instanceof Optional) {
|
||||
return !((Optional<?>) obj).isPresent();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否不为空
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象不为空,false-对象为空
|
||||
*/
|
||||
public static boolean isNotEmpty(Object obj) {
|
||||
return !isEmpty(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象是否都为空
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-所有对象都为空,false-至少有一个对象不为空
|
||||
*/
|
||||
public static boolean isAllEmpty(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return true;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isNotEmpty(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象是否都不为空
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-所有对象都不为空,false-至少有一个对象为空
|
||||
*/
|
||||
public static boolean isAllNotEmpty(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isEmpty(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象中是否存在空值
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-存在空值,false-不存在空值
|
||||
*/
|
||||
public static boolean hasEmpty(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return true;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isEmpty(obj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ======================== 深度递归判断 ========================
|
||||
|
||||
/**
|
||||
* 深度判断对象是否为空(递归检查)
|
||||
* 对于集合、数组等容器类型,会递归检查其内部元素
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象为空(包括递归检查),false-对象不为空
|
||||
*/
|
||||
public static boolean isDeepEmpty(Object obj) {
|
||||
return isDeepEmpty(obj, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度判断对象是否为空(递归检查,防止循环引用)
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @param visited 已访问对象集合,用于防止循环引用
|
||||
* @return true-对象为空(包括递归检查),false-对象不为空
|
||||
*/
|
||||
private static boolean isDeepEmpty(Object obj, Set<Object> visited) {
|
||||
if (isEmpty(obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 防止循环引用
|
||||
if (visited.contains(obj)) {
|
||||
return false;
|
||||
}
|
||||
visited.add(obj);
|
||||
|
||||
try {
|
||||
// 集合类型递归检查
|
||||
if (obj instanceof Collection) {
|
||||
Collection<?> collection = (Collection<?>) obj;
|
||||
for (Object item : collection) {
|
||||
if (!isDeepEmpty(item, visited)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Map类型递归检查
|
||||
if (obj instanceof Map) {
|
||||
Map<?, ?> map = (Map<?, ?>) obj;
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (!isDeepEmpty(entry.getKey(), visited) || !isDeepEmpty(entry.getValue(), visited)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 数组类型递归检查
|
||||
if (obj.getClass().isArray()) {
|
||||
int length = Array.getLength(obj);
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (!isDeepEmpty(Array.get(obj, i), visited)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optional类型递归检查
|
||||
if (obj instanceof Optional) {
|
||||
Optional<?> optional = (Optional<?>) obj;
|
||||
return !optional.isPresent() || isDeepEmpty(optional.get(), visited);
|
||||
}
|
||||
|
||||
// 其他类型认为不为空
|
||||
return false;
|
||||
|
||||
} finally {
|
||||
visited.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度判断对象是否不为空(递归检查)
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象不为空(包括递归检查),false-对象为空
|
||||
*/
|
||||
public static boolean isDeepNotEmpty(Object obj) {
|
||||
return !isDeepEmpty(obj);
|
||||
}
|
||||
|
||||
// ======================== 集合专用方法 ========================
|
||||
|
||||
/**
|
||||
* 判断集合是否为空或null
|
||||
*
|
||||
* @param collection 待判断的集合
|
||||
* @return true-集合为null或空,false-集合不为空
|
||||
*/
|
||||
public static boolean isEmptyCollection(Collection<?> collection) {
|
||||
return collection == null || collection.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空且不为null
|
||||
*
|
||||
* @param collection 待判断的集合
|
||||
* @return true-集合不为null且不为空,false-集合为null或空
|
||||
*/
|
||||
public static boolean isNotEmptyCollection(Collection<?> collection) {
|
||||
return collection != null && !collection.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否包含有效元素(非null且非空的元素)
|
||||
*
|
||||
* @param collection 待判断的集合
|
||||
* @return true-集合包含有效元素,false-集合为空或只包含null/空元素
|
||||
*/
|
||||
public static boolean hasValidElements(Collection<?> collection) {
|
||||
if (isEmptyCollection(collection)) {
|
||||
return false;
|
||||
}
|
||||
for (Object item : collection) {
|
||||
if (isNotEmpty(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否所有元素都有效(非null且非空)
|
||||
*
|
||||
* @param collection 待判断的集合
|
||||
* @return true-集合所有元素都有效,false-集合为空或包含null/空元素
|
||||
*/
|
||||
public static boolean allValidElements(Collection<?> collection) {
|
||||
if (isEmptyCollection(collection)) {
|
||||
return false;
|
||||
}
|
||||
for (Object item : collection) {
|
||||
if (isEmpty(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤集合中的空元素,返回新集合
|
||||
*
|
||||
* @param collection 原集合
|
||||
* @param <T> 集合元素类型
|
||||
* @return 过滤后的新集合
|
||||
*/
|
||||
public static <T> List<T> filterEmpty(Collection<T> collection) {
|
||||
if (isEmptyCollection(collection)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<T> result = new ArrayList<>();
|
||||
for (T item : collection) {
|
||||
if (isNotEmpty(item)) {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ======================== Map专用方法 ========================
|
||||
|
||||
/**
|
||||
* 判断Map是否为空或null
|
||||
*
|
||||
* @param map 待判断的Map
|
||||
* @return true-Map为null或空,false-Map不为空
|
||||
*/
|
||||
public static boolean isEmptyMap(Map<?, ?> map) {
|
||||
return map == null || map.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Map是否不为空且不为null
|
||||
*
|
||||
* @param map 待判断的Map
|
||||
* @return true-Map不为null且不为空,false-Map为null或空
|
||||
*/
|
||||
public static boolean isNotEmptyMap(Map<?, ?> map) {
|
||||
return map != null && !map.isEmpty();
|
||||
}
|
||||
|
||||
// ======================== 数组专用方法 ========================
|
||||
|
||||
/**
|
||||
* 判断数组是否为空或null
|
||||
*
|
||||
* @param array 待判断的数组
|
||||
* @return true-数组为null或空,false-数组不为空
|
||||
*/
|
||||
public static boolean isEmptyArray(Object array) {
|
||||
return array == null || !array.getClass().isArray() || Array.getLength(array) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数组是否不为空且不为null
|
||||
*
|
||||
* @param array 待判断的数组
|
||||
* @return true-数组不为null且不为空,false-数组为null或空
|
||||
*/
|
||||
public static boolean isNotEmptyArray(Object array) {
|
||||
return array != null && array.getClass().isArray() && Array.getLength(array) > 0;
|
||||
}
|
||||
|
||||
// ======================== 字符串专用方法 ========================
|
||||
|
||||
/**
|
||||
* 判断字符串是否为空或null(包括空白字符串)
|
||||
*
|
||||
* @param str 待判断的字符串
|
||||
* @return true-字符串为null、空或只包含空白字符,false-字符串有有效内容
|
||||
*/
|
||||
public static boolean isEmptyString(String str) {
|
||||
return str == null || str.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否不为空且不为null
|
||||
*
|
||||
* @param str 待判断的字符串
|
||||
* @return true-字符串不为null且有有效内容,false-字符串为null、空或只包含空白字符
|
||||
*/
|
||||
public static boolean isNotEmptyString(String str) {
|
||||
return str != null && !str.trim().isEmpty();
|
||||
}
|
||||
|
||||
// ======================== 条件判断方法 ========================
|
||||
|
||||
/**
|
||||
* 如果对象为空则返回默认值
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @param defaultValue 默认值
|
||||
* @param <T> 对象类型
|
||||
* @return 如果obj为空则返回defaultValue,否则返回obj
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T defaultIfEmpty(T obj, T defaultValue) {
|
||||
return isEmpty(obj) ? defaultValue : obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果对象为null则返回默认值
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @param defaultValue 默认值
|
||||
* @param <T> 对象类型
|
||||
* @return 如果obj为null则返回defaultValue,否则返回obj
|
||||
*/
|
||||
public static <T> T defaultIfNull(T obj, T defaultValue) {
|
||||
return isNull(obj) ? defaultValue : obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第一个非空的对象
|
||||
*
|
||||
* @param objects 对象数组
|
||||
* @param <T> 对象类型
|
||||
* @return 第一个非空的对象,如果都为空则返回null
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> T firstNotEmpty(T... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return null;
|
||||
}
|
||||
for (T obj : objects) {
|
||||
if (isNotEmpty(obj)) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第一个非null的对象
|
||||
*
|
||||
* @param objects 对象数组
|
||||
* @param <T> 对象类型
|
||||
* @return 第一个非null的对象,如果都为null则返回null
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> T firstNotNull(T... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return null;
|
||||
}
|
||||
for (T obj : objects) {
|
||||
if (isNotNull(obj)) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ======================== 统计方法 ========================
|
||||
|
||||
/**
|
||||
* 统计数组中非空元素的个数
|
||||
*
|
||||
* @param objects 对象数组
|
||||
* @return 非空元素个数
|
||||
*/
|
||||
public static int countNotEmpty(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (Object obj : objects) {
|
||||
if (isNotEmpty(obj)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计数组中非null元素的个数
|
||||
*
|
||||
* @param objects 对象数组
|
||||
* @return 非null元素个数
|
||||
*/
|
||||
public static int countNotNull(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (Object obj : objects) {
|
||||
if (isNotNull(obj)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ======================== 断言方法 ========================
|
||||
|
||||
/**
|
||||
* 断言对象不为null,如果为null则抛出异常
|
||||
*
|
||||
* @param obj 待断言的对象
|
||||
* @param message 异常消息
|
||||
* @throws IllegalArgumentException 如果对象为null
|
||||
*/
|
||||
public static void requireNotNull(Object obj, String message) {
|
||||
if (isNull(obj)) {
|
||||
throw new IllegalArgumentException(message != null ? message : "对象不能为null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言对象不为空,如果为空则抛出异常
|
||||
*
|
||||
* @param obj 待断言的对象
|
||||
* @param message 异常消息
|
||||
* @throws IllegalArgumentException 如果对象为空
|
||||
*/
|
||||
public static void requireNotEmpty(Object obj, String message) {
|
||||
if (isEmpty(obj)) {
|
||||
throw new IllegalArgumentException(message != null ? message : "对象不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 类型检查方法 ========================
|
||||
|
||||
/**
|
||||
* 检查对象是否为指定类型且不为null
|
||||
*
|
||||
* @param obj 待检查的对象
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return true-对象不为null且为指定类型,false-否则
|
||||
*/
|
||||
public static <T> boolean isInstanceAndNotNull(Object obj, Class<T> clazz) {
|
||||
return isNotNull(obj) && clazz.isInstance(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的类型转换,如果对象为null或不是目标类型则返回null
|
||||
*
|
||||
* @param obj 待转换的对象
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的对象,如果转换失败则返回null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T safeCast(Object obj, Class<T> clazz) {
|
||||
if (isInstanceAndNotNull(obj, clazz)) {
|
||||
return (T) obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.BufferedReader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* @description ServletUtils.java文件描述:Servlet相关常用工具方法
|
||||
* @filename ServletUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ServletUtils {
|
||||
|
||||
/**
|
||||
* @description 获取请求的真实IP地址
|
||||
* @param request HTTP请求对象
|
||||
* @return 客户端真实IP地址
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getClientIp(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
// 多级代理时取第一个
|
||||
int idx = ip.indexOf(',');
|
||||
if (idx > -1) {
|
||||
ip = ip.substring(0, idx);
|
||||
}
|
||||
return ip.trim();
|
||||
}
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
ip = request.getRemoteAddr();
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 向响应写出JSON字符串
|
||||
* @param response HTTP响应对象
|
||||
* @param json 要写出的JSON字符串
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void writeJson(HttpServletResponse response, String json) throws IOException {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(json);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求参数(支持默认值)
|
||||
* @param request HTTP请求对象
|
||||
* @param name 参数名
|
||||
* @param defaultValue 默认值
|
||||
* @return 参数值或默认值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getParameter(HttpServletRequest request, String name, String defaultValue) {
|
||||
String value = request.getParameter(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为Ajax
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为Ajax请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isAjaxRequest(HttpServletRequest request) {
|
||||
String header = request.getHeader("X-Requested-With");
|
||||
return "XMLHttpRequest".equalsIgnoreCase(header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求体内容
|
||||
* @param request HTTP请求对象
|
||||
* @return 请求体内容
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getRequestBody(HttpServletRequest request) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = request.getReader();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重定向到指定URL
|
||||
* @param response HTTP响应对象
|
||||
* @param url 目标URL
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void redirect(HttpServletResponse response, String url) throws IOException {
|
||||
response.sendRedirect(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取完整请求URL
|
||||
* @param request HTTP请求对象
|
||||
* @return 完整URL
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getFullUrl(HttpServletRequest request) {
|
||||
StringBuilder url = new StringBuilder();
|
||||
url.append(request.getScheme()).append("://");
|
||||
url.append(request.getServerName());
|
||||
int port = request.getServerPort();
|
||||
if (("http".equals(request.getScheme()) && port != 80) ||
|
||||
("https".equals(request.getScheme()) && port != 443)) {
|
||||
url.append(":").append(port);
|
||||
}
|
||||
url.append(request.getRequestURI());
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null) {
|
||||
url.append("?").append(queryString);
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为GET方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为GET请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isGet(HttpServletRequest request) {
|
||||
return "GET".equalsIgnoreCase(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为POST方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为POST请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isPost(HttpServletRequest request) {
|
||||
return "POST".equalsIgnoreCase(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为PUT方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为PUT请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isPut(HttpServletRequest request) {
|
||||
return "PUT".equalsIgnoreCase(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为DELETE方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为DELETE请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isDelete(HttpServletRequest request) {
|
||||
return "DELETE".equalsIgnoreCase(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有请求参数
|
||||
* @param request HTTP请求对象
|
||||
* @return 参数Map
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Map<String, String> getParameterMap(HttpServletRequest request) {
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
Enumeration<String> parameterNames = request.getParameterNames();
|
||||
while (parameterNames.hasMoreElements()) {
|
||||
String paramName = parameterNames.nextElement();
|
||||
String paramValue = request.getParameter(paramName);
|
||||
paramMap.put(paramName, paramValue);
|
||||
}
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求头信息
|
||||
* @param request HTTP请求对象
|
||||
* @return 头信息Map
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Map<String, String> getHeaderMap(HttpServletRequest request) {
|
||||
Map<String, String> headerMap = new HashMap<>();
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
String headerValue = request.getHeader(headerName);
|
||||
headerMap.put(headerName, headerValue);
|
||||
}
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Cookie值
|
||||
* @param request HTTP请求对象
|
||||
* @param name Cookie名
|
||||
* @return Cookie值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCookieValue(HttpServletRequest request, String name) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (name.equals(cookie.getName())) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置Cookie
|
||||
* @param response HTTP响应对象
|
||||
* @param name Cookie名
|
||||
* @param value Cookie值
|
||||
* @param maxAge 过期时间(秒)
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void setCookie(HttpServletResponse response, String name, String value, int maxAge) {
|
||||
Cookie cookie = new Cookie(name, value);
|
||||
cookie.setMaxAge(maxAge);
|
||||
cookie.setPath("/");
|
||||
cookie.setHttpOnly(true);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除Cookie
|
||||
* @param response HTTP响应对象
|
||||
* @param name Cookie名
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void removeCookie(HttpServletResponse response, String name) {
|
||||
Cookie cookie = new Cookie(name, null);
|
||||
cookie.setMaxAge(0);
|
||||
cookie.setPath("/");
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断是否为HTTPS请求
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为HTTPS请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isHttps(HttpServletRequest request) {
|
||||
return "https".equals(request.getScheme()) || request.isSecure() ||
|
||||
"443".equals(request.getHeader("X-Forwarded-Port")) ||
|
||||
"https".equals(request.getHeader("X-Forwarded-Proto"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取上下文路径
|
||||
* @param request HTTP请求对象
|
||||
* @return 上下文路径
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getContextPath(HttpServletRequest request) {
|
||||
return request.getContextPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测请求是否包含指定参数
|
||||
* @param request HTTP请求对象
|
||||
* @param paramName 参数名
|
||||
* @return 是否包含参数
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean hasParameter(HttpServletRequest request, String paramName) {
|
||||
return request.getParameter(paramName) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Session对象
|
||||
* @param request HTTP请求对象
|
||||
* @return HttpSession对象
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static HttpSession getSession(HttpServletRequest request) {
|
||||
return request.getSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @return 属性值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Object getSessionAttribute(HttpServletRequest request, String attributeName) {
|
||||
HttpSession session = request.getSession(false);
|
||||
return session != null ? session.getAttribute(attributeName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @param attributeValue 属性值
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void setSessionAttribute(HttpServletRequest request, String attributeName, Object attributeValue) {
|
||||
request.getSession().setAttribute(attributeName, attributeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 移除Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void removeSessionAttribute(HttpServletRequest request, String attributeName) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 防止XSS攻击的字符串过滤
|
||||
* @param input 输入字符串
|
||||
* @return 过滤后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String escapeXss(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
return input
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("'", "'")
|
||||
.replace("\"", """)
|
||||
.replace("&", "&");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有请求参数名
|
||||
* @param request HTTP请求对象
|
||||
* @return 参数名集合
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Set<String> getParameterNames(HttpServletRequest request) {
|
||||
Set<String> paramNames = new HashSet<>();
|
||||
Enumeration<String> names = request.getParameterNames();
|
||||
while (names.hasMoreElements()) {
|
||||
paramNames.add(names.nextElement());
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
/**
|
||||
* @description StringUtils.java文件描述 字符串工具类
|
||||
* @filename StringUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class StringUtils {
|
||||
/**
|
||||
* @description 字符串是否为空
|
||||
* @param str String 字符串
|
||||
* @return 是否为空
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isEmpty(String str) {
|
||||
return str == null || str.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串是否不为空
|
||||
* @param str String 字符串
|
||||
* @return 是否不为空
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isNotEmpty(String str) {
|
||||
return !isEmpty(str);
|
||||
}
|
||||
|
||||
public static String format(String template, Object... args) {
|
||||
if (template == null || args == null) return template;
|
||||
return String.format(template, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 去除字符串首尾空格
|
||||
* @param str String 字符串
|
||||
* @return 去除空格后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String trim(String str) {
|
||||
return str == null ? null : str.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断两个字符串是否相等(支持null)
|
||||
* @param a String 字符串A
|
||||
* @param b String 字符串B
|
||||
* @return 是否相等
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean equals(String a, String b) {
|
||||
return a == null ? b == null : a.equals(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断字符串是否包含子串
|
||||
* @param str String 原字符串
|
||||
* @param sub String 子串
|
||||
* @return 是否包含
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean contains(String str, String sub) {
|
||||
return str != null && sub != null && str.contains(sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串拼接(用分隔符)
|
||||
* @param delimiter String 分隔符
|
||||
* @param elements String[] 待拼接字符串数组
|
||||
* @return 拼接后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String join(String delimiter, String... elements) {
|
||||
if (elements == null) return null;
|
||||
return String.join(delimiter, elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串分割
|
||||
* @param str String 原字符串
|
||||
* @param regex String 分割正则表达式
|
||||
* @return 分割后的字符串数组
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String[] split(String str, String regex) {
|
||||
return str == null ? null : str.split(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串替换
|
||||
* @param str String 原字符串
|
||||
* @param target String 替换目标
|
||||
* @param replacement String 替换内容
|
||||
* @return 替换后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String replace(String str, String target, String replacement) {
|
||||
return str == null ? null : str.replace(target, replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 是否以指定前缀开头
|
||||
* @param str String 原字符串
|
||||
* @param prefix String 前缀
|
||||
* @return 是否以前缀开头
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean startsWith(String str, String prefix) {
|
||||
return str != null && prefix != null && str.startsWith(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 是否以指定后缀结尾
|
||||
* @param str String 原字符串
|
||||
* @param suffix String 后缀
|
||||
* @return 是否以后缀结尾
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean endsWith(String str, String suffix) {
|
||||
return str != null && suffix != null && str.endsWith(suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 转为大写
|
||||
* @param str String 原字符串
|
||||
* @return 大写字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String toUpperCase(String str) {
|
||||
return str == null ? null : str.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 转为小写
|
||||
* @param str String 原字符串
|
||||
* @return 小写字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String toLowerCase(String str) {
|
||||
return str == null ? null : str.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 反转字符串
|
||||
* @param str String 原字符串
|
||||
* @return 反转后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String reverse(String str) {
|
||||
if (str == null) return null;
|
||||
return new StringBuilder(str).reverse().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重复字符串n次
|
||||
* @param str String 原字符串
|
||||
* @param n int 重复次数
|
||||
* @return 重复后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String repeat(String str, int n) {
|
||||
if (str == null || n <= 0) return "";
|
||||
return str.repeat(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 截取字符串
|
||||
* @param str String 原字符串
|
||||
* @param beginIndex int 起始索引
|
||||
* @param endIndex int 结束索引
|
||||
* @return 截取后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String substring(String str, int beginIndex, int endIndex) {
|
||||
if (str == null) return null;
|
||||
if (beginIndex < 0) beginIndex = 0;
|
||||
if (endIndex > str.length()) endIndex = str.length();
|
||||
if (beginIndex > endIndex) return "";
|
||||
return str.substring(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断字符串是否为数字
|
||||
* @param str String 原字符串
|
||||
* @return 是否为数字
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isNumeric(String str) {
|
||||
if (isEmpty(str)) return false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
if (!Character.isDigit(str.charAt(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串是否为空白
|
||||
* @param str String 原字符串
|
||||
* @return 是否为空白
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isBlank(String str) {
|
||||
return str == null || str.isBlank();
|
||||
}
|
||||
/**
|
||||
* @description 字符串是否不为空白
|
||||
* @param str String 原字符串
|
||||
* @return 是否不为空白
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isNotBlank(String str) {
|
||||
return !isBlank(str);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.text.ParseException;
|
||||
/**
|
||||
* @description TimeUtils.java文件描述:时间相关的常用工具方法
|
||||
* @filename TimeUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
|
||||
public class TimeUtils {
|
||||
|
||||
|
||||
/**
|
||||
* @description 将时间戳字符串转为指定格式的日期时间字符串
|
||||
* @param time 毫秒时间戳字符串
|
||||
* @param format 日期格式化对象
|
||||
* @return 格式化后的日期时间字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String timeFormat(String time, DateFormat format){
|
||||
try {
|
||||
Date date = format.parse(time);
|
||||
return format.format(date);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化Date为指定格式字符串
|
||||
* @param date Date对象
|
||||
* @param pattern 格式化模式,如"yyyy-MM-dd HH:mm:ss"
|
||||
* @return String 格式化后的字符串
|
||||
*/
|
||||
public static String format(Date date, String pattern) {
|
||||
if (date == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
LocalDateTime ldt = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
return ldt.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化LocalDate为指定格式字符串
|
||||
* @param localDate LocalDate对象
|
||||
* @param pattern 格式化模式
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public static String format(LocalDate localDate, String pattern) {
|
||||
if (localDate == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return localDate.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化LocalDateTime为指定格式字符串
|
||||
* @param localDateTime LocalDateTime对象
|
||||
* @param pattern 格式化模式
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public static String format(LocalDateTime localDateTime, String pattern) {
|
||||
if (localDateTime == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return localDateTime.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化时间戳为指定格式字符串
|
||||
* @param timestampMillis 毫秒时间戳
|
||||
* @param pattern 格式化模式
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public static String format(long timestampMillis, String pattern) {
|
||||
if (pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
LocalDateTime ldt = Instant.ofEpochMilli(timestampMillis).atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
return ldt.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将字符串按指定格式解析为LocalDateTime
|
||||
* @param dateTimeStr 日期时间字符串
|
||||
* @param pattern 格式化模式
|
||||
* @return LocalDateTime对象,解析失败返回null
|
||||
*/
|
||||
public static LocalDateTime parseToLocalDateTime(String dateTimeStr, String pattern) {
|
||||
if (dateTimeStr == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
try {
|
||||
return LocalDateTime.parse(dateTimeStr, formatter);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将字符串按指定格式解析为LocalDate
|
||||
* @param dateStr 日期字符串
|
||||
* @param pattern 格式化模式
|
||||
* @return LocalDate对象,解析失败返回null
|
||||
*/
|
||||
public static LocalDate parseToLocalDate(String dateStr, String pattern) {
|
||||
if (dateStr == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
try {
|
||||
return LocalDate.parse(dateStr, formatter);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将字符串或Date对象按指定DateTimeFormatter格式化为标准时间字符串(yyyy-MM-dd HH:mm:ss)
|
||||
* @param input 可以为String类型的日期、Date对象或LocalDateTime对象
|
||||
* @param formatter 指定的DateTimeFormatter
|
||||
* @return 标准化时间字符串,无法解析时返回null
|
||||
*/
|
||||
public static String normalizeToDateTimeString(Object input, DateTimeFormatter formatter) {
|
||||
if (input == null || formatter == null) return null;
|
||||
try {
|
||||
if (input instanceof String str) {
|
||||
LocalDateTime ldt;
|
||||
try {
|
||||
ldt = LocalDateTime.parse(str, formatter);
|
||||
} catch (Exception e) {
|
||||
LocalDate ld = LocalDate.parse(str, formatter);
|
||||
ldt = ld.atStartOfDay();
|
||||
}
|
||||
return ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
if (input instanceof Date date) {
|
||||
LocalDateTime ldt = dateToLocalDateTime(date);
|
||||
return ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
if (input instanceof LocalDateTime ldt) {
|
||||
return ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
if (input instanceof LocalDate ld) {
|
||||
return ld.atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前时间戳,单位毫秒
|
||||
* @return 当前时间戳字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentTimestamp() {
|
||||
return String.valueOf(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前时间戳,单位秒
|
||||
* @return 当前时间戳(秒)字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentTimestampSeconds() {
|
||||
return String.valueOf(System.currentTimeMillis() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前日期时间,格式:yyyy-MM-dd HH:mm:ss
|
||||
* @return 当前日期时间字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentDateTime() {
|
||||
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前日期,格式:yyyy-MM-dd
|
||||
* @return 当前日期字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentDate() {
|
||||
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前时间,格式:HH:mm:ss
|
||||
* @return 当前时间字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentTime() {
|
||||
return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将时间戳(毫秒)转为日期时间字符串,格式:yyyy-MM-dd HH:mm:ss
|
||||
* @param timestampMillis 毫秒时间戳
|
||||
* @return 日期时间字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String timestampToDateTime(long timestampMillis) {
|
||||
return Instant.ofEpochMilli(timestampMillis)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将日期时间字符串(yyyy-MM-dd HH:mm:ss)转为时间戳(毫秒)
|
||||
* @param dateTimeStr 日期时间字符串
|
||||
* @return 毫秒时间戳
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static long dateTimeToTimestamp(String dateTimeStr) {
|
||||
LocalDateTime ldt = LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取指定日期加减天数后的日期字符串(yyyy-MM-dd)
|
||||
* @param dateStr 原始日期字符串
|
||||
* @param days 增加或减少的天数(可为负数)
|
||||
* @return 计算后的日期字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String plusDays(String dateStr, int days) {
|
||||
LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
return date.plusDays(days).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取两个日期之间的天数差
|
||||
* @param startDate 开始日期字符串(yyyy-MM-dd)
|
||||
* @param endDate 结束日期字符串(yyyy-MM-dd)
|
||||
* @return 天数差
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static long daysBetween(String startDate, String endDate) {
|
||||
LocalDate start = LocalDate.parse(startDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
LocalDate end = LocalDate.parse(endDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
return Duration.between(start.atStartOfDay(), end.atStartOfDay()).toDays();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断当前时间是否在指定时间段内
|
||||
* @param startTime 开始时间字符串(HH:mm:ss)
|
||||
* @param endTime 结束时间字符串(HH:mm:ss)
|
||||
* @return 是否在时间段内
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isNowBetween(String startTime, String endTime) {
|
||||
LocalTime now = LocalTime.now();
|
||||
LocalTime start = LocalTime.parse(startTime, DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
LocalTime end = LocalTime.parse(endTime, DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
return !now.isBefore(start) && !now.isAfter(end);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Date转LocalDateTime
|
||||
* @param date java.util.Date对象
|
||||
* @return LocalDateTime对象
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static LocalDateTime dateToLocalDateTime(Date date) {
|
||||
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description LocalDateTime转Date
|
||||
* @param localDateTime LocalDateTime对象
|
||||
* @return java.util.Date对象
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Date localDateTimeToDate(LocalDateTime localDateTime) {
|
||||
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package org.xyzh.common.utils.excel;
|
||||
|
||||
import org.xyzh.common.utils.validation.ValidationParam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description Excel列映射配置
|
||||
* @filename ExcelColumnMapping.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ExcelColumnMapping {
|
||||
|
||||
/**
|
||||
* @description Excel列名(表头名称)
|
||||
*/
|
||||
private String columnName;
|
||||
|
||||
/**
|
||||
* @description Excel列索引(从0开始,优先级高于列名)
|
||||
*/
|
||||
private Integer columnIndex;
|
||||
|
||||
/**
|
||||
* @description 对象字段名
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* @description 字段类型
|
||||
*/
|
||||
private Class<?> fieldType;
|
||||
|
||||
/**
|
||||
* @description 是否必填
|
||||
*/
|
||||
private boolean required = false;
|
||||
|
||||
/**
|
||||
* @description 默认值
|
||||
*/
|
||||
private String defaultValue;
|
||||
|
||||
/**
|
||||
* @description 日期格式(当字段类型为Date时使用)
|
||||
*/
|
||||
private String dateFormat = "yyyy-MM-dd";
|
||||
|
||||
/**
|
||||
* @description 校验参数列表
|
||||
*/
|
||||
private List<ValidationParam> validationParams;
|
||||
|
||||
private ExcelColumnMapping() {
|
||||
this.validationParams = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
public Integer getColumnIndex() {
|
||||
return columnIndex;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public Class<?> getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public String getDateFormat() {
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
public List<ValidationParam> getValidationParams() {
|
||||
return validationParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builder类
|
||||
*/
|
||||
public static class Builder {
|
||||
private ExcelColumnMapping mapping = new ExcelColumnMapping();
|
||||
|
||||
/**
|
||||
* 设置Excel列名
|
||||
*/
|
||||
public Builder columnName(String columnName) {
|
||||
mapping.columnName = columnName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Excel列索引(从0开始)
|
||||
*/
|
||||
public Builder columnIndex(int columnIndex) {
|
||||
mapping.columnIndex = columnIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对象字段名
|
||||
*/
|
||||
public Builder fieldName(String fieldName) {
|
||||
mapping.fieldName = fieldName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段类型
|
||||
*/
|
||||
public Builder fieldType(Class<?> fieldType) {
|
||||
mapping.fieldType = fieldType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否必填
|
||||
*/
|
||||
public Builder required(boolean required) {
|
||||
mapping.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置为必填
|
||||
*/
|
||||
public Builder required() {
|
||||
mapping.required = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认值
|
||||
*/
|
||||
public Builder defaultValue(String defaultValue) {
|
||||
mapping.defaultValue = defaultValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置日期格式
|
||||
*/
|
||||
public Builder dateFormat(String dateFormat) {
|
||||
mapping.dateFormat = dateFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加校验参数
|
||||
*/
|
||||
public Builder addValidation(ValidationParam param) {
|
||||
mapping.validationParams.add(param);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置校验参数列表
|
||||
*/
|
||||
public Builder validations(List<ValidationParam> params) {
|
||||
mapping.validationParams = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExcelColumnMapping build() {
|
||||
if (mapping.fieldName == null || mapping.fieldName.isEmpty()) {
|
||||
throw new IllegalArgumentException("字段名不能为空");
|
||||
}
|
||||
if (mapping.columnName == null && mapping.columnIndex == null) {
|
||||
throw new IllegalArgumentException("必须指定列名或列索引");
|
||||
}
|
||||
if (mapping.fieldType == null) {
|
||||
mapping.fieldType = String.class;
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExcelColumnMapping{" +
|
||||
"columnName='" + columnName + '\'' +
|
||||
", columnIndex=" + columnIndex +
|
||||
", fieldName='" + fieldName + '\'' +
|
||||
", fieldType=" + (fieldType != null ? fieldType.getSimpleName() : "null") +
|
||||
", required=" + required +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.xyzh.common.utils.excel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description Excel读取结果
|
||||
* @filename ExcelReadResult.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ExcelReadResult<T> {
|
||||
|
||||
/**
|
||||
* @description 是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* @description 成功读取的数据列表
|
||||
*/
|
||||
private List<T> dataList;
|
||||
|
||||
/**
|
||||
* @description 失败的行数据(行号 -> 错误信息)
|
||||
*/
|
||||
private Map<Integer, String> errorRowsMap;
|
||||
|
||||
/**
|
||||
* @description 总行数(不包括表头)
|
||||
*/
|
||||
private int totalRows;
|
||||
|
||||
/**
|
||||
* @description 成功行数
|
||||
*/
|
||||
private int successRows;
|
||||
|
||||
/**
|
||||
* @description 失败行数
|
||||
*/
|
||||
private int errorRowsCount;
|
||||
|
||||
/**
|
||||
* @description 错误信息
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
public ExcelReadResult() {
|
||||
this.success = true;
|
||||
this.dataList = new ArrayList<>();
|
||||
this.errorRowsMap = new HashMap<>();
|
||||
this.totalRows = 0;
|
||||
this.successRows = 0;
|
||||
this.errorRowsCount = 0;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public List<T> getDataList() {
|
||||
return dataList;
|
||||
}
|
||||
|
||||
public void setDataList(List<T> dataList) {
|
||||
this.dataList = dataList;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getErrorRows() {
|
||||
return errorRowsMap;
|
||||
}
|
||||
|
||||
public void setErrorRows(Map<Integer, String> errorRowsMap) {
|
||||
this.errorRowsMap = errorRowsMap;
|
||||
}
|
||||
|
||||
public int getTotalRows() {
|
||||
return totalRows;
|
||||
}
|
||||
|
||||
public void setTotalRows(int totalRows) {
|
||||
this.totalRows = totalRows;
|
||||
}
|
||||
|
||||
public int getSuccessRows() {
|
||||
return successRows;
|
||||
}
|
||||
|
||||
public void setSuccessRows(int successRows) {
|
||||
this.successRows = successRows;
|
||||
}
|
||||
|
||||
public int getErrorRowsCount() {
|
||||
return errorRowsCount;
|
||||
}
|
||||
|
||||
public void setErrorRowsCount(int errorRowsCount) {
|
||||
this.errorRowsCount = errorRowsCount;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public void addData(T data) {
|
||||
this.dataList.add(data);
|
||||
this.successRows++;
|
||||
}
|
||||
|
||||
public void addError(int rowNum, String error) {
|
||||
this.errorRowsMap.put(rowNum, error);
|
||||
this.errorRowsCount++;
|
||||
}
|
||||
|
||||
public boolean hasErrors() {
|
||||
return this.errorRowsCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExcelReadResult{" +
|
||||
"success=" + success +
|
||||
", totalRows=" + totalRows +
|
||||
", successRows=" + successRows +
|
||||
", errorRows=" + errorRowsCount +
|
||||
", errorMessage='" + errorMessage + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
package org.xyzh.common.utils.excel;
|
||||
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.xyzh.common.utils.validation.ValidationParam;
|
||||
import org.xyzh.common.utils.validation.ValidationResult;
|
||||
import org.xyzh.common.utils.validation.ValidationUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @description Excel读取工具类(非泛型版本)
|
||||
* @filename ExcelReaderUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ExcelReaderUtils {
|
||||
|
||||
/**
|
||||
* @description 从文件读取Excel
|
||||
* @param file Excel文件
|
||||
* @param targetClass 目标对象Class
|
||||
* @param columnMappings Excel列映射配置列表
|
||||
* @return ExcelReadResult
|
||||
*/
|
||||
public static ExcelReadResult<Object> readExcel(File file, Class<?> targetClass, List<ExcelColumnMapping> columnMappings) {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
return readExcel(fis, file.getName(), targetClass, columnMappings, new HashMap<>());
|
||||
} catch (Exception e) {
|
||||
ExcelReadResult<Object> result = new ExcelReadResult<>();
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage("读取Excel文件失败: " + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从文件读取Excel(带配置)
|
||||
* @param file Excel文件
|
||||
* @param targetClass 目标对象Class
|
||||
* @param columnMappings Excel列映射配置列表
|
||||
* @param options 配置选项
|
||||
* @return ExcelReadResult
|
||||
*/
|
||||
public static ExcelReadResult<Object> readExcel(File file, Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings,
|
||||
Map<String, Object> options) {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
return readExcel(fis, file.getName(), targetClass, columnMappings, options);
|
||||
} catch (Exception e) {
|
||||
ExcelReadResult<Object> result = new ExcelReadResult<>();
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage("读取Excel文件失败: " + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从输入流读取Excel
|
||||
* @param inputStream 输入流
|
||||
* @param fileName 文件名
|
||||
* @param targetClass 目标对象Class
|
||||
* @param columnMappings Excel列映射配置列表
|
||||
* @return ExcelReadResult
|
||||
*/
|
||||
public static ExcelReadResult<Object> readExcel(InputStream inputStream, String fileName,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings) {
|
||||
return readExcel(inputStream, fileName, targetClass, columnMappings, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从输入流读取Excel(带配置)
|
||||
* @param inputStream 输入流
|
||||
* @param fileName 文件名
|
||||
* @param targetClass 目标对象Class
|
||||
* @param columnMappings Excel列映射配置列表
|
||||
* @param options 配置选项(headerRowIndex, dataStartRowIndex, sheetIndex, sheetName, skipEmptyRow, maxRows, continueOnError)
|
||||
* @return ExcelReadResult
|
||||
*/
|
||||
public static ExcelReadResult<Object> readExcel(InputStream inputStream, String fileName,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings,
|
||||
Map<String, Object> options) {
|
||||
ExcelReadResult<Object> result = new ExcelReadResult<>();
|
||||
|
||||
try {
|
||||
// 获取配置
|
||||
int headerRowIndex = (int) options.getOrDefault("headerRowIndex", 0);
|
||||
int dataStartRowIndex = (int) options.getOrDefault("dataStartRowIndex", 1);
|
||||
int sheetIndex = (int) options.getOrDefault("sheetIndex", 0);
|
||||
String sheetName = (String) options.get("sheetName");
|
||||
boolean skipEmptyRow = (boolean) options.getOrDefault("skipEmptyRow", true);
|
||||
int maxRows = (int) options.getOrDefault("maxRows", 0);
|
||||
boolean continueOnError = (boolean) options.getOrDefault("continueOnError", true);
|
||||
|
||||
// 创建Workbook
|
||||
Workbook workbook = createWorkbook(inputStream, fileName);
|
||||
|
||||
// 获取Sheet
|
||||
Sheet sheet = getSheet(workbook, sheetIndex, sheetName);
|
||||
if (sheet == null) {
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage("未找到指定的Sheet");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 解析表头
|
||||
Map<String, Integer> headerMap = parseHeader(sheet, headerRowIndex);
|
||||
|
||||
// 读取数据
|
||||
readData(sheet, headerMap, targetClass, columnMappings, dataStartRowIndex,
|
||||
skipEmptyRow, maxRows, continueOnError, result);
|
||||
|
||||
workbook.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage("读取Excel失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建Workbook对象
|
||||
*/
|
||||
private static Workbook createWorkbook(InputStream inputStream, String fileName) throws Exception {
|
||||
if (fileName.endsWith(".xlsx")) {
|
||||
return new XSSFWorkbook(inputStream);
|
||||
} else if (fileName.endsWith(".xls")) {
|
||||
return new HSSFWorkbook(inputStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的文件格式,仅支持.xls和.xlsx");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Sheet
|
||||
*/
|
||||
private static Sheet getSheet(Workbook workbook, int sheetIndex, String sheetName) {
|
||||
if (sheetName != null && !sheetName.isEmpty()) {
|
||||
return workbook.getSheet(sheetName);
|
||||
} else {
|
||||
return workbook.getSheetAt(sheetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 解析表头
|
||||
*/
|
||||
private static Map<String, Integer> parseHeader(Sheet sheet, int headerRowIndex) {
|
||||
Map<String, Integer> headerMap = new HashMap<>();
|
||||
Row headerRow = sheet.getRow(headerRowIndex);
|
||||
|
||||
if (headerRow != null) {
|
||||
for (Cell cell : headerRow) {
|
||||
String headerName = getCellValue(cell).toString().trim();
|
||||
if (!headerName.isEmpty()) {
|
||||
headerMap.put(headerName, cell.getColumnIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 读取数据
|
||||
*/
|
||||
private static void readData(Sheet sheet, Map<String, Integer> headerMap,
|
||||
Class<?> targetClass, List<ExcelColumnMapping> columnMappings,
|
||||
int startRow, boolean skipEmptyRow, int maxRows,
|
||||
boolean continueOnError, ExcelReadResult<Object> result) {
|
||||
int lastRowNum = sheet.getLastRowNum();
|
||||
int endRow = maxRows > 0 ? Math.min(startRow + maxRows, lastRowNum + 1) : lastRowNum + 1;
|
||||
|
||||
for (int rowNum = startRow; rowNum < endRow; rowNum++) {
|
||||
Row row = sheet.getRow(rowNum);
|
||||
|
||||
// 跳过空行
|
||||
if (skipEmptyRow && isEmptyRow(row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.setTotalRows(result.getTotalRows() + 1);
|
||||
|
||||
try {
|
||||
// 将行数据转换为对象
|
||||
Object data = convertRowToObject(row, headerMap, targetClass, columnMappings, rowNum + 1);
|
||||
|
||||
// 数据校验
|
||||
List<ValidationParam> allValidations = new ArrayList<>();
|
||||
for (ExcelColumnMapping mapping : columnMappings) {
|
||||
if (!mapping.getValidationParams().isEmpty()) {
|
||||
allValidations.addAll(mapping.getValidationParams());
|
||||
}
|
||||
}
|
||||
|
||||
if (!allValidations.isEmpty()) {
|
||||
ValidationResult validationResult = ValidationUtils.validate(data, allValidations);
|
||||
if (!validationResult.isValid()) {
|
||||
result.addError(rowNum + 1, validationResult.getFirstError());
|
||||
if (!continueOnError) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result.addData(data);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.addError(rowNum + 1, e.getMessage());
|
||||
if (!continueOnError) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断是否为空行
|
||||
*/
|
||||
private static boolean isEmptyRow(Row row) {
|
||||
if (row == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Cell cell : row) {
|
||||
if (cell != null && cell.getCellType() != CellType.BLANK) {
|
||||
String value = getCellValue(cell).toString().trim();
|
||||
if (!value.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将行数据转换为对象
|
||||
*/
|
||||
private static Object convertRowToObject(Row row, Map<String, Integer> headerMap,
|
||||
Class<?> targetClass, List<ExcelColumnMapping> columnMappings,
|
||||
int rowNum) throws Exception {
|
||||
Object obj = targetClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
for (ExcelColumnMapping mapping : columnMappings) {
|
||||
// 获取单元格
|
||||
Cell cell = getCell(row, mapping, headerMap);
|
||||
|
||||
// 获取单元格值
|
||||
Object cellValue = getCellValue(cell);
|
||||
|
||||
// 处理空值
|
||||
if (cellValue == null || cellValue.toString().trim().isEmpty()) {
|
||||
if (mapping.isRequired()) {
|
||||
throw new IllegalArgumentException("第" + rowNum + "行,字段[" +
|
||||
(mapping.getColumnName() != null ? mapping.getColumnName() : "索引" + mapping.getColumnIndex())
|
||||
+ "]不能为空");
|
||||
}
|
||||
// 使用默认值
|
||||
if (mapping.getDefaultValue() != null && !mapping.getDefaultValue().isEmpty()) {
|
||||
cellValue = mapping.getDefaultValue();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 类型转换
|
||||
Object fieldValue = convertValue(cellValue, mapping.getFieldType(), mapping.getDateFormat());
|
||||
|
||||
// 设置字段值
|
||||
Field field = getField(targetClass, mapping.getFieldName());
|
||||
field.setAccessible(true);
|
||||
field.set(obj, fieldValue);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取字段(支持父类)
|
||||
*/
|
||||
private static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
|
||||
try {
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
// 尝试从父类获取
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass != null) {
|
||||
return getField(superClass, fieldName);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取单元格
|
||||
*/
|
||||
private static Cell getCell(Row row, ExcelColumnMapping mapping, Map<String, Integer> headerMap) {
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 优先使用索引
|
||||
if (mapping.getColumnIndex() != null) {
|
||||
return row.getCell(mapping.getColumnIndex());
|
||||
}
|
||||
|
||||
// 使用列名
|
||||
Integer columnIndex = headerMap.get(mapping.getColumnName());
|
||||
if (columnIndex != null) {
|
||||
return row.getCell(columnIndex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取单元格值
|
||||
*/
|
||||
private static Object getCellValue(Cell cell) {
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue();
|
||||
} else {
|
||||
return cell.getNumericCellValue();
|
||||
}
|
||||
case BOOLEAN:
|
||||
return cell.getBooleanCellValue();
|
||||
case FORMULA:
|
||||
return cell.getCellFormula();
|
||||
case BLANK:
|
||||
return "";
|
||||
default:
|
||||
return cell.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 类型转换
|
||||
*/
|
||||
private static Object convertValue(Object value, Class<?> targetType, String dateFormat) throws Exception {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String strValue = value.toString().trim();
|
||||
|
||||
// String类型
|
||||
if (targetType == String.class) {
|
||||
return strValue;
|
||||
}
|
||||
|
||||
// Integer类型
|
||||
if (targetType == Integer.class || targetType == int.class) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
return Integer.parseInt(strValue);
|
||||
}
|
||||
|
||||
// Long类型
|
||||
if (targetType == Long.class || targetType == long.class) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
return Long.parseLong(strValue);
|
||||
}
|
||||
|
||||
// Double类型
|
||||
if (targetType == Double.class || targetType == double.class) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
return Double.parseDouble(strValue);
|
||||
}
|
||||
|
||||
// Float类型
|
||||
if (targetType == Float.class || targetType == float.class) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).floatValue();
|
||||
}
|
||||
return Float.parseFloat(strValue);
|
||||
}
|
||||
|
||||
// Boolean类型
|
||||
if (targetType == Boolean.class || targetType == boolean.class) {
|
||||
if (value instanceof Boolean) {
|
||||
return value;
|
||||
}
|
||||
return Boolean.parseBoolean(strValue) || "1".equals(strValue) ||
|
||||
"是".equals(strValue) || "true".equalsIgnoreCase(strValue);
|
||||
}
|
||||
|
||||
// Date类型
|
||||
if (targetType == Date.class) {
|
||||
if (value instanceof Date) {
|
||||
return value;
|
||||
}
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
|
||||
return sdf.parse(strValue);
|
||||
}
|
||||
|
||||
// 其他类型
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
package org.xyzh.common.utils.excel;
|
||||
|
||||
import org.xyzh.common.utils.validation.ValidationParam;
|
||||
import org.xyzh.common.utils.validation.method.ValidateMethodType;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @description Excel工具使用示例
|
||||
* @filename ExcelUtilsExample.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ExcelUtilsExample {
|
||||
|
||||
/**
|
||||
* 示例实体类:用户信息
|
||||
*/
|
||||
public static class UserInfo {
|
||||
private String name;
|
||||
private Integer age;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String idCard;
|
||||
private Date joinDate;
|
||||
private Boolean active;
|
||||
|
||||
// Getters and Setters
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public Integer getAge() { return age; }
|
||||
public void setAge(Integer age) { this.age = age; }
|
||||
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
|
||||
public String getIdCard() { return idCard; }
|
||||
public void setIdCard(String idCard) { this.idCard = idCard; }
|
||||
|
||||
public Date getJoinDate() { return joinDate; }
|
||||
public void setJoinDate(Date joinDate) { this.joinDate = joinDate; }
|
||||
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserInfo{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
", phone='" + phone + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", idCard='" + idCard + '\'' +
|
||||
", joinDate=" + joinDate +
|
||||
", active=" + active +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例1:基本使用
|
||||
*/
|
||||
public static void example1_BasicUsage() {
|
||||
System.out.println("========== 示例1: 基本使用 ==========");
|
||||
|
||||
// 1. 定义列映射关系
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.validations(Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build()
|
||||
))
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("入职日期")
|
||||
.fieldName("joinDate")
|
||||
.fieldType(Date.class)
|
||||
.dateFormat("yyyy-MM-dd")
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("是否在职")
|
||||
.fieldName("active")
|
||||
.fieldType(Boolean.class)
|
||||
.defaultValue("true")
|
||||
.build()
|
||||
);
|
||||
|
||||
// 2. 读取Excel
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
// 3. 处理结果
|
||||
if (result.isSuccess()) {
|
||||
System.out.println("读取成功!");
|
||||
System.out.println("总行数: " + result.getTotalRows());
|
||||
System.out.println("成功行数: " + result.getSuccessRows());
|
||||
|
||||
for (Object obj : result.getDataList()) {
|
||||
UserInfo user = (UserInfo) obj;
|
||||
System.out.println(user);
|
||||
}
|
||||
} else {
|
||||
System.out.println("读取失败: " + result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2:带数据校验
|
||||
*/
|
||||
public static void example2_WithValidation() {
|
||||
System.out.println("\n========== 示例2: 带数据校验 ==========");
|
||||
|
||||
// 定义列映射关系(带校验)
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.minLength(2)
|
||||
.maxLength(20)
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("age")
|
||||
.fieldLabel("年龄")
|
||||
.required()
|
||||
.customValidator(value -> {
|
||||
Integer age = (Integer) value;
|
||||
return age >= 18 && age <= 65;
|
||||
})
|
||||
.customErrorMessage("年龄必须在18-65岁之间")
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required(false)
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("身份证号")
|
||||
.fieldName("idCard")
|
||||
.fieldType(String.class)
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("idCard")
|
||||
.fieldLabel("身份证号")
|
||||
.required(false)
|
||||
.validateMethod(ValidateMethodType.ID_CARD)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 读取配置
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("continueOnError", true); // 遇到错误继续读取
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings,
|
||||
options
|
||||
);
|
||||
|
||||
// 处理结果
|
||||
System.out.println("读取完成!");
|
||||
System.out.println("总行数: " + result.getTotalRows());
|
||||
System.out.println("成功行数: " + result.getSuccessRows());
|
||||
System.out.println("失败行数: " + result.getErrorRowsCount());
|
||||
|
||||
// 显示错误信息
|
||||
if (result.hasErrors()) {
|
||||
System.out.println("\n错误信息:");
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
System.out.println("第" + rowNum + "行: " + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3:使用列索引
|
||||
*/
|
||||
public static void example3_UseColumnIndex() {
|
||||
System.out.println("\n========== 示例3: 使用列索引 ==========");
|
||||
|
||||
// 使用列索引而非列名
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(0) // 第1列
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(1) // 第2列
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(2) // 第3列
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build()
|
||||
);
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
System.out.println("成功读取: " + result.getSuccessRows() + " 行");
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4:自定义配置
|
||||
*/
|
||||
public static void example4_CustomConfig() {
|
||||
System.out.println("\n========== 示例4: 自定义配置 ==========");
|
||||
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build()
|
||||
);
|
||||
|
||||
// 自定义配置
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("sheetName", "员工信息"); // 指定Sheet名称
|
||||
options.put("headerRowIndex", 0); // 表头在第1行
|
||||
options.put("dataStartRowIndex", 1); // 数据从第2行开始
|
||||
options.put("skipEmptyRow", true); // 跳过空行
|
||||
options.put("maxRows", 1000); // 最多读取1000行
|
||||
options.put("continueOnError", true); // 遇到错误继续
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings,
|
||||
options
|
||||
);
|
||||
|
||||
System.out.println("读取结果: " + result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例5:在Controller中使用
|
||||
*/
|
||||
public static void example5_InController() {
|
||||
System.out.println("\n========== 示例5: 在Controller中使用(代码示例)==========");
|
||||
System.out.println("""
|
||||
@PostMapping("/import")
|
||||
public ResultDomain<String> importUsers(@RequestParam("file") MultipartFile file) {
|
||||
try {
|
||||
// 1. 定义列映射关系
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 2. 读取Excel
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file.getInputStream(),
|
||||
file.getOriginalFilename(),
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
// 3. 处理结果
|
||||
if (result.hasErrors()) {
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
errorMsg.append("导入失败,共").append(result.getErrorRowsCount()).append("行数据有误:\\n");
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
errorMsg.append("第").append(rowNum).append("行: ").append(error).append("\\n");
|
||||
});
|
||||
return ResultDomain.fail(errorMsg.toString());
|
||||
}
|
||||
|
||||
// 4. 保存数据
|
||||
List<UserInfo> users = new ArrayList<>();
|
||||
for (Object obj : result.getDataList()) {
|
||||
users.add((UserInfo) obj);
|
||||
}
|
||||
userService.batchSave(users);
|
||||
|
||||
return ResultDomain.success("导入成功,共导入" + result.getSuccessRows() + "条数据");
|
||||
|
||||
} catch (Exception e) {
|
||||
return ResultDomain.fail("导入失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 运行示例
|
||||
// example1_BasicUsage();
|
||||
// example2_WithValidation();
|
||||
// example3_UseColumnIndex();
|
||||
// example4_CustomConfig();
|
||||
example5_InController();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,542 @@
|
||||
# Excel读取工具使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
这是一个功能强大的Excel读取工具,通过配置 **Excel列对象** 来定义Excel列与Java对象字段的映射关系,支持自动构建对象并集成数据校验功能。
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. ExcelColumnMapping - Excel列映射配置
|
||||
定义Excel列与对象字段的映射关系:
|
||||
- **columnName** - Excel列名(表头名称)
|
||||
- **columnIndex** - Excel列索引(从0开始,优先级高于列名)
|
||||
- **fieldName** - 对象字段名
|
||||
- **fieldType** - 字段类型
|
||||
- **required** - 是否必填
|
||||
- **defaultValue** - 默认值
|
||||
- **dateFormat** - 日期格式
|
||||
- **validationParams** - 校验参数列表
|
||||
|
||||
### 2. ExcelReaderUtils - Excel读取工具类
|
||||
提供静态方法读取Excel:
|
||||
- `readExcel(File, Class, List<ExcelColumnMapping>)` - 从文件读取
|
||||
- `readExcel(File, Class, List<ExcelColumnMapping>, Map<options>)` - 从文件读取(带配置)
|
||||
- `readExcel(InputStream, fileName, Class, List<ExcelColumnMapping>)` - 从输入流读取
|
||||
- `readExcel(InputStream, fileName, Class, List<ExcelColumnMapping>, Map<options>)` - 从输入流读取(带配置)
|
||||
|
||||
### 3. ExcelReadResult - 读取结果
|
||||
包含读取结果的详细信息:
|
||||
- 成功数据列表
|
||||
- 错误行信息(行号 -> 错误消息)
|
||||
- 统计信息(总行数、成功数、失败数)
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义实体类
|
||||
|
||||
```java
|
||||
public class UserInfo {
|
||||
private String name;
|
||||
private Integer age;
|
||||
private String phone;
|
||||
private String email;
|
||||
private Date joinDate;
|
||||
private Boolean active;
|
||||
|
||||
// Getters and Setters...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 定义列映射关系
|
||||
|
||||
```java
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名") // Excel列名
|
||||
.fieldName("name") // 对象字段名
|
||||
.fieldType(String.class) // 字段类型
|
||||
.required() // 必填
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("入职日期")
|
||||
.fieldName("joinDate")
|
||||
.fieldType(Date.class)
|
||||
.dateFormat("yyyy-MM-dd")
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("是否在职")
|
||||
.fieldName("active")
|
||||
.fieldType(Boolean.class)
|
||||
.defaultValue("true")
|
||||
.build()
|
||||
);
|
||||
```
|
||||
|
||||
### 3. 读取Excel
|
||||
|
||||
```java
|
||||
// 读取文件
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
// 处理结果
|
||||
if (result.isSuccess()) {
|
||||
for (Object obj : result.getDataList()) {
|
||||
UserInfo user = (UserInfo) obj;
|
||||
System.out.println(user);
|
||||
}
|
||||
System.out.println("成功读取: " + result.getSuccessRows() + " 行");
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 带数据校验的读取
|
||||
|
||||
```java
|
||||
// 定义列映射关系(带校验)
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.minLength(2)
|
||||
.maxLength(20)
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("age")
|
||||
.fieldLabel("年龄")
|
||||
.required()
|
||||
.customValidator(value -> {
|
||||
Integer age = (Integer) value;
|
||||
return age >= 18 && age <= 65;
|
||||
})
|
||||
.customErrorMessage("年龄必须在18-65岁之间")
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required(false)
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 配置选项
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("continueOnError", true); // 遇到错误继续读取
|
||||
|
||||
// 读取Excel
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings,
|
||||
options
|
||||
);
|
||||
|
||||
// 处理结果
|
||||
System.out.println("总行数: " + result.getTotalRows());
|
||||
System.out.println("成功: " + result.getSuccessRows());
|
||||
System.out.println("失败: " + result.getErrorRowsCount());
|
||||
|
||||
// 显示错误
|
||||
if (result.hasErrors()) {
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
System.out.println("第" + rowNum + "行: " + error);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 使用列索引而非列名
|
||||
|
||||
```java
|
||||
// 使用列索引(从0开始)
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(0) // 第1列
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(1) // 第2列
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(2) // 第3列
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build()
|
||||
);
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
```
|
||||
|
||||
### 6. 自定义配置选项
|
||||
|
||||
以下是旧版本的校验代码,现在已经整合到ExcelColumnMapping中:
|
||||
|
||||
```java
|
||||
// 旧版本(已弃用)
|
||||
List<ValidationParam> validationParams = Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.minLength(2)
|
||||
.maxLength(20)
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required(false)
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("age")
|
||||
.fieldLabel("年龄")
|
||||
.required()
|
||||
.customValidator(value -> {
|
||||
Integer age = (Integer) value;
|
||||
return age >= 18 && age <= 65;
|
||||
})
|
||||
.customErrorMessage("年龄必须在18-65岁之间")
|
||||
.build()
|
||||
);
|
||||
|
||||
// 创建配置
|
||||
ExcelReaderConfig<UserInfo> config = new ExcelReaderConfig<>(UserInfo.class)
|
||||
.setValidationParams(validationParams)
|
||||
.setContinueOnError(true); // 遇到错误继续读取
|
||||
|
||||
// 读取
|
||||
ExcelReader<UserInfo> reader = ExcelReader.create(config);
|
||||
ExcelReadResult<UserInfo> result = reader.read(file);
|
||||
|
||||
// 处理结果
|
||||
System.out.println("总行数: " + result.getTotalRows());
|
||||
System.out.println("成功: " + result.getSuccessRows());
|
||||
System.out.println("失败: " + result.getErrorRowsCount());
|
||||
|
||||
// 显示错误
|
||||
if (result.hasErrors()) {
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
System.out.println("第" + rowNum + "行: " + error);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
// 列映射配置...
|
||||
);
|
||||
|
||||
// 自定义配置选项
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("sheetName", "员工信息"); // 指定Sheet名称
|
||||
options.put("headerRowIndex", 0); // 表头在第1行
|
||||
options.put("dataStartRowIndex", 1); // 数据从第2行开始
|
||||
options.put("skipEmptyRow", true); // 跳过空行
|
||||
options.put("maxRows", 1000); // 最多读取1000行
|
||||
options.put("continueOnError", true); // 遇到错误继续
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings,
|
||||
options
|
||||
);
|
||||
```
|
||||
|
||||
## 在Controller中使用
|
||||
|
||||
```java
|
||||
@PostMapping("/import")
|
||||
public ResultDomain<String> importUsers(@RequestParam("file") MultipartFile file) {
|
||||
try {
|
||||
// 1. 定义列映射关系(带校验)
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 2. 读取Excel
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file.getInputStream(),
|
||||
file.getOriginalFilename(),
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
// 3. 处理结果
|
||||
if (result.hasErrors()) {
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
errorMsg.append("导入失败,共").append(result.getErrorRowsCount()).append("行数据有误:\n");
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
errorMsg.append("第").append(rowNum).append("行: ").append(error).append("\n");
|
||||
});
|
||||
return ResultDomain.fail(errorMsg.toString());
|
||||
}
|
||||
|
||||
// 4. 保存数据
|
||||
List<UserInfo> users = new ArrayList<>();
|
||||
for (Object obj : result.getDataList()) {
|
||||
users.add((UserInfo) obj);
|
||||
}
|
||||
userService.batchSave(users);
|
||||
|
||||
return ResultDomain.success("导入成功,共导入" + result.getSuccessRows() + "条数据");
|
||||
|
||||
} catch (Exception e) {
|
||||
return ResultDomain.fail("导入失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 支持的数据类型
|
||||
|
||||
- **String** - 字符串
|
||||
- **Integer/int** - 整数
|
||||
- **Long/long** - 长整数
|
||||
- **Double/double** - 双精度浮点数
|
||||
- **Float/float** - 单精度浮点数
|
||||
- **Boolean/boolean** - 布尔值(支持:true/false、1/0、是/否)
|
||||
- **Date** - 日期(需指定dateFormat)
|
||||
|
||||
## 配置选项
|
||||
|
||||
### ExcelColumnMapping Builder方法
|
||||
|
||||
| 方法 | 说明 | 必填 |
|
||||
|------|------|------|
|
||||
| columnName(String) | 设置Excel列名 | columnName和columnIndex至少一个 |
|
||||
| columnIndex(int) | 设置Excel列索引(从0开始,优先级高于columnName) | columnName和columnIndex至少一个 |
|
||||
| fieldName(String) | 设置对象字段名 | 是 |
|
||||
| fieldType(Class<?>) | 设置字段类型 | 否(默认String.class) |
|
||||
| required() / required(boolean) | 设置是否必填 | 否(默认false) |
|
||||
| defaultValue(String) | 设置默认值 | 否 |
|
||||
| dateFormat(String) | 设置日期格式 | 否(默认"yyyy-MM-dd") |
|
||||
| addValidation(ValidationParam) | 添加校验参数 | 否 |
|
||||
| validations(List<ValidationParam>) | 设置校验参数列表 | 否 |
|
||||
|
||||
### Options配置项
|
||||
|
||||
传递给 `readExcel` 方法的 `Map<String, Object> options` 支持以下选项:
|
||||
|
||||
| 键名 | 类型 | 说明 | 默认值 |
|
||||
|------|------|------|--------|
|
||||
| headerRowIndex | int | 表头所在行(从0开始) | 0 |
|
||||
| dataStartRowIndex | int | 数据起始行(从0开始) | 1 |
|
||||
| sheetIndex | int | Sheet索引(从0开始) | 0 |
|
||||
| sheetName | String | Sheet名称(优先级高于sheetIndex) | null |
|
||||
| skipEmptyRow | boolean | 跳过空行 | true |
|
||||
| maxRows | int | 最大读取行数(0=不限制) | 0 |
|
||||
| continueOnError | boolean | 遇到错误继续读取 | true |
|
||||
|
||||
## 核心特性
|
||||
|
||||
1. **配置驱动**:通过ExcelColumnMapping配置Excel列与对象字段的映射关系
|
||||
2. **无需注解**:不需要在实体类上添加注解,更加灵活
|
||||
3. **数据校验**:集成ValidationUtils和ValidateMethodType进行专业数据校验
|
||||
4. **灵活配置**:支持多种配置选项(Sheet选择、行范围、错误处理等)
|
||||
5. **错误处理**:详细的错误信息和错误行记录
|
||||
6. **类型转换**:自动进行类型转换
|
||||
7. **空值处理**:支持默认值和空值校验
|
||||
8. **多Sheet支持**:可指定Sheet名称或索引
|
||||
9. **两种映射方式**:支持列名和列索引两种方式
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **实体类要求**:必须有无参构造函数
|
||||
2. **字段访问**:字段必须有setter方法或可访问(支持父类字段)
|
||||
3. **列映射优先级**:columnIndex优先级高于columnName
|
||||
4. **日期类型**:必须指定dateFormat
|
||||
5. **布尔类型**:支持多种格式(true/false、1/0、是/否)
|
||||
6. **错误处理**:根据continueOnError选项决定遇到错误时是否继续
|
||||
7. **文件格式**:支持.xls和.xlsx两种格式
|
||||
8. **类型转换**:自动转换支持String、Integer、Long、Double、Float、Boolean、Date
|
||||
|
||||
## 依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>5.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.3</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## API说明
|
||||
|
||||
### ExcelReaderUtils 静态方法
|
||||
|
||||
```java
|
||||
// 从文件读取(基本)
|
||||
public static ExcelReadResult<Object> readExcel(
|
||||
File file,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings
|
||||
)
|
||||
|
||||
// 从文件读取(带配置)
|
||||
public static ExcelReadResult<Object> readExcel(
|
||||
File file,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings,
|
||||
Map<String, Object> options
|
||||
)
|
||||
|
||||
// 从输入流读取(基本)
|
||||
public static ExcelReadResult<Object> readExcel(
|
||||
InputStream inputStream,
|
||||
String fileName,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings
|
||||
)
|
||||
|
||||
// 从输入流读取(带配置)
|
||||
public static ExcelReadResult<Object> readExcel(
|
||||
InputStream inputStream,
|
||||
String fileName,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings,
|
||||
Map<String, Object> options
|
||||
)
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
参考 `ExcelUtilsExample.java` 查看完整使用示例。
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
# 数据校验工具使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
这是一个灵活、可扩展的Java数据校验工具,支持对对象和Map进行多种类型的校验。
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. ValidationParam - 校验参数对象
|
||||
定义字段的校验规则,支持:
|
||||
- 字段名称和中文标签
|
||||
- 是否必传
|
||||
- 字段类型校验
|
||||
- 字符串长度限制
|
||||
- 数字范围限制
|
||||
- 正则表达式校验
|
||||
- 自定义校验函数
|
||||
- 预定义的校验方法(ValidateMethod)
|
||||
|
||||
### 2. ValidationResult - 校验结果对象
|
||||
保存校验结果,包含:
|
||||
- 是否校验通过
|
||||
- 错误信息列表
|
||||
- 第一个错误信息
|
||||
- 错误数量统计
|
||||
|
||||
### 3. ValidationUtils - 校验工具类
|
||||
执行校验逻辑,提供:
|
||||
- 校验Java对象
|
||||
- 校验Map对象
|
||||
- 快捷方法:`requiredString()`, `requiredNumber()`, `email()`, `phone()`
|
||||
|
||||
### 4. ValidateMethod - 校验方法接口
|
||||
预定义的专业校验方法,已实现:
|
||||
- **PasswordValidateMethod** - 密码校验
|
||||
- **IdCardValidateMethod** - 身份证号校验
|
||||
- **PhoneValidateMethod** - 手机号码校验
|
||||
- **EmailValidateMethod** - 邮箱地址校验
|
||||
- **UrlValidateMethod** - URL链接校验
|
||||
- **BankCardValidateMethod** - 银行卡号校验
|
||||
- **ChineseValidateMethod** - 中文字符校验
|
||||
|
||||
### 5. ValidateMethodType - 校验方法类型枚举 ⭐推荐使用
|
||||
枚举类型指向预定义的校验方法,使用更简洁:
|
||||
- **PASSWORD** - 密码校验(6-20位,字母+数字)
|
||||
- **STRONG_PASSWORD** - 强密码校验(8-20位,大小写+数字+特殊字符)
|
||||
- **ID_CARD** - 身份证号校验
|
||||
- **PHONE** - 手机号码校验(中国大陆)
|
||||
- **PHONE_LOOSE** - 手机号码校验(支持大陆/香港/台湾)
|
||||
- **EMAIL** - 邮箱地址校验
|
||||
- **URL** - URL链接校验
|
||||
- **HTTPS_URL** - HTTPS链接校验
|
||||
- **BANK_CARD** - 银行卡号校验
|
||||
- **CHINESE** - 中文字符校验(纯中文)
|
||||
- **CHINESE_WITH_PUNCTUATION** - 中文字符校验(允许标点)
|
||||
|
||||
## 基本使用示例
|
||||
|
||||
### 1. 使用枚举类型校验(⭐推荐)
|
||||
|
||||
```java
|
||||
List<ValidationParam> params = Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("password")
|
||||
.fieldLabel("密码")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PASSWORD) // 使用枚举
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.EMAIL) // 使用枚举
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE) // 使用枚举
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("idCard")
|
||||
.fieldLabel("身份证号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.ID_CARD) // 使用枚举
|
||||
.build()
|
||||
);
|
||||
|
||||
ValidationResult result = ValidationUtils.validateMap(data, params);
|
||||
if (!result.isValid()) {
|
||||
System.out.println(result.getFirstError());
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 简单字段校验
|
||||
|
||||
```java
|
||||
List<ValidationParam> params = Arrays.asList(
|
||||
ValidationUtils.requiredString("username", "用户名", 3, 20),
|
||||
ValidationUtils.email("email", "邮箱", true),
|
||||
ValidationUtils.phone("phone", "手机号", false)
|
||||
);
|
||||
|
||||
// 校验对象
|
||||
ValidationResult result = ValidationUtils.validate(userObject, params);
|
||||
|
||||
// 校验Map
|
||||
ValidationResult result = ValidationUtils.validateMap(userMap, params);
|
||||
|
||||
// 检查结果
|
||||
if (result.isValid()) {
|
||||
// 校验通过
|
||||
} else {
|
||||
// 获取错误信息
|
||||
String firstError = result.getFirstError();
|
||||
String allErrors = result.getAllErrors();
|
||||
List<String> errors = result.getErrors();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用ValidateMethod进行专业校验(兼容旧方式)
|
||||
|
||||
```java
|
||||
List<ValidationParam> params = Arrays.asList(
|
||||
// 方式1:使用枚举(推荐)
|
||||
ValidationParam.builder()
|
||||
.fieldName("password")
|
||||
.fieldLabel("密码")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.STRONG_PASSWORD) // 使用预定义的强密码
|
||||
.build(),
|
||||
|
||||
// 方式2:直接实例化(如需自定义参数)
|
||||
ValidationParam.builder()
|
||||
.fieldName("password2")
|
||||
.fieldLabel("自定义密码")
|
||||
.required()
|
||||
.validateMethod(new PasswordValidateMethod(8, 20, true, true, true, true))
|
||||
.build(),
|
||||
|
||||
// 身份证号校验
|
||||
ValidationParam.builder()
|
||||
.fieldName("idCard")
|
||||
.fieldLabel("身份证号")
|
||||
.required()
|
||||
.validateMethod(new IdCardValidateMethod())
|
||||
.build(),
|
||||
|
||||
// 手机号校验
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(new PhoneValidateMethod())
|
||||
.build(),
|
||||
|
||||
// 限制域名的邮箱校验
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required()
|
||||
.validateMethod(new EmailValidateMethod(new String[]{"company.com"}))
|
||||
.build()
|
||||
);
|
||||
|
||||
ValidationResult result = ValidationUtils.validateMap(data, params);
|
||||
```
|
||||
|
||||
### 4. 自定义校验
|
||||
|
||||
```java
|
||||
ValidationParam param = ValidationParam.builder()
|
||||
.fieldName("age")
|
||||
.fieldLabel("年龄")
|
||||
.required()
|
||||
.customValidator(value -> {
|
||||
Integer age = (Integer) value;
|
||||
return age >= 18 && age <= 60;
|
||||
})
|
||||
.customErrorMessage("年龄必须在18-60岁之间")
|
||||
.build();
|
||||
```
|
||||
|
||||
### 5. 复合校验
|
||||
|
||||
```java
|
||||
List<ValidationParam> params = Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("username")
|
||||
.fieldLabel("用户名")
|
||||
.required()
|
||||
.fieldType(String.class)
|
||||
.minLength(3)
|
||||
.maxLength(20)
|
||||
.pattern("^[a-zA-Z0-9_]+$")
|
||||
.patternDesc("只能包含字母、数字和下划线")
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("password")
|
||||
.fieldLabel("密码")
|
||||
.required()
|
||||
.minLength(6)
|
||||
.validateMethod(new PasswordValidateMethod())
|
||||
.build()
|
||||
);
|
||||
```
|
||||
|
||||
## 预定义校验方法详解
|
||||
|
||||
### PasswordValidateMethod - 密码校验
|
||||
|
||||
```java
|
||||
// 默认规则:6-20位,必须包含字母和数字
|
||||
new PasswordValidateMethod()
|
||||
|
||||
// 自定义规则
|
||||
new PasswordValidateMethod(
|
||||
8, // 最小长度
|
||||
20, // 最大长度
|
||||
true, // 需要大写字母
|
||||
true, // 需要小写字母
|
||||
true, // 需要数字
|
||||
true // 需要特殊字符
|
||||
)
|
||||
```
|
||||
|
||||
### IdCardValidateMethod - 身份证号校验
|
||||
|
||||
```java
|
||||
// 支持15位和18位身份证号
|
||||
// 自动校验:格式、省份代码、出生日期、校验码
|
||||
new IdCardValidateMethod()
|
||||
```
|
||||
|
||||
### PhoneValidateMethod - 手机号码校验
|
||||
|
||||
```java
|
||||
// 严格模式:仅中国大陆手机号
|
||||
new PhoneValidateMethod()
|
||||
|
||||
// 宽松模式:支持大陆、香港、台湾
|
||||
new PhoneValidateMethod(false)
|
||||
```
|
||||
|
||||
### EmailValidateMethod - 邮箱地址校验
|
||||
|
||||
```java
|
||||
// 允许所有域名
|
||||
new EmailValidateMethod()
|
||||
|
||||
// 限制特定域名
|
||||
new EmailValidateMethod(new String[]{"company.com", "example.com"})
|
||||
```
|
||||
|
||||
### UrlValidateMethod - URL链接校验
|
||||
|
||||
```java
|
||||
// 允许HTTP和HTTPS
|
||||
new UrlValidateMethod()
|
||||
|
||||
// 仅允许HTTPS
|
||||
new UrlValidateMethod(true)
|
||||
```
|
||||
|
||||
### BankCardValidateMethod - 银行卡号校验
|
||||
|
||||
```java
|
||||
// 使用Luhn算法校验银行卡号
|
||||
new BankCardValidateMethod()
|
||||
```
|
||||
|
||||
### ChineseValidateMethod - 中文字符校验
|
||||
|
||||
```java
|
||||
// 仅纯中文字符
|
||||
new ChineseValidateMethod()
|
||||
|
||||
// 允许中文标点符号
|
||||
new ChineseValidateMethod(true)
|
||||
```
|
||||
|
||||
## 在Controller中使用
|
||||
|
||||
```java
|
||||
@PostMapping("/register")
|
||||
public ResultDomain<User> register(@RequestBody Map<String, Object> params) {
|
||||
// 定义校验规则(使用枚举,更简洁)
|
||||
List<ValidationParam> validationParams = Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("username")
|
||||
.fieldLabel("用户名")
|
||||
.required()
|
||||
.minLength(3)
|
||||
.maxLength(20)
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("password")
|
||||
.fieldLabel("密码")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PASSWORD) // 使用枚举!
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.EMAIL) // 使用枚举!
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE) // 使用枚举!
|
||||
.build()
|
||||
);
|
||||
|
||||
// 执行校验
|
||||
ValidationResult validationResult = ValidationUtils.validateMap(params, validationParams);
|
||||
|
||||
if (!validationResult.isValid()) {
|
||||
ResultDomain<User> result = new ResultDomain<>();
|
||||
result.fail(validationResult.getFirstError());
|
||||
return result;
|
||||
}
|
||||
|
||||
// 校验通过,继续业务逻辑
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义ValidateMethod
|
||||
|
||||
如需添加新的校验方法,只需实现`ValidateMethod`接口:
|
||||
|
||||
```java
|
||||
public class CustomValidateMethod implements ValidateMethod {
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
// 实现校验逻辑
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return "自定义错误信息";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "自定义校验";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
1. **简洁性**:使用枚举类型,无需每次new对象 ⭐
|
||||
2. **灵活性**:支持多种校验方式组合使用
|
||||
3. **可扩展性**:易于添加新的校验方法
|
||||
4. **可读性**:Builder模式让代码清晰易懂
|
||||
5. **可复用性**:预定义的校验方法可在项目中重复使用
|
||||
6. **专业性**:内置多种常用的专业校验算法(身份证、银行卡等)
|
||||
7. **类型安全**:枚举类型提供编译时类型检查
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **推荐使用枚举类型**:`ValidateMethodType` 比直接 `new` 对象更简洁
|
||||
2. 校验顺序:必填 -> 类型 -> 长度/范围 -> 正则 -> 自定义 -> ValidateMethodType -> ValidateMethod
|
||||
3. ValidateMethod和customValidator可以同时使用,都会执行
|
||||
4. 当值为null且非必填时,会跳过后续所有校验
|
||||
5. 错误信息会累积,可以获取所有错误或只获取第一个错误
|
||||
6. 枚举方式和实例方式可以并存,但推荐统一使用枚举方式
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
package org.xyzh.common.utils.validation;
|
||||
|
||||
import org.xyzh.common.utils.validation.method.ValidateMethod;
|
||||
import org.xyzh.common.utils.validation.method.ValidateMethodType;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @description 校验参数对象,定义字段的校验规则
|
||||
* @filename ValidationParam.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ValidationParam {
|
||||
|
||||
/**
|
||||
* @description 字段名称
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* @description 字段中文名称(用于错误提示)
|
||||
*/
|
||||
private String fieldLabel;
|
||||
|
||||
/**
|
||||
* @description 是否必传
|
||||
*/
|
||||
private boolean required;
|
||||
|
||||
/**
|
||||
* @description 字段类型
|
||||
*/
|
||||
private Class<?> fieldType;
|
||||
|
||||
/**
|
||||
* @description 最小长度(字符串)
|
||||
*/
|
||||
private Integer minLength;
|
||||
|
||||
/**
|
||||
* @description 最大长度(字符串)
|
||||
*/
|
||||
private Integer maxLength;
|
||||
|
||||
/**
|
||||
* @description 最小值(数字)
|
||||
*/
|
||||
private Number minValue;
|
||||
|
||||
/**
|
||||
* @description 最大值(数字)
|
||||
*/
|
||||
private Number maxValue;
|
||||
|
||||
/**
|
||||
* @description 正则表达式
|
||||
*/
|
||||
private String pattern;
|
||||
|
||||
/**
|
||||
* @description 正则表达式描述(用于错误提示)
|
||||
*/
|
||||
private String patternDesc;
|
||||
|
||||
/**
|
||||
* @description 自定义校验函数
|
||||
*/
|
||||
private Predicate<Object> customValidator;
|
||||
|
||||
/**
|
||||
* @description 自定义校验失败消息
|
||||
*/
|
||||
private String customErrorMessage;
|
||||
|
||||
/**
|
||||
* @description 是否允许为空字符串(默认不允许)
|
||||
*/
|
||||
private boolean allowEmpty = false;
|
||||
|
||||
/**
|
||||
* @description 校验方法(使用预定义的校验方法)
|
||||
*/
|
||||
private ValidateMethod validateMethod;
|
||||
|
||||
/**
|
||||
* @description 校验方法类型枚举
|
||||
*/
|
||||
private ValidateMethodType validateMethodType;
|
||||
|
||||
/**
|
||||
* @description 校验方法配置参数(用于需要自定义参数的校验方法)
|
||||
*/
|
||||
private Object[] methodParams;
|
||||
|
||||
// 私有构造函数,使用Builder模式
|
||||
private ValidationParam() {
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public String getFieldLabel() {
|
||||
return fieldLabel;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public Class<?> getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
public Integer getMinLength() {
|
||||
return minLength;
|
||||
}
|
||||
|
||||
public Integer getMaxLength() {
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
public Number getMinValue() {
|
||||
return minValue;
|
||||
}
|
||||
|
||||
public Number getMaxValue() {
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public String getPatternDesc() {
|
||||
return patternDesc;
|
||||
}
|
||||
|
||||
public Predicate<Object> getCustomValidator() {
|
||||
return customValidator;
|
||||
}
|
||||
|
||||
public String getCustomErrorMessage() {
|
||||
return customErrorMessage;
|
||||
}
|
||||
|
||||
public boolean isAllowEmpty() {
|
||||
return allowEmpty;
|
||||
}
|
||||
|
||||
public ValidateMethod getValidateMethod() {
|
||||
return validateMethod;
|
||||
}
|
||||
|
||||
public ValidateMethodType getValidateMethodType() {
|
||||
return validateMethodType;
|
||||
}
|
||||
|
||||
public Object[] getMethodParams() {
|
||||
return methodParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builder类用于构建ValidationParam对象
|
||||
*/
|
||||
public static class Builder {
|
||||
private ValidationParam param = new ValidationParam();
|
||||
|
||||
public Builder fieldName(String fieldName) {
|
||||
param.fieldName = fieldName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fieldLabel(String fieldLabel) {
|
||||
param.fieldLabel = fieldLabel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder required(boolean required) {
|
||||
param.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder required() {
|
||||
param.required = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fieldType(Class<?> fieldType) {
|
||||
param.fieldType = fieldType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder minLength(Integer minLength) {
|
||||
param.minLength = minLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxLength(Integer maxLength) {
|
||||
param.maxLength = maxLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder minValue(Number minValue) {
|
||||
param.minValue = minValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxValue(Number maxValue) {
|
||||
param.maxValue = maxValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder pattern(String pattern) {
|
||||
param.pattern = pattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder patternDesc(String patternDesc) {
|
||||
param.patternDesc = patternDesc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder customValidator(Predicate<Object> customValidator) {
|
||||
param.customValidator = customValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder customErrorMessage(String customErrorMessage) {
|
||||
param.customErrorMessage = customErrorMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allowEmpty(boolean allowEmpty) {
|
||||
param.allowEmpty = allowEmpty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder validateMethod(ValidateMethod validateMethod) {
|
||||
param.validateMethod = validateMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder validateMethod(ValidateMethodType methodType) {
|
||||
param.validateMethodType = methodType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder validateMethod(ValidateMethodType methodType, Object... params) {
|
||||
param.validateMethodType = methodType;
|
||||
param.methodParams = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidationParam build() {
|
||||
if (param.fieldName == null || param.fieldName.isEmpty()) {
|
||||
throw new IllegalArgumentException("fieldName不能为空");
|
||||
}
|
||||
if (param.fieldLabel == null || param.fieldLabel.isEmpty()) {
|
||||
param.fieldLabel = param.fieldName;
|
||||
}
|
||||
return param;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建Builder对象
|
||||
* @return Builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.xyzh.common.utils.validation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 校验结果类
|
||||
* @filename ValidationResult.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ValidationResult {
|
||||
|
||||
/**
|
||||
* @description 是否校验通过
|
||||
*/
|
||||
private boolean valid;
|
||||
|
||||
/**
|
||||
* @description 错误信息列表
|
||||
*/
|
||||
private List<String> errors;
|
||||
|
||||
/**
|
||||
* @description 第一个错误信息
|
||||
*/
|
||||
private String firstError;
|
||||
|
||||
public ValidationResult() {
|
||||
this.valid = true;
|
||||
this.errors = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public void setValid(boolean valid) {
|
||||
this.valid = valid;
|
||||
}
|
||||
|
||||
public List<String> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
public String getFirstError() {
|
||||
return firstError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 添加错误信息
|
||||
* @param error 错误信息
|
||||
*/
|
||||
public void addError(String error) {
|
||||
this.valid = false;
|
||||
this.errors.add(error);
|
||||
if (this.firstError == null) {
|
||||
this.firstError = error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有错误信息的字符串
|
||||
* @return 错误信息字符串
|
||||
*/
|
||||
public String getAllErrors() {
|
||||
return String.join("; ", errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 是否有错误
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean hasErrors() {
|
||||
return !valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取错误数量
|
||||
* @return 错误数量
|
||||
*/
|
||||
public int getErrorCount() {
|
||||
return errors.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValidationResult{" +
|
||||
"valid=" + valid +
|
||||
", errorCount=" + errors.size() +
|
||||
", errors=" + errors +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
package org.xyzh.common.utils.validation;
|
||||
|
||||
import org.xyzh.common.utils.validation.method.ValidateMethod;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 校验工具类
|
||||
* @filename ValidationUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ValidationUtils {
|
||||
|
||||
/**
|
||||
* @description 校验对象
|
||||
* @param obj 待校验的对象
|
||||
* @param validationParams 校验参数列表
|
||||
* @return ValidationResult 校验结果
|
||||
*/
|
||||
public static ValidationResult validate(Object obj, List<ValidationParam> validationParams) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
|
||||
if (obj == null) {
|
||||
result.addError("待校验对象不能为null");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (validationParams == null || validationParams.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (ValidationParam param : validationParams) {
|
||||
try {
|
||||
Object fieldValue = getFieldValue(obj, param.getFieldName());
|
||||
validateField(param, fieldValue, result);
|
||||
} catch (Exception e) {
|
||||
result.addError(param.getFieldLabel() + "字段获取失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验Map对象
|
||||
* @param map 待校验的Map
|
||||
* @param validationParams 校验参数列表
|
||||
* @return ValidationResult 校验结果
|
||||
*/
|
||||
public static ValidationResult validateMap(Map<String, Object> map, List<ValidationParam> validationParams) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
|
||||
if (map == null) {
|
||||
result.addError("待校验Map不能为null");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (validationParams == null || validationParams.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (ValidationParam param : validationParams) {
|
||||
Object fieldValue = map.get(param.getFieldName());
|
||||
validateField(param, fieldValue, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验单个字段
|
||||
* @param param 校验参数
|
||||
* @param fieldValue 字段值
|
||||
* @param result 校验结果
|
||||
*/
|
||||
private static void validateField(ValidationParam param, Object fieldValue, ValidationResult result) {
|
||||
String fieldLabel = param.getFieldLabel();
|
||||
|
||||
// 1. 必填校验
|
||||
if (param.isRequired()) {
|
||||
if (fieldValue == null) {
|
||||
result.addError(fieldLabel + "不能为空");
|
||||
return;
|
||||
}
|
||||
if (fieldValue instanceof String) {
|
||||
String strValue = (String) fieldValue;
|
||||
if (!param.isAllowEmpty() && strValue.trim().isEmpty()) {
|
||||
result.addError(fieldLabel + "不能为空字符串");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果值为null且非必填,跳过后续校验
|
||||
if (fieldValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 类型校验
|
||||
if (param.getFieldType() != null) {
|
||||
if (!param.getFieldType().isAssignableFrom(fieldValue.getClass())) {
|
||||
result.addError(fieldLabel + "类型错误,期望类型: " + param.getFieldType().getSimpleName() +
|
||||
", 实际类型: " + fieldValue.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 字符串长度校验
|
||||
if (fieldValue instanceof String) {
|
||||
String strValue = (String) fieldValue;
|
||||
if (param.getMinLength() != null && strValue.length() < param.getMinLength()) {
|
||||
result.addError(fieldLabel + "长度不能少于" + param.getMinLength() + "个字符");
|
||||
}
|
||||
if (param.getMaxLength() != null && strValue.length() > param.getMaxLength()) {
|
||||
result.addError(fieldLabel + "长度不能超过" + param.getMaxLength() + "个字符");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 数字范围校验
|
||||
if (fieldValue instanceof Number) {
|
||||
double numValue = ((Number) fieldValue).doubleValue();
|
||||
if (param.getMinValue() != null && numValue < param.getMinValue().doubleValue()) {
|
||||
result.addError(fieldLabel + "不能小于" + param.getMinValue());
|
||||
}
|
||||
if (param.getMaxValue() != null && numValue > param.getMaxValue().doubleValue()) {
|
||||
result.addError(fieldLabel + "不能大于" + param.getMaxValue());
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 正则表达式校验
|
||||
if (param.getPattern() != null && fieldValue instanceof String) {
|
||||
String strValue = (String) fieldValue;
|
||||
if (!Pattern.matches(param.getPattern(), strValue)) {
|
||||
String errorMsg = fieldLabel + "格式不正确";
|
||||
if (param.getPatternDesc() != null) {
|
||||
errorMsg += "," + param.getPatternDesc();
|
||||
}
|
||||
result.addError(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 自定义校验
|
||||
if (param.getCustomValidator() != null) {
|
||||
try {
|
||||
if (!param.getCustomValidator().test(fieldValue)) {
|
||||
String errorMsg = param.getCustomErrorMessage();
|
||||
if (errorMsg == null || errorMsg.isEmpty()) {
|
||||
errorMsg = fieldLabel + "校验失败";
|
||||
}
|
||||
result.addError(errorMsg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.addError(fieldLabel + "自定义校验异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 使用ValidateMethod校验(枚举类型)
|
||||
if (param.getValidateMethodType() != null) {
|
||||
try {
|
||||
ValidateMethod method = param.getValidateMethodType().createInstance();
|
||||
if (!method.validate(fieldValue)) {
|
||||
String errorMsg = method.getErrorMessage();
|
||||
if (errorMsg != null && !errorMsg.isEmpty()) {
|
||||
result.addError(errorMsg);
|
||||
} else {
|
||||
result.addError(fieldLabel + "校验失败");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.addError(fieldLabel + "校验异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 使用ValidateMethod校验(直接传入实例,保留兼容性)
|
||||
if (param.getValidateMethod() != null) {
|
||||
try {
|
||||
if (!param.getValidateMethod().validate(fieldValue)) {
|
||||
String errorMsg = param.getValidateMethod().getErrorMessage();
|
||||
if (errorMsg != null && !errorMsg.isEmpty()) {
|
||||
result.addError(errorMsg);
|
||||
} else {
|
||||
result.addError(fieldLabel + "校验失败");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.addError(fieldLabel + "校验异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取对象字段值(支持getter方法和直接访问)
|
||||
* @param obj 对象
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
private static 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);
|
||||
return clazz.getMethod(getterName).invoke(obj);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// getter方法不存在,尝试直接访问字段
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建必填字符串校验参数
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam requiredString(String fieldName, String fieldLabel) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required()
|
||||
.fieldType(String.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建必填字符串校验参数(带长度限制)
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @param minLength 最小长度
|
||||
* @param maxLength 最大长度
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam requiredString(String fieldName, String fieldLabel, int minLength, int maxLength) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required()
|
||||
.fieldType(String.class)
|
||||
.minLength(minLength)
|
||||
.maxLength(maxLength)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建必填数字校验参数
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @param minValue 最小值
|
||||
* @param maxValue 最大值
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam requiredNumber(String fieldName, String fieldLabel, Number minValue, Number maxValue) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required()
|
||||
.minValue(minValue)
|
||||
.maxValue(maxValue)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建邮箱校验参数
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @param required 是否必填
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam email(String fieldName, String fieldLabel, boolean required) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required(required)
|
||||
.fieldType(String.class)
|
||||
.pattern("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
|
||||
.patternDesc("请输入有效的邮箱地址")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建手机号校验参数
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @param required 是否必填
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam phone(String fieldName, String fieldLabel, boolean required) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required(required)
|
||||
.fieldType(String.class)
|
||||
.pattern("^1[3-9]\\d{9}$")
|
||||
.patternDesc("请输入有效的手机号码")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
/**
|
||||
* @description 银行卡号校验方法(Luhn算法)
|
||||
* @filename BankCardValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class BankCardValidateMethod implements ValidateMethod {
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String cardNumber = ((String) value).replaceAll("\\s", "");
|
||||
|
||||
// 长度校验(银行卡号通常为16-19位)
|
||||
if (cardNumber.length() < 16 || cardNumber.length() > 19) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 数字校验
|
||||
if (!cardNumber.matches("^\\d+$")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Luhn算法校验
|
||||
return luhnCheck(cardNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Luhn算法校验(银行卡校验算法)
|
||||
* @param cardNumber 银行卡号
|
||||
* @return boolean 是否通过校验
|
||||
*/
|
||||
private boolean luhnCheck(String cardNumber) {
|
||||
int sum = 0;
|
||||
boolean alternate = false;
|
||||
|
||||
// 从右向左遍历
|
||||
for (int i = cardNumber.length() - 1; i >= 0; i--) {
|
||||
int digit = Character.getNumericValue(cardNumber.charAt(i));
|
||||
|
||||
if (alternate) {
|
||||
digit *= 2;
|
||||
if (digit > 9) {
|
||||
digit = digit - 9;
|
||||
}
|
||||
}
|
||||
|
||||
sum += digit;
|
||||
alternate = !alternate;
|
||||
}
|
||||
|
||||
return sum % 10 == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return "请输入有效的银行卡号";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "银行卡号校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 中文字符校验方法
|
||||
* @filename ChineseValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ChineseValidateMethod implements ValidateMethod {
|
||||
|
||||
// 中文字符正则(包括中文标点符号)
|
||||
private static final Pattern CHINESE_PATTERN = Pattern.compile("^[\u4e00-\u9fa5]+$");
|
||||
|
||||
private final boolean allowPunctuation; // 是否允许中文标点符号
|
||||
|
||||
public ChineseValidateMethod() {
|
||||
this.allowPunctuation = false;
|
||||
}
|
||||
|
||||
public ChineseValidateMethod(boolean allowPunctuation) {
|
||||
this.allowPunctuation = allowPunctuation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String str = (String) value;
|
||||
|
||||
if (allowPunctuation) {
|
||||
// 允许中文字符和中文标点符号
|
||||
return Pattern.matches("^[\u4e00-\u9fa5\\u3000-\\u303f]+$", str);
|
||||
} else {
|
||||
// 仅允许纯中文字符
|
||||
return CHINESE_PATTERN.matcher(str).matches();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return allowPunctuation ? "请输入中文字符" : "请输入纯中文字符(不含标点符号)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "中文字符校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 邮箱校验方法
|
||||
* @filename EmailValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class EmailValidateMethod implements ValidateMethod {
|
||||
|
||||
// 邮箱正则表达式
|
||||
private static final Pattern EMAIL_PATTERN = Pattern.compile(
|
||||
"^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
|
||||
);
|
||||
|
||||
private final String[] allowedDomains; // 允许的域名列表
|
||||
|
||||
/**
|
||||
* @description 默认构造函数,允许所有域名
|
||||
*/
|
||||
public EmailValidateMethod() {
|
||||
this.allowedDomains = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 限制域名的构造函数
|
||||
* @param allowedDomains 允许的域名列表,例如:["company.com", "example.com"]
|
||||
*/
|
||||
public EmailValidateMethod(String[] allowedDomains) {
|
||||
this.allowedDomains = allowedDomains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String email = ((String) value).trim().toLowerCase();
|
||||
|
||||
// 基本格式校验
|
||||
if (!EMAIL_PATTERN.matcher(email).matches()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 域名限制校验
|
||||
if (allowedDomains != null && allowedDomains.length > 0) {
|
||||
boolean domainMatched = false;
|
||||
for (String domain : allowedDomains) {
|
||||
if (email.endsWith("@" + domain.toLowerCase())) {
|
||||
domainMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return domainMatched;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
if (allowedDomains != null && allowedDomains.length > 0) {
|
||||
return "请输入有效的邮箱地址(仅支持: " + String.join(", ", allowedDomains) + ")";
|
||||
}
|
||||
return "请输入有效的邮箱地址";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "邮箱校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 身份证号码校验方法(支持15位和18位身份证)
|
||||
* @filename IdCardValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class IdCardValidateMethod implements ValidateMethod {
|
||||
|
||||
// 加权因子
|
||||
private static final int[] WEIGHT = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
|
||||
|
||||
// 校验码对应值
|
||||
private static final char[] VALIDATE_CODE = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
|
||||
|
||||
// 省份代码
|
||||
private static final Map<String, String> PROVINCE_CODES = new HashMap<>();
|
||||
|
||||
static {
|
||||
PROVINCE_CODES.put("11", "北京");
|
||||
PROVINCE_CODES.put("12", "天津");
|
||||
PROVINCE_CODES.put("13", "河北");
|
||||
PROVINCE_CODES.put("14", "山西");
|
||||
PROVINCE_CODES.put("15", "内蒙古");
|
||||
PROVINCE_CODES.put("21", "辽宁");
|
||||
PROVINCE_CODES.put("22", "吉林");
|
||||
PROVINCE_CODES.put("23", "黑龙江");
|
||||
PROVINCE_CODES.put("31", "上海");
|
||||
PROVINCE_CODES.put("32", "江苏");
|
||||
PROVINCE_CODES.put("33", "浙江");
|
||||
PROVINCE_CODES.put("34", "安徽");
|
||||
PROVINCE_CODES.put("35", "福建");
|
||||
PROVINCE_CODES.put("36", "江西");
|
||||
PROVINCE_CODES.put("37", "山东");
|
||||
PROVINCE_CODES.put("41", "河南");
|
||||
PROVINCE_CODES.put("42", "湖北");
|
||||
PROVINCE_CODES.put("43", "湖南");
|
||||
PROVINCE_CODES.put("44", "广东");
|
||||
PROVINCE_CODES.put("45", "广西");
|
||||
PROVINCE_CODES.put("46", "海南");
|
||||
PROVINCE_CODES.put("50", "重庆");
|
||||
PROVINCE_CODES.put("51", "四川");
|
||||
PROVINCE_CODES.put("52", "贵州");
|
||||
PROVINCE_CODES.put("53", "云南");
|
||||
PROVINCE_CODES.put("54", "西藏");
|
||||
PROVINCE_CODES.put("61", "陕西");
|
||||
PROVINCE_CODES.put("62", "甘肃");
|
||||
PROVINCE_CODES.put("63", "青海");
|
||||
PROVINCE_CODES.put("64", "宁夏");
|
||||
PROVINCE_CODES.put("65", "新疆");
|
||||
PROVINCE_CODES.put("71", "台湾");
|
||||
PROVINCE_CODES.put("81", "香港");
|
||||
PROVINCE_CODES.put("82", "澳门");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String idCard = ((String) value).toUpperCase();
|
||||
|
||||
// 长度校验
|
||||
if (idCard.length() != 15 && idCard.length() != 18) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 格式校验
|
||||
if (idCard.length() == 15) {
|
||||
return validate15IdCard(idCard);
|
||||
} else {
|
||||
return validate18IdCard(idCard);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验15位身份证
|
||||
*/
|
||||
private boolean validate15IdCard(String idCard) {
|
||||
// 15位身份证格式:省(2位)市(2位)县(2位)年(2位)月(2位)日(2位)顺序号(3位)
|
||||
if (!Pattern.matches("^\\d{15}$", idCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 省份代码校验
|
||||
String provinceCode = idCard.substring(0, 2);
|
||||
if (!PROVINCE_CODES.containsKey(provinceCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 出生日期校验
|
||||
String year = "19" + idCard.substring(6, 8);
|
||||
String month = idCard.substring(8, 10);
|
||||
String day = idCard.substring(10, 12);
|
||||
|
||||
return validateDate(year, month, day);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验18位身份证
|
||||
*/
|
||||
private boolean validate18IdCard(String idCard) {
|
||||
// 18位身份证格式:省(2位)市(2位)县(2位)年(4位)月(2位)日(2位)顺序号(3位)校验码(1位)
|
||||
if (!Pattern.matches("^\\d{17}[0-9Xx]$", idCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 省份代码校验
|
||||
String provinceCode = idCard.substring(0, 2);
|
||||
if (!PROVINCE_CODES.containsKey(provinceCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 出生日期校验
|
||||
String year = idCard.substring(6, 10);
|
||||
String month = idCard.substring(10, 12);
|
||||
String day = idCard.substring(12, 14);
|
||||
|
||||
if (!validateDate(year, month, day)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 校验码校验
|
||||
return validateCheckCode(idCard);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验日期是否合法
|
||||
*/
|
||||
private boolean validateDate(String year, String month, String day) {
|
||||
try {
|
||||
int y = Integer.parseInt(year);
|
||||
int m = Integer.parseInt(month);
|
||||
int d = Integer.parseInt(day);
|
||||
|
||||
// 年份范围:1900-当前年份
|
||||
int currentYear = java.time.Year.now().getValue();
|
||||
if (y < 1900 || y > currentYear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 月份范围:1-12
|
||||
if (m < 1 || m > 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 日期范围
|
||||
int[] daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
// 闰年2月29天
|
||||
if (isLeapYear(y)) {
|
||||
daysInMonth[1] = 29;
|
||||
}
|
||||
|
||||
return d >= 1 && d <= daysInMonth[m - 1];
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断是否为闰年
|
||||
*/
|
||||
private boolean isLeapYear(int year) {
|
||||
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验18位身份证的校验码
|
||||
*/
|
||||
private boolean validateCheckCode(String idCard) {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < 17; i++) {
|
||||
sum += (idCard.charAt(i) - '0') * WEIGHT[i];
|
||||
}
|
||||
|
||||
char checkCode = VALIDATE_CODE[sum % 11];
|
||||
return checkCode == idCard.charAt(17);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return "请输入有效的身份证号码(15位或18位)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "身份证号码校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 密码校验方法
|
||||
* @filename PasswordValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class PasswordValidateMethod implements ValidateMethod {
|
||||
|
||||
private final int minLength;
|
||||
private final int maxLength;
|
||||
private final boolean requireUpperCase;
|
||||
private final boolean requireLowerCase;
|
||||
private final boolean requireDigit;
|
||||
private final boolean requireSpecialChar;
|
||||
|
||||
/**
|
||||
* @description 默认密码规则:6-20位,必须包含字母和数字
|
||||
*/
|
||||
public PasswordValidateMethod() {
|
||||
this(6, 20, false, false, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 自定义密码规则
|
||||
* @param minLength 最小长度
|
||||
* @param maxLength 最大长度
|
||||
* @param requireUpperCase 是否需要大写字母
|
||||
* @param requireLowerCase 是否需要小写字母
|
||||
* @param requireDigit 是否需要数字
|
||||
* @param requireSpecialChar 是否需要特殊字符
|
||||
*/
|
||||
public PasswordValidateMethod(int minLength, int maxLength,
|
||||
boolean requireUpperCase, boolean requireLowerCase,
|
||||
boolean requireDigit, boolean requireSpecialChar) {
|
||||
this.minLength = minLength;
|
||||
this.maxLength = maxLength;
|
||||
this.requireUpperCase = requireUpperCase;
|
||||
this.requireLowerCase = requireLowerCase;
|
||||
this.requireDigit = requireDigit;
|
||||
this.requireSpecialChar = requireSpecialChar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String password = (String) value;
|
||||
|
||||
// 长度校验
|
||||
if (password.length() < minLength || password.length() > maxLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 大写字母校验
|
||||
if (requireUpperCase && !Pattern.compile("[A-Z]").matcher(password).find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 小写字母校验
|
||||
if (requireLowerCase && !Pattern.compile("[a-z]").matcher(password).find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 数字校验
|
||||
if (requireDigit && !Pattern.compile("[0-9]").matcher(password).find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 特殊字符校验
|
||||
if (requireSpecialChar && !Pattern.compile("[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?]").matcher(password).find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
StringBuilder msg = new StringBuilder("密码必须是");
|
||||
msg.append(minLength).append("-").append(maxLength).append("位");
|
||||
|
||||
if (requireUpperCase || requireLowerCase || requireDigit || requireSpecialChar) {
|
||||
msg.append(",且包含");
|
||||
boolean first = true;
|
||||
|
||||
if (requireUpperCase) {
|
||||
msg.append("大写字母");
|
||||
first = false;
|
||||
}
|
||||
if (requireLowerCase) {
|
||||
if (!first) msg.append("、");
|
||||
msg.append("小写字母");
|
||||
first = false;
|
||||
}
|
||||
if (requireDigit) {
|
||||
if (!first) msg.append("、");
|
||||
msg.append("数字");
|
||||
first = false;
|
||||
}
|
||||
if (requireSpecialChar) {
|
||||
if (!first) msg.append("、");
|
||||
msg.append("特殊字符");
|
||||
}
|
||||
}
|
||||
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "密码校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 手机号码校验方法
|
||||
* @filename PhoneValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class PhoneValidateMethod implements ValidateMethod {
|
||||
|
||||
// 中国大陆手机号正则
|
||||
private static final Pattern CHINA_PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
|
||||
|
||||
// 香港手机号正则
|
||||
private static final Pattern HK_PHONE_PATTERN = Pattern.compile("^[5-9]\\d{7}$");
|
||||
|
||||
// 台湾手机号正则
|
||||
private static final Pattern TW_PHONE_PATTERN = Pattern.compile("^09\\d{8}$");
|
||||
|
||||
private final boolean strictMode; // 严格模式,只验证中国大陆手机号
|
||||
|
||||
public PhoneValidateMethod() {
|
||||
this.strictMode = true;
|
||||
}
|
||||
|
||||
public PhoneValidateMethod(boolean strictMode) {
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String phone = (String) value;
|
||||
|
||||
// 去除空格和横线
|
||||
phone = phone.replaceAll("[\\s-]", "");
|
||||
|
||||
if (strictMode) {
|
||||
// 严格模式:只验证中国大陆手机号
|
||||
return CHINA_PHONE_PATTERN.matcher(phone).matches();
|
||||
} else {
|
||||
// 宽松模式:支持大陆、香港、台湾手机号
|
||||
return CHINA_PHONE_PATTERN.matcher(phone).matches()
|
||||
|| HK_PHONE_PATTERN.matcher(phone).matches()
|
||||
|| TW_PHONE_PATTERN.matcher(phone).matches();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return strictMode ? "请输入有效的手机号码" : "请输入有效的手机号码(支持大陆、香港、台湾)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "手机号码校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description URL校验方法
|
||||
* @filename UrlValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class UrlValidateMethod implements ValidateMethod {
|
||||
|
||||
// URL正则表达式
|
||||
private static final Pattern URL_PATTERN = Pattern.compile(
|
||||
"^(https?|ftp)://[a-zA-Z0-9+&@#/%?=~_|!:,.;-]*[a-zA-Z0-9+&@#/%=~_|-]$"
|
||||
);
|
||||
|
||||
private final boolean requireHttps; // 是否要求HTTPS
|
||||
|
||||
public UrlValidateMethod() {
|
||||
this.requireHttps = false;
|
||||
}
|
||||
|
||||
public UrlValidateMethod(boolean requireHttps) {
|
||||
this.requireHttps = requireHttps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String url = ((String) value).trim();
|
||||
|
||||
// 基本格式校验
|
||||
if (!URL_PATTERN.matcher(url).matches()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// HTTPS校验
|
||||
if (requireHttps && !url.startsWith("https://")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return requireHttps ? "请输入有效的HTTPS链接" : "请输入有效的URL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "URL校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
/**
|
||||
* @description 校验方法接口,定义不同类型的校验方式
|
||||
* @filename ValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public interface ValidateMethod {
|
||||
|
||||
/**
|
||||
* @description 校验方法
|
||||
* @param value 待校验的值
|
||||
* @return boolean 是否校验通过
|
||||
*/
|
||||
boolean validate(Object value);
|
||||
|
||||
/**
|
||||
* @description 获取校验失败的错误提示信息
|
||||
* @return String 错误提示信息
|
||||
*/
|
||||
String getErrorMessage();
|
||||
|
||||
/**
|
||||
* @description 获取校验方法的名称
|
||||
* @return String 校验方法名称
|
||||
*/
|
||||
String getName();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @description 校验方法类型枚举
|
||||
* @filename ValidateMethodType.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public enum ValidateMethodType {
|
||||
|
||||
/**
|
||||
* 密码校验(默认:6-20位,必须包含字母和数字)
|
||||
*/
|
||||
PASSWORD("密码校验", PasswordValidateMethod.class, PasswordValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 强密码校验(8-20位,必须包含大小写字母、数字和特殊字符)
|
||||
*/
|
||||
STRONG_PASSWORD("强密码校验", PasswordValidateMethod.class,
|
||||
() -> new PasswordValidateMethod(8, 20, true, true, true, true)),
|
||||
|
||||
/**
|
||||
* 身份证号校验(支持15位和18位)
|
||||
*/
|
||||
ID_CARD("身份证号校验", IdCardValidateMethod.class, IdCardValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 手机号码校验(中国大陆)
|
||||
*/
|
||||
PHONE("手机号码校验", PhoneValidateMethod.class, PhoneValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 手机号码校验(宽松模式,支持大陆、香港、台湾)
|
||||
*/
|
||||
PHONE_LOOSE("手机号码校验(宽松)", PhoneValidateMethod.class,
|
||||
() -> new PhoneValidateMethod(false)),
|
||||
|
||||
/**
|
||||
* 邮箱地址校验
|
||||
*/
|
||||
EMAIL("邮箱地址校验", EmailValidateMethod.class, EmailValidateMethod::new),
|
||||
|
||||
/**
|
||||
* URL链接校验
|
||||
*/
|
||||
URL("URL链接校验", UrlValidateMethod.class, UrlValidateMethod::new),
|
||||
|
||||
/**
|
||||
* HTTPS链接校验
|
||||
*/
|
||||
HTTPS_URL("HTTPS链接校验", UrlValidateMethod.class,
|
||||
() -> new UrlValidateMethod(true)),
|
||||
|
||||
/**
|
||||
* 银行卡号校验
|
||||
*/
|
||||
BANK_CARD("银行卡号校验", BankCardValidateMethod.class, BankCardValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 中文字符校验(纯中文)
|
||||
*/
|
||||
CHINESE("中文字符校验", ChineseValidateMethod.class, ChineseValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 中文字符校验(允许标点符号)
|
||||
*/
|
||||
CHINESE_WITH_PUNCTUATION("中文字符校验(含标点)", ChineseValidateMethod.class,
|
||||
() -> new ChineseValidateMethod(true));
|
||||
|
||||
/**
|
||||
* 校验方法名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 校验方法实现类
|
||||
*/
|
||||
private final Class<? extends ValidateMethod> methodClass;
|
||||
|
||||
/**
|
||||
* 校验方法实例提供者
|
||||
*/
|
||||
private final Supplier<ValidateMethod> methodSupplier;
|
||||
|
||||
ValidateMethodType(String name, Class<? extends ValidateMethod> methodClass,
|
||||
Supplier<ValidateMethod> methodSupplier) {
|
||||
this.name = name;
|
||||
this.methodClass = methodClass;
|
||||
this.methodSupplier = methodSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取校验方法名称
|
||||
* @return String
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取校验方法实现类
|
||||
* @return Class
|
||||
*/
|
||||
public Class<? extends ValidateMethod> getMethodClass() {
|
||||
return methodClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建校验方法实例
|
||||
* @return ValidateMethod
|
||||
*/
|
||||
public ValidateMethod createInstance() {
|
||||
return methodSupplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据名称获取枚举
|
||||
* @param name 名称
|
||||
* @return ValidateMethodType
|
||||
*/
|
||||
public static ValidateMethodType fromName(String name) {
|
||||
for (ValidateMethodType type : values()) {
|
||||
if (type.getName().equals(name)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
64
urbanLifelineServ/common/pom.xml
Normal file
64
urbanLifelineServ/common/pom.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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>urban-lifeline</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.xyzh</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>common-dto</module>
|
||||
<module>common-core</module>
|
||||
<module>common-auth</module>
|
||||
<module>common-redis</module>
|
||||
<module>common-utils</module>
|
||||
<module>common-all</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-all</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-auth</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-dto</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-utils</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
Reference in New Issue
Block a user