This commit is contained in:
2025-12-02 13:21:18 +08:00
parent fab8c13cb3
commit ee6dd64f98
192 changed files with 25783 additions and 0 deletions

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

View File

@@ -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("-", "");
}
}

View File

@@ -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-对象为nullfalse-对象不为null
*/
public static boolean isNull(Object obj) {
return obj == null;
}
/**
* 判断对象是否不为null
*
* @param obj 待判断的对象
* @return true-对象不为nullfalse-对象为null
*/
public static boolean isNotNull(Object obj) {
return obj != null;
}
/**
* 判断多个对象是否都为null
*
* @param objects 待判断的对象数组
* @return true-所有对象都为nullfalse-至少有一个对象不为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-所有对象都不为nullfalse-至少有一个对象为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;
}
}

View File

@@ -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("<", "&lt;")
.replace(">", "&gt;")
.replace("'", "&#39;")
.replace("\"", "&quot;")
.replace("&", "&amp;");
}
/**
* @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;
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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 + '\'' +
'}';
}
}

View File

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

View File

@@ -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();
}
}

View File

@@ -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` 查看完整使用示例。

View File

@@ -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. 枚举方式和实例方式可以并存,但推荐统一使用枚举方式

View File

@@ -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();
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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();
}
}

View File

@@ -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 "银行卡号校验";
}
}

View File

@@ -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 "中文字符校验";
}
}

View File

@@ -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 "邮箱校验";
}
}

View File

@@ -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 "身份证号码校验";
}
}

View File

@@ -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 "密码校验";
}
}

View File

@@ -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 "手机号码校验";
}
}

View File

@@ -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校验";
}
}

View File

@@ -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();
}

View File

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