create: excel导入导出工具类提交

This commit is contained in:
Damonny 2025-03-13 15:20:01 +08:00
parent 3ec00f30fb
commit 967dcf182f
28 changed files with 5327 additions and 1 deletions

View File

@ -52,6 +52,30 @@
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<!-- Apache Commons Pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Commons Io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!-- Commons Fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<!-- Google 图片处理工具 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>

View File

@ -0,0 +1,310 @@
package cn.iocoder.yudao.framework.common.constants;
/**
* 通用常量信息
*
* @author 范劲松
* @date 2022/08/05 16:05
*/
public class Constants {
/**
* UTF-8 字符集
*/
public static final String UTF8 = "UTF-8";
/**
* GBK 字符集
*/
public static final String GBK = "GBK";
/**
* www主域
*/
public static final String WWW = "www.";
/**
* RMI 远程方法调用
*/
public static final String LOOKUP_RMI = "rmi:";
/**
* LDAP 远程方法调用
*/
public static final String LOOKUP_LDAP = "ldap:";
/**
* LDAPS 远程方法调用
*/
public static final String LOOKUP_LDAPS = "ldaps:";
/**
* http请求
*/
public static final String HTTP = "http://";
/**
* https请求
*/
public static final String HTTPS = "https://";
/**
* 成功标记
*/
public static final Integer SUCCESS = 200;
/**
* 失败标记
*/
public static final Integer FAIL = 500;
/**
* 登录成功状态
*/
public static final String LOGIN_SUCCESS_STATUS = "0";
/**
* 登录失败状态
*/
public static final String LOGIN_FAIL_STATUS = "1";
/**
* 登录成功
*/
public static final String LOGIN_SUCCESS = "LoginSuccess";
/**
* 注销
*/
public static final String LOGOUT = "Logout";
/**
* 注册
*/
public static final String REGISTER = "Register";
/**
* 登录失败
*/
public static final String LOGIN_FAIL = "Error";
/**
* 当前记录起始索引
*/
public static final String PAGE_NUM = "pageNum";
/**
* 每页显示记录数
*/
public static final String PAGE_SIZE = "pageSize";
/**
* 排序列
*/
public static final String ORDER_BY_COLUMN = "orderByColumn";
/**
* 排序的方向 "desc" 或者 "asc".
*/
public static final String IS_ASC = "isAsc";
/**
* 验证码 redis key
*/
public static final String CAPTCHA_CODE_KEY = "captcha_code:";
/**
* 验证码有效期分钟
*/
public static final long CAPTCHA_EXPIRATION = 2;
/**
* 资源映射路径 前缀
*/
public static final String RESOURCE_PREFIX = "/profile";
/**
* 字典翻译文本后缀
* */
public static final String DICT_TEXT_SUFFIX = "_dictText";
//*********数据库类型****************************************
/**MYSQL数据库*/
public static final String DB_TYPE_MYSQL = "MYSQL";
/** ORACLE*/
public static final String DB_TYPE_ORACLE = "ORACLE";
/**达梦数据库*/
public static final String DB_TYPE_DM = "DM";
/**postgreSQL达梦数据库*/
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
/**sqlserver数据库*/
public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
/**mariadb 数据库*/
public static final String DB_TYPE_MARIADB = "MARIADB";
/**DB2 数据库*/
public static final String DB_TYPE_DB2 = "DB2";
/**HSQL 数据库*/
public static final String DB_TYPE_HSQL = "HSQL";
/**
* gateway通过header传递根路径 basePath
*/
public static String X_GATEWAY_BASE_PATH = "X_GATEWAY_BASE_PATH";
/**
* 符号
*/
public static final String SPOT = ".";
/**
* 符号双斜杠
*/
public static final String DOUBLE_BACKSLASH = "\\";
/**
* 符号冒号
*/
public static final String COLON = ":";
/**
* 符号逗号
*/
public static final String COMMA = ",";
/**
* 符号左花括号 }
*/
public static final String LEFT_CURLY_BRACKET = "{";
/**
* 符号右花括号 }
*/
public static final String RIGHT_CURLY_BRACKET = "}";
/**
* 符号左圆括号 (
*/
public static final String LEFT_PARENTHESES = "(";
/**
* 符号右圆括号 )
*/
public static final String RIGHT_PARENTHESES = ")";
/**
* 符号井号 #
*/
public static final String WELL_NUMBER = "#";
/**
* 符号单斜杠
*/
public static final String SINGLE_SLASH = "/";
/**
* 符号双斜杠
*/
public static final String DOUBLE_SLASH = "//";
/**
* 符号感叹号
*/
public static final String EXCLAMATORY_MARK = "!";
/**
* 符号下划线
*/
public static final String UNDERLINE = "_";
/**
* 符号单引号
*/
public static final String SINGLE_QUOTATION_MARK = "'";
/**
* 符号星号
*/
public static final String ASTERISK = "*";
/**
* 符号百分号
*/
public static final String PERCENT_SIGN = "%";
/**
* 符号美元 $
*/
public static final String DOLLAR = "$";
/**
* 符号 &
*/
public static final String AND = "&";
/**
* 符号../
*/
public static final String SPOT_SINGLE_SLASH = "../";
/**
* 符号..\\
*/
public static final String SPOT_DOUBLE_BACKSLASH = "..\\";
/**
* 系统变量前缀 #{
*/
public static final String SYS_VAR_PREFIX = "#{";
/**
* 符号 {{
*/
public static final String DOUBLE_LEFT_CURLY_BRACKET = "{{";
/**
* 符号[
*/
public static final String SQUARE_BRACKETS_LEFT = "[";
/**
* 符号]
*/
public static final String SQUARE_BRACKETS_RIGHT = "]";
/**
* 数字1
*/
public static final String ONE_STR = "1";
/**
* 数字0
*/
public static final String ZERO_STR = "0";
/**
* 符号=
*/
public static final String EQUALS_SIGN = "=";
/**
* 成功 数字1
*/
public static final int TRUE_INT = 1;
/**
* 失败 数字0
*/
public static final int FALSE_INT = 0;
}

View File

@ -0,0 +1,172 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.hutool.core.text.StrFormatter;
/**
* 响应状态码枚举
*
* @author 范劲松
* @date 2022/08/06 13:44
*/
public enum ResultEnum {
/**
* 枚举状态对应码
*/
ILLEGAL_OPERATION(-1, "非法操作"),
SUCCESS_CODE(200, "操作成功"),
LOGIN_SUCCESS(201, "登录成功"),
SIGN_IN_SUCCESS(202, "注册成功"),
SEND_VALID_CODE(205, "验证码已发送,请耐心等待~"),
LOGIN_OUT_SUCCESS(207, "退出登录成功"),
WARNING(300, "操作警告"),
AOP_PARAM_ERROR(409, "参数绑定异常"),
SMS_SEND_ERROR(410, "短信发送失败"),
FILE_SAVE_ERROR(418, "文件保存失败!"),
FILE_SIZE_LIMIT_ERROR(419, "文件过大"),
FILE_EXTENSION_NOT_ALLOWED(420, "文件后缀不是允许上传的范围"),
OUT_DATE_VALIDATE_IMAGE(421, "校验图片已过期"),
SYSTEM_DATA_ERROR(423, "禁止操作系统创建的数据"),
LOCKED(424, "请求失败,请稍后重试"),
INFO_NOT_FOUND(430, "未查询到相关信息"),
IMPORT_DATA_IS_NULL(432, "导入的数据为空"),
LOGIN_COUNT_MAX(433, "登录错误次数超过{}次,请{}分钟后再试"),
CAPTCHA_CHECK_FAIL(444, "验证码错误"),
NO_TOKEN(1001, "未提供访问令牌"),
INVALID_TOKEN(1002, "登录信息失效"),
INVALID_CHECK_TOKEN(1003, "令牌验证失败"),
NOT_PERMISSION_ERROR(1007, "无访问权限"),
JWT_VALID_ERROR(1008, "Token校验错误"),
ERROR_2001(2001, "统计日期纬度错误"),
NO_HAVE_APP_ERROR(1131, "app不存在, 请先联系管理员申请app"),
APP_SIGN_ERROR(1132, "签名出错"),
MAX_DAY_THIRTY(9500, "最多查询30天数据"),
MAX_DAY_EXPORT(9180, "仅支持导出180天内时间段内车流信息不执行导出。"),
EXPORT_IS_NULL(9181, "数据为空"),
UNKNOWN_ERROR(10000, "未知错误"),
BUSY_BUSINESS(10001, "业务繁忙"),
BUSINESS_ERROR(10002, "业务异常"),
SERVICE_ERROR(10003, "服务异常"),
DATA_ERROR(10004, "数据异常"),
SIGN_DISABLE(10005, "暂停访问"),
FIELD_VALIDATION_ERROR(10006, "数据校验异常"),
DEFAULT_FAILED_MSG(10008, "短信服务繁忙,请稍后再请求"),
SENSITIVE_DEFAULT_FAILED(10009, "内容合法性校验服务繁忙"),
REMOTE_SERVICE_ERROR(10010, "远程服务异常"),
REPEATED_REQUESTS(10011, "重复请求,请稍后重试"),
MAX_MESSAGE_SEND_ERROR(10020,"短信发送上限"),
EMPOWER_SERVICE_ERROR(10010, "第三方授权服务异常"),
GET_ERROR_VALIDATE_CODE(15000, "验证码获取失败"),
OUT_DATE_VALIDATE_CODE(15001, "验证码已过期"),
INVALID_VALIDATE_CODE(15002, "无效的验证码"),
PROJECT_BUSINESS(16001,"请选择至少一项业务"),
PROJECT_DEVICE(16002,"视联网未返回导入失败设备数据"),
PROJECT_EXCEL(16003,"设备导入失败"),
PROJECT_FAILLIST(16004,"导入失败的设备清单"),
DEVICE_NOT_EXIST(16005,"设备不存在"),
DECODE_ERROR(16006,"解密视联网参数发生异常"),
USER_LOGIN_FAILED(20000, "登录失败"),
USER_NOT_FOUND(20001, "账号密码错误"), // 原用户不存在
TELEPHONE_NOT_SIGN_IN(20002, "该手机号尚未注册"),
TELEPHONE_ALREADY_EXIST(20003, "手机号码已经存在"),
ACCOUNT_OR_PWD_ERROR(20004, "用户名或密码错误"),
NOT_BIND_PHONE(20005, "未绑定手机号"),
USER_STATUS_IS_DISABLED(20006, "账号已停用"),
USER_IS_FOUND(20007, "用户已存在"),
USER_ADD_FAILED(20008, "用户新增失败"),
USER_EDIT_FAILED(20009, "用户编辑失败"),
USER_THIS_DONT_DELETE(20010, "当前用户不能删除"),
ACCOUNT_ALREADY_EXIST(20011, "账号已经存在"),
PWD_OLR_NEW_NOT_EQUALS(20012, "新旧密码不能相同"),
OLD_PWD_VALID_FAILED(20013, "旧密码验证错误"),
USER_EDIT_FAILED_IS_SYSTEM(20014, "系统用户禁止操作"),
USER_LOGIN_FAILED_NO_ORG(20015, "用户登录失败: 无可用组织"),
USER_PASSWORD_CHANGE_FAILED(20016, "用户密码更改失败"),
PROHIBIT_UNBINDING_CURRENTLY_USER(20017, "禁止解绑当前登录用户"),
ACCOUNT_NOT_BIND_CURRENTLY_USER(20018, "当前账号为绑定该用户"),
ACCOUNT_HAS_NOT_UNBIND_USER(20019, "账号下仍有未解绑的用户"),
ORIGINAL_PHONE_OR_PASSWORD_ERROR(20020, "原手机号或密码错误!"),
ACCOUNT_IS_ACTIVATION(20021, "账号已被激活,无需此方式修改密码!"),
FORBIDDEN_EDIT_SELF(20022, "用户编辑失败:禁止编辑自己"),
ORG_IS_NOT_FOUND(20030, "组织不存在"),
ORG_IS_FOUND(20031, "组织已存在"),
ORG_DONT_DELETE(20032, "禁止删除!请将人员移出组织后重试~"),
ORG_DONT_DELETE_HAS_CHILDREN(20033, "禁止删除!请将人员移出组织后重试~"),
ENOUGH_ORG_ERROR(20033, "不可以添加更多的组织架构了"),
ORG_STATUS_FALSE_ERROR(20034, "您的组织已被停用"),
ORG_BIND_FAILED(20035, "关联组织失败"),
ROLE_IS_NOT_FOUND(20050, "角色不存在"),
ROLE_IS_FOUND(20051, "角色已存在"),
ROLE_ADD_FAILED(20052, "角色新增失败"),
ROLE_EDIT_FAILED(20053, "角色编辑失败"),
ROLE_DONT_DELETE(20054, "禁止删除!当前角色已分配"),
ROLE_IS_FIND_IN_DEPT(20055, "角色名已在部门存在"),
PROJECT_CLOSED(20056, "项目已被关闭"),
REGION_IS_NOT_FOUND(20070, "行政区划不存在"),
ENOUGH_REGION_ERROR(20071, "不可以添加更多的行政区划了"),
USER_NO_USERNAME_INCOMING(20153, "未传入用户姓名"),
USER_NO_TELEPHONE_INCOMING(20154, "未传入用户电话号码"),
TELEPHONE_CHECK_FAILED(20155, "手机号不是原手机号"),
USER_NO_GENDER_INCOMING(20156, "未传入用户性别"),
USER_NO_ACCOUNT_INCOMING(20157, "未传入用户用户名"),
SECURITY_EVENT_CANT_HANDLE_AGAIN(20158, "该告警事件已处理,不得二次处理"),
DEVICE_CANT_ADD_AGING(20159, "当前监控页已添加该设备"),
DEVICE_MAX_NUM_NINE(20160, "监控页最多绑定9个设备"),
SEND_VOICE_LIMIT(20161, "两次语音过于频繁请间隔6s以上。"),
NO_BIND_AI_APPLY(20162, "未绑定AI应用"),
MAKE_BUCKET_FAILED(31001, "创建桶失败"),
REDIS_LOCK_FAILED(31002, "获取锁失败"),
SOCKET_BUSINESS_NOT_FOUND(32001, "未找到业务实现"),
CHECK_FAILURE(4000, "签名校验失败"),
PARAMETER_MISSING(4001, "鉴权参数缺失"),
SIGN_CANNOT_BE_REUSED(4005, "Sign不可重复使用"),
;
/**
* 响应状态码
*/
private final Integer code;
/**
* 响应信息内容
*/
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
public Boolean equals(ResultEnum en) {
return this.getCode().equals(en.getCode());
}
public ResultEnum format(Object... str) {
this.msg = StrFormatter.format(this.msg, str);
return this;
}
}

View File

@ -0,0 +1,81 @@
package cn.iocoder.yudao.framework.common.exception;
import cn.iocoder.yudao.framework.common.enums.ResultEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 自定义异常
*
* @author 范劲松
* @date 2022/08/06 13:46
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class CustomException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code = 500;
private String message;
public CustomException(String message) {
this.message = message;
}
public CustomException(String message, Integer code) {
this.message = message;
this.code = code;
}
public CustomException(String message, Throwable e) {
super(message, e);
this.message = message;
}
/**
* 业务异常
*
* @param resultEnum 异常状态编码
*/
public CustomException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
this.message = resultEnum.getMsg();
}
/**
* 业务异常
*
* @param errorCode 异常状态编码
*/
public CustomException(ErrorCode errorCode) {
super(errorCode.getMsg());
this.code = errorCode.getCode();
this.message = errorCode.getMsg();
}
/**
* 业务异常
*
* @param resultEnum 异常状态编码
*/
public CustomException(ResultEnum resultEnum, String message) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
this.message = message;
}
/**
* 业务异常
*
* @param errorCode 异常状态编码
*/
public CustomException(ErrorCode errorCode, String message) {
super(errorCode.getMsg());
this.code = errorCode.getCode();
this.message = message;
}
}

View File

@ -0,0 +1,414 @@
package cn.iocoder.yudao.framework.common.reflect;
import cn.iocoder.yudao.framework.common.text.Convert;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.*;
import java.util.Date;
/**
* 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
*
* @author 范劲松
* @date 2022/08/06 13:11
*/
public class ReflectUtils {
private static final String SETTER_PREFIX = "set";
private static final String GETTER_PREFIX = "get";
private static final String CGLIB_CLASS_SEPARATOR = "$$";
private static final Logger logger = LoggerFactory.getLogger(ReflectUtils.class);
/**
* 调用Getter方法.
* 支持多级对象名.对象名.方法
*/
@SuppressWarnings("unchecked")
public static <E> E invokeGetter(Object obj, String propertyName)
{
Object object = obj;
for (String name : StringUtils.split(propertyName, "."))
{
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}
return (E) object;
}
/**
* 调用Setter方法, 仅匹配方法名
* 支持多级对象名.对象名.方法
*/
public static <E> void invokeSetter(Object obj, String propertyName, E value)
{
Object object = obj;
String[] names = StringUtils.split(propertyName, ".");
for (int i = 0; i < names.length; i++)
{
if (i < names.length - 1)
{
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}
else
{
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
invokeMethodByName(object, setterMethodName, new Object[] { value });
}
}
}
/**
* 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
*/
@SuppressWarnings("unchecked")
public static <E> E getFieldValue(final Object obj, final String fieldName)
{
Field field = getAccessibleField(obj, fieldName);
if (field == null)
{
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
return null;
}
E result = null;
try
{
result = (E) field.get(obj);
}
catch (IllegalAccessException e)
{
logger.error("不可能抛出的异常{}", e.getMessage());
}
return result;
}
/**
* 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
*/
public static <E> void setFieldValue(final Object obj, final String fieldName, final E value)
{
Field field = getAccessibleField(obj, fieldName);
if (field == null)
{
// throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
return;
}
try
{
field.set(obj, value);
}
catch (IllegalAccessException e)
{
logger.error("不可能抛出的异常: {}", e.getMessage());
}
}
/**
* 直接调用对象方法, 无视private/protected修饰符.
* 用于一次性调用的情况否则应使用getAccessibleMethod()函数获得Method后反复调用.
* 同时匹配方法名+参数类型
*/
@SuppressWarnings("unchecked")
public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
final Object[] args)
{
if (obj == null || methodName == null)
{
return null;
}
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null)
{
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
return null;
}
try
{
return (E) method.invoke(obj, args);
}
catch (Exception e)
{
String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
throw convertReflectionExceptionToUnchecked(msg, e);
}
}
/**
* 直接调用对象方法, 无视private/protected修饰符
* 用于一次性调用的情况否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
* 只匹配函数名如果有多个同名函数调用第一个
*/
@SuppressWarnings("unchecked")
public static <E> E invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
Method method = getAccessibleMethodByName(obj, methodName, args.length);
return invokeMethod(obj, methodName, method, args);
}
/**
* 直接调用对象方法, 无视private/protected修饰符
*
* 只匹配函数名如果有多个同名函数调用第一个
*/
@SuppressWarnings("unchecked")
public static <E> E invokeMethod(final Object obj, final String methodName, final Method method, final Object[] args) {
if (method == null) {
// 如果为空不报错直接返回空
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
return null;
}
try
{
// 类型转换将参数数据类型转换为目标方法参数类型
Class<?>[] cs = method.getParameterTypes();
for (int i = 0; i < cs.length; i++)
{
if (args[i] != null && !args[i].getClass().equals(cs[i]))
{
if (cs[i] == String.class)
{
args[i] = Convert.toStr(args[i]);
if (StringUtils.endsWith((String) args[i], ".0"))
{
args[i] = StringUtils.substringBefore((String) args[i], ".0");
}
}
else if (cs[i] == Integer.class)
{
args[i] = Convert.toInt(args[i]);
}
else if (cs[i] == Long.class)
{
args[i] = Convert.toLong(args[i]);
}
else if (cs[i] == Double.class)
{
args[i] = Convert.toDouble(args[i]);
}
else if (cs[i] == Float.class)
{
args[i] = Convert.toFloat(args[i]);
}
else if (cs[i] == Date.class)
{
if (args[i] instanceof String)
{
args[i] = DateUtils.parseDate(args[i]);
}
else
{
args[i] = DateUtils.getJavaDate((Double) args[i]);
}
}
else if (cs[i] == boolean.class || cs[i] == Boolean.class)
{
args[i] = Convert.toBool(args[i]);
}
}
}
return (E) method.invoke(obj, args);
}
catch (Exception e)
{
String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
throw convertReflectionExceptionToUnchecked(msg, e);
}
}
/**
* 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
* 如向上转型到Object仍无法找到, 返回null.
*/
public static Field getAccessibleField(final Object obj, final String fieldName)
{
// 为空不报错直接返回 null
if (obj == null)
{
return null;
}
Validate.notBlank(fieldName, "fieldName can't be blank");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass())
{
try
{
Field field = superClass.getDeclaredField(fieldName);
makeAccessible(field);
return field;
}
catch (NoSuchFieldException e)
{
continue;
}
}
return null;
}
/**
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
* 如向上转型到Object仍无法找到, 返回null.
* 匹配函数名+参数类型
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethod(final Object obj, final String methodName,
final Class<?>... parameterTypes)
{
// 为空不报错直接返回 null
if (obj == null)
{
return null;
}
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
{
try
{
Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
makeAccessible(method);
return method;
}
catch (NoSuchMethodException e)
{
continue;
}
}
return null;
}
/**
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
* 如向上转型到Object仍无法找到, 返回null.
* 只匹配函数名
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum)
{
// 为空不报错直接返回 null
if (obj == null)
{
return null;
}
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
{
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods)
{
if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum)
{
makeAccessible(method);
return method;
}
}
}
return null;
}
/**
* 改变private/protected的方法为public尽量不调用实际改动的语句避免JDK的SecurityManager抱怨
*/
public static void makeAccessible(Method method)
{
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible())
{
method.setAccessible(true);
}
}
/**
* 改变private/protected的成员变量为public尽量不调用实际改动的语句避免JDK的SecurityManager抱怨
*/
public static void makeAccessible(Field field)
{
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
|| Modifier.isFinal(field.getModifiers())) && !field.isAccessible())
{
field.setAccessible(true);
}
}
/**
* 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
* 如无法找到, 返回Object.class.
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> getClassGenricType(final Class clazz)
{
return getClassGenricType(clazz, 0);
}
/**
* 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
* 如无法找到, 返回Object.class.
*/
public static Class getClassGenricType(final Class clazz, final int index)
{
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType))
{
logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType");
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0)
{
logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+ params.length);
return Object.class;
}
if (!(params[index] instanceof Class))
{
logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
return Object.class;
}
return (Class) params[index];
}
public static Class<?> getUserClass(Object instance)
{
if (instance == null)
{
throw new RuntimeException("Instance must not be null");
}
Class clazz = instance.getClass();
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR))
{
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && !Object.class.equals(superClass))
{
return superClass;
}
}
return clazz;
}
/**
* 将反射时的checked exception转换为unchecked exception.
*/
public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e)
{
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|| e instanceof NoSuchMethodException)
{
return new IllegalArgumentException(msg, e);
}
else if (e instanceof InvocationTargetException)
{
return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());
}
return new RuntimeException(msg, e);
}
}

View File

@ -0,0 +1,96 @@
package cn.iocoder.yudao.framework.common.text;
import cn.iocoder.yudao.framework.common.util.string.StringUtils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* 字符集工具类
*
* @author 范劲松
* @date 2022/08/06 12:53
*/
public class CharsetKit {
/**
* ISO-8859-1
*/
public static final String ISO_8859_1 = "ISO-8859-1";
/**
* UTF-8
*/
public static final String UTF_8 = "UTF-8";
/**
* GBK
*/
public static final String GBK = "GBK";
/**
* ISO-8859-1
*/
public static final Charset CHARSET_ISO_8859_1 = StandardCharsets.ISO_8859_1;
/**
* UTF-8
*/
public static final Charset CHARSET_UTF_8 = StandardCharsets.UTF_8;
/**
* GBK
*/
public static final Charset CHARSET_GBK = Charset.forName(GBK);
/**
* 转换为Charset对象
*
* @param charset 字符集为空则返回默认字符集
* @return Charset
*/
public static Charset charset(String charset) {
return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);
}
/**
* 转换字符串的字符集编码
*
* @param source 字符串
* @param srcCharset 源字符集默认ISO-8859-1
* @param destCharset 目标字符集默认UTF-8
* @return 转换后的字符集
*/
public static String convert(String source, String srcCharset, String destCharset) {
return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));
}
/**
* 转换字符串的字符集编码
*
* @param source 字符串
* @param srcCharset 源字符集默认ISO-8859-1
* @param destCharset 目标字符集默认UTF-8
* @return 转换后的字符集
*/
public static String convert(String source, Charset srcCharset, Charset destCharset) {
if (null == srcCharset) {
srcCharset = StandardCharsets.ISO_8859_1;
}
if (null == destCharset) {
destCharset = StandardCharsets.UTF_8;
}
if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) {
return source;
}
return new String(source.getBytes(srcCharset), destCharset);
}
/**
* @return 系统字符集编码
*/
public static String systemCharset() {
return Charset.defaultCharset().name();
}
}

View File

@ -0,0 +1,859 @@
package cn.iocoder.yudao.framework.common.text;
import cn.iocoder.yudao.framework.common.util.string.StringUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.util.Set;
/**
* 类型转换器
*
* @author 范劲松
* @date 2022/08/06 12:53
*/
public class Convert {
/**
* 转换为字符串<br>
* 如果给定的值为null或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static String toStr(Object value, String defaultValue) {
if (null == value) {
return defaultValue;
}
if (value instanceof String) {
return (String) value;
}
return value.toString();
}
/**
* 转换为字符串<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static String toStr(Object value) {
return toStr(value, null);
}
/**
* 转换为字符<br>
* 如果给定的值为null或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Character toChar(Object value, Character defaultValue) {
if (null == value) {
return defaultValue;
}
if (value instanceof Character) {
return (Character) value;
}
final String valueStr = toStr(value, null);
return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0);
}
/**
* 转换为字符<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Character toChar(Object value) {
return toChar(value, null);
}
/**
* 转换为byte<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Byte toByte(Object value, Byte defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Byte) {
return (Byte) value;
}
if (value instanceof Number) {
return ((Number) value).byteValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Byte.parseByte(valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为byte<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Byte toByte(Object value) {
return toByte(value, null);
}
/**
* 转换为Short<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Short toShort(Object value, Short defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Short) {
return (Short) value;
}
if (value instanceof Number) {
return ((Number) value).shortValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Short.parseShort(valueStr.trim());
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为Short<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Short toShort(Object value) {
return toShort(value, null);
}
/**
* 转换为Number<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Number toNumber(Object value, Number defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Number) {
return (Number) value;
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return NumberFormat.getInstance().parse(valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为Number<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Number toNumber(Object value) {
return toNumber(value, null);
}
/**
* 转换为int<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Integer toInt(Object value, Integer defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Integer) {
return (Integer) value;
}
if (value instanceof Number) {
return ((Number) value).intValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Integer.parseInt(valueStr.trim());
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为int<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Integer toInt(Object value) {
return toInt(value, null);
}
/**
* 转换为Integer数组<br>
*
* @param str 被转换的值
* @return 结果
*/
public static Integer[] toIntArray(String str) {
return toIntArray(",", str);
}
/**
* 转换为Long数组<br>
*
* @param str 被转换的值
* @return 结果
*/
public static Long[] toLongArray(String str) {
return toLongArray(",", str);
}
/**
* 转换为Integer数组<br>
*
* @param split 分隔符
* @param split 被转换的值
* @return 结果
*/
public static Integer[] toIntArray(String split, String str) {
if (StringUtils.isEmpty(str)) {
return new Integer[]{};
}
String[] arr = str.split(split);
final Integer[] ints = new Integer[arr.length];
for (int i = 0; i < arr.length; i++) {
final Integer v = toInt(arr[i], 0);
ints[i] = v;
}
return ints;
}
/**
* 转换为Long数组<br>
*
* @param split 分隔符
* @param str 被转换的值
* @return 结果
*/
public static Long[] toLongArray(String split, String str) {
if (StringUtils.isEmpty(str)) {
return new Long[]{};
}
String[] arr = str.split(split);
final Long[] longs = new Long[arr.length];
for (int i = 0; i < arr.length; i++) {
final Long v = toLong(arr[i], null);
longs[i] = v;
}
return longs;
}
/**
* 转换为String数组<br>
*
* @param str 被转换的值
* @return 结果
*/
public static String[] toStrArray(String str) {
return toStrArray(",", str);
}
/**
* 转换为String数组<br>
*
* @param split 分隔符
* @param split 被转换的值
* @return 结果
*/
public static String[] toStrArray(String split, String str) {
return str.split(split);
}
/**
* 转换为long<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Long toLong(Object value, Long defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Long) {
return (Long) value;
}
if (value instanceof Number) {
return ((Number) value).longValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
// 支持科学计数法
return new BigDecimal(valueStr.trim()).longValue();
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为long<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Long toLong(Object value) {
return toLong(value, null);
}
/**
* 转换为double<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Double toDouble(Object value, Double defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Double) {
return (Double) value;
}
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
// 支持科学计数法
return new BigDecimal(valueStr.trim()).doubleValue();
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为double<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Double toDouble(Object value) {
return toDouble(value, null);
}
/**
* 转换为Float<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Float toFloat(Object value, Float defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Float) {
return (Float) value;
}
if (value instanceof Number) {
return ((Number) value).floatValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Float.parseFloat(valueStr.trim());
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为Float<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Float toFloat(Object value) {
return toFloat(value, null);
}
/**
* 转换为boolean<br>
* String支持的值为truefalseyesokno1,0 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Boolean toBool(Object value, Boolean defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
valueStr = valueStr.trim().toLowerCase();
switch (valueStr) {
case "true":
case "yes":
case "ok":
case "1":
return true;
case "false":
case "no":
case "0":
return false;
default:
return defaultValue;
}
}
/**
* 转换为boolean<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Boolean toBool(Object value) {
return toBool(value, null);
}
/**
* 转换为Enum对象<br>
* 如果给定的值为空或者转换失败返回默认值<br>
*
* @param clazz Enum的Class
* @param value
* @param defaultValue 默认值
* @return Enum
*/
public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value, E defaultValue) {
if (value == null) {
return defaultValue;
}
if (clazz.isAssignableFrom(value.getClass())) {
@SuppressWarnings("unchecked")
E myE = (E) value;
return myE;
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Enum.valueOf(clazz, valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为Enum对象<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
*
* @param clazz Enum的Class
* @param value
* @return Enum
*/
public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value) {
return toEnum(clazz, value, null);
}
/**
* 转换为BigInteger<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static BigInteger toBigInteger(Object value, BigInteger defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof BigInteger) {
return (BigInteger) value;
}
if (value instanceof Long) {
return BigInteger.valueOf((Long) value);
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return new BigInteger(valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为BigInteger<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static BigInteger toBigInteger(Object value) {
return toBigInteger(value, null);
}
/**
* 转换为BigDecimal<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
if (value instanceof Long) {
return new BigDecimal((Long) value);
}
if (value instanceof Double) {
return new BigDecimal((Double) value);
}
if (value instanceof Integer) {
return new BigDecimal((Integer) value);
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return new BigDecimal(valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为BigDecimal<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static BigDecimal toBigDecimal(Object value) {
return toBigDecimal(value, null);
}
/**
* 将对象转为字符串<br>
* 1Byte数组和ByteBuffer会被转换为对应字符串的数组 2对象数组会调用Arrays.toString方法
*
* @param obj 对象
* @return 字符串
*/
public static String utf8Str(Object obj) {
return str(obj, CharsetKit.CHARSET_UTF_8);
}
/**
* 将对象转为字符串<br>
* 1Byte数组和ByteBuffer会被转换为对应字符串的数组 2对象数组会调用Arrays.toString方法
*
* @param obj 对象
* @param charsetName 字符集
* @return 字符串
*/
public static String str(Object obj, String charsetName) {
return str(obj, Charset.forName(charsetName));
}
/**
* 将对象转为字符串<br>
* 1Byte数组和ByteBuffer会被转换为对应字符串的数组 2对象数组会调用Arrays.toString方法
*
* @param obj 对象
* @param charset 字符集
* @return 字符串
*/
public static String str(Object obj, Charset charset) {
if (null == obj) {
return null;
}
if (obj instanceof String) {
return (String) obj;
} else if (obj instanceof byte[] || obj instanceof Byte[]) {
if (obj instanceof byte[]) {
return str((byte[]) obj, charset);
} else {
Byte[] bytes = (Byte[]) obj;
int length = bytes.length;
byte[] dest = new byte[length];
for (int i = 0; i < length; i++) {
dest[i] = bytes[i];
}
return str(dest, charset);
}
} else if (obj instanceof ByteBuffer) {
return str((ByteBuffer) obj, charset);
}
return obj.toString();
}
/**
* 将byte数组转为字符串
*
* @param bytes byte数组
* @param charset 字符集
* @return 字符串
*/
public static String str(byte[] bytes, String charset) {
return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset));
}
/**
* 解码字节码
*
* @param data 字符串
* @param charset 字符集如果此字段为空则解码的结果取决于平台
* @return 解码后的字符串
*/
public static String str(byte[] data, Charset charset) {
if (data == null) {
return null;
}
if (null == charset) {
return new String(data);
}
return new String(data, charset);
}
/**
* 将编码的byteBuffer数据转换为字符串
*
* @param data 数据
* @param charset 字符集如果为空使用当前系统字符集
* @return 字符串
*/
public static String str(ByteBuffer data, String charset) {
if (data == null) {
return null;
}
return str(data, Charset.forName(charset));
}
/**
* 将编码的byteBuffer数据转换为字符串
*
* @param data 数据
* @param charset 字符集如果为空使用当前系统字符集
* @return 字符串
*/
public static String str(ByteBuffer data, Charset charset) {
if (null == charset) {
charset = Charset.defaultCharset();
}
return charset.decode(data).toString();
}
// ----------------------------------------------------------------------- 全角半角转换
/**
* 半角转全角
*
* @param input String.
* @return 全角字符串.
*/
public static String toSbc(String input) {
return toSbc(input, null);
}
/**
* 半角转全角
*
* @param input String
* @param notConvertSet 不替换的字符集合
* @return 全角字符串.
*/
public static String toSbc(String input, Set<Character> notConvertSet) {
char[] c = input.toCharArray();
for (int i = 0; i < c.length; i++) {
if (null != notConvertSet && notConvertSet.contains(c[i])) {
// 跳过不替换的字符
continue;
}
if (c[i] == ' ') {
c[i] = '\u3000';
} else if (c[i] < '\177') {
c[i] = (char) (c[i] + 65248);
}
}
return new String(c);
}
/**
* 全角转半角
*
* @param input String.
* @return 半角字符串
*/
public static String toDbc(String input) {
return toDbc(input, null);
}
/**
* 替换全角为半角
*
* @param text 文本
* @param notConvertSet 不替换的字符集合
* @return 替换后的字符
*/
public static String toDbc(String text, Set<Character> notConvertSet) {
char[] c = text.toCharArray();
for (int i = 0; i < c.length; i++) {
if (null != notConvertSet && notConvertSet.contains(c[i])) {
// 跳过不替换的字符
continue;
}
if (c[i] == '\u3000') {
c[i] = ' ';
} else if (c[i] > '\uFF00' && c[i] < '\uFF5F') {
c[i] = (char) (c[i] - 65248);
}
}
String returnString = new String(c);
return returnString;
}
/**
* 数字金额大写转换 先写个完整的然后将如零拾替换成零
*
* @param n 数字
* @return 中文大写数字
*/
public static String digitUppercase(double n) {
String[] fraction = {"", ""};
String[] digit = {"", "", "", "", "", "", "", "", "", ""};
String[][] unit = {{"", "", "亿"}, {"", "", "", ""}};
String head = n < 0 ? "" : "";
n = Math.abs(n);
String s = "";
for (int i = 0; i < fraction.length; i++) {
s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
}
if (s.length() < 1) {
s = "";
}
int integerPart = (int) Math.floor(n);
for (int i = 0; i < unit[0].length && integerPart > 0; i++) {
String p = "";
for (int j = 0; j < unit[1].length && n > 0; j++) {
p = digit[integerPart % 10] + unit[1][j] + p;
integerPart = integerPart / 10;
}
s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "") + unit[0][i] + s;
}
return head + s.replaceAll("(零.)*零元", "").replaceFirst("(零.)+", "").replaceAll("(零.)+", "").replaceAll("^整$", "零元整");
}
}

View File

@ -0,0 +1,80 @@
package cn.iocoder.yudao.framework.common.text;
import cn.iocoder.yudao.framework.common.util.string.StringUtils;
/**
* 字符串格式化
*
* @author 范劲松
* @date 2022/08/06 12:54
*/
public class StrFormatter {
public static final String EMPTY_JSON = "{}";
public static final char C_BACKSLASH = '\\';
public static final char C_DELIM_START = '{';
public static final char C_DELIM_END = '}';
/**
* 格式化字符串<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
* 如果想输出 {} 使用 \\转义 { 即可如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* <br>
* 通常使用format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{} format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义\ format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param strPattern 字符串模板
* @param argArray 参数列表
* @return 结果
*/
public static String format(final String strPattern, final Object... argArray) {
if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) {
return strPattern;
}
final int strPatternLength = strPattern.length();
// 初始化定义好的长度以获得更好的性能
StringBuilder sbuf = new StringBuilder(strPatternLength + 50);
int handledPosition = 0;
int delimIndex;// 占位符所在位置
for (int argIndex = 0; argIndex < argArray.length; argIndex++) {
delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition);
if (delimIndex == -1) {
if (handledPosition == 0) {
return strPattern;
} else { // 字符串模板剩余部分不再包含占位符加入剩余部分后返回结果
sbuf.append(strPattern, handledPosition, strPatternLength);
return sbuf.toString();
}
} else {
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) {
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) {
// 转义符之前还有一个转义符占位符依旧有效
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(Convert.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + 2;
} else {
// 占位符被转义
argIndex--;
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(C_DELIM_START);
handledPosition = delimIndex + 1;
}
} else {
// 正常占位符
sbuf.append(strPattern, handledPosition, delimIndex);
sbuf.append(Convert.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + 2;
}
}
}
// 加入最后一个占位符后所有的字符
sbuf.append(strPattern, handledPosition, strPattern.length());
return sbuf.toString();
}
}

View File

@ -1,17 +1,22 @@
package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.date.LocalDateTimeUtil;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* 时间工具类
*
* @author 芋道源码
*/
public class DateUtils {
public class DateUtils extends org.apache.commons.lang3.time.DateUtils{
/**
* 时区 - 默认
@ -27,6 +32,247 @@ public class DateUtils {
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
public static final String YYYY = "yyyy";
public static final String YYYY_MM = "yyyy-MM";
public static final String YYYY_MM_DD = "yyyy-MM-dd";
public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
private static final String[] PARSE_PATTERNS = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
/**
* 获取当前Date型日期
*
* @return Date() 当前日期
*/
public static Date getNowDate() {
return new Date();
}
/**
* 获取当前日期, 默认格式为yyyy-MM-dd
*
* @return String
*/
public static String getDate() {
return dateTimeNow(YYYY_MM_DD);
}
public static final String getTime() {
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
}
public static final String dateTimeNow() {
return dateTimeNow(YYYYMMDDHHMMSS);
}
public static final String dateTimeNow(final String format) {
return parseDateToStr(format, new Date());
}
public static final String dateTime(final Date date) {
return parseDateToStr(YYYY_MM_DD, date);
}
public static final String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
}
public static final Date dateTime(final String format, final String ts) {
try {
return new SimpleDateFormat(format).parse(ts);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* 日期路径 即年// 如2018/08/08
*/
public static final String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}
/**
* 日期路径 即年// 如20180808
*/
public static final String dateTime() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}
/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object str) {
if (str == null) {
return null;
}
try {
return parseDate(str.toString(), PARSE_PATTERNS);
} catch (ParseException e) {
return null;
}
}
/**
* 获取服务器启动时间
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
/**
* 计算两个时间差
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "" + hour + "小时" + min + "分钟";
}
/**
* 增加 LocalDateTime ==> Date
*/
public static Date toDate(LocalDateTime temporalAccessor) {
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 增加 LocalDate ==> Date
*/
public static Date toDate(LocalDate temporalAccessor) {
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
public static Date getJavaDate(double date, TimeZone tz) {
return getJavaDate(date, false, tz, false);
}
public static Date getJavaDate(double date) {
return getJavaDate(date, false, null, false);
}
public static Date getJavaDate(double date, boolean use1904windowing, TimeZone tz) {
return getJavaDate(date, use1904windowing, tz, false);
}
public static Date getJavaDate(double date, boolean use1904windowing, TimeZone tz, boolean roundSeconds) {
Calendar calendar = getJavaCalendar(date, use1904windowing, tz, roundSeconds);
return calendar == null ? null : calendar.getTime();
}
public static Date getJavaDate(double date, boolean use1904windowing) {
return getJavaDate(date, use1904windowing, null, false);
}
public static void setCalendar(Calendar calendar, int wholeDays, int millisecondsInDay, boolean use1904windowing, boolean roundSeconds) {
int startYear = 1900;
int dayAdjust = -1;
if (use1904windowing) {
startYear = 1904;
dayAdjust = 1;
} else if (wholeDays < 61) {
dayAdjust = 0;
}
calendar.set(startYear, 0, wholeDays + dayAdjust, 0, 0, 0);
calendar.set(14, millisecondsInDay);
if (calendar.get(14) == 0) {
calendar.clear(14);
}
if (roundSeconds) {
calendar.add(14, 500);
calendar.clear(14);
}
}
public static Calendar getJavaCalendar(double date) {
return getJavaCalendar(date, false, null, false);
}
public static Calendar getJavaCalendar(double date, boolean use1904windowing) {
return getJavaCalendar(date, use1904windowing, null, false);
}
public static Calendar getJavaCalendarUtc(double date, boolean use1904windowing) {
return getJavaCalendar(date, use1904windowing, LocaleUtil.TIMEZONE_UTC, false);
}
public static Calendar getJavaCalendar(double date, boolean use1904windowing, TimeZone timeZone) {
return getJavaCalendar(date, use1904windowing, timeZone, false);
}
public static Calendar getJavaCalendar(double date, boolean use1904windowing, TimeZone timeZone, boolean roundSeconds) {
if (!isValidExcelDate(date)) {
return null;
} else {
int wholeDays = (int) Math.floor(date);
int millisecondsInDay = (int) ((date - (double) wholeDays) * 8.64E7D + 0.5D);
Calendar calendar;
if (timeZone != null) {
calendar = LocaleUtil.getLocaleCalendar(timeZone);
} else {
calendar = LocaleUtil.getLocaleCalendar();
}
setCalendar(calendar, wholeDays, millisecondsInDay, use1904windowing, roundSeconds);
return calendar;
}
}
public static boolean isValidExcelDate(double value) {
return value > -4.9E-324D;
}
/**
* 字符串转换成日期
*
* @param str
* @param sdf
* @return
*/
public static Date str2Date(String str, SimpleDateFormat sdf) {
if (null == str || "".equals(str)) {
return null;
}
Date date = null;
try {
date = sdf.parse(str);
return date;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
/**
* LocalDateTime 转换成 Date
*

View File

@ -0,0 +1,69 @@
package cn.iocoder.yudao.framework.common.util.date;
import java.nio.charset.Charset;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
/**
*
*
* @author 范劲松
* @date 2022/08/06 13:17
*/
public class LocaleUtil {
public static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone("UTC");
public static final Charset CHARSET_1252 = Charset.forName("CP1252");
private static final ThreadLocal<TimeZone> USER_TIME_ZONE = new ThreadLocal();
private static final ThreadLocal<Locale> USER_LOCALE = new ThreadLocal();
private LocaleUtil() {
}
public static void setUserTimeZone(TimeZone timezone) {
USER_TIME_ZONE.set(timezone);
}
public static TimeZone getUserTimeZone() {
TimeZone timeZone = USER_TIME_ZONE.get();
return timeZone != null ? timeZone : TimeZone.getDefault();
}
public static void resetUserTimeZone() {
USER_TIME_ZONE.remove();
}
public static void setUserLocale(Locale locale) {
USER_LOCALE.set(locale);
}
public static Locale getUserLocale() {
Locale locale = USER_LOCALE.get();
return locale != null ? locale : Locale.getDefault();
}
public static void resetUserLocale() {
USER_LOCALE.remove();
}
public static Calendar getLocaleCalendar() {
return getLocaleCalendar(getUserTimeZone());
}
public static Calendar getLocaleCalendar(int year, int month, int day) {
return getLocaleCalendar(year, month, day, 0, 0, 0);
}
public static Calendar getLocaleCalendar(int year, int month, int day, int hour, int minute, int second) {
Calendar cal = getLocaleCalendar();
cal.set(year, month, day, hour, minute, second);
cal.clear(14);
return cal;
}
public static Calendar getLocaleCalendar(TimeZone timeZone) {
return Calendar.getInstance(timeZone, getUserLocale());
}
}

View File

@ -0,0 +1,91 @@
package cn.iocoder.yudao.framework.common.util.file;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.Objects;
/**
* 文件类型工具类
*
* @author 范劲松
*/
public class FileTypeUtils {
/**
* 获取文件类型
* <p>
* 例如: test.txt, 返回: txt
*
* @param file 文件名
* @return 后缀不含".")
*/
public static String getFileType(File file) {
if (null == file) {
return StringUtils.EMPTY;
}
return getFileType(file.getName());
}
/**
* 获取文件类型
* <p>
* 例如: test.txt, 返回: txt
*
* @param fileName 文件名
* @return 后缀不含".")
*/
public static String getFileType(String fileName) {
int separatorIndex = fileName.lastIndexOf(".");
if (separatorIndex < 0) {
return "";
}
return fileName.substring(separatorIndex + 1).toLowerCase();
}
/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public static String getExtension(MultipartFile file) {
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension)) {
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
return extension;
}
/**
* 获取文件名的后缀
*
* @param filename 文件名
* @return 后缀名
*/
public static String getExtension(String filename) {
return FilenameUtils.getExtension(filename);
}
/**
* 获取文件类型
*
* @param photoByte 文件字节码
* @return 后缀不含".")
*/
public static String getFileExtendName(byte[] photoByte) {
String strFileExtendName = "JPG";
if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
&& ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) {
strFileExtendName = "GIF";
} else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) {
strFileExtendName = "JPG";
} else if ((photoByte[0] == 66) && (photoByte[1] == 77)) {
strFileExtendName = "BMP";
} else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) {
strFileExtendName = "PNG";
}
return strFileExtendName;
}
}

View File

@ -0,0 +1,329 @@
package cn.iocoder.yudao.framework.common.util.file;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.common.util.string.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ArrayUtils;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 文件处理工具类
*
* @author 范劲松
*/
@Log4j2
public class FileUtils {
/**
* 字符常量斜杠 {@code '/'}
*/
public static final char SLASH = '/';
/**
* 字符常量反斜杠 {@code '\\'}
*/
public static final char BACKSLASH = '\\';
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
/**
* 输出指定文件的byte数组
*
* @param filePath 文件路径
* @param os 输出流
* @return
*/
public static void writeBytes(String filePath, OutputStream os) throws IOException {
FileInputStream fis = null;
try {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException(filePath);
}
fis = new FileInputStream(file);
byte[] b = new byte[1024];
int length;
while ((length = fis.read(b)) > 0) {
os.write(b, 0, length);
}
} catch (IOException e) {
throw e;
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
/**
* 将inputstream转为Base64
*
* @param is InputStream
* @return base64
* @throws Exception 可能的异常
*/
public static String getBase64FromInputStream(InputStream is) throws Exception {
// 将图片文件转化为字节数组字符串并对其进行Base64编码处理
byte[] data = null;
// 读取图片字节数组
try {
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int rc = 0;
while ((rc = is.read(buff, 0, 1024)) > 0) {
swapStream.write(buff, 0, rc);
}
data = swapStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
throw new Exception("输入流关闭异常");
}
}
}
// log.info("byte string: {}", new String(data));
return Base64.encode(data);
}
/**
* 删除文件
*
* @param filePath 文件
* @return
*/
public static boolean deleteFile(String filePath) {
boolean flag = false;
File file = new File(filePath);
// 路径为文件且不为空则进行删除
if (file.isFile() && file.exists()) {
file.delete();
flag = true;
}
return flag;
}
/**
* 文件名称验证
*
* @param filename 文件名称
* @return true 正常 false 非法
*/
public static boolean isValidFilename(String filename) {
return filename.matches(FILENAME_PATTERN);
}
/**
* 检查文件是否可下载
*
* @param resource 需要下载的文件
* @return true 正常 false 非法
*/
public static boolean checkAllowDownload(String resource) {
// 禁止目录上跳级别
if (StringUtils.contains(resource, "..")) {
return false;
}
// 检查允许下载的文件规则
if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) {
return true;
}
// 不在允许下载的文件规则
return false;
}
/**
* 下载文件名重新编码
*
* @param request 请求对象
* @param fileName 文件名
* @return 编码后的文件名
*/
public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {
final String agent = request.getHeader("USER-AGENT");
String filename = fileName;
if (agent.contains("MSIE")) {
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
filename = new String(fileName.getBytes(), "ISO8859-1");
} else if (agent.contains("Chrome")) {
// google浏览器
filename = URLEncoder.encode(filename, "utf-8");
} else {
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
/**
* 返回文件名
*
* @param filePath 文件
* @return 文件名
*/
public static String getName(String filePath) {
if (null == filePath) {
return null;
}
int len = filePath.length();
if (0 == len) {
return filePath;
}
if (isFileSeparator(filePath.charAt(len - 1))) {
// 以分隔符结尾的去掉结尾分隔符
len--;
}
int begin = 0;
char c;
for (int i = len - 1; i > -1; i--) {
c = filePath.charAt(i);
if (isFileSeparator(c)) {
// 查找最后一个路径分隔符/或者\
begin = i + 1;
break;
}
}
return filePath.substring(begin, len);
}
/**
* 是否为Windows或者LinuxUnix文件分隔符<br>
* Windows平台下分隔符为\LinuxUnix/
*
* @param c 字符
* @return 是否为Windows或者LinuxUnix文件分隔符
*/
public static boolean isFileSeparator(char c) {
return SLASH == c || BACKSLASH == c;
}
/**
* 下载文件名重新编码
*
* @param response 响应对象
* @param realFileName 真实文件名
* @return
*/
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {
String percentEncodedFileName = percentEncode(realFileName);
StringBuilder contentDispositionValue = new StringBuilder();
contentDispositionValue.append("attachment; filename=")
.append(percentEncodedFileName)
.append(";")
.append("filename*=")
.append("utf-8''")
.append(percentEncodedFileName);
response.setHeader("Content-disposition", contentDispositionValue.toString());
}
/**
* 百分号编码工具方法
*
* @param s 需要百分号编码的字符串
* @return 百分号编码后的字符串
*/
public static String percentEncode(String s) throws UnsupportedEncodingException {
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
return encode.replaceAll("\\+", "%20");
}
/**
* 创建临时文件
* 该文件会在 JVM 退出时进行删除
*
* @param data 文件内容
* @return 文件
*/
@SneakyThrows
public static File createTempFile(String data) {
File file = createTempFile();
// 写入内容
FileUtil.writeUtf8String(data, file);
return file;
}
/**
* 创建临时文件
* 该文件会在 JVM 退出时进行删除
*
* @param data 文件内容
* @return 文件
*/
@SneakyThrows
public static File createTempFile(byte[] data) {
File file = createTempFile();
// 写入内容
FileUtil.writeBytes(data, file);
return file;
}
/**
* 创建临时文件无内容
* 该文件会在 JVM 退出时进行删除
*
* @return 文件
*/
@SneakyThrows
public static File createTempFile() {
// 创建文件通过 UUID 保证唯一
File file = File.createTempFile(IdUtil.simpleUUID(), null);
// 标记 JVM 退出时自动删除
file.deleteOnExit();
return file;
}
/**
* 生成文件路径
*
* @param content 文件内容
* @param originalName 原始文件名
* @return path唯一不可重复
*/
public static String generatePath(byte[] content, String originalName) {
String sha256Hex = DigestUtil.sha256Hex(content);
// 情况一如果存在 name则优先使用 name 的后缀
if (StrUtil.isNotBlank(originalName)) {
String extName = FileNameUtil.extName(originalName);
return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
}
// 情况二基于 content 计算
return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
}
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.framework.common.util.file;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
/**
* 图片处理工具类
*
* @author 范劲松
*/
public class ImageUtils {
private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
public static byte[] getImage(String imagePath) {
InputStream is = getFile(imagePath);
try {
return IOUtils.toByteArray(is);
} catch (Exception e) {
log.error("图片加载异常 {}", e);
return null;
} finally {
IOUtils.closeQuietly(is);
}
}
public static InputStream getFile(String imagePath) {
try {
byte[] result = readFile(imagePath);
result = Arrays.copyOf(result, result.length);
return new ByteArrayInputStream(result);
} catch (Exception e) {
log.error("获取图片异常 {}", e);
}
return null;
}
/**
* 读取文件为字节数据
*
* @param url 地址
* @return 字节数据
*/
public static byte[] readFile(String url) {
InputStream in = null;
ByteArrayOutputStream baos = null;
try {
// 网络地址
URL urlObj = new URL(url);
URLConnection urlConnection = urlObj.openConnection();
urlConnection.setConnectTimeout(30 * 1000);
urlConnection.setReadTimeout(60 * 1000);
urlConnection.setDoInput(true);
in = urlConnection.getInputStream();
return IOUtils.toByteArray(in);
} catch (Exception e) {
log.error("访问文件异常 {}", e);
return null;
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(baos);
}
}
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.framework.common.util.file;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import java.io.InputStream;
/**
* IO 工具类用于 {@link IoUtil} 缺失的方法
*
* @author 范劲松
*/
public class IoUtils {
/**
* 从流中读取 UTF8 编码的内容
*
* @param in 输入流
* @param isClose 是否关闭
* @return 内容
* @throws IORuntimeException IO 异常
*/
public static String readUtf8(InputStream in, boolean isClose) throws IORuntimeException {
return StrUtil.utf8Str(IoUtil.read(in, isClose));
}
}

View File

@ -0,0 +1,59 @@
package cn.iocoder.yudao.framework.common.util.file;
/**
* 媒体类型工具类
*
* @author 范劲松
*/
public class MimeTypeUtils {
public static final String IMAGE_PNG = "image/png";
public static final String IMAGE_JPG = "image/jpg";
public static final String IMAGE_JPEG = "image/jpeg";
public static final String IMAGE_BMP = "image/bmp";
public static final String IMAGE_GIF = "image/gif";
public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
public static final String[] FLASH_EXTENSION = {"swf", "flv"};
public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb"};
public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
//音频格式
"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb",
// pdf
"pdf"};
public static String getExtension(String prefix) {
switch (prefix) {
case IMAGE_PNG:
return "png";
case IMAGE_JPG:
return "jpg";
case IMAGE_JPEG:
return "jpeg";
case IMAGE_BMP:
return "bmp";
case IMAGE_GIF:
return "gif";
default:
return "";
}
}
}

View File

@ -0,0 +1,69 @@
package cn.iocoder.yudao.framework.common.util.file;
import lombok.extern.log4j.Log4j2;
import net.coobird.thumbnailator.Thumbnails;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
/**
* 图片处理工具类
*
* @author 范劲松
*/
@Log4j2
public class PicUtils {
/**
* 根据指定大小压缩图片
*
* @param imageBytes 源图片字节数组
* @param desFileSize 指定图片大小单位kb
* @param imageId 影像编号
* @return 压缩质量后的图片字节数组
*/
public static byte[] compressPicForScale(byte[] imageBytes, long desFileSize, String imageId) {
if (imageBytes == null || imageBytes.length <= 0 || imageBytes.length < desFileSize * 1024) {
return imageBytes;
}
long srcSize = imageBytes.length;
double accuracy = getAccuracy(srcSize / 1024);
try {
while (imageBytes.length > desFileSize * 1024) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length);
Thumbnails.of(inputStream)
.scale(accuracy)
.outputQuality(accuracy)
.toOutputStream(outputStream);
imageBytes = outputStream.toByteArray();
}
log.info("【图片压缩】imageId={} | 图片原大小={}kb | 压缩后大小={}kb",
imageId, srcSize / 1024, imageBytes.length / 1024);
} catch (Exception e) {
log.error("【图片压缩】msg=图片压缩失败!", e);
}
return imageBytes;
}
/**
* 自动调节精度(经验数值)
*
* @param size 源图片大小
* @return 图片压缩质量比
*/
private static double getAccuracy(long size) {
double accuracy;
if (size < 900) {
accuracy = 0.85;
} else if (size < 2047) {
accuracy = 0.6;
} else if (size < 3275) {
accuracy = 0.44;
} else {
accuracy = 0.4;
}
return accuracy;
}
}

View File

@ -54,6 +54,17 @@ public class ServletUtils {
return ((ServletRequestAttributes) requestAttributes).getRequest();
}
/**
* 获取response
*/
public static HttpServletResponse getResponse() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (!(requestAttributes instanceof ServletRequestAttributes)) {
return null;
}
return ((ServletRequestAttributes) requestAttributes).getResponse();
}
public static String getUserAgent() {
HttpServletRequest request = getRequest();
if (request == null) {

View File

@ -0,0 +1,478 @@
package cn.iocoder.yudao.framework.common.util.string;
import cn.hutool.core.text.AntPathMatcher;
import cn.iocoder.yudao.framework.common.constants.Constants;
import cn.iocoder.yudao.framework.common.text.StrFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 字符串工具类
*
* @author 范劲松
* @date 2022/08/05 16:03
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 空字符串
*/
private static final String NULLSTR = "";
/**
* 下划线
*/
private static final char SEPARATOR = '_';
/**
* 获取参数不为空值
*
* @param value defaultValue 要判断的value
* @return value 返回值
*/
public static <T> T nvl(T value, T defaultValue) {
return value != null ? value : defaultValue;
}
/**
* * 判断一个Collection是否为空 包含ListSetQueue
*
* @param coll 要判断的Collection
* @return true为空 false非空
*/
public static boolean isEmpty(Collection<?> coll) {
return isNull(coll) || coll.isEmpty();
}
/**
* * 判断一个Collection是否非空包含ListSetQueue
*
* @param coll 要判断的Collection
* @return true非空 false
*/
public static boolean isNotEmpty(Collection<?> coll) {
return !isEmpty(coll);
}
/**
* * 判断一个对象数组是否为空
*
* @param objects 要判断的对象数组
* * @return true为空 false非空
*/
public static boolean isEmpty(Object[] objects) {
return isNull(objects) || (objects.length == 0);
}
/**
* * 判断一个对象数组是否非空
*
* @param objects 要判断的对象数组
* @return true非空 false
*/
public static boolean isNotEmpty(Object[] objects) {
return !isEmpty(objects);
}
/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true为空 false非空
*/
public static boolean isEmpty(Map<?, ?> map) {
return isNull(map) || map.isEmpty();
}
/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true非空 false
*/
public static boolean isNotEmpty(Map<?, ?> map) {
return !isEmpty(map);
}
/**
* * 判断一个字符串是否为空串
*
* @param str String
* @return true为空 false非空
*/
public static boolean isEmpty(String str) {
return isNull(str) || NULLSTR.equals(str.trim());
}
/**
* * 判断一个字符串是否为非空串
*
* @param str String
* @return true非空串 false空串
*/
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* * 判断一个对象是否为空
*
* @param object Object
* @return true为空 false非空
*/
public static boolean isNull(Object object) {
return object == null;
}
/**
* * 判断一个对象是否非空
*
* @param object Object
* @return true非空 false
*/
public static boolean isNotNull(Object object) {
return !isNull(object);
}
/**
* * 判断一个对象是否是数组类型Java基本型别的数组
*
* @param object 对象
* @return true是数组 false不是数组
*/
public static boolean isArray(Object object) {
return isNotNull(object) && object.getClass().isArray();
}
/**
* 去空格
*/
public static String trim(String str) {
return (str == null ? "" : str.trim());
}
/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @return 结果
*/
public static String substring(final String str, int start) {
if (str == null) {
return NULLSTR;
}
if (start < 0) {
start = str.length() + start;
}
if (start < 0) {
start = 0;
}
if (start > str.length()) {
return NULLSTR;
}
return str.substring(start);
}
/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @param end 结束
* @return 结果
*/
public static String substring(final String str, int start, int end) {
if (str == null) {
return NULLSTR;
}
if (end < 0) {
end = str.length() + end;
}
if (start < 0) {
start = str.length() + start;
}
if (end > str.length()) {
end = str.length();
}
if (start > end) {
return NULLSTR;
}
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
return str.substring(start, end);
}
/**
* 判断是否为空并且不是空白字符
*
* @param str 要判断的value
* @return 结果
*/
public static boolean hasText(String str) {
return (str != null && !str.isEmpty() && containsText(str));
}
private static boolean containsText(CharSequence str) {
int strLen = str.length();
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return true;
}
}
return false;
}
/**
* 格式化文本, {} 表示占位符<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
* 如果想输出 {} 使用 \\转义 { 即可如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* <br>
* 通常使用format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{} format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义\ format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param template 文本模板被替换的部分用 {} 表示
* @param params 参数值
* @return 格式化后的文本
*/
public static String format(String template, Object... params) {
if (isEmpty(params) || isEmpty(template)) {
return template;
}
return StrFormatter.format(template, params);
}
/**
* 是否为http(s)://开头
*
* @param link 链接
* @return 结果
*/
public static boolean ishttp(String link) {
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
}
/**
* 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
*
* @param set 给定的集合
* @param array 给定的数组
* @return boolean 结果
*/
public static boolean containsAny(Collection<String> collection, String... array) {
if (isEmpty(collection) || isEmpty(array)) {
return false;
} else {
for (String str : array) {
if (collection.contains(str)) {
return true;
}
}
return false;
}
}
/**
* 驼峰转下划线命名
*/
public static String toUnderScoreCase(String str) {
if (str == null) {
return null;
}
StringBuilder sb = new StringBuilder();
// 前置字符是否大写
boolean preCharIsUpperCase = true;
// 当前字符是否大写
boolean curreCharIsUpperCase = true;
// 下一字符是否大写
boolean nexteCharIsUpperCase = true;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (i > 0) {
preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
} else {
preCharIsUpperCase = false;
}
curreCharIsUpperCase = Character.isUpperCase(c);
if (i < (str.length() - 1)) {
nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
}
if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
sb.append(SEPARATOR);
} else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
sb.append(SEPARATOR);
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 是否包含字符串
*
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
*/
public static boolean inStringIgnoreCase(String str, String... strs) {
if (str != null && strs != null) {
for (String s : strs) {
if (str.equalsIgnoreCase(trim(s))) {
return true;
}
}
}
return false;
}
/**
* 将下划线大写方式命名的字符串转换为驼峰式如果转换前的下划线大写方式命名的字符串为空则返回空字符串 例如HELLO_WORLD->HelloWorld
*
* @param name 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String convertToCamelCase(String name) {
StringBuilder result = new StringBuilder();
// 快速检查
if (name == null || name.isEmpty()) {
// 没必要转换
return "";
} else if (!name.contains("_")) {
// 不含下划线仅将首字母大写
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
// 用下划线将原始字符串分割
String[] camels = name.split("_");
for (String camel : camels) {
// 跳过原始字符串中开头结尾的下换线或双重下划线
if (camel.isEmpty()) {
continue;
}
// 首字母大写
result.append(camel.substring(0, 1).toUpperCase());
result.append(camel.substring(1).toLowerCase());
}
return result.toString();
}
/**
* 驼峰式命名法 例如user_name->userName
*/
public static String toCamelCase(String s) {
if (s == null) {
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == SEPARATOR) {
upperCase = true;
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
*
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
*/
public static boolean matches(String str, List<String> strs) {
if (isEmpty(str) || isEmpty(strs)) {
return false;
}
for (String pattern : strs) {
if (isMatch(pattern, str)) {
return true;
}
}
return false;
}
/**
* 判断url是否与规则配置:
* ? 表示单个字符;
* * 表示一层路径内的任意字符串不可跨层级;
* ** 表示任意层路径;
*
* @param pattern 匹配规则
* @param url 需要匹配的url
* @return
*/
public static boolean isMatch(String pattern, String url) {
AntPathMatcher matcher = new AntPathMatcher();
return matcher.match(pattern, url);
}
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj) {
return (T) obj;
}
/**
* 数字左边补齐0使之达到指定长度注意如果数字转换为字符串后长度大于size则只保留 最后size个字符
*
* @param num 数字对象
* @param size 字符串指定长度
* @return 返回数字的字符串格式该字符串为指定长度
*/
public static final String padl(final Number num, final int size) {
return padl(num.toString(), size, '0');
}
/**
* 字符串左补齐如果原始字符串s长度大于size则只保留最后size个字符
*
* @param s 原始字符串
* @param size 字符串指定长度
* @param c 用于补齐的字符
* @return 返回指定长度的字符串由原字符串左补齐或截取得到
*/
public static final String padl(final String s, final int size, final char c) {
final StringBuilder sb = new StringBuilder(size);
if (s != null) {
final int len = s.length();
if (s.length() <= size) {
for (int i = size - len; i > 0; i--) {
sb.append(c);
}
sb.append(s);
} else {
return s.substring(len - size, len);
}
} else {
for (int i = size; i > 0; i--) {
sb.append(c);
}
}
return sb.toString();
}
}

View File

@ -0,0 +1,203 @@
package cn.iocoder.yudao.framework.excel.core.annotations;
import cn.iocoder.yudao.framework.excel.core.util.ExcelHandlerAdapter;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigDecimal;
/**
* 自定义导出Excel数据注解
*
* @author 范劲松
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel {
/**
* 导出时在excel中排序
*/
public int sort() default Integer.MAX_VALUE;
/**
* 导出到Excel中的名字.
*/
public String name() default "";
/**
* 日期格式, : yyyy-MM-dd
*/
public String dateFormat() default "";
/**
* 读取内容转表达式 (: 0=,1=,2=未知)
*/
public String readConverterExp() default "";
/**
* 分隔符读取字符串组内容
*/
public String separator() default ",";
/**
* BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
*/
public int scale() default -1;
/**
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
*/
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
/**
* 导出时在excel中每个列的高度 单位为字符
*/
public double height() default 14;
/**
* 导出时在excel中每个列的宽 单位为字符
*/
public double width() default 16;
/**
* 文字后缀,% 90 变成90%
*/
public String suffix() default "";
/**
* 当值为空时,字段的默认值
*/
public String defaultValue() default "";
/**
* 提示信息
*/
public String prompt() default "";
/**
* 设置只能选择不能输入的列内容.
*/
public String[] combo() default {};
/**
* 是否需要纵向合并单元格,应对需求:含有list集合单元格)
*/
public boolean needMerge() default false;
/**
* 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
*/
public boolean isExport() default true;
/**
* 另一个类中的属性名称,支持多级获取,以小数点隔开
*/
public String targetAttr() default "";
/**
* 是否自动统计数据,在最后追加一行统计数据总和
*/
public boolean isStatistics() default false;
/**
* 导出类型0数字 1字符串
*/
public ColumnType cellType() default ColumnType.STRING;
/**
* 导出列头背景色
*/
public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;
/**
* 导出列头字体颜色
*/
public IndexedColors headerColor() default IndexedColors.WHITE;
/**
* 导出单元格背景色
*/
public IndexedColors backgroundColor() default IndexedColors.WHITE;
/**
* 导出单元格字体颜色
*/
public IndexedColors color() default IndexedColors.BLACK;
/**
* 导出字段对齐方式
*/
public HorizontalAlignment align() default HorizontalAlignment.CENTER;
/**
* 自定义数据处理器
*/
public Class<?> handler() default ExcelHandlerAdapter.class;
/**
* 自定义数据处理器参数
*/
public String[] args() default {};
/**
* 字段类型0导出导入1仅导出2仅导入
*/
Type type() default Type.ALL;
public enum Type {
/**
* 导出导入
*/
ALL(0),
/**
* 仅导出
*/
EXPORT(1),
/**
* 仅导入
*/
IMPORT(2);
private final int value;
Type(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
public enum ColumnType {
/**
* 0数字
*/
NUMERIC(0),
/**
* 1字符串
*/
STRING(1),
/**
* 2图片
*/
IMAGE(2);
private final int value;
ColumnType(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.framework.excel.core.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @USER: LiLiang
* @DATE: 2022/11/5 11:03
* @DESCRIPTION: excel导入自定义校验注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelValid {
String message() default "此列数据不能为空";
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.framework.excel.core.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Excel注解集
*
* @author 范劲松
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excels {
Excel[] value();
}

View File

@ -0,0 +1,12 @@
package cn.iocoder.yudao.framework.excel.core.function;
import java.util.List;
/**
* @USER: LiLiang
* @DATE: 2022/11/5 10:17
* @DESCRIPTION: EasyExcel大数据量分页导出实现接口
*/
public interface IEasyExcelExportServer {
List selectForExcelExport (Object param, int pageNum);
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.framework.excel.core.function;
import cn.iocoder.yudao.framework.excel.core.util.ExcelIOResultVo;
import java.util.List;
/**
* @USER: LiLiang
* @DATE: 2022/11/5 10:17
* @DESCRIPTION: EasyExcel大数据量分批导入实现接口
*/
public interface IEasyExcelImportServer <T,R>{
/**
* 批量数据库校验去重查询in一次控制在1000条数据
* @param onlyCode
* @return
*/
List<R> batchCheckByOnlyCode(List<R> onlyCode);
/**
* 批量持久化导入数据
* @param list
*/
void importBatchSave(List<T> list);
void syncSaveSchedule(ExcelIOResultVo excelIoResultVo);
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.excel.core.handler;
import cn.iocoder.yudao.framework.common.exception.CustomException;
import cn.iocoder.yudao.framework.excel.core.annotations.ExcelValid;
import java.lang.reflect.Field;
import java.util.Objects;
/**
* @author liliang
* @DATE: 2022/11/5 11:03
* @DESCRIPTION: 自定义导入校验抽象类
*/
public abstract class AbstractEasyExcelImportValidHandler<T>{
/**
* Excel导入基础校验
*/
public void baseValid(T t) throws IllegalAccessException {
Field[] fields = t.getClass().getDeclaredFields();
for (Field field : fields) {
//设置可访问
field.setAccessible(true);
//属性的值
Object fieldValue = field.get(t);
//是否包含必填校验注解
boolean isExcelValid = field.isAnnotationPresent(ExcelValid.class);
if (isExcelValid && Objects.isNull(fieldValue)) {
throw new CustomException(field.getAnnotation(ExcelValid.class).message());
}
}
}
/**
* Excel导入自定义校验(请勿在此方法中进行数据库连接操作)
* @param t
*/
public abstract void valid(T t) throws Exception;
}

View File

@ -0,0 +1,282 @@
package cn.iocoder.yudao.framework.excel.core.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.exception.CustomException;
import cn.iocoder.yudao.framework.excel.core.function.IEasyExcelExportServer;
import cn.iocoder.yudao.framework.excel.core.function.IEasyExcelImportServer;
import cn.iocoder.yudao.framework.excel.core.handler.AbstractEasyExcelImportValidHandler;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @USER: LiLiang
* @DATE: 2022/11/5 10:17
* @DESCRIPTION: Excel大数据量处理工具类
*/
@Slf4j
public class EasyExcelUtil {
private static final String LOCAL_PATH = System.getProperty("user.dir") + File.separator + "temp";
/**
* 每隔1000条存储数据库然后清理list方便内存回收
*/
private static final int BATCH_COUNT = 1000;
/**
* 大数据量分页导出
*
* @param pageParams
* @return
*/
public static <T> void bigDataExport(IEasyExcelExportServer excelExportServer, Class<T> cls, Object pageParams, String fileName, long total, int thresholdValue, List<String> ignoreColumns) {
File file = new File(LOCAL_PATH);
if (!file.exists()) {
file.mkdir();
}
long st = System.currentTimeMillis();
String filePath = LOCAL_PATH + File.separator + fileName + st + ".xlsx";
String path = null;
thresholdValue = Optional.ofNullable(thresholdValue).orElse(10000);
ExcelWriter excelWriter = null;
try {
long st1 = System.currentTimeMillis();
excelWriter = EasyExcel.write(filePath, cls).excludeColumnFieldNames(ignoreColumns).build();
WriteSheet writeSheet = EasyExcel.writerSheet(fileName).build();
// 避免内存溢出大于1万条数据采用分页分批导出底层workbook会随时往磁盘写入数据内存中始终保持恒定数据量
for (int pageNum = 1; pageNum <= total / thresholdValue + (total % thresholdValue > 0 ? 1 : 0); pageNum++) {
List list = excelExportServer.selectForExcelExport(pageParams, pageNum);
excelWriter.write(list, writeSheet);
log.info(fileName + "分批导出当前次数{} " + pageNum + "");
}
// 调用此方法后后续才能读取文件并在线程结束后自动关闭流
excelWriter.finish();
log.info("导出excel耗时:{}ms", System.currentTimeMillis() - st1);
HttpServletResponse response = ServletUtils.getResponse();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
FileUtil.writeToStream(filePath, response.getOutputStream());
} catch (Exception e) {
log.error(fileName + "导出失败,失败原因:{}", e);
} finally {
// 删除临时文件
FileUtil.del(filePath);
}
}
/**
* @param multipartFile 导入文件
* @param excelImportServer 导入服务实现
* @param importValidHandler 导入校验器
* @param businessName 业务名称
* @param cls 实体类型
* @param onlyCodeFun 去重字段
* @param remarkFiledName 错误备注字段
* @return
* @throws IOException
*/
public static <T, R> void bigDataImport(MultipartFile multipartFile, IEasyExcelImportServer<T, R> excelImportServer, AbstractEasyExcelImportValidHandler importValidHandler, String businessName, Class<T> cls, Function<T, R> onlyCodeFun, String remarkFiledName) throws IOException {
try {
EasyExcel.read(multipartFile.getInputStream(),
cls,
new ReadListener<T>() {
// 缓存的数据
private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
// 初始化变量
Long st = System.currentTimeMillis();
File file = new File(LOCAL_PATH);
{
if (!file.exists()) {
file.mkdir();
}
}
final String fileName = businessName + "校验失败";
final String filePath = LOCAL_PATH + File.separator + fileName + st + ".xlsx";
final WriteSheet writeSheet = EasyExcel.writerSheet(fileName).build();
ExcelWriter excelWriter = EasyExcel.write(filePath, cls).build();
Map<Integer, String> sortedMap = new TreeMap();
Integer successRowNum = 0;
Integer failRowNum = 0;
Integer currentNum = 0;
@SneakyThrows
@Override
public void invoke(T t, AnalysisContext analysisContext) {
try {
// 导入自定义校验
importValidHandler.valid(t);
} catch (CustomException e) {
// 获取当前行号
Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
// 设置行号和导入校验提示的映射
sortedMap.put(rowIndex, e.getMessage());
}
cachedDataList.add(t);
currentNum = analysisContext.readRowHolder().getRowIndex();
// 达到BATCH_COUNT了需要去存储一次数据库防止数据几万条数据在内存容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
ExcelIOResultVo excelIoResultVo = importExecutor(cachedDataList, onlyCodeFun, excelImportServer, sortedMap, remarkFiledName
, currentNum, successRowNum, failRowNum, excelWriter, writeSheet);
// 异步保存导入结果通常保存到redis中
excelImportServer.syncSaveSchedule(excelIoResultVo);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
ExcelIOResultVo resultVo = importExecutor(cachedDataList, onlyCodeFun, excelImportServer, sortedMap, remarkFiledName
, currentNum, successRowNum, failRowNum, excelWriter, writeSheet);
// 关闭校验文件写出流
excelWriter.finish();
String uploadPath = null;
// 保存导入结果通常保存到redis中
resultVo.setFilePath(uploadPath);
resultVo.setEnd(true);
excelImportServer.syncSaveSchedule(resultVo);
}
}).doReadAll();
} catch (Exception e) {
log.error(businessName + "解析异常{}", e);
throw e;
}
}
/**
* 校验保存进度持久化数据导出错误文件
*
* @param cachedDataList
* @param onlyCodeFun
* @param excelImportServer
* @param sortedMap
* @param remarkFiledName
* @param successRowNum
* @param failRowNum
* @param excelWriter
* @param writeSheet
*/
private static <T, R> ExcelIOResultVo importExecutor(List<T> cachedDataList, Function<T, R> onlyCodeFun, IEasyExcelImportServer excelImportServer, Map<Integer, String> sortedMap, String remarkFiledName, Integer currentNum, Integer successRowNum, Integer failRowNum, ExcelWriter excelWriter, WriteSheet writeSheet) {
// 二次校验批量去重
List<R> onlyCodes = cachedDataList.stream().map(onlyCodeFun).collect(Collectors.toList());
// 数据库中存在的数据
List<R> onlyCodeList = Optional.ofNullable(excelImportServer.batchCheckByOnlyCode(onlyCodes)).orElse(new ArrayList<R>());
Map<R, T> groupMap = cachedDataList.stream().collect(Collectors
.groupingBy(onlyCodeFun, Collectors.collectingAndThen(Collectors.toList(), value -> value.get(0))));
List<T> validErrorList = new ArrayList<>();
// 设置校验失败列提示
for (int i = 0; i < cachedDataList.size(); i++) {
if (sortedMap.containsKey(i + 1)) {
T var = cachedDataList.get(i);
ReflectUtil.setFieldValue(var, remarkFiledName, sortedMap.get(i + 1));
validErrorList.add(var);
}
}
// 设置重复数据列提示
for (R onlyCode : onlyCodeList) {
int index = onlyCodes.indexOf(onlyCode);
String value = sortedMap.get(index + 1);
// 校验不通过列数据不重复设置提示
if (value == null) {
T var = groupMap.get(onlyCode);
ReflectUtil.setFieldValue(var, remarkFiledName, "数据重复");
validErrorList.add(var);
}
}
// 剔除校验不通过的数据
cachedDataList.removeAll(validErrorList);
if (CollectionUtil.isNotEmpty(validErrorList)) {
failRowNum += validErrorList.size();
// 写出错误校验文件
excelWriter.write(validErrorList, writeSheet);
}
// 批量入库
if (CollectionUtil.isNotEmpty(cachedDataList)) {
excelImportServer.importBatchSave(cachedDataList);
}
successRowNum += cachedDataList.size();
// 异步保存导入结果通常保存到redis中
ExcelIOResultVo excelIoResultVo = new ExcelIOResultVo();
excelIoResultVo.setTotal(currentNum);
excelIoResultVo.setSheduleNum(currentNum);
excelIoResultVo.setSuccessNum(successRowNum);
excelIoResultVo.setFailNum(failRowNum);
excelIoResultVo.setEnd(false);
// 处理完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
return excelIoResultVo;
}
public static void write(HttpServletResponse response, Class<?> headClazz, List<?> data,
WriteHandler writeHandler, String sheetName, String fileName) throws Exception {
EasyExcel.write(getOutputStream(fileName, response), headClazz)
.excelType(ExcelTypeEnum.XLSX)
.registerWriteHandler(writeHandler)
.sheet(sheetName)
.doWrite(data);
}
private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
try {
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf8");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx");
response.setHeader("Pragma", "public");
response.setHeader("Cache-Control", "no-store");
response.addHeader("Cache-Control", "max-age=0");
return response.getOutputStream();
} catch (IOException var3) {
resetResponse(response, var3);
return null;
}
}
private static void resetResponse(HttpServletResponse response, Exception e) {
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
throw new RuntimeException("导出Excel异常:", e);
}
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.framework.excel.core.util;
/**
* Excel数据格式处理适配器
*
* @author 范劲松
*/
public interface ExcelHandlerAdapter {
/**
* 格式化
*
* @param value 单元格数据值
* @param args excel注解args参数组
* @return 处理后的值
*/
Object format(Object value, String[] args);
}

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.framework.excel.core.util;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 导出结果
*
* @author LiLiang
* @since 1.0.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExcelIOResultVo implements Serializable {
private static final long serialVersionUID = 8552255701105369006L;
/**
* 文件地址
*/
private String filePath;
/**
* 描述
*/
private String description;
/**
* 全局唯一id
*/
private String uid;
/**
* 总条数
*/
private Integer total;
/**
* 进度数
*/
private Integer sheduleNum;
/**
* 成功条数
*/
private Integer successNum;
/**
* 失败条数
*/
private Integer failNum;
/**
* 结束标志
*/
private boolean end = false;
}