temp
This commit is contained in:
34
urbanLifelineServ/common/common-utils/pom.xml
Normal file
34
urbanLifelineServ/common/common-utils/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.xyzh</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-utils</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Apache POI for Excel -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @description IDUtils.java文件描述
|
||||
* @filename IDUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class IDUtils {
|
||||
|
||||
/**
|
||||
* @description 生成UUID
|
||||
* @return UUID
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String generateID() {
|
||||
return UUID.randomUUID().toString().replaceAll("-", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @description NonUtils.java文件描述 空值判断工具类
|
||||
* @filename NonUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class NonUtils {
|
||||
|
||||
private NonUtils() {
|
||||
throw new UnsupportedOperationException("工具类不能被实例化");
|
||||
}
|
||||
|
||||
// ======================== 基础null判断 ========================
|
||||
|
||||
/**
|
||||
* 判断对象是否为null
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象为null,false-对象不为null
|
||||
*/
|
||||
public static boolean isNull(Object obj) {
|
||||
return obj == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否不为null
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象不为null,false-对象为null
|
||||
*/
|
||||
public static boolean isNotNull(Object obj) {
|
||||
return obj != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象是否都为null
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-所有对象都为null,false-至少有一个对象不为null
|
||||
*/
|
||||
public static boolean isAllNull(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return true;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isNotNull(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象是否都不为null
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-所有对象都不为null,false-至少有一个对象为null
|
||||
*/
|
||||
public static boolean isAllNotNull(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isNull(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象中是否存在null
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-存在null对象,false-不存在null对象
|
||||
*/
|
||||
public static boolean hasNull(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return true;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isNull(obj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ======================== 空值判断(包含null、空字符串、空集合等) ========================
|
||||
|
||||
/**
|
||||
* 判断对象是否为空
|
||||
* - null -> true
|
||||
* - "" -> true
|
||||
* - " " -> true (仅包含空白字符)
|
||||
* - 空集合 -> true
|
||||
* - 空数组 -> true
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象为空,false-对象不为空
|
||||
*/
|
||||
public static boolean isEmpty(Object obj) {
|
||||
if (isNull(obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 字符串判断
|
||||
if (obj instanceof CharSequence) {
|
||||
return ((CharSequence) obj).length() == 0 || obj.toString().trim().isEmpty();
|
||||
}
|
||||
|
||||
// 集合判断
|
||||
if (obj instanceof Collection) {
|
||||
return ((Collection<?>) obj).isEmpty();
|
||||
}
|
||||
|
||||
// Map判断
|
||||
if (obj instanceof Map) {
|
||||
return ((Map<?, ?>) obj).isEmpty();
|
||||
}
|
||||
|
||||
// 数组判断
|
||||
if (obj.getClass().isArray()) {
|
||||
return Array.getLength(obj) == 0;
|
||||
}
|
||||
|
||||
// Optional判断
|
||||
if (obj instanceof Optional) {
|
||||
return !((Optional<?>) obj).isPresent();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否不为空
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象不为空,false-对象为空
|
||||
*/
|
||||
public static boolean isNotEmpty(Object obj) {
|
||||
return !isEmpty(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象是否都为空
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-所有对象都为空,false-至少有一个对象不为空
|
||||
*/
|
||||
public static boolean isAllEmpty(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return true;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isNotEmpty(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象是否都不为空
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-所有对象都不为空,false-至少有一个对象为空
|
||||
*/
|
||||
public static boolean isAllNotEmpty(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isEmpty(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断多个对象中是否存在空值
|
||||
*
|
||||
* @param objects 待判断的对象数组
|
||||
* @return true-存在空值,false-不存在空值
|
||||
*/
|
||||
public static boolean hasEmpty(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return true;
|
||||
}
|
||||
for (Object obj : objects) {
|
||||
if (isEmpty(obj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ======================== 深度递归判断 ========================
|
||||
|
||||
/**
|
||||
* 深度判断对象是否为空(递归检查)
|
||||
* 对于集合、数组等容器类型,会递归检查其内部元素
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象为空(包括递归检查),false-对象不为空
|
||||
*/
|
||||
public static boolean isDeepEmpty(Object obj) {
|
||||
return isDeepEmpty(obj, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度判断对象是否为空(递归检查,防止循环引用)
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @param visited 已访问对象集合,用于防止循环引用
|
||||
* @return true-对象为空(包括递归检查),false-对象不为空
|
||||
*/
|
||||
private static boolean isDeepEmpty(Object obj, Set<Object> visited) {
|
||||
if (isEmpty(obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 防止循环引用
|
||||
if (visited.contains(obj)) {
|
||||
return false;
|
||||
}
|
||||
visited.add(obj);
|
||||
|
||||
try {
|
||||
// 集合类型递归检查
|
||||
if (obj instanceof Collection) {
|
||||
Collection<?> collection = (Collection<?>) obj;
|
||||
for (Object item : collection) {
|
||||
if (!isDeepEmpty(item, visited)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Map类型递归检查
|
||||
if (obj instanceof Map) {
|
||||
Map<?, ?> map = (Map<?, ?>) obj;
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (!isDeepEmpty(entry.getKey(), visited) || !isDeepEmpty(entry.getValue(), visited)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 数组类型递归检查
|
||||
if (obj.getClass().isArray()) {
|
||||
int length = Array.getLength(obj);
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (!isDeepEmpty(Array.get(obj, i), visited)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optional类型递归检查
|
||||
if (obj instanceof Optional) {
|
||||
Optional<?> optional = (Optional<?>) obj;
|
||||
return !optional.isPresent() || isDeepEmpty(optional.get(), visited);
|
||||
}
|
||||
|
||||
// 其他类型认为不为空
|
||||
return false;
|
||||
|
||||
} finally {
|
||||
visited.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度判断对象是否不为空(递归检查)
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return true-对象不为空(包括递归检查),false-对象为空
|
||||
*/
|
||||
public static boolean isDeepNotEmpty(Object obj) {
|
||||
return !isDeepEmpty(obj);
|
||||
}
|
||||
|
||||
// ======================== 集合专用方法 ========================
|
||||
|
||||
/**
|
||||
* 判断集合是否为空或null
|
||||
*
|
||||
* @param collection 待判断的集合
|
||||
* @return true-集合为null或空,false-集合不为空
|
||||
*/
|
||||
public static boolean isEmptyCollection(Collection<?> collection) {
|
||||
return collection == null || collection.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空且不为null
|
||||
*
|
||||
* @param collection 待判断的集合
|
||||
* @return true-集合不为null且不为空,false-集合为null或空
|
||||
*/
|
||||
public static boolean isNotEmptyCollection(Collection<?> collection) {
|
||||
return collection != null && !collection.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否包含有效元素(非null且非空的元素)
|
||||
*
|
||||
* @param collection 待判断的集合
|
||||
* @return true-集合包含有效元素,false-集合为空或只包含null/空元素
|
||||
*/
|
||||
public static boolean hasValidElements(Collection<?> collection) {
|
||||
if (isEmptyCollection(collection)) {
|
||||
return false;
|
||||
}
|
||||
for (Object item : collection) {
|
||||
if (isNotEmpty(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否所有元素都有效(非null且非空)
|
||||
*
|
||||
* @param collection 待判断的集合
|
||||
* @return true-集合所有元素都有效,false-集合为空或包含null/空元素
|
||||
*/
|
||||
public static boolean allValidElements(Collection<?> collection) {
|
||||
if (isEmptyCollection(collection)) {
|
||||
return false;
|
||||
}
|
||||
for (Object item : collection) {
|
||||
if (isEmpty(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤集合中的空元素,返回新集合
|
||||
*
|
||||
* @param collection 原集合
|
||||
* @param <T> 集合元素类型
|
||||
* @return 过滤后的新集合
|
||||
*/
|
||||
public static <T> List<T> filterEmpty(Collection<T> collection) {
|
||||
if (isEmptyCollection(collection)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<T> result = new ArrayList<>();
|
||||
for (T item : collection) {
|
||||
if (isNotEmpty(item)) {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ======================== Map专用方法 ========================
|
||||
|
||||
/**
|
||||
* 判断Map是否为空或null
|
||||
*
|
||||
* @param map 待判断的Map
|
||||
* @return true-Map为null或空,false-Map不为空
|
||||
*/
|
||||
public static boolean isEmptyMap(Map<?, ?> map) {
|
||||
return map == null || map.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Map是否不为空且不为null
|
||||
*
|
||||
* @param map 待判断的Map
|
||||
* @return true-Map不为null且不为空,false-Map为null或空
|
||||
*/
|
||||
public static boolean isNotEmptyMap(Map<?, ?> map) {
|
||||
return map != null && !map.isEmpty();
|
||||
}
|
||||
|
||||
// ======================== 数组专用方法 ========================
|
||||
|
||||
/**
|
||||
* 判断数组是否为空或null
|
||||
*
|
||||
* @param array 待判断的数组
|
||||
* @return true-数组为null或空,false-数组不为空
|
||||
*/
|
||||
public static boolean isEmptyArray(Object array) {
|
||||
return array == null || !array.getClass().isArray() || Array.getLength(array) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数组是否不为空且不为null
|
||||
*
|
||||
* @param array 待判断的数组
|
||||
* @return true-数组不为null且不为空,false-数组为null或空
|
||||
*/
|
||||
public static boolean isNotEmptyArray(Object array) {
|
||||
return array != null && array.getClass().isArray() && Array.getLength(array) > 0;
|
||||
}
|
||||
|
||||
// ======================== 字符串专用方法 ========================
|
||||
|
||||
/**
|
||||
* 判断字符串是否为空或null(包括空白字符串)
|
||||
*
|
||||
* @param str 待判断的字符串
|
||||
* @return true-字符串为null、空或只包含空白字符,false-字符串有有效内容
|
||||
*/
|
||||
public static boolean isEmptyString(String str) {
|
||||
return str == null || str.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否不为空且不为null
|
||||
*
|
||||
* @param str 待判断的字符串
|
||||
* @return true-字符串不为null且有有效内容,false-字符串为null、空或只包含空白字符
|
||||
*/
|
||||
public static boolean isNotEmptyString(String str) {
|
||||
return str != null && !str.trim().isEmpty();
|
||||
}
|
||||
|
||||
// ======================== 条件判断方法 ========================
|
||||
|
||||
/**
|
||||
* 如果对象为空则返回默认值
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @param defaultValue 默认值
|
||||
* @param <T> 对象类型
|
||||
* @return 如果obj为空则返回defaultValue,否则返回obj
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T defaultIfEmpty(T obj, T defaultValue) {
|
||||
return isEmpty(obj) ? defaultValue : obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果对象为null则返回默认值
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @param defaultValue 默认值
|
||||
* @param <T> 对象类型
|
||||
* @return 如果obj为null则返回defaultValue,否则返回obj
|
||||
*/
|
||||
public static <T> T defaultIfNull(T obj, T defaultValue) {
|
||||
return isNull(obj) ? defaultValue : obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第一个非空的对象
|
||||
*
|
||||
* @param objects 对象数组
|
||||
* @param <T> 对象类型
|
||||
* @return 第一个非空的对象,如果都为空则返回null
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> T firstNotEmpty(T... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return null;
|
||||
}
|
||||
for (T obj : objects) {
|
||||
if (isNotEmpty(obj)) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第一个非null的对象
|
||||
*
|
||||
* @param objects 对象数组
|
||||
* @param <T> 对象类型
|
||||
* @return 第一个非null的对象,如果都为null则返回null
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> T firstNotNull(T... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return null;
|
||||
}
|
||||
for (T obj : objects) {
|
||||
if (isNotNull(obj)) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ======================== 统计方法 ========================
|
||||
|
||||
/**
|
||||
* 统计数组中非空元素的个数
|
||||
*
|
||||
* @param objects 对象数组
|
||||
* @return 非空元素个数
|
||||
*/
|
||||
public static int countNotEmpty(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (Object obj : objects) {
|
||||
if (isNotEmpty(obj)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计数组中非null元素的个数
|
||||
*
|
||||
* @param objects 对象数组
|
||||
* @return 非null元素个数
|
||||
*/
|
||||
public static int countNotNull(Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (Object obj : objects) {
|
||||
if (isNotNull(obj)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ======================== 断言方法 ========================
|
||||
|
||||
/**
|
||||
* 断言对象不为null,如果为null则抛出异常
|
||||
*
|
||||
* @param obj 待断言的对象
|
||||
* @param message 异常消息
|
||||
* @throws IllegalArgumentException 如果对象为null
|
||||
*/
|
||||
public static void requireNotNull(Object obj, String message) {
|
||||
if (isNull(obj)) {
|
||||
throw new IllegalArgumentException(message != null ? message : "对象不能为null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言对象不为空,如果为空则抛出异常
|
||||
*
|
||||
* @param obj 待断言的对象
|
||||
* @param message 异常消息
|
||||
* @throws IllegalArgumentException 如果对象为空
|
||||
*/
|
||||
public static void requireNotEmpty(Object obj, String message) {
|
||||
if (isEmpty(obj)) {
|
||||
throw new IllegalArgumentException(message != null ? message : "对象不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 类型检查方法 ========================
|
||||
|
||||
/**
|
||||
* 检查对象是否为指定类型且不为null
|
||||
*
|
||||
* @param obj 待检查的对象
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return true-对象不为null且为指定类型,false-否则
|
||||
*/
|
||||
public static <T> boolean isInstanceAndNotNull(Object obj, Class<T> clazz) {
|
||||
return isNotNull(obj) && clazz.isInstance(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的类型转换,如果对象为null或不是目标类型则返回null
|
||||
*
|
||||
* @param obj 待转换的对象
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的对象,如果转换失败则返回null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T safeCast(Object obj, Class<T> clazz) {
|
||||
if (isInstanceAndNotNull(obj, clazz)) {
|
||||
return (T) obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.BufferedReader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* @description ServletUtils.java文件描述:Servlet相关常用工具方法
|
||||
* @filename ServletUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ServletUtils {
|
||||
|
||||
/**
|
||||
* @description 获取请求的真实IP地址
|
||||
* @param request HTTP请求对象
|
||||
* @return 客户端真实IP地址
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getClientIp(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
// 多级代理时取第一个
|
||||
int idx = ip.indexOf(',');
|
||||
if (idx > -1) {
|
||||
ip = ip.substring(0, idx);
|
||||
}
|
||||
return ip.trim();
|
||||
}
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
ip = request.getRemoteAddr();
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 向响应写出JSON字符串
|
||||
* @param response HTTP响应对象
|
||||
* @param json 要写出的JSON字符串
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void writeJson(HttpServletResponse response, String json) throws IOException {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(json);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求参数(支持默认值)
|
||||
* @param request HTTP请求对象
|
||||
* @param name 参数名
|
||||
* @param defaultValue 默认值
|
||||
* @return 参数值或默认值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getParameter(HttpServletRequest request, String name, String defaultValue) {
|
||||
String value = request.getParameter(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为Ajax
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为Ajax请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isAjaxRequest(HttpServletRequest request) {
|
||||
String header = request.getHeader("X-Requested-With");
|
||||
return "XMLHttpRequest".equalsIgnoreCase(header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求体内容
|
||||
* @param request HTTP请求对象
|
||||
* @return 请求体内容
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getRequestBody(HttpServletRequest request) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = request.getReader();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重定向到指定URL
|
||||
* @param response HTTP响应对象
|
||||
* @param url 目标URL
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void redirect(HttpServletResponse response, String url) throws IOException {
|
||||
response.sendRedirect(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取完整请求URL
|
||||
* @param request HTTP请求对象
|
||||
* @return 完整URL
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getFullUrl(HttpServletRequest request) {
|
||||
StringBuilder url = new StringBuilder();
|
||||
url.append(request.getScheme()).append("://");
|
||||
url.append(request.getServerName());
|
||||
int port = request.getServerPort();
|
||||
if (("http".equals(request.getScheme()) && port != 80) ||
|
||||
("https".equals(request.getScheme()) && port != 443)) {
|
||||
url.append(":").append(port);
|
||||
}
|
||||
url.append(request.getRequestURI());
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null) {
|
||||
url.append("?").append(queryString);
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为GET方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为GET请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isGet(HttpServletRequest request) {
|
||||
return "GET".equalsIgnoreCase(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为POST方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为POST请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isPost(HttpServletRequest request) {
|
||||
return "POST".equalsIgnoreCase(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为PUT方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为PUT请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isPut(HttpServletRequest request) {
|
||||
return "PUT".equalsIgnoreCase(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为DELETE方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为DELETE请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isDelete(HttpServletRequest request) {
|
||||
return "DELETE".equalsIgnoreCase(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有请求参数
|
||||
* @param request HTTP请求对象
|
||||
* @return 参数Map
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Map<String, String> getParameterMap(HttpServletRequest request) {
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
Enumeration<String> parameterNames = request.getParameterNames();
|
||||
while (parameterNames.hasMoreElements()) {
|
||||
String paramName = parameterNames.nextElement();
|
||||
String paramValue = request.getParameter(paramName);
|
||||
paramMap.put(paramName, paramValue);
|
||||
}
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求头信息
|
||||
* @param request HTTP请求对象
|
||||
* @return 头信息Map
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Map<String, String> getHeaderMap(HttpServletRequest request) {
|
||||
Map<String, String> headerMap = new HashMap<>();
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
String headerValue = request.getHeader(headerName);
|
||||
headerMap.put(headerName, headerValue);
|
||||
}
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Cookie值
|
||||
* @param request HTTP请求对象
|
||||
* @param name Cookie名
|
||||
* @return Cookie值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCookieValue(HttpServletRequest request, String name) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (name.equals(cookie.getName())) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置Cookie
|
||||
* @param response HTTP响应对象
|
||||
* @param name Cookie名
|
||||
* @param value Cookie值
|
||||
* @param maxAge 过期时间(秒)
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void setCookie(HttpServletResponse response, String name, String value, int maxAge) {
|
||||
Cookie cookie = new Cookie(name, value);
|
||||
cookie.setMaxAge(maxAge);
|
||||
cookie.setPath("/");
|
||||
cookie.setHttpOnly(true);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除Cookie
|
||||
* @param response HTTP响应对象
|
||||
* @param name Cookie名
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void removeCookie(HttpServletResponse response, String name) {
|
||||
Cookie cookie = new Cookie(name, null);
|
||||
cookie.setMaxAge(0);
|
||||
cookie.setPath("/");
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断是否为HTTPS请求
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为HTTPS请求
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isHttps(HttpServletRequest request) {
|
||||
return "https".equals(request.getScheme()) || request.isSecure() ||
|
||||
"443".equals(request.getHeader("X-Forwarded-Port")) ||
|
||||
"https".equals(request.getHeader("X-Forwarded-Proto"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取上下文路径
|
||||
* @param request HTTP请求对象
|
||||
* @return 上下文路径
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getContextPath(HttpServletRequest request) {
|
||||
return request.getContextPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测请求是否包含指定参数
|
||||
* @param request HTTP请求对象
|
||||
* @param paramName 参数名
|
||||
* @return 是否包含参数
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean hasParameter(HttpServletRequest request, String paramName) {
|
||||
return request.getParameter(paramName) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Session对象
|
||||
* @param request HTTP请求对象
|
||||
* @return HttpSession对象
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static HttpSession getSession(HttpServletRequest request) {
|
||||
return request.getSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @return 属性值
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Object getSessionAttribute(HttpServletRequest request, String attributeName) {
|
||||
HttpSession session = request.getSession(false);
|
||||
return session != null ? session.getAttribute(attributeName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @param attributeValue 属性值
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void setSessionAttribute(HttpServletRequest request, String attributeName, Object attributeValue) {
|
||||
request.getSession().setAttribute(attributeName, attributeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 移除Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static void removeSessionAttribute(HttpServletRequest request, String attributeName) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 防止XSS攻击的字符串过滤
|
||||
* @param input 输入字符串
|
||||
* @return 过滤后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String escapeXss(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
return input
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("'", "'")
|
||||
.replace("\"", """)
|
||||
.replace("&", "&");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有请求参数名
|
||||
* @param request HTTP请求对象
|
||||
* @return 参数名集合
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Set<String> getParameterNames(HttpServletRequest request) {
|
||||
Set<String> paramNames = new HashSet<>();
|
||||
Enumeration<String> names = request.getParameterNames();
|
||||
while (names.hasMoreElements()) {
|
||||
paramNames.add(names.nextElement());
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
/**
|
||||
* @description StringUtils.java文件描述 字符串工具类
|
||||
* @filename StringUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class StringUtils {
|
||||
/**
|
||||
* @description 字符串是否为空
|
||||
* @param str String 字符串
|
||||
* @return 是否为空
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isEmpty(String str) {
|
||||
return str == null || str.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串是否不为空
|
||||
* @param str String 字符串
|
||||
* @return 是否不为空
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isNotEmpty(String str) {
|
||||
return !isEmpty(str);
|
||||
}
|
||||
|
||||
public static String format(String template, Object... args) {
|
||||
if (template == null || args == null) return template;
|
||||
return String.format(template, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 去除字符串首尾空格
|
||||
* @param str String 字符串
|
||||
* @return 去除空格后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String trim(String str) {
|
||||
return str == null ? null : str.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断两个字符串是否相等(支持null)
|
||||
* @param a String 字符串A
|
||||
* @param b String 字符串B
|
||||
* @return 是否相等
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean equals(String a, String b) {
|
||||
return a == null ? b == null : a.equals(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断字符串是否包含子串
|
||||
* @param str String 原字符串
|
||||
* @param sub String 子串
|
||||
* @return 是否包含
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean contains(String str, String sub) {
|
||||
return str != null && sub != null && str.contains(sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串拼接(用分隔符)
|
||||
* @param delimiter String 分隔符
|
||||
* @param elements String[] 待拼接字符串数组
|
||||
* @return 拼接后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String join(String delimiter, String... elements) {
|
||||
if (elements == null) return null;
|
||||
return String.join(delimiter, elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串分割
|
||||
* @param str String 原字符串
|
||||
* @param regex String 分割正则表达式
|
||||
* @return 分割后的字符串数组
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String[] split(String str, String regex) {
|
||||
return str == null ? null : str.split(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串替换
|
||||
* @param str String 原字符串
|
||||
* @param target String 替换目标
|
||||
* @param replacement String 替换内容
|
||||
* @return 替换后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String replace(String str, String target, String replacement) {
|
||||
return str == null ? null : str.replace(target, replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 是否以指定前缀开头
|
||||
* @param str String 原字符串
|
||||
* @param prefix String 前缀
|
||||
* @return 是否以前缀开头
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean startsWith(String str, String prefix) {
|
||||
return str != null && prefix != null && str.startsWith(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 是否以指定后缀结尾
|
||||
* @param str String 原字符串
|
||||
* @param suffix String 后缀
|
||||
* @return 是否以后缀结尾
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean endsWith(String str, String suffix) {
|
||||
return str != null && suffix != null && str.endsWith(suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 转为大写
|
||||
* @param str String 原字符串
|
||||
* @return 大写字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String toUpperCase(String str) {
|
||||
return str == null ? null : str.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 转为小写
|
||||
* @param str String 原字符串
|
||||
* @return 小写字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String toLowerCase(String str) {
|
||||
return str == null ? null : str.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 反转字符串
|
||||
* @param str String 原字符串
|
||||
* @return 反转后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String reverse(String str) {
|
||||
if (str == null) return null;
|
||||
return new StringBuilder(str).reverse().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重复字符串n次
|
||||
* @param str String 原字符串
|
||||
* @param n int 重复次数
|
||||
* @return 重复后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String repeat(String str, int n) {
|
||||
if (str == null || n <= 0) return "";
|
||||
return str.repeat(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 截取字符串
|
||||
* @param str String 原字符串
|
||||
* @param beginIndex int 起始索引
|
||||
* @param endIndex int 结束索引
|
||||
* @return 截取后的字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String substring(String str, int beginIndex, int endIndex) {
|
||||
if (str == null) return null;
|
||||
if (beginIndex < 0) beginIndex = 0;
|
||||
if (endIndex > str.length()) endIndex = str.length();
|
||||
if (beginIndex > endIndex) return "";
|
||||
return str.substring(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断字符串是否为数字
|
||||
* @param str String 原字符串
|
||||
* @return 是否为数字
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isNumeric(String str) {
|
||||
if (isEmpty(str)) return false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
if (!Character.isDigit(str.charAt(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串是否为空白
|
||||
* @param str String 原字符串
|
||||
* @return 是否为空白
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isBlank(String str) {
|
||||
return str == null || str.isBlank();
|
||||
}
|
||||
/**
|
||||
* @description 字符串是否不为空白
|
||||
* @param str String 原字符串
|
||||
* @return 是否不为空白
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isNotBlank(String str) {
|
||||
return !isBlank(str);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.text.ParseException;
|
||||
/**
|
||||
* @description TimeUtils.java文件描述:时间相关的常用工具方法
|
||||
* @filename TimeUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
|
||||
public class TimeUtils {
|
||||
|
||||
|
||||
/**
|
||||
* @description 将时间戳字符串转为指定格式的日期时间字符串
|
||||
* @param time 毫秒时间戳字符串
|
||||
* @param format 日期格式化对象
|
||||
* @return 格式化后的日期时间字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String timeFormat(String time, DateFormat format){
|
||||
try {
|
||||
Date date = format.parse(time);
|
||||
return format.format(date);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化Date为指定格式字符串
|
||||
* @param date Date对象
|
||||
* @param pattern 格式化模式,如"yyyy-MM-dd HH:mm:ss"
|
||||
* @return String 格式化后的字符串
|
||||
*/
|
||||
public static String format(Date date, String pattern) {
|
||||
if (date == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
LocalDateTime ldt = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
return ldt.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化LocalDate为指定格式字符串
|
||||
* @param localDate LocalDate对象
|
||||
* @param pattern 格式化模式
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public static String format(LocalDate localDate, String pattern) {
|
||||
if (localDate == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return localDate.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化LocalDateTime为指定格式字符串
|
||||
* @param localDateTime LocalDateTime对象
|
||||
* @param pattern 格式化模式
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public static String format(LocalDateTime localDateTime, String pattern) {
|
||||
if (localDateTime == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return localDateTime.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化时间戳为指定格式字符串
|
||||
* @param timestampMillis 毫秒时间戳
|
||||
* @param pattern 格式化模式
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public static String format(long timestampMillis, String pattern) {
|
||||
if (pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
LocalDateTime ldt = Instant.ofEpochMilli(timestampMillis).atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
return ldt.format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将字符串按指定格式解析为LocalDateTime
|
||||
* @param dateTimeStr 日期时间字符串
|
||||
* @param pattern 格式化模式
|
||||
* @return LocalDateTime对象,解析失败返回null
|
||||
*/
|
||||
public static LocalDateTime parseToLocalDateTime(String dateTimeStr, String pattern) {
|
||||
if (dateTimeStr == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
try {
|
||||
return LocalDateTime.parse(dateTimeStr, formatter);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将字符串按指定格式解析为LocalDate
|
||||
* @param dateStr 日期字符串
|
||||
* @param pattern 格式化模式
|
||||
* @return LocalDate对象,解析失败返回null
|
||||
*/
|
||||
public static LocalDate parseToLocalDate(String dateStr, String pattern) {
|
||||
if (dateStr == null || pattern == null) return null;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
try {
|
||||
return LocalDate.parse(dateStr, formatter);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将字符串或Date对象按指定DateTimeFormatter格式化为标准时间字符串(yyyy-MM-dd HH:mm:ss)
|
||||
* @param input 可以为String类型的日期、Date对象或LocalDateTime对象
|
||||
* @param formatter 指定的DateTimeFormatter
|
||||
* @return 标准化时间字符串,无法解析时返回null
|
||||
*/
|
||||
public static String normalizeToDateTimeString(Object input, DateTimeFormatter formatter) {
|
||||
if (input == null || formatter == null) return null;
|
||||
try {
|
||||
if (input instanceof String str) {
|
||||
LocalDateTime ldt;
|
||||
try {
|
||||
ldt = LocalDateTime.parse(str, formatter);
|
||||
} catch (Exception e) {
|
||||
LocalDate ld = LocalDate.parse(str, formatter);
|
||||
ldt = ld.atStartOfDay();
|
||||
}
|
||||
return ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
if (input instanceof Date date) {
|
||||
LocalDateTime ldt = dateToLocalDateTime(date);
|
||||
return ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
if (input instanceof LocalDateTime ldt) {
|
||||
return ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
if (input instanceof LocalDate ld) {
|
||||
return ld.atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前时间戳,单位毫秒
|
||||
* @return 当前时间戳字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentTimestamp() {
|
||||
return String.valueOf(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前时间戳,单位秒
|
||||
* @return 当前时间戳(秒)字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentTimestampSeconds() {
|
||||
return String.valueOf(System.currentTimeMillis() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前日期时间,格式:yyyy-MM-dd HH:mm:ss
|
||||
* @return 当前日期时间字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentDateTime() {
|
||||
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前日期,格式:yyyy-MM-dd
|
||||
* @return 当前日期字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentDate() {
|
||||
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前时间,格式:HH:mm:ss
|
||||
* @return 当前时间字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String getCurrentTime() {
|
||||
return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将时间戳(毫秒)转为日期时间字符串,格式:yyyy-MM-dd HH:mm:ss
|
||||
* @param timestampMillis 毫秒时间戳
|
||||
* @return 日期时间字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String timestampToDateTime(long timestampMillis) {
|
||||
return Instant.ofEpochMilli(timestampMillis)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将日期时间字符串(yyyy-MM-dd HH:mm:ss)转为时间戳(毫秒)
|
||||
* @param dateTimeStr 日期时间字符串
|
||||
* @return 毫秒时间戳
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static long dateTimeToTimestamp(String dateTimeStr) {
|
||||
LocalDateTime ldt = LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取指定日期加减天数后的日期字符串(yyyy-MM-dd)
|
||||
* @param dateStr 原始日期字符串
|
||||
* @param days 增加或减少的天数(可为负数)
|
||||
* @return 计算后的日期字符串
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static String plusDays(String dateStr, int days) {
|
||||
LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
return date.plusDays(days).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取两个日期之间的天数差
|
||||
* @param startDate 开始日期字符串(yyyy-MM-dd)
|
||||
* @param endDate 结束日期字符串(yyyy-MM-dd)
|
||||
* @return 天数差
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static long daysBetween(String startDate, String endDate) {
|
||||
LocalDate start = LocalDate.parse(startDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
LocalDate end = LocalDate.parse(endDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
return Duration.between(start.atStartOfDay(), end.atStartOfDay()).toDays();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断当前时间是否在指定时间段内
|
||||
* @param startTime 开始时间字符串(HH:mm:ss)
|
||||
* @param endTime 结束时间字符串(HH:mm:ss)
|
||||
* @return 是否在时间段内
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static boolean isNowBetween(String startTime, String endTime) {
|
||||
LocalTime now = LocalTime.now();
|
||||
LocalTime start = LocalTime.parse(startTime, DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
LocalTime end = LocalTime.parse(endTime, DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
return !now.isBefore(start) && !now.isAfter(end);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Date转LocalDateTime
|
||||
* @param date java.util.Date对象
|
||||
* @return LocalDateTime对象
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static LocalDateTime dateToLocalDateTime(Date date) {
|
||||
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description LocalDateTime转Date
|
||||
* @param localDateTime LocalDateTime对象
|
||||
* @return java.util.Date对象
|
||||
* @author yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public static Date localDateTimeToDate(LocalDateTime localDateTime) {
|
||||
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package org.xyzh.common.utils.excel;
|
||||
|
||||
import org.xyzh.common.utils.validation.ValidationParam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description Excel列映射配置
|
||||
* @filename ExcelColumnMapping.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ExcelColumnMapping {
|
||||
|
||||
/**
|
||||
* @description Excel列名(表头名称)
|
||||
*/
|
||||
private String columnName;
|
||||
|
||||
/**
|
||||
* @description Excel列索引(从0开始,优先级高于列名)
|
||||
*/
|
||||
private Integer columnIndex;
|
||||
|
||||
/**
|
||||
* @description 对象字段名
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* @description 字段类型
|
||||
*/
|
||||
private Class<?> fieldType;
|
||||
|
||||
/**
|
||||
* @description 是否必填
|
||||
*/
|
||||
private boolean required = false;
|
||||
|
||||
/**
|
||||
* @description 默认值
|
||||
*/
|
||||
private String defaultValue;
|
||||
|
||||
/**
|
||||
* @description 日期格式(当字段类型为Date时使用)
|
||||
*/
|
||||
private String dateFormat = "yyyy-MM-dd";
|
||||
|
||||
/**
|
||||
* @description 校验参数列表
|
||||
*/
|
||||
private List<ValidationParam> validationParams;
|
||||
|
||||
private ExcelColumnMapping() {
|
||||
this.validationParams = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
public Integer getColumnIndex() {
|
||||
return columnIndex;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public Class<?> getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public String getDateFormat() {
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
public List<ValidationParam> getValidationParams() {
|
||||
return validationParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builder类
|
||||
*/
|
||||
public static class Builder {
|
||||
private ExcelColumnMapping mapping = new ExcelColumnMapping();
|
||||
|
||||
/**
|
||||
* 设置Excel列名
|
||||
*/
|
||||
public Builder columnName(String columnName) {
|
||||
mapping.columnName = columnName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Excel列索引(从0开始)
|
||||
*/
|
||||
public Builder columnIndex(int columnIndex) {
|
||||
mapping.columnIndex = columnIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对象字段名
|
||||
*/
|
||||
public Builder fieldName(String fieldName) {
|
||||
mapping.fieldName = fieldName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段类型
|
||||
*/
|
||||
public Builder fieldType(Class<?> fieldType) {
|
||||
mapping.fieldType = fieldType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否必填
|
||||
*/
|
||||
public Builder required(boolean required) {
|
||||
mapping.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置为必填
|
||||
*/
|
||||
public Builder required() {
|
||||
mapping.required = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认值
|
||||
*/
|
||||
public Builder defaultValue(String defaultValue) {
|
||||
mapping.defaultValue = defaultValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置日期格式
|
||||
*/
|
||||
public Builder dateFormat(String dateFormat) {
|
||||
mapping.dateFormat = dateFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加校验参数
|
||||
*/
|
||||
public Builder addValidation(ValidationParam param) {
|
||||
mapping.validationParams.add(param);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置校验参数列表
|
||||
*/
|
||||
public Builder validations(List<ValidationParam> params) {
|
||||
mapping.validationParams = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExcelColumnMapping build() {
|
||||
if (mapping.fieldName == null || mapping.fieldName.isEmpty()) {
|
||||
throw new IllegalArgumentException("字段名不能为空");
|
||||
}
|
||||
if (mapping.columnName == null && mapping.columnIndex == null) {
|
||||
throw new IllegalArgumentException("必须指定列名或列索引");
|
||||
}
|
||||
if (mapping.fieldType == null) {
|
||||
mapping.fieldType = String.class;
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExcelColumnMapping{" +
|
||||
"columnName='" + columnName + '\'' +
|
||||
", columnIndex=" + columnIndex +
|
||||
", fieldName='" + fieldName + '\'' +
|
||||
", fieldType=" + (fieldType != null ? fieldType.getSimpleName() : "null") +
|
||||
", required=" + required +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.xyzh.common.utils.excel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description Excel读取结果
|
||||
* @filename ExcelReadResult.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ExcelReadResult<T> {
|
||||
|
||||
/**
|
||||
* @description 是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* @description 成功读取的数据列表
|
||||
*/
|
||||
private List<T> dataList;
|
||||
|
||||
/**
|
||||
* @description 失败的行数据(行号 -> 错误信息)
|
||||
*/
|
||||
private Map<Integer, String> errorRowsMap;
|
||||
|
||||
/**
|
||||
* @description 总行数(不包括表头)
|
||||
*/
|
||||
private int totalRows;
|
||||
|
||||
/**
|
||||
* @description 成功行数
|
||||
*/
|
||||
private int successRows;
|
||||
|
||||
/**
|
||||
* @description 失败行数
|
||||
*/
|
||||
private int errorRowsCount;
|
||||
|
||||
/**
|
||||
* @description 错误信息
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
public ExcelReadResult() {
|
||||
this.success = true;
|
||||
this.dataList = new ArrayList<>();
|
||||
this.errorRowsMap = new HashMap<>();
|
||||
this.totalRows = 0;
|
||||
this.successRows = 0;
|
||||
this.errorRowsCount = 0;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public List<T> getDataList() {
|
||||
return dataList;
|
||||
}
|
||||
|
||||
public void setDataList(List<T> dataList) {
|
||||
this.dataList = dataList;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getErrorRows() {
|
||||
return errorRowsMap;
|
||||
}
|
||||
|
||||
public void setErrorRows(Map<Integer, String> errorRowsMap) {
|
||||
this.errorRowsMap = errorRowsMap;
|
||||
}
|
||||
|
||||
public int getTotalRows() {
|
||||
return totalRows;
|
||||
}
|
||||
|
||||
public void setTotalRows(int totalRows) {
|
||||
this.totalRows = totalRows;
|
||||
}
|
||||
|
||||
public int getSuccessRows() {
|
||||
return successRows;
|
||||
}
|
||||
|
||||
public void setSuccessRows(int successRows) {
|
||||
this.successRows = successRows;
|
||||
}
|
||||
|
||||
public int getErrorRowsCount() {
|
||||
return errorRowsCount;
|
||||
}
|
||||
|
||||
public void setErrorRowsCount(int errorRowsCount) {
|
||||
this.errorRowsCount = errorRowsCount;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public void addData(T data) {
|
||||
this.dataList.add(data);
|
||||
this.successRows++;
|
||||
}
|
||||
|
||||
public void addError(int rowNum, String error) {
|
||||
this.errorRowsMap.put(rowNum, error);
|
||||
this.errorRowsCount++;
|
||||
}
|
||||
|
||||
public boolean hasErrors() {
|
||||
return this.errorRowsCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExcelReadResult{" +
|
||||
"success=" + success +
|
||||
", totalRows=" + totalRows +
|
||||
", successRows=" + successRows +
|
||||
", errorRows=" + errorRowsCount +
|
||||
", errorMessage='" + errorMessage + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
package org.xyzh.common.utils.excel;
|
||||
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.xyzh.common.utils.validation.ValidationParam;
|
||||
import org.xyzh.common.utils.validation.ValidationResult;
|
||||
import org.xyzh.common.utils.validation.ValidationUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @description Excel读取工具类(非泛型版本)
|
||||
* @filename ExcelReaderUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ExcelReaderUtils {
|
||||
|
||||
/**
|
||||
* @description 从文件读取Excel
|
||||
* @param file Excel文件
|
||||
* @param targetClass 目标对象Class
|
||||
* @param columnMappings Excel列映射配置列表
|
||||
* @return ExcelReadResult
|
||||
*/
|
||||
public static ExcelReadResult<Object> readExcel(File file, Class<?> targetClass, List<ExcelColumnMapping> columnMappings) {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
return readExcel(fis, file.getName(), targetClass, columnMappings, new HashMap<>());
|
||||
} catch (Exception e) {
|
||||
ExcelReadResult<Object> result = new ExcelReadResult<>();
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage("读取Excel文件失败: " + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从文件读取Excel(带配置)
|
||||
* @param file Excel文件
|
||||
* @param targetClass 目标对象Class
|
||||
* @param columnMappings Excel列映射配置列表
|
||||
* @param options 配置选项
|
||||
* @return ExcelReadResult
|
||||
*/
|
||||
public static ExcelReadResult<Object> readExcel(File file, Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings,
|
||||
Map<String, Object> options) {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
return readExcel(fis, file.getName(), targetClass, columnMappings, options);
|
||||
} catch (Exception e) {
|
||||
ExcelReadResult<Object> result = new ExcelReadResult<>();
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage("读取Excel文件失败: " + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从输入流读取Excel
|
||||
* @param inputStream 输入流
|
||||
* @param fileName 文件名
|
||||
* @param targetClass 目标对象Class
|
||||
* @param columnMappings Excel列映射配置列表
|
||||
* @return ExcelReadResult
|
||||
*/
|
||||
public static ExcelReadResult<Object> readExcel(InputStream inputStream, String fileName,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings) {
|
||||
return readExcel(inputStream, fileName, targetClass, columnMappings, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从输入流读取Excel(带配置)
|
||||
* @param inputStream 输入流
|
||||
* @param fileName 文件名
|
||||
* @param targetClass 目标对象Class
|
||||
* @param columnMappings Excel列映射配置列表
|
||||
* @param options 配置选项(headerRowIndex, dataStartRowIndex, sheetIndex, sheetName, skipEmptyRow, maxRows, continueOnError)
|
||||
* @return ExcelReadResult
|
||||
*/
|
||||
public static ExcelReadResult<Object> readExcel(InputStream inputStream, String fileName,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings,
|
||||
Map<String, Object> options) {
|
||||
ExcelReadResult<Object> result = new ExcelReadResult<>();
|
||||
|
||||
try {
|
||||
// 获取配置
|
||||
int headerRowIndex = (int) options.getOrDefault("headerRowIndex", 0);
|
||||
int dataStartRowIndex = (int) options.getOrDefault("dataStartRowIndex", 1);
|
||||
int sheetIndex = (int) options.getOrDefault("sheetIndex", 0);
|
||||
String sheetName = (String) options.get("sheetName");
|
||||
boolean skipEmptyRow = (boolean) options.getOrDefault("skipEmptyRow", true);
|
||||
int maxRows = (int) options.getOrDefault("maxRows", 0);
|
||||
boolean continueOnError = (boolean) options.getOrDefault("continueOnError", true);
|
||||
|
||||
// 创建Workbook
|
||||
Workbook workbook = createWorkbook(inputStream, fileName);
|
||||
|
||||
// 获取Sheet
|
||||
Sheet sheet = getSheet(workbook, sheetIndex, sheetName);
|
||||
if (sheet == null) {
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage("未找到指定的Sheet");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 解析表头
|
||||
Map<String, Integer> headerMap = parseHeader(sheet, headerRowIndex);
|
||||
|
||||
// 读取数据
|
||||
readData(sheet, headerMap, targetClass, columnMappings, dataStartRowIndex,
|
||||
skipEmptyRow, maxRows, continueOnError, result);
|
||||
|
||||
workbook.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage("读取Excel失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建Workbook对象
|
||||
*/
|
||||
private static Workbook createWorkbook(InputStream inputStream, String fileName) throws Exception {
|
||||
if (fileName.endsWith(".xlsx")) {
|
||||
return new XSSFWorkbook(inputStream);
|
||||
} else if (fileName.endsWith(".xls")) {
|
||||
return new HSSFWorkbook(inputStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的文件格式,仅支持.xls和.xlsx");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Sheet
|
||||
*/
|
||||
private static Sheet getSheet(Workbook workbook, int sheetIndex, String sheetName) {
|
||||
if (sheetName != null && !sheetName.isEmpty()) {
|
||||
return workbook.getSheet(sheetName);
|
||||
} else {
|
||||
return workbook.getSheetAt(sheetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 解析表头
|
||||
*/
|
||||
private static Map<String, Integer> parseHeader(Sheet sheet, int headerRowIndex) {
|
||||
Map<String, Integer> headerMap = new HashMap<>();
|
||||
Row headerRow = sheet.getRow(headerRowIndex);
|
||||
|
||||
if (headerRow != null) {
|
||||
for (Cell cell : headerRow) {
|
||||
String headerName = getCellValue(cell).toString().trim();
|
||||
if (!headerName.isEmpty()) {
|
||||
headerMap.put(headerName, cell.getColumnIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 读取数据
|
||||
*/
|
||||
private static void readData(Sheet sheet, Map<String, Integer> headerMap,
|
||||
Class<?> targetClass, List<ExcelColumnMapping> columnMappings,
|
||||
int startRow, boolean skipEmptyRow, int maxRows,
|
||||
boolean continueOnError, ExcelReadResult<Object> result) {
|
||||
int lastRowNum = sheet.getLastRowNum();
|
||||
int endRow = maxRows > 0 ? Math.min(startRow + maxRows, lastRowNum + 1) : lastRowNum + 1;
|
||||
|
||||
for (int rowNum = startRow; rowNum < endRow; rowNum++) {
|
||||
Row row = sheet.getRow(rowNum);
|
||||
|
||||
// 跳过空行
|
||||
if (skipEmptyRow && isEmptyRow(row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.setTotalRows(result.getTotalRows() + 1);
|
||||
|
||||
try {
|
||||
// 将行数据转换为对象
|
||||
Object data = convertRowToObject(row, headerMap, targetClass, columnMappings, rowNum + 1);
|
||||
|
||||
// 数据校验
|
||||
List<ValidationParam> allValidations = new ArrayList<>();
|
||||
for (ExcelColumnMapping mapping : columnMappings) {
|
||||
if (!mapping.getValidationParams().isEmpty()) {
|
||||
allValidations.addAll(mapping.getValidationParams());
|
||||
}
|
||||
}
|
||||
|
||||
if (!allValidations.isEmpty()) {
|
||||
ValidationResult validationResult = ValidationUtils.validate(data, allValidations);
|
||||
if (!validationResult.isValid()) {
|
||||
result.addError(rowNum + 1, validationResult.getFirstError());
|
||||
if (!continueOnError) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result.addData(data);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.addError(rowNum + 1, e.getMessage());
|
||||
if (!continueOnError) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断是否为空行
|
||||
*/
|
||||
private static boolean isEmptyRow(Row row) {
|
||||
if (row == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Cell cell : row) {
|
||||
if (cell != null && cell.getCellType() != CellType.BLANK) {
|
||||
String value = getCellValue(cell).toString().trim();
|
||||
if (!value.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将行数据转换为对象
|
||||
*/
|
||||
private static Object convertRowToObject(Row row, Map<String, Integer> headerMap,
|
||||
Class<?> targetClass, List<ExcelColumnMapping> columnMappings,
|
||||
int rowNum) throws Exception {
|
||||
Object obj = targetClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
for (ExcelColumnMapping mapping : columnMappings) {
|
||||
// 获取单元格
|
||||
Cell cell = getCell(row, mapping, headerMap);
|
||||
|
||||
// 获取单元格值
|
||||
Object cellValue = getCellValue(cell);
|
||||
|
||||
// 处理空值
|
||||
if (cellValue == null || cellValue.toString().trim().isEmpty()) {
|
||||
if (mapping.isRequired()) {
|
||||
throw new IllegalArgumentException("第" + rowNum + "行,字段[" +
|
||||
(mapping.getColumnName() != null ? mapping.getColumnName() : "索引" + mapping.getColumnIndex())
|
||||
+ "]不能为空");
|
||||
}
|
||||
// 使用默认值
|
||||
if (mapping.getDefaultValue() != null && !mapping.getDefaultValue().isEmpty()) {
|
||||
cellValue = mapping.getDefaultValue();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 类型转换
|
||||
Object fieldValue = convertValue(cellValue, mapping.getFieldType(), mapping.getDateFormat());
|
||||
|
||||
// 设置字段值
|
||||
Field field = getField(targetClass, mapping.getFieldName());
|
||||
field.setAccessible(true);
|
||||
field.set(obj, fieldValue);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取字段(支持父类)
|
||||
*/
|
||||
private static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
|
||||
try {
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
// 尝试从父类获取
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass != null) {
|
||||
return getField(superClass, fieldName);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取单元格
|
||||
*/
|
||||
private static Cell getCell(Row row, ExcelColumnMapping mapping, Map<String, Integer> headerMap) {
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 优先使用索引
|
||||
if (mapping.getColumnIndex() != null) {
|
||||
return row.getCell(mapping.getColumnIndex());
|
||||
}
|
||||
|
||||
// 使用列名
|
||||
Integer columnIndex = headerMap.get(mapping.getColumnName());
|
||||
if (columnIndex != null) {
|
||||
return row.getCell(columnIndex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取单元格值
|
||||
*/
|
||||
private static Object getCellValue(Cell cell) {
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue();
|
||||
} else {
|
||||
return cell.getNumericCellValue();
|
||||
}
|
||||
case BOOLEAN:
|
||||
return cell.getBooleanCellValue();
|
||||
case FORMULA:
|
||||
return cell.getCellFormula();
|
||||
case BLANK:
|
||||
return "";
|
||||
default:
|
||||
return cell.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 类型转换
|
||||
*/
|
||||
private static Object convertValue(Object value, Class<?> targetType, String dateFormat) throws Exception {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String strValue = value.toString().trim();
|
||||
|
||||
// String类型
|
||||
if (targetType == String.class) {
|
||||
return strValue;
|
||||
}
|
||||
|
||||
// Integer类型
|
||||
if (targetType == Integer.class || targetType == int.class) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
return Integer.parseInt(strValue);
|
||||
}
|
||||
|
||||
// Long类型
|
||||
if (targetType == Long.class || targetType == long.class) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
return Long.parseLong(strValue);
|
||||
}
|
||||
|
||||
// Double类型
|
||||
if (targetType == Double.class || targetType == double.class) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
return Double.parseDouble(strValue);
|
||||
}
|
||||
|
||||
// Float类型
|
||||
if (targetType == Float.class || targetType == float.class) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).floatValue();
|
||||
}
|
||||
return Float.parseFloat(strValue);
|
||||
}
|
||||
|
||||
// Boolean类型
|
||||
if (targetType == Boolean.class || targetType == boolean.class) {
|
||||
if (value instanceof Boolean) {
|
||||
return value;
|
||||
}
|
||||
return Boolean.parseBoolean(strValue) || "1".equals(strValue) ||
|
||||
"是".equals(strValue) || "true".equalsIgnoreCase(strValue);
|
||||
}
|
||||
|
||||
// Date类型
|
||||
if (targetType == Date.class) {
|
||||
if (value instanceof Date) {
|
||||
return value;
|
||||
}
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
|
||||
return sdf.parse(strValue);
|
||||
}
|
||||
|
||||
// 其他类型
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
package org.xyzh.common.utils.excel;
|
||||
|
||||
import org.xyzh.common.utils.validation.ValidationParam;
|
||||
import org.xyzh.common.utils.validation.method.ValidateMethodType;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @description Excel工具使用示例
|
||||
* @filename ExcelUtilsExample.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ExcelUtilsExample {
|
||||
|
||||
/**
|
||||
* 示例实体类:用户信息
|
||||
*/
|
||||
public static class UserInfo {
|
||||
private String name;
|
||||
private Integer age;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String idCard;
|
||||
private Date joinDate;
|
||||
private Boolean active;
|
||||
|
||||
// Getters and Setters
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public Integer getAge() { return age; }
|
||||
public void setAge(Integer age) { this.age = age; }
|
||||
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
|
||||
public String getIdCard() { return idCard; }
|
||||
public void setIdCard(String idCard) { this.idCard = idCard; }
|
||||
|
||||
public Date getJoinDate() { return joinDate; }
|
||||
public void setJoinDate(Date joinDate) { this.joinDate = joinDate; }
|
||||
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserInfo{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
", phone='" + phone + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", idCard='" + idCard + '\'' +
|
||||
", joinDate=" + joinDate +
|
||||
", active=" + active +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例1:基本使用
|
||||
*/
|
||||
public static void example1_BasicUsage() {
|
||||
System.out.println("========== 示例1: 基本使用 ==========");
|
||||
|
||||
// 1. 定义列映射关系
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.validations(Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build()
|
||||
))
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("入职日期")
|
||||
.fieldName("joinDate")
|
||||
.fieldType(Date.class)
|
||||
.dateFormat("yyyy-MM-dd")
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("是否在职")
|
||||
.fieldName("active")
|
||||
.fieldType(Boolean.class)
|
||||
.defaultValue("true")
|
||||
.build()
|
||||
);
|
||||
|
||||
// 2. 读取Excel
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
// 3. 处理结果
|
||||
if (result.isSuccess()) {
|
||||
System.out.println("读取成功!");
|
||||
System.out.println("总行数: " + result.getTotalRows());
|
||||
System.out.println("成功行数: " + result.getSuccessRows());
|
||||
|
||||
for (Object obj : result.getDataList()) {
|
||||
UserInfo user = (UserInfo) obj;
|
||||
System.out.println(user);
|
||||
}
|
||||
} else {
|
||||
System.out.println("读取失败: " + result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2:带数据校验
|
||||
*/
|
||||
public static void example2_WithValidation() {
|
||||
System.out.println("\n========== 示例2: 带数据校验 ==========");
|
||||
|
||||
// 定义列映射关系(带校验)
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.minLength(2)
|
||||
.maxLength(20)
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("age")
|
||||
.fieldLabel("年龄")
|
||||
.required()
|
||||
.customValidator(value -> {
|
||||
Integer age = (Integer) value;
|
||||
return age >= 18 && age <= 65;
|
||||
})
|
||||
.customErrorMessage("年龄必须在18-65岁之间")
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required(false)
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("身份证号")
|
||||
.fieldName("idCard")
|
||||
.fieldType(String.class)
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("idCard")
|
||||
.fieldLabel("身份证号")
|
||||
.required(false)
|
||||
.validateMethod(ValidateMethodType.ID_CARD)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 读取配置
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("continueOnError", true); // 遇到错误继续读取
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings,
|
||||
options
|
||||
);
|
||||
|
||||
// 处理结果
|
||||
System.out.println("读取完成!");
|
||||
System.out.println("总行数: " + result.getTotalRows());
|
||||
System.out.println("成功行数: " + result.getSuccessRows());
|
||||
System.out.println("失败行数: " + result.getErrorRowsCount());
|
||||
|
||||
// 显示错误信息
|
||||
if (result.hasErrors()) {
|
||||
System.out.println("\n错误信息:");
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
System.out.println("第" + rowNum + "行: " + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3:使用列索引
|
||||
*/
|
||||
public static void example3_UseColumnIndex() {
|
||||
System.out.println("\n========== 示例3: 使用列索引 ==========");
|
||||
|
||||
// 使用列索引而非列名
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(0) // 第1列
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(1) // 第2列
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(2) // 第3列
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build()
|
||||
);
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
System.out.println("成功读取: " + result.getSuccessRows() + " 行");
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4:自定义配置
|
||||
*/
|
||||
public static void example4_CustomConfig() {
|
||||
System.out.println("\n========== 示例4: 自定义配置 ==========");
|
||||
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build()
|
||||
);
|
||||
|
||||
// 自定义配置
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("sheetName", "员工信息"); // 指定Sheet名称
|
||||
options.put("headerRowIndex", 0); // 表头在第1行
|
||||
options.put("dataStartRowIndex", 1); // 数据从第2行开始
|
||||
options.put("skipEmptyRow", true); // 跳过空行
|
||||
options.put("maxRows", 1000); // 最多读取1000行
|
||||
options.put("continueOnError", true); // 遇到错误继续
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings,
|
||||
options
|
||||
);
|
||||
|
||||
System.out.println("读取结果: " + result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例5:在Controller中使用
|
||||
*/
|
||||
public static void example5_InController() {
|
||||
System.out.println("\n========== 示例5: 在Controller中使用(代码示例)==========");
|
||||
System.out.println("""
|
||||
@PostMapping("/import")
|
||||
public ResultDomain<String> importUsers(@RequestParam("file") MultipartFile file) {
|
||||
try {
|
||||
// 1. 定义列映射关系
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 2. 读取Excel
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file.getInputStream(),
|
||||
file.getOriginalFilename(),
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
// 3. 处理结果
|
||||
if (result.hasErrors()) {
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
errorMsg.append("导入失败,共").append(result.getErrorRowsCount()).append("行数据有误:\\n");
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
errorMsg.append("第").append(rowNum).append("行: ").append(error).append("\\n");
|
||||
});
|
||||
return ResultDomain.fail(errorMsg.toString());
|
||||
}
|
||||
|
||||
// 4. 保存数据
|
||||
List<UserInfo> users = new ArrayList<>();
|
||||
for (Object obj : result.getDataList()) {
|
||||
users.add((UserInfo) obj);
|
||||
}
|
||||
userService.batchSave(users);
|
||||
|
||||
return ResultDomain.success("导入成功,共导入" + result.getSuccessRows() + "条数据");
|
||||
|
||||
} catch (Exception e) {
|
||||
return ResultDomain.fail("导入失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 运行示例
|
||||
// example1_BasicUsage();
|
||||
// example2_WithValidation();
|
||||
// example3_UseColumnIndex();
|
||||
// example4_CustomConfig();
|
||||
example5_InController();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,542 @@
|
||||
# Excel读取工具使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
这是一个功能强大的Excel读取工具,通过配置 **Excel列对象** 来定义Excel列与Java对象字段的映射关系,支持自动构建对象并集成数据校验功能。
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. ExcelColumnMapping - Excel列映射配置
|
||||
定义Excel列与对象字段的映射关系:
|
||||
- **columnName** - Excel列名(表头名称)
|
||||
- **columnIndex** - Excel列索引(从0开始,优先级高于列名)
|
||||
- **fieldName** - 对象字段名
|
||||
- **fieldType** - 字段类型
|
||||
- **required** - 是否必填
|
||||
- **defaultValue** - 默认值
|
||||
- **dateFormat** - 日期格式
|
||||
- **validationParams** - 校验参数列表
|
||||
|
||||
### 2. ExcelReaderUtils - Excel读取工具类
|
||||
提供静态方法读取Excel:
|
||||
- `readExcel(File, Class, List<ExcelColumnMapping>)` - 从文件读取
|
||||
- `readExcel(File, Class, List<ExcelColumnMapping>, Map<options>)` - 从文件读取(带配置)
|
||||
- `readExcel(InputStream, fileName, Class, List<ExcelColumnMapping>)` - 从输入流读取
|
||||
- `readExcel(InputStream, fileName, Class, List<ExcelColumnMapping>, Map<options>)` - 从输入流读取(带配置)
|
||||
|
||||
### 3. ExcelReadResult - 读取结果
|
||||
包含读取结果的详细信息:
|
||||
- 成功数据列表
|
||||
- 错误行信息(行号 -> 错误消息)
|
||||
- 统计信息(总行数、成功数、失败数)
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义实体类
|
||||
|
||||
```java
|
||||
public class UserInfo {
|
||||
private String name;
|
||||
private Integer age;
|
||||
private String phone;
|
||||
private String email;
|
||||
private Date joinDate;
|
||||
private Boolean active;
|
||||
|
||||
// Getters and Setters...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 定义列映射关系
|
||||
|
||||
```java
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名") // Excel列名
|
||||
.fieldName("name") // 对象字段名
|
||||
.fieldType(String.class) // 字段类型
|
||||
.required() // 必填
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("入职日期")
|
||||
.fieldName("joinDate")
|
||||
.fieldType(Date.class)
|
||||
.dateFormat("yyyy-MM-dd")
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("是否在职")
|
||||
.fieldName("active")
|
||||
.fieldType(Boolean.class)
|
||||
.defaultValue("true")
|
||||
.build()
|
||||
);
|
||||
```
|
||||
|
||||
### 3. 读取Excel
|
||||
|
||||
```java
|
||||
// 读取文件
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
// 处理结果
|
||||
if (result.isSuccess()) {
|
||||
for (Object obj : result.getDataList()) {
|
||||
UserInfo user = (UserInfo) obj;
|
||||
System.out.println(user);
|
||||
}
|
||||
System.out.println("成功读取: " + result.getSuccessRows() + " 行");
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 带数据校验的读取
|
||||
|
||||
```java
|
||||
// 定义列映射关系(带校验)
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.minLength(2)
|
||||
.maxLength(20)
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("年龄")
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("age")
|
||||
.fieldLabel("年龄")
|
||||
.required()
|
||||
.customValidator(value -> {
|
||||
Integer age = (Integer) value;
|
||||
return age >= 18 && age <= 65;
|
||||
})
|
||||
.customErrorMessage("年龄必须在18-65岁之间")
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required(false)
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 配置选项
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("continueOnError", true); // 遇到错误继续读取
|
||||
|
||||
// 读取Excel
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings,
|
||||
options
|
||||
);
|
||||
|
||||
// 处理结果
|
||||
System.out.println("总行数: " + result.getTotalRows());
|
||||
System.out.println("成功: " + result.getSuccessRows());
|
||||
System.out.println("失败: " + result.getErrorRowsCount());
|
||||
|
||||
// 显示错误
|
||||
if (result.hasErrors()) {
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
System.out.println("第" + rowNum + "行: " + error);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 使用列索引而非列名
|
||||
|
||||
```java
|
||||
// 使用列索引(从0开始)
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(0) // 第1列
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(1) // 第2列
|
||||
.fieldName("age")
|
||||
.fieldType(Integer.class)
|
||||
.required()
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnIndex(2) // 第3列
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.build()
|
||||
);
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
```
|
||||
|
||||
### 6. 自定义配置选项
|
||||
|
||||
以下是旧版本的校验代码,现在已经整合到ExcelColumnMapping中:
|
||||
|
||||
```java
|
||||
// 旧版本(已弃用)
|
||||
List<ValidationParam> validationParams = Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.minLength(2)
|
||||
.maxLength(20)
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required(false)
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("age")
|
||||
.fieldLabel("年龄")
|
||||
.required()
|
||||
.customValidator(value -> {
|
||||
Integer age = (Integer) value;
|
||||
return age >= 18 && age <= 65;
|
||||
})
|
||||
.customErrorMessage("年龄必须在18-65岁之间")
|
||||
.build()
|
||||
);
|
||||
|
||||
// 创建配置
|
||||
ExcelReaderConfig<UserInfo> config = new ExcelReaderConfig<>(UserInfo.class)
|
||||
.setValidationParams(validationParams)
|
||||
.setContinueOnError(true); // 遇到错误继续读取
|
||||
|
||||
// 读取
|
||||
ExcelReader<UserInfo> reader = ExcelReader.create(config);
|
||||
ExcelReadResult<UserInfo> result = reader.read(file);
|
||||
|
||||
// 处理结果
|
||||
System.out.println("总行数: " + result.getTotalRows());
|
||||
System.out.println("成功: " + result.getSuccessRows());
|
||||
System.out.println("失败: " + result.getErrorRowsCount());
|
||||
|
||||
// 显示错误
|
||||
if (result.hasErrors()) {
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
System.out.println("第" + rowNum + "行: " + error);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
// 列映射配置...
|
||||
);
|
||||
|
||||
// 自定义配置选项
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("sheetName", "员工信息"); // 指定Sheet名称
|
||||
options.put("headerRowIndex", 0); // 表头在第1行
|
||||
options.put("dataStartRowIndex", 1); // 数据从第2行开始
|
||||
options.put("skipEmptyRow", true); // 跳过空行
|
||||
options.put("maxRows", 1000); // 最多读取1000行
|
||||
options.put("continueOnError", true); // 遇到错误继续
|
||||
|
||||
File file = new File("users.xlsx");
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file,
|
||||
UserInfo.class,
|
||||
columnMappings,
|
||||
options
|
||||
);
|
||||
```
|
||||
|
||||
## 在Controller中使用
|
||||
|
||||
```java
|
||||
@PostMapping("/import")
|
||||
public ResultDomain<String> importUsers(@RequestParam("file") MultipartFile file) {
|
||||
try {
|
||||
// 1. 定义列映射关系(带校验)
|
||||
List<ExcelColumnMapping> columnMappings = Arrays.asList(
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("姓名")
|
||||
.fieldName("name")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("name")
|
||||
.fieldLabel("姓名")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.CHINESE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("手机号")
|
||||
.fieldName("phone")
|
||||
.fieldType(String.class)
|
||||
.required()
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE)
|
||||
.build()
|
||||
)
|
||||
.build(),
|
||||
|
||||
ExcelColumnMapping.builder()
|
||||
.columnName("邮箱")
|
||||
.fieldName("email")
|
||||
.fieldType(String.class)
|
||||
.addValidation(
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.validateMethod(ValidateMethodType.EMAIL)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 2. 读取Excel
|
||||
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
|
||||
file.getInputStream(),
|
||||
file.getOriginalFilename(),
|
||||
UserInfo.class,
|
||||
columnMappings
|
||||
);
|
||||
|
||||
// 3. 处理结果
|
||||
if (result.hasErrors()) {
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
errorMsg.append("导入失败,共").append(result.getErrorRowsCount()).append("行数据有误:\n");
|
||||
result.getErrorRows().forEach((rowNum, error) -> {
|
||||
errorMsg.append("第").append(rowNum).append("行: ").append(error).append("\n");
|
||||
});
|
||||
return ResultDomain.fail(errorMsg.toString());
|
||||
}
|
||||
|
||||
// 4. 保存数据
|
||||
List<UserInfo> users = new ArrayList<>();
|
||||
for (Object obj : result.getDataList()) {
|
||||
users.add((UserInfo) obj);
|
||||
}
|
||||
userService.batchSave(users);
|
||||
|
||||
return ResultDomain.success("导入成功,共导入" + result.getSuccessRows() + "条数据");
|
||||
|
||||
} catch (Exception e) {
|
||||
return ResultDomain.fail("导入失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 支持的数据类型
|
||||
|
||||
- **String** - 字符串
|
||||
- **Integer/int** - 整数
|
||||
- **Long/long** - 长整数
|
||||
- **Double/double** - 双精度浮点数
|
||||
- **Float/float** - 单精度浮点数
|
||||
- **Boolean/boolean** - 布尔值(支持:true/false、1/0、是/否)
|
||||
- **Date** - 日期(需指定dateFormat)
|
||||
|
||||
## 配置选项
|
||||
|
||||
### ExcelColumnMapping Builder方法
|
||||
|
||||
| 方法 | 说明 | 必填 |
|
||||
|------|------|------|
|
||||
| columnName(String) | 设置Excel列名 | columnName和columnIndex至少一个 |
|
||||
| columnIndex(int) | 设置Excel列索引(从0开始,优先级高于columnName) | columnName和columnIndex至少一个 |
|
||||
| fieldName(String) | 设置对象字段名 | 是 |
|
||||
| fieldType(Class<?>) | 设置字段类型 | 否(默认String.class) |
|
||||
| required() / required(boolean) | 设置是否必填 | 否(默认false) |
|
||||
| defaultValue(String) | 设置默认值 | 否 |
|
||||
| dateFormat(String) | 设置日期格式 | 否(默认"yyyy-MM-dd") |
|
||||
| addValidation(ValidationParam) | 添加校验参数 | 否 |
|
||||
| validations(List<ValidationParam>) | 设置校验参数列表 | 否 |
|
||||
|
||||
### Options配置项
|
||||
|
||||
传递给 `readExcel` 方法的 `Map<String, Object> options` 支持以下选项:
|
||||
|
||||
| 键名 | 类型 | 说明 | 默认值 |
|
||||
|------|------|------|--------|
|
||||
| headerRowIndex | int | 表头所在行(从0开始) | 0 |
|
||||
| dataStartRowIndex | int | 数据起始行(从0开始) | 1 |
|
||||
| sheetIndex | int | Sheet索引(从0开始) | 0 |
|
||||
| sheetName | String | Sheet名称(优先级高于sheetIndex) | null |
|
||||
| skipEmptyRow | boolean | 跳过空行 | true |
|
||||
| maxRows | int | 最大读取行数(0=不限制) | 0 |
|
||||
| continueOnError | boolean | 遇到错误继续读取 | true |
|
||||
|
||||
## 核心特性
|
||||
|
||||
1. **配置驱动**:通过ExcelColumnMapping配置Excel列与对象字段的映射关系
|
||||
2. **无需注解**:不需要在实体类上添加注解,更加灵活
|
||||
3. **数据校验**:集成ValidationUtils和ValidateMethodType进行专业数据校验
|
||||
4. **灵活配置**:支持多种配置选项(Sheet选择、行范围、错误处理等)
|
||||
5. **错误处理**:详细的错误信息和错误行记录
|
||||
6. **类型转换**:自动进行类型转换
|
||||
7. **空值处理**:支持默认值和空值校验
|
||||
8. **多Sheet支持**:可指定Sheet名称或索引
|
||||
9. **两种映射方式**:支持列名和列索引两种方式
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **实体类要求**:必须有无参构造函数
|
||||
2. **字段访问**:字段必须有setter方法或可访问(支持父类字段)
|
||||
3. **列映射优先级**:columnIndex优先级高于columnName
|
||||
4. **日期类型**:必须指定dateFormat
|
||||
5. **布尔类型**:支持多种格式(true/false、1/0、是/否)
|
||||
6. **错误处理**:根据continueOnError选项决定遇到错误时是否继续
|
||||
7. **文件格式**:支持.xls和.xlsx两种格式
|
||||
8. **类型转换**:自动转换支持String、Integer、Long、Double、Float、Boolean、Date
|
||||
|
||||
## 依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>5.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.3</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## API说明
|
||||
|
||||
### ExcelReaderUtils 静态方法
|
||||
|
||||
```java
|
||||
// 从文件读取(基本)
|
||||
public static ExcelReadResult<Object> readExcel(
|
||||
File file,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings
|
||||
)
|
||||
|
||||
// 从文件读取(带配置)
|
||||
public static ExcelReadResult<Object> readExcel(
|
||||
File file,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings,
|
||||
Map<String, Object> options
|
||||
)
|
||||
|
||||
// 从输入流读取(基本)
|
||||
public static ExcelReadResult<Object> readExcel(
|
||||
InputStream inputStream,
|
||||
String fileName,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings
|
||||
)
|
||||
|
||||
// 从输入流读取(带配置)
|
||||
public static ExcelReadResult<Object> readExcel(
|
||||
InputStream inputStream,
|
||||
String fileName,
|
||||
Class<?> targetClass,
|
||||
List<ExcelColumnMapping> columnMappings,
|
||||
Map<String, Object> options
|
||||
)
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
参考 `ExcelUtilsExample.java` 查看完整使用示例。
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
# 数据校验工具使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
这是一个灵活、可扩展的Java数据校验工具,支持对对象和Map进行多种类型的校验。
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. ValidationParam - 校验参数对象
|
||||
定义字段的校验规则,支持:
|
||||
- 字段名称和中文标签
|
||||
- 是否必传
|
||||
- 字段类型校验
|
||||
- 字符串长度限制
|
||||
- 数字范围限制
|
||||
- 正则表达式校验
|
||||
- 自定义校验函数
|
||||
- 预定义的校验方法(ValidateMethod)
|
||||
|
||||
### 2. ValidationResult - 校验结果对象
|
||||
保存校验结果,包含:
|
||||
- 是否校验通过
|
||||
- 错误信息列表
|
||||
- 第一个错误信息
|
||||
- 错误数量统计
|
||||
|
||||
### 3. ValidationUtils - 校验工具类
|
||||
执行校验逻辑,提供:
|
||||
- 校验Java对象
|
||||
- 校验Map对象
|
||||
- 快捷方法:`requiredString()`, `requiredNumber()`, `email()`, `phone()`
|
||||
|
||||
### 4. ValidateMethod - 校验方法接口
|
||||
预定义的专业校验方法,已实现:
|
||||
- **PasswordValidateMethod** - 密码校验
|
||||
- **IdCardValidateMethod** - 身份证号校验
|
||||
- **PhoneValidateMethod** - 手机号码校验
|
||||
- **EmailValidateMethod** - 邮箱地址校验
|
||||
- **UrlValidateMethod** - URL链接校验
|
||||
- **BankCardValidateMethod** - 银行卡号校验
|
||||
- **ChineseValidateMethod** - 中文字符校验
|
||||
|
||||
### 5. ValidateMethodType - 校验方法类型枚举 ⭐推荐使用
|
||||
枚举类型指向预定义的校验方法,使用更简洁:
|
||||
- **PASSWORD** - 密码校验(6-20位,字母+数字)
|
||||
- **STRONG_PASSWORD** - 强密码校验(8-20位,大小写+数字+特殊字符)
|
||||
- **ID_CARD** - 身份证号校验
|
||||
- **PHONE** - 手机号码校验(中国大陆)
|
||||
- **PHONE_LOOSE** - 手机号码校验(支持大陆/香港/台湾)
|
||||
- **EMAIL** - 邮箱地址校验
|
||||
- **URL** - URL链接校验
|
||||
- **HTTPS_URL** - HTTPS链接校验
|
||||
- **BANK_CARD** - 银行卡号校验
|
||||
- **CHINESE** - 中文字符校验(纯中文)
|
||||
- **CHINESE_WITH_PUNCTUATION** - 中文字符校验(允许标点)
|
||||
|
||||
## 基本使用示例
|
||||
|
||||
### 1. 使用枚举类型校验(⭐推荐)
|
||||
|
||||
```java
|
||||
List<ValidationParam> params = Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("password")
|
||||
.fieldLabel("密码")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PASSWORD) // 使用枚举
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.EMAIL) // 使用枚举
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE) // 使用枚举
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("idCard")
|
||||
.fieldLabel("身份证号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.ID_CARD) // 使用枚举
|
||||
.build()
|
||||
);
|
||||
|
||||
ValidationResult result = ValidationUtils.validateMap(data, params);
|
||||
if (!result.isValid()) {
|
||||
System.out.println(result.getFirstError());
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 简单字段校验
|
||||
|
||||
```java
|
||||
List<ValidationParam> params = Arrays.asList(
|
||||
ValidationUtils.requiredString("username", "用户名", 3, 20),
|
||||
ValidationUtils.email("email", "邮箱", true),
|
||||
ValidationUtils.phone("phone", "手机号", false)
|
||||
);
|
||||
|
||||
// 校验对象
|
||||
ValidationResult result = ValidationUtils.validate(userObject, params);
|
||||
|
||||
// 校验Map
|
||||
ValidationResult result = ValidationUtils.validateMap(userMap, params);
|
||||
|
||||
// 检查结果
|
||||
if (result.isValid()) {
|
||||
// 校验通过
|
||||
} else {
|
||||
// 获取错误信息
|
||||
String firstError = result.getFirstError();
|
||||
String allErrors = result.getAllErrors();
|
||||
List<String> errors = result.getErrors();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用ValidateMethod进行专业校验(兼容旧方式)
|
||||
|
||||
```java
|
||||
List<ValidationParam> params = Arrays.asList(
|
||||
// 方式1:使用枚举(推荐)
|
||||
ValidationParam.builder()
|
||||
.fieldName("password")
|
||||
.fieldLabel("密码")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.STRONG_PASSWORD) // 使用预定义的强密码
|
||||
.build(),
|
||||
|
||||
// 方式2:直接实例化(如需自定义参数)
|
||||
ValidationParam.builder()
|
||||
.fieldName("password2")
|
||||
.fieldLabel("自定义密码")
|
||||
.required()
|
||||
.validateMethod(new PasswordValidateMethod(8, 20, true, true, true, true))
|
||||
.build(),
|
||||
|
||||
// 身份证号校验
|
||||
ValidationParam.builder()
|
||||
.fieldName("idCard")
|
||||
.fieldLabel("身份证号")
|
||||
.required()
|
||||
.validateMethod(new IdCardValidateMethod())
|
||||
.build(),
|
||||
|
||||
// 手机号校验
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(new PhoneValidateMethod())
|
||||
.build(),
|
||||
|
||||
// 限制域名的邮箱校验
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required()
|
||||
.validateMethod(new EmailValidateMethod(new String[]{"company.com"}))
|
||||
.build()
|
||||
);
|
||||
|
||||
ValidationResult result = ValidationUtils.validateMap(data, params);
|
||||
```
|
||||
|
||||
### 4. 自定义校验
|
||||
|
||||
```java
|
||||
ValidationParam param = ValidationParam.builder()
|
||||
.fieldName("age")
|
||||
.fieldLabel("年龄")
|
||||
.required()
|
||||
.customValidator(value -> {
|
||||
Integer age = (Integer) value;
|
||||
return age >= 18 && age <= 60;
|
||||
})
|
||||
.customErrorMessage("年龄必须在18-60岁之间")
|
||||
.build();
|
||||
```
|
||||
|
||||
### 5. 复合校验
|
||||
|
||||
```java
|
||||
List<ValidationParam> params = Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("username")
|
||||
.fieldLabel("用户名")
|
||||
.required()
|
||||
.fieldType(String.class)
|
||||
.minLength(3)
|
||||
.maxLength(20)
|
||||
.pattern("^[a-zA-Z0-9_]+$")
|
||||
.patternDesc("只能包含字母、数字和下划线")
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("password")
|
||||
.fieldLabel("密码")
|
||||
.required()
|
||||
.minLength(6)
|
||||
.validateMethod(new PasswordValidateMethod())
|
||||
.build()
|
||||
);
|
||||
```
|
||||
|
||||
## 预定义校验方法详解
|
||||
|
||||
### PasswordValidateMethod - 密码校验
|
||||
|
||||
```java
|
||||
// 默认规则:6-20位,必须包含字母和数字
|
||||
new PasswordValidateMethod()
|
||||
|
||||
// 自定义规则
|
||||
new PasswordValidateMethod(
|
||||
8, // 最小长度
|
||||
20, // 最大长度
|
||||
true, // 需要大写字母
|
||||
true, // 需要小写字母
|
||||
true, // 需要数字
|
||||
true // 需要特殊字符
|
||||
)
|
||||
```
|
||||
|
||||
### IdCardValidateMethod - 身份证号校验
|
||||
|
||||
```java
|
||||
// 支持15位和18位身份证号
|
||||
// 自动校验:格式、省份代码、出生日期、校验码
|
||||
new IdCardValidateMethod()
|
||||
```
|
||||
|
||||
### PhoneValidateMethod - 手机号码校验
|
||||
|
||||
```java
|
||||
// 严格模式:仅中国大陆手机号
|
||||
new PhoneValidateMethod()
|
||||
|
||||
// 宽松模式:支持大陆、香港、台湾
|
||||
new PhoneValidateMethod(false)
|
||||
```
|
||||
|
||||
### EmailValidateMethod - 邮箱地址校验
|
||||
|
||||
```java
|
||||
// 允许所有域名
|
||||
new EmailValidateMethod()
|
||||
|
||||
// 限制特定域名
|
||||
new EmailValidateMethod(new String[]{"company.com", "example.com"})
|
||||
```
|
||||
|
||||
### UrlValidateMethod - URL链接校验
|
||||
|
||||
```java
|
||||
// 允许HTTP和HTTPS
|
||||
new UrlValidateMethod()
|
||||
|
||||
// 仅允许HTTPS
|
||||
new UrlValidateMethod(true)
|
||||
```
|
||||
|
||||
### BankCardValidateMethod - 银行卡号校验
|
||||
|
||||
```java
|
||||
// 使用Luhn算法校验银行卡号
|
||||
new BankCardValidateMethod()
|
||||
```
|
||||
|
||||
### ChineseValidateMethod - 中文字符校验
|
||||
|
||||
```java
|
||||
// 仅纯中文字符
|
||||
new ChineseValidateMethod()
|
||||
|
||||
// 允许中文标点符号
|
||||
new ChineseValidateMethod(true)
|
||||
```
|
||||
|
||||
## 在Controller中使用
|
||||
|
||||
```java
|
||||
@PostMapping("/register")
|
||||
public ResultDomain<User> register(@RequestBody Map<String, Object> params) {
|
||||
// 定义校验规则(使用枚举,更简洁)
|
||||
List<ValidationParam> validationParams = Arrays.asList(
|
||||
ValidationParam.builder()
|
||||
.fieldName("username")
|
||||
.fieldLabel("用户名")
|
||||
.required()
|
||||
.minLength(3)
|
||||
.maxLength(20)
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("password")
|
||||
.fieldLabel("密码")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PASSWORD) // 使用枚举!
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("email")
|
||||
.fieldLabel("邮箱")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.EMAIL) // 使用枚举!
|
||||
.build(),
|
||||
|
||||
ValidationParam.builder()
|
||||
.fieldName("phone")
|
||||
.fieldLabel("手机号")
|
||||
.required()
|
||||
.validateMethod(ValidateMethodType.PHONE) // 使用枚举!
|
||||
.build()
|
||||
);
|
||||
|
||||
// 执行校验
|
||||
ValidationResult validationResult = ValidationUtils.validateMap(params, validationParams);
|
||||
|
||||
if (!validationResult.isValid()) {
|
||||
ResultDomain<User> result = new ResultDomain<>();
|
||||
result.fail(validationResult.getFirstError());
|
||||
return result;
|
||||
}
|
||||
|
||||
// 校验通过,继续业务逻辑
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义ValidateMethod
|
||||
|
||||
如需添加新的校验方法,只需实现`ValidateMethod`接口:
|
||||
|
||||
```java
|
||||
public class CustomValidateMethod implements ValidateMethod {
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
// 实现校验逻辑
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return "自定义错误信息";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "自定义校验";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
1. **简洁性**:使用枚举类型,无需每次new对象 ⭐
|
||||
2. **灵活性**:支持多种校验方式组合使用
|
||||
3. **可扩展性**:易于添加新的校验方法
|
||||
4. **可读性**:Builder模式让代码清晰易懂
|
||||
5. **可复用性**:预定义的校验方法可在项目中重复使用
|
||||
6. **专业性**:内置多种常用的专业校验算法(身份证、银行卡等)
|
||||
7. **类型安全**:枚举类型提供编译时类型检查
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **推荐使用枚举类型**:`ValidateMethodType` 比直接 `new` 对象更简洁
|
||||
2. 校验顺序:必填 -> 类型 -> 长度/范围 -> 正则 -> 自定义 -> ValidateMethodType -> ValidateMethod
|
||||
3. ValidateMethod和customValidator可以同时使用,都会执行
|
||||
4. 当值为null且非必填时,会跳过后续所有校验
|
||||
5. 错误信息会累积,可以获取所有错误或只获取第一个错误
|
||||
6. 枚举方式和实例方式可以并存,但推荐统一使用枚举方式
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
package org.xyzh.common.utils.validation;
|
||||
|
||||
import org.xyzh.common.utils.validation.method.ValidateMethod;
|
||||
import org.xyzh.common.utils.validation.method.ValidateMethodType;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @description 校验参数对象,定义字段的校验规则
|
||||
* @filename ValidationParam.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ValidationParam {
|
||||
|
||||
/**
|
||||
* @description 字段名称
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* @description 字段中文名称(用于错误提示)
|
||||
*/
|
||||
private String fieldLabel;
|
||||
|
||||
/**
|
||||
* @description 是否必传
|
||||
*/
|
||||
private boolean required;
|
||||
|
||||
/**
|
||||
* @description 字段类型
|
||||
*/
|
||||
private Class<?> fieldType;
|
||||
|
||||
/**
|
||||
* @description 最小长度(字符串)
|
||||
*/
|
||||
private Integer minLength;
|
||||
|
||||
/**
|
||||
* @description 最大长度(字符串)
|
||||
*/
|
||||
private Integer maxLength;
|
||||
|
||||
/**
|
||||
* @description 最小值(数字)
|
||||
*/
|
||||
private Number minValue;
|
||||
|
||||
/**
|
||||
* @description 最大值(数字)
|
||||
*/
|
||||
private Number maxValue;
|
||||
|
||||
/**
|
||||
* @description 正则表达式
|
||||
*/
|
||||
private String pattern;
|
||||
|
||||
/**
|
||||
* @description 正则表达式描述(用于错误提示)
|
||||
*/
|
||||
private String patternDesc;
|
||||
|
||||
/**
|
||||
* @description 自定义校验函数
|
||||
*/
|
||||
private Predicate<Object> customValidator;
|
||||
|
||||
/**
|
||||
* @description 自定义校验失败消息
|
||||
*/
|
||||
private String customErrorMessage;
|
||||
|
||||
/**
|
||||
* @description 是否允许为空字符串(默认不允许)
|
||||
*/
|
||||
private boolean allowEmpty = false;
|
||||
|
||||
/**
|
||||
* @description 校验方法(使用预定义的校验方法)
|
||||
*/
|
||||
private ValidateMethod validateMethod;
|
||||
|
||||
/**
|
||||
* @description 校验方法类型枚举
|
||||
*/
|
||||
private ValidateMethodType validateMethodType;
|
||||
|
||||
/**
|
||||
* @description 校验方法配置参数(用于需要自定义参数的校验方法)
|
||||
*/
|
||||
private Object[] methodParams;
|
||||
|
||||
// 私有构造函数,使用Builder模式
|
||||
private ValidationParam() {
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public String getFieldLabel() {
|
||||
return fieldLabel;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public Class<?> getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
public Integer getMinLength() {
|
||||
return minLength;
|
||||
}
|
||||
|
||||
public Integer getMaxLength() {
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
public Number getMinValue() {
|
||||
return minValue;
|
||||
}
|
||||
|
||||
public Number getMaxValue() {
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public String getPatternDesc() {
|
||||
return patternDesc;
|
||||
}
|
||||
|
||||
public Predicate<Object> getCustomValidator() {
|
||||
return customValidator;
|
||||
}
|
||||
|
||||
public String getCustomErrorMessage() {
|
||||
return customErrorMessage;
|
||||
}
|
||||
|
||||
public boolean isAllowEmpty() {
|
||||
return allowEmpty;
|
||||
}
|
||||
|
||||
public ValidateMethod getValidateMethod() {
|
||||
return validateMethod;
|
||||
}
|
||||
|
||||
public ValidateMethodType getValidateMethodType() {
|
||||
return validateMethodType;
|
||||
}
|
||||
|
||||
public Object[] getMethodParams() {
|
||||
return methodParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builder类用于构建ValidationParam对象
|
||||
*/
|
||||
public static class Builder {
|
||||
private ValidationParam param = new ValidationParam();
|
||||
|
||||
public Builder fieldName(String fieldName) {
|
||||
param.fieldName = fieldName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fieldLabel(String fieldLabel) {
|
||||
param.fieldLabel = fieldLabel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder required(boolean required) {
|
||||
param.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder required() {
|
||||
param.required = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fieldType(Class<?> fieldType) {
|
||||
param.fieldType = fieldType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder minLength(Integer minLength) {
|
||||
param.minLength = minLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxLength(Integer maxLength) {
|
||||
param.maxLength = maxLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder minValue(Number minValue) {
|
||||
param.minValue = minValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxValue(Number maxValue) {
|
||||
param.maxValue = maxValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder pattern(String pattern) {
|
||||
param.pattern = pattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder patternDesc(String patternDesc) {
|
||||
param.patternDesc = patternDesc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder customValidator(Predicate<Object> customValidator) {
|
||||
param.customValidator = customValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder customErrorMessage(String customErrorMessage) {
|
||||
param.customErrorMessage = customErrorMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allowEmpty(boolean allowEmpty) {
|
||||
param.allowEmpty = allowEmpty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder validateMethod(ValidateMethod validateMethod) {
|
||||
param.validateMethod = validateMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder validateMethod(ValidateMethodType methodType) {
|
||||
param.validateMethodType = methodType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder validateMethod(ValidateMethodType methodType, Object... params) {
|
||||
param.validateMethodType = methodType;
|
||||
param.methodParams = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidationParam build() {
|
||||
if (param.fieldName == null || param.fieldName.isEmpty()) {
|
||||
throw new IllegalArgumentException("fieldName不能为空");
|
||||
}
|
||||
if (param.fieldLabel == null || param.fieldLabel.isEmpty()) {
|
||||
param.fieldLabel = param.fieldName;
|
||||
}
|
||||
return param;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建Builder对象
|
||||
* @return Builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.xyzh.common.utils.validation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 校验结果类
|
||||
* @filename ValidationResult.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ValidationResult {
|
||||
|
||||
/**
|
||||
* @description 是否校验通过
|
||||
*/
|
||||
private boolean valid;
|
||||
|
||||
/**
|
||||
* @description 错误信息列表
|
||||
*/
|
||||
private List<String> errors;
|
||||
|
||||
/**
|
||||
* @description 第一个错误信息
|
||||
*/
|
||||
private String firstError;
|
||||
|
||||
public ValidationResult() {
|
||||
this.valid = true;
|
||||
this.errors = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public void setValid(boolean valid) {
|
||||
this.valid = valid;
|
||||
}
|
||||
|
||||
public List<String> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
public String getFirstError() {
|
||||
return firstError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 添加错误信息
|
||||
* @param error 错误信息
|
||||
*/
|
||||
public void addError(String error) {
|
||||
this.valid = false;
|
||||
this.errors.add(error);
|
||||
if (this.firstError == null) {
|
||||
this.firstError = error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有错误信息的字符串
|
||||
* @return 错误信息字符串
|
||||
*/
|
||||
public String getAllErrors() {
|
||||
return String.join("; ", errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 是否有错误
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean hasErrors() {
|
||||
return !valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取错误数量
|
||||
* @return 错误数量
|
||||
*/
|
||||
public int getErrorCount() {
|
||||
return errors.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValidationResult{" +
|
||||
"valid=" + valid +
|
||||
", errorCount=" + errors.size() +
|
||||
", errors=" + errors +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
package org.xyzh.common.utils.validation;
|
||||
|
||||
import org.xyzh.common.utils.validation.method.ValidateMethod;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 校验工具类
|
||||
* @filename ValidationUtils.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ValidationUtils {
|
||||
|
||||
/**
|
||||
* @description 校验对象
|
||||
* @param obj 待校验的对象
|
||||
* @param validationParams 校验参数列表
|
||||
* @return ValidationResult 校验结果
|
||||
*/
|
||||
public static ValidationResult validate(Object obj, List<ValidationParam> validationParams) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
|
||||
if (obj == null) {
|
||||
result.addError("待校验对象不能为null");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (validationParams == null || validationParams.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (ValidationParam param : validationParams) {
|
||||
try {
|
||||
Object fieldValue = getFieldValue(obj, param.getFieldName());
|
||||
validateField(param, fieldValue, result);
|
||||
} catch (Exception e) {
|
||||
result.addError(param.getFieldLabel() + "字段获取失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验Map对象
|
||||
* @param map 待校验的Map
|
||||
* @param validationParams 校验参数列表
|
||||
* @return ValidationResult 校验结果
|
||||
*/
|
||||
public static ValidationResult validateMap(Map<String, Object> map, List<ValidationParam> validationParams) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
|
||||
if (map == null) {
|
||||
result.addError("待校验Map不能为null");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (validationParams == null || validationParams.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (ValidationParam param : validationParams) {
|
||||
Object fieldValue = map.get(param.getFieldName());
|
||||
validateField(param, fieldValue, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验单个字段
|
||||
* @param param 校验参数
|
||||
* @param fieldValue 字段值
|
||||
* @param result 校验结果
|
||||
*/
|
||||
private static void validateField(ValidationParam param, Object fieldValue, ValidationResult result) {
|
||||
String fieldLabel = param.getFieldLabel();
|
||||
|
||||
// 1. 必填校验
|
||||
if (param.isRequired()) {
|
||||
if (fieldValue == null) {
|
||||
result.addError(fieldLabel + "不能为空");
|
||||
return;
|
||||
}
|
||||
if (fieldValue instanceof String) {
|
||||
String strValue = (String) fieldValue;
|
||||
if (!param.isAllowEmpty() && strValue.trim().isEmpty()) {
|
||||
result.addError(fieldLabel + "不能为空字符串");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果值为null且非必填,跳过后续校验
|
||||
if (fieldValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 类型校验
|
||||
if (param.getFieldType() != null) {
|
||||
if (!param.getFieldType().isAssignableFrom(fieldValue.getClass())) {
|
||||
result.addError(fieldLabel + "类型错误,期望类型: " + param.getFieldType().getSimpleName() +
|
||||
", 实际类型: " + fieldValue.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 字符串长度校验
|
||||
if (fieldValue instanceof String) {
|
||||
String strValue = (String) fieldValue;
|
||||
if (param.getMinLength() != null && strValue.length() < param.getMinLength()) {
|
||||
result.addError(fieldLabel + "长度不能少于" + param.getMinLength() + "个字符");
|
||||
}
|
||||
if (param.getMaxLength() != null && strValue.length() > param.getMaxLength()) {
|
||||
result.addError(fieldLabel + "长度不能超过" + param.getMaxLength() + "个字符");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 数字范围校验
|
||||
if (fieldValue instanceof Number) {
|
||||
double numValue = ((Number) fieldValue).doubleValue();
|
||||
if (param.getMinValue() != null && numValue < param.getMinValue().doubleValue()) {
|
||||
result.addError(fieldLabel + "不能小于" + param.getMinValue());
|
||||
}
|
||||
if (param.getMaxValue() != null && numValue > param.getMaxValue().doubleValue()) {
|
||||
result.addError(fieldLabel + "不能大于" + param.getMaxValue());
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 正则表达式校验
|
||||
if (param.getPattern() != null && fieldValue instanceof String) {
|
||||
String strValue = (String) fieldValue;
|
||||
if (!Pattern.matches(param.getPattern(), strValue)) {
|
||||
String errorMsg = fieldLabel + "格式不正确";
|
||||
if (param.getPatternDesc() != null) {
|
||||
errorMsg += "," + param.getPatternDesc();
|
||||
}
|
||||
result.addError(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 自定义校验
|
||||
if (param.getCustomValidator() != null) {
|
||||
try {
|
||||
if (!param.getCustomValidator().test(fieldValue)) {
|
||||
String errorMsg = param.getCustomErrorMessage();
|
||||
if (errorMsg == null || errorMsg.isEmpty()) {
|
||||
errorMsg = fieldLabel + "校验失败";
|
||||
}
|
||||
result.addError(errorMsg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.addError(fieldLabel + "自定义校验异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 使用ValidateMethod校验(枚举类型)
|
||||
if (param.getValidateMethodType() != null) {
|
||||
try {
|
||||
ValidateMethod method = param.getValidateMethodType().createInstance();
|
||||
if (!method.validate(fieldValue)) {
|
||||
String errorMsg = method.getErrorMessage();
|
||||
if (errorMsg != null && !errorMsg.isEmpty()) {
|
||||
result.addError(errorMsg);
|
||||
} else {
|
||||
result.addError(fieldLabel + "校验失败");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.addError(fieldLabel + "校验异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 使用ValidateMethod校验(直接传入实例,保留兼容性)
|
||||
if (param.getValidateMethod() != null) {
|
||||
try {
|
||||
if (!param.getValidateMethod().validate(fieldValue)) {
|
||||
String errorMsg = param.getValidateMethod().getErrorMessage();
|
||||
if (errorMsg != null && !errorMsg.isEmpty()) {
|
||||
result.addError(errorMsg);
|
||||
} else {
|
||||
result.addError(fieldLabel + "校验失败");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.addError(fieldLabel + "校验异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取对象字段值(支持getter方法和直接访问)
|
||||
* @param obj 对象
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
private static Object getFieldValue(Object obj, String fieldName) throws Exception {
|
||||
if (obj instanceof Map) {
|
||||
return ((Map<?, ?>) obj).get(fieldName);
|
||||
}
|
||||
|
||||
Class<?> clazz = obj.getClass();
|
||||
|
||||
// 首先尝试getter方法
|
||||
try {
|
||||
String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
|
||||
return clazz.getMethod(getterName).invoke(obj);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// getter方法不存在,尝试直接访问字段
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
} catch (NoSuchFieldException ex) {
|
||||
// 尝试父类
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass != null) {
|
||||
Field field = superClass.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建必填字符串校验参数
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam requiredString(String fieldName, String fieldLabel) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required()
|
||||
.fieldType(String.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建必填字符串校验参数(带长度限制)
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @param minLength 最小长度
|
||||
* @param maxLength 最大长度
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam requiredString(String fieldName, String fieldLabel, int minLength, int maxLength) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required()
|
||||
.fieldType(String.class)
|
||||
.minLength(minLength)
|
||||
.maxLength(maxLength)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建必填数字校验参数
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @param minValue 最小值
|
||||
* @param maxValue 最大值
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam requiredNumber(String fieldName, String fieldLabel, Number minValue, Number maxValue) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required()
|
||||
.minValue(minValue)
|
||||
.maxValue(maxValue)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建邮箱校验参数
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @param required 是否必填
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam email(String fieldName, String fieldLabel, boolean required) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required(required)
|
||||
.fieldType(String.class)
|
||||
.pattern("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
|
||||
.patternDesc("请输入有效的邮箱地址")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 快速创建手机号校验参数
|
||||
* @param fieldName 字段名
|
||||
* @param fieldLabel 字段标签
|
||||
* @param required 是否必填
|
||||
* @return ValidationParam
|
||||
*/
|
||||
public static ValidationParam phone(String fieldName, String fieldLabel, boolean required) {
|
||||
return ValidationParam.builder()
|
||||
.fieldName(fieldName)
|
||||
.fieldLabel(fieldLabel)
|
||||
.required(required)
|
||||
.fieldType(String.class)
|
||||
.pattern("^1[3-9]\\d{9}$")
|
||||
.patternDesc("请输入有效的手机号码")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
/**
|
||||
* @description 银行卡号校验方法(Luhn算法)
|
||||
* @filename BankCardValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class BankCardValidateMethod implements ValidateMethod {
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String cardNumber = ((String) value).replaceAll("\\s", "");
|
||||
|
||||
// 长度校验(银行卡号通常为16-19位)
|
||||
if (cardNumber.length() < 16 || cardNumber.length() > 19) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 数字校验
|
||||
if (!cardNumber.matches("^\\d+$")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Luhn算法校验
|
||||
return luhnCheck(cardNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Luhn算法校验(银行卡校验算法)
|
||||
* @param cardNumber 银行卡号
|
||||
* @return boolean 是否通过校验
|
||||
*/
|
||||
private boolean luhnCheck(String cardNumber) {
|
||||
int sum = 0;
|
||||
boolean alternate = false;
|
||||
|
||||
// 从右向左遍历
|
||||
for (int i = cardNumber.length() - 1; i >= 0; i--) {
|
||||
int digit = Character.getNumericValue(cardNumber.charAt(i));
|
||||
|
||||
if (alternate) {
|
||||
digit *= 2;
|
||||
if (digit > 9) {
|
||||
digit = digit - 9;
|
||||
}
|
||||
}
|
||||
|
||||
sum += digit;
|
||||
alternate = !alternate;
|
||||
}
|
||||
|
||||
return sum % 10 == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return "请输入有效的银行卡号";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "银行卡号校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 中文字符校验方法
|
||||
* @filename ChineseValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class ChineseValidateMethod implements ValidateMethod {
|
||||
|
||||
// 中文字符正则(包括中文标点符号)
|
||||
private static final Pattern CHINESE_PATTERN = Pattern.compile("^[\u4e00-\u9fa5]+$");
|
||||
|
||||
private final boolean allowPunctuation; // 是否允许中文标点符号
|
||||
|
||||
public ChineseValidateMethod() {
|
||||
this.allowPunctuation = false;
|
||||
}
|
||||
|
||||
public ChineseValidateMethod(boolean allowPunctuation) {
|
||||
this.allowPunctuation = allowPunctuation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String str = (String) value;
|
||||
|
||||
if (allowPunctuation) {
|
||||
// 允许中文字符和中文标点符号
|
||||
return Pattern.matches("^[\u4e00-\u9fa5\\u3000-\\u303f]+$", str);
|
||||
} else {
|
||||
// 仅允许纯中文字符
|
||||
return CHINESE_PATTERN.matcher(str).matches();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return allowPunctuation ? "请输入中文字符" : "请输入纯中文字符(不含标点符号)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "中文字符校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 邮箱校验方法
|
||||
* @filename EmailValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class EmailValidateMethod implements ValidateMethod {
|
||||
|
||||
// 邮箱正则表达式
|
||||
private static final Pattern EMAIL_PATTERN = Pattern.compile(
|
||||
"^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
|
||||
);
|
||||
|
||||
private final String[] allowedDomains; // 允许的域名列表
|
||||
|
||||
/**
|
||||
* @description 默认构造函数,允许所有域名
|
||||
*/
|
||||
public EmailValidateMethod() {
|
||||
this.allowedDomains = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 限制域名的构造函数
|
||||
* @param allowedDomains 允许的域名列表,例如:["company.com", "example.com"]
|
||||
*/
|
||||
public EmailValidateMethod(String[] allowedDomains) {
|
||||
this.allowedDomains = allowedDomains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String email = ((String) value).trim().toLowerCase();
|
||||
|
||||
// 基本格式校验
|
||||
if (!EMAIL_PATTERN.matcher(email).matches()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 域名限制校验
|
||||
if (allowedDomains != null && allowedDomains.length > 0) {
|
||||
boolean domainMatched = false;
|
||||
for (String domain : allowedDomains) {
|
||||
if (email.endsWith("@" + domain.toLowerCase())) {
|
||||
domainMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return domainMatched;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
if (allowedDomains != null && allowedDomains.length > 0) {
|
||||
return "请输入有效的邮箱地址(仅支持: " + String.join(", ", allowedDomains) + ")";
|
||||
}
|
||||
return "请输入有效的邮箱地址";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "邮箱校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 身份证号码校验方法(支持15位和18位身份证)
|
||||
* @filename IdCardValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class IdCardValidateMethod implements ValidateMethod {
|
||||
|
||||
// 加权因子
|
||||
private static final int[] WEIGHT = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
|
||||
|
||||
// 校验码对应值
|
||||
private static final char[] VALIDATE_CODE = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
|
||||
|
||||
// 省份代码
|
||||
private static final Map<String, String> PROVINCE_CODES = new HashMap<>();
|
||||
|
||||
static {
|
||||
PROVINCE_CODES.put("11", "北京");
|
||||
PROVINCE_CODES.put("12", "天津");
|
||||
PROVINCE_CODES.put("13", "河北");
|
||||
PROVINCE_CODES.put("14", "山西");
|
||||
PROVINCE_CODES.put("15", "内蒙古");
|
||||
PROVINCE_CODES.put("21", "辽宁");
|
||||
PROVINCE_CODES.put("22", "吉林");
|
||||
PROVINCE_CODES.put("23", "黑龙江");
|
||||
PROVINCE_CODES.put("31", "上海");
|
||||
PROVINCE_CODES.put("32", "江苏");
|
||||
PROVINCE_CODES.put("33", "浙江");
|
||||
PROVINCE_CODES.put("34", "安徽");
|
||||
PROVINCE_CODES.put("35", "福建");
|
||||
PROVINCE_CODES.put("36", "江西");
|
||||
PROVINCE_CODES.put("37", "山东");
|
||||
PROVINCE_CODES.put("41", "河南");
|
||||
PROVINCE_CODES.put("42", "湖北");
|
||||
PROVINCE_CODES.put("43", "湖南");
|
||||
PROVINCE_CODES.put("44", "广东");
|
||||
PROVINCE_CODES.put("45", "广西");
|
||||
PROVINCE_CODES.put("46", "海南");
|
||||
PROVINCE_CODES.put("50", "重庆");
|
||||
PROVINCE_CODES.put("51", "四川");
|
||||
PROVINCE_CODES.put("52", "贵州");
|
||||
PROVINCE_CODES.put("53", "云南");
|
||||
PROVINCE_CODES.put("54", "西藏");
|
||||
PROVINCE_CODES.put("61", "陕西");
|
||||
PROVINCE_CODES.put("62", "甘肃");
|
||||
PROVINCE_CODES.put("63", "青海");
|
||||
PROVINCE_CODES.put("64", "宁夏");
|
||||
PROVINCE_CODES.put("65", "新疆");
|
||||
PROVINCE_CODES.put("71", "台湾");
|
||||
PROVINCE_CODES.put("81", "香港");
|
||||
PROVINCE_CODES.put("82", "澳门");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String idCard = ((String) value).toUpperCase();
|
||||
|
||||
// 长度校验
|
||||
if (idCard.length() != 15 && idCard.length() != 18) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 格式校验
|
||||
if (idCard.length() == 15) {
|
||||
return validate15IdCard(idCard);
|
||||
} else {
|
||||
return validate18IdCard(idCard);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验15位身份证
|
||||
*/
|
||||
private boolean validate15IdCard(String idCard) {
|
||||
// 15位身份证格式:省(2位)市(2位)县(2位)年(2位)月(2位)日(2位)顺序号(3位)
|
||||
if (!Pattern.matches("^\\d{15}$", idCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 省份代码校验
|
||||
String provinceCode = idCard.substring(0, 2);
|
||||
if (!PROVINCE_CODES.containsKey(provinceCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 出生日期校验
|
||||
String year = "19" + idCard.substring(6, 8);
|
||||
String month = idCard.substring(8, 10);
|
||||
String day = idCard.substring(10, 12);
|
||||
|
||||
return validateDate(year, month, day);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验18位身份证
|
||||
*/
|
||||
private boolean validate18IdCard(String idCard) {
|
||||
// 18位身份证格式:省(2位)市(2位)县(2位)年(4位)月(2位)日(2位)顺序号(3位)校验码(1位)
|
||||
if (!Pattern.matches("^\\d{17}[0-9Xx]$", idCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 省份代码校验
|
||||
String provinceCode = idCard.substring(0, 2);
|
||||
if (!PROVINCE_CODES.containsKey(provinceCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 出生日期校验
|
||||
String year = idCard.substring(6, 10);
|
||||
String month = idCard.substring(10, 12);
|
||||
String day = idCard.substring(12, 14);
|
||||
|
||||
if (!validateDate(year, month, day)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 校验码校验
|
||||
return validateCheckCode(idCard);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验日期是否合法
|
||||
*/
|
||||
private boolean validateDate(String year, String month, String day) {
|
||||
try {
|
||||
int y = Integer.parseInt(year);
|
||||
int m = Integer.parseInt(month);
|
||||
int d = Integer.parseInt(day);
|
||||
|
||||
// 年份范围:1900-当前年份
|
||||
int currentYear = java.time.Year.now().getValue();
|
||||
if (y < 1900 || y > currentYear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 月份范围:1-12
|
||||
if (m < 1 || m > 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 日期范围
|
||||
int[] daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
// 闰年2月29天
|
||||
if (isLeapYear(y)) {
|
||||
daysInMonth[1] = 29;
|
||||
}
|
||||
|
||||
return d >= 1 && d <= daysInMonth[m - 1];
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断是否为闰年
|
||||
*/
|
||||
private boolean isLeapYear(int year) {
|
||||
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 校验18位身份证的校验码
|
||||
*/
|
||||
private boolean validateCheckCode(String idCard) {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < 17; i++) {
|
||||
sum += (idCard.charAt(i) - '0') * WEIGHT[i];
|
||||
}
|
||||
|
||||
char checkCode = VALIDATE_CODE[sum % 11];
|
||||
return checkCode == idCard.charAt(17);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return "请输入有效的身份证号码(15位或18位)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "身份证号码校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 密码校验方法
|
||||
* @filename PasswordValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class PasswordValidateMethod implements ValidateMethod {
|
||||
|
||||
private final int minLength;
|
||||
private final int maxLength;
|
||||
private final boolean requireUpperCase;
|
||||
private final boolean requireLowerCase;
|
||||
private final boolean requireDigit;
|
||||
private final boolean requireSpecialChar;
|
||||
|
||||
/**
|
||||
* @description 默认密码规则:6-20位,必须包含字母和数字
|
||||
*/
|
||||
public PasswordValidateMethod() {
|
||||
this(6, 20, false, false, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 自定义密码规则
|
||||
* @param minLength 最小长度
|
||||
* @param maxLength 最大长度
|
||||
* @param requireUpperCase 是否需要大写字母
|
||||
* @param requireLowerCase 是否需要小写字母
|
||||
* @param requireDigit 是否需要数字
|
||||
* @param requireSpecialChar 是否需要特殊字符
|
||||
*/
|
||||
public PasswordValidateMethod(int minLength, int maxLength,
|
||||
boolean requireUpperCase, boolean requireLowerCase,
|
||||
boolean requireDigit, boolean requireSpecialChar) {
|
||||
this.minLength = minLength;
|
||||
this.maxLength = maxLength;
|
||||
this.requireUpperCase = requireUpperCase;
|
||||
this.requireLowerCase = requireLowerCase;
|
||||
this.requireDigit = requireDigit;
|
||||
this.requireSpecialChar = requireSpecialChar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String password = (String) value;
|
||||
|
||||
// 长度校验
|
||||
if (password.length() < minLength || password.length() > maxLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 大写字母校验
|
||||
if (requireUpperCase && !Pattern.compile("[A-Z]").matcher(password).find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 小写字母校验
|
||||
if (requireLowerCase && !Pattern.compile("[a-z]").matcher(password).find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 数字校验
|
||||
if (requireDigit && !Pattern.compile("[0-9]").matcher(password).find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 特殊字符校验
|
||||
if (requireSpecialChar && !Pattern.compile("[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?]").matcher(password).find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
StringBuilder msg = new StringBuilder("密码必须是");
|
||||
msg.append(minLength).append("-").append(maxLength).append("位");
|
||||
|
||||
if (requireUpperCase || requireLowerCase || requireDigit || requireSpecialChar) {
|
||||
msg.append(",且包含");
|
||||
boolean first = true;
|
||||
|
||||
if (requireUpperCase) {
|
||||
msg.append("大写字母");
|
||||
first = false;
|
||||
}
|
||||
if (requireLowerCase) {
|
||||
if (!first) msg.append("、");
|
||||
msg.append("小写字母");
|
||||
first = false;
|
||||
}
|
||||
if (requireDigit) {
|
||||
if (!first) msg.append("、");
|
||||
msg.append("数字");
|
||||
first = false;
|
||||
}
|
||||
if (requireSpecialChar) {
|
||||
if (!first) msg.append("、");
|
||||
msg.append("特殊字符");
|
||||
}
|
||||
}
|
||||
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "密码校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description 手机号码校验方法
|
||||
* @filename PhoneValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class PhoneValidateMethod implements ValidateMethod {
|
||||
|
||||
// 中国大陆手机号正则
|
||||
private static final Pattern CHINA_PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
|
||||
|
||||
// 香港手机号正则
|
||||
private static final Pattern HK_PHONE_PATTERN = Pattern.compile("^[5-9]\\d{7}$");
|
||||
|
||||
// 台湾手机号正则
|
||||
private static final Pattern TW_PHONE_PATTERN = Pattern.compile("^09\\d{8}$");
|
||||
|
||||
private final boolean strictMode; // 严格模式,只验证中国大陆手机号
|
||||
|
||||
public PhoneValidateMethod() {
|
||||
this.strictMode = true;
|
||||
}
|
||||
|
||||
public PhoneValidateMethod(boolean strictMode) {
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String phone = (String) value;
|
||||
|
||||
// 去除空格和横线
|
||||
phone = phone.replaceAll("[\\s-]", "");
|
||||
|
||||
if (strictMode) {
|
||||
// 严格模式:只验证中国大陆手机号
|
||||
return CHINA_PHONE_PATTERN.matcher(phone).matches();
|
||||
} else {
|
||||
// 宽松模式:支持大陆、香港、台湾手机号
|
||||
return CHINA_PHONE_PATTERN.matcher(phone).matches()
|
||||
|| HK_PHONE_PATTERN.matcher(phone).matches()
|
||||
|| TW_PHONE_PATTERN.matcher(phone).matches();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return strictMode ? "请输入有效的手机号码" : "请输入有效的手机号码(支持大陆、香港、台湾)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "手机号码校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @description URL校验方法
|
||||
* @filename UrlValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public class UrlValidateMethod implements ValidateMethod {
|
||||
|
||||
// URL正则表达式
|
||||
private static final Pattern URL_PATTERN = Pattern.compile(
|
||||
"^(https?|ftp)://[a-zA-Z0-9+&@#/%?=~_|!:,.;-]*[a-zA-Z0-9+&@#/%=~_|-]$"
|
||||
);
|
||||
|
||||
private final boolean requireHttps; // 是否要求HTTPS
|
||||
|
||||
public UrlValidateMethod() {
|
||||
this.requireHttps = false;
|
||||
}
|
||||
|
||||
public UrlValidateMethod(boolean requireHttps) {
|
||||
this.requireHttps = requireHttps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Object value) {
|
||||
if (value == null || !(value instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String url = ((String) value).trim();
|
||||
|
||||
// 基本格式校验
|
||||
if (!URL_PATTERN.matcher(url).matches()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// HTTPS校验
|
||||
if (requireHttps && !url.startsWith("https://")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
return requireHttps ? "请输入有效的HTTPS链接" : "请输入有效的URL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "URL校验";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
/**
|
||||
* @description 校验方法接口,定义不同类型的校验方式
|
||||
* @filename ValidateMethod.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public interface ValidateMethod {
|
||||
|
||||
/**
|
||||
* @description 校验方法
|
||||
* @param value 待校验的值
|
||||
* @return boolean 是否校验通过
|
||||
*/
|
||||
boolean validate(Object value);
|
||||
|
||||
/**
|
||||
* @description 获取校验失败的错误提示信息
|
||||
* @return String 错误提示信息
|
||||
*/
|
||||
String getErrorMessage();
|
||||
|
||||
/**
|
||||
* @description 获取校验方法的名称
|
||||
* @return String 校验方法名称
|
||||
*/
|
||||
String getName();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.xyzh.common.utils.validation.method;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @description 校验方法类型枚举
|
||||
* @filename ValidateMethodType.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public enum ValidateMethodType {
|
||||
|
||||
/**
|
||||
* 密码校验(默认:6-20位,必须包含字母和数字)
|
||||
*/
|
||||
PASSWORD("密码校验", PasswordValidateMethod.class, PasswordValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 强密码校验(8-20位,必须包含大小写字母、数字和特殊字符)
|
||||
*/
|
||||
STRONG_PASSWORD("强密码校验", PasswordValidateMethod.class,
|
||||
() -> new PasswordValidateMethod(8, 20, true, true, true, true)),
|
||||
|
||||
/**
|
||||
* 身份证号校验(支持15位和18位)
|
||||
*/
|
||||
ID_CARD("身份证号校验", IdCardValidateMethod.class, IdCardValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 手机号码校验(中国大陆)
|
||||
*/
|
||||
PHONE("手机号码校验", PhoneValidateMethod.class, PhoneValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 手机号码校验(宽松模式,支持大陆、香港、台湾)
|
||||
*/
|
||||
PHONE_LOOSE("手机号码校验(宽松)", PhoneValidateMethod.class,
|
||||
() -> new PhoneValidateMethod(false)),
|
||||
|
||||
/**
|
||||
* 邮箱地址校验
|
||||
*/
|
||||
EMAIL("邮箱地址校验", EmailValidateMethod.class, EmailValidateMethod::new),
|
||||
|
||||
/**
|
||||
* URL链接校验
|
||||
*/
|
||||
URL("URL链接校验", UrlValidateMethod.class, UrlValidateMethod::new),
|
||||
|
||||
/**
|
||||
* HTTPS链接校验
|
||||
*/
|
||||
HTTPS_URL("HTTPS链接校验", UrlValidateMethod.class,
|
||||
() -> new UrlValidateMethod(true)),
|
||||
|
||||
/**
|
||||
* 银行卡号校验
|
||||
*/
|
||||
BANK_CARD("银行卡号校验", BankCardValidateMethod.class, BankCardValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 中文字符校验(纯中文)
|
||||
*/
|
||||
CHINESE("中文字符校验", ChineseValidateMethod.class, ChineseValidateMethod::new),
|
||||
|
||||
/**
|
||||
* 中文字符校验(允许标点符号)
|
||||
*/
|
||||
CHINESE_WITH_PUNCTUATION("中文字符校验(含标点)", ChineseValidateMethod.class,
|
||||
() -> new ChineseValidateMethod(true));
|
||||
|
||||
/**
|
||||
* 校验方法名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 校验方法实现类
|
||||
*/
|
||||
private final Class<? extends ValidateMethod> methodClass;
|
||||
|
||||
/**
|
||||
* 校验方法实例提供者
|
||||
*/
|
||||
private final Supplier<ValidateMethod> methodSupplier;
|
||||
|
||||
ValidateMethodType(String name, Class<? extends ValidateMethod> methodClass,
|
||||
Supplier<ValidateMethod> methodSupplier) {
|
||||
this.name = name;
|
||||
this.methodClass = methodClass;
|
||||
this.methodSupplier = methodSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取校验方法名称
|
||||
* @return String
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取校验方法实现类
|
||||
* @return Class
|
||||
*/
|
||||
public Class<? extends ValidateMethod> getMethodClass() {
|
||||
return methodClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建校验方法实例
|
||||
* @return ValidateMethod
|
||||
*/
|
||||
public ValidateMethod createInstance() {
|
||||
return methodSupplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据名称获取枚举
|
||||
* @param name 名称
|
||||
* @return ValidateMethodType
|
||||
*/
|
||||
public static ValidateMethodType fromName(String name) {
|
||||
for (ValidateMethodType type : values()) {
|
||||
if (type.getName().equals(name)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user