diff --git a/blade-common/pom.xml b/blade-common/pom.xml index 5fb0c22..0879b91 100644 --- a/blade-common/pom.xml +++ b/blade-common/pom.xml @@ -22,6 +22,10 @@ org.springblade blade-starter-loadbalancer + + org.springblade + blade-starter-log + org.springblade blade-core-auto diff --git a/blade-common/src/main/java/org/springblade/common/excel/LenientDateParser.java b/blade-common/src/main/java/org/springblade/common/excel/LenientDateParser.java new file mode 100644 index 0000000..750c101 --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/excel/LenientDateParser.java @@ -0,0 +1,198 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.common.excel; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 批量导入宽容日期解析器(口径见根工作区 docs/import-spec.md)。 + *

+ * 解析规则: + *

+ * + * @author Chill + */ +public final class LenientDateParser { + + /** + * 日期部分:4 位年 + 分隔符 + 1~2 位月 + 同一分隔符 + 1~2 位日(年在前,拒绝两位年份与日在前)。 + */ + private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4})([-/.])(\\d{1,2})\\2(\\d{1,2})"); + + /** + * 时间部分:1~2 位时[:1~2 位分[:1~2 位秒]],逐级可省略。 + */ + private static final Pattern TIME_PATTERN = Pattern.compile("(\\d{1,2})(?::(\\d{1,2})(?::(\\d{1,2}))?)?"); + + private LenientDateParser() { + } + + /** + * 解析日期文本,失败返回 {@code null}。 + * + * @param value 单元格原始文本 + * @return 日期;无法识别时返回 null + */ + public static LocalDate parseDateOrNull(String value) { + String normalized = normalize(value); + if (normalized == null) { + return null; + } + Matcher matcher = DATE_PATTERN.matcher(normalized); + if (!matcher.matches()) { + return null; + } + try { + return LocalDate.of(Integer.parseInt(matcher.group(1)), + Integer.parseInt(matcher.group(3)), Integer.parseInt(matcher.group(4))); + } catch (NumberFormatException | java.time.DateTimeException exception) { + return null; + } + } + + /** + * 解析日期文本;允许携带合法时间部分并截断(如 2026-9-1 8:0:0 按 2026-09-01 解析, + * 与 fastexcel 内置 LocalDate 转换的既有宽容度保持一致),失败返回 {@code null}。 + * + * @param value 单元格原始文本 + * @return 日期;无法识别时返回 null + */ + public static LocalDate parseDateLenientlyOrNull(String value) { + LocalDate date = parseDateOrNull(value); + if (date != null) { + return date; + } + LocalDateTime dateTime = parseDateTimeOrNull(value); + return dateTime == null ? null : dateTime.toLocalDate(); + } + + /** + * 解析日期时间文本;纯日期按当日零点处理,失败返回 {@code null}。 + * + * @param value 单元格原始文本 + * @return 日期时间;无法识别时返回 null + */ + public static LocalDateTime parseDateTimeOrNull(String value) { + String normalized = normalize(value); + if (normalized == null) { + return null; + } + // 拆出日期与时间两部分;中间允许 1 个及以上空白或小写 t(2026-8-2t12:3 亦接受)。 + String[] parts = normalized.split("[ \\t]+|(?<=\\d)t", 2); + if (parts.length == 0) { + return null; + } + LocalDate date = parseDateOrNull(parts[0]); + if (date == null) { + return null; + } + if (parts.length == 1) { + return date.atStartOfDay(); + } + Matcher matcher = TIME_PATTERN.matcher(parts[1]); + if (!matcher.matches()) { + return null; + } + try { + int hour = Integer.parseInt(matcher.group(1)); + int minute = matcher.group(2) == null ? 0 : Integer.parseInt(matcher.group(2)); + int second = matcher.group(3) == null ? 0 : Integer.parseInt(matcher.group(3)); + return LocalDateTime.of(date, LocalTime.of(hour, minute, second)); + } catch (NumberFormatException | java.time.DateTimeException exception) { + return null; + } + } + + /** + * 解析日期文本,失败抛出携带口径文案的 {@link org.springblade.core.log.exception.ServiceException}。 + * 空白与 {@code null} 返回 {@code null}(可选字段由业务校验决定是否必填)。 + * + * @param value 单元格原始文本 + * @param columnName 导入模板列名(用于失败原因文案与失败明细标红定位) + * @return 日期 + */ + public static LocalDate parseDate(String value, String columnName) { + if (value == null || value.trim().isEmpty()) { + return null; + } + LocalDate date = parseDateLenientlyOrNull(value); + if (date == null) { + throw unrecognizedDate(value, columnName); + } + return date; + } + + /** + * 解析日期时间文本,失败抛出携带口径文案的 {@link org.springblade.core.log.exception.ServiceException}。 + * 空白与 {@code null} 返回 {@code null}(可选字段由业务校验决定是否必填)。 + * + * @param value 单元格原始文本 + * @param columnName 导入模板列名(用于失败原因文案与失败明细标红定位) + * @return 日期时间 + */ + public static LocalDateTime parseDateTime(String value, String columnName) { + if (value == null || value.trim().isEmpty()) { + return null; + } + LocalDateTime dateTime = parseDateTimeOrNull(value); + if (dateTime == null) { + throw unrecognizedDate(value, columnName); + } + return dateTime; + } + + /** + * 按口径构造「日期格式无法识别」错误。 + * + * @param value 单元格原始文本 + * @param columnName 导入模板列名 + * @return 业务异常 + */ + public static org.springblade.core.log.exception.ServiceException unrecognizedDate(String value, String columnName) { + String displayName = columnName == null || columnName.isBlank() ? "日期" : columnName; + return new org.springblade.core.log.exception.ServiceException( + displayName + " 日期格式无法识别:" + (value == null ? "" : value.trim())); + } + + /** + * 归一化输入:去首尾空白,跳过空白值。 + */ + private static String normalize(String value) { + if (value == null) { + return null; + } + String normalized = value.trim(); + return normalized.isEmpty() ? null : normalized; + } + +} diff --git a/blade-common/src/main/java/org/springblade/common/excel/LenientDateStringConverter.java b/blade-common/src/main/java/org/springblade/common/excel/LenientDateStringConverter.java new file mode 100644 index 0000000..970dde7 --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/excel/LenientDateStringConverter.java @@ -0,0 +1,75 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.common.excel; + +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; +import cn.idev.excel.util.DateUtils; + +import java.time.LocalDate; + +/** + * 批量导入宽容日期转换器(String 承载):Excel 数值日期(序列号)转 ISO 文本,文本原样透传。 + *

+ * 文本的宽容解析由服务层调用 {@link LenientDateParser} 完成,不在此处抛错—— + * 转换器阶段抛出的异常会被 fastexcel 包装成 ExcelDataConvertException 直接中断读取, + * 无法进入导入失败明细流程。口径见根工作区 docs/import-spec.md。 + * + * @author Chill + */ +public class LenientDateStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getType() == CellDataTypeEnum.NUMBER) { + LocalDate date = DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), + globalConfiguration.getUse1904windowing()).toLocalDate(); + return date.toString(); + } + return cellData.getStringValue(); + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value); + } + +} diff --git a/blade-common/src/main/java/org/springblade/common/excel/LenientDateTimeStringConverter.java b/blade-common/src/main/java/org/springblade/common/excel/LenientDateTimeStringConverter.java new file mode 100644 index 0000000..bcb070a --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/excel/LenientDateTimeStringConverter.java @@ -0,0 +1,82 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.common.excel; + +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; +import cn.idev.excel.util.DateUtils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 批量导入宽容日期时间转换器(String 承载):Excel 数值日期(序列号)转 + * {@code yyyy-MM-dd HH:mm:ss} 文本,文本原样透传。 + *

+ * 文本的宽容解析由服务层调用 {@link LenientDateParser} 完成,不在此处抛错—— + * 转换器阶段抛出的异常会被 fastexcel 包装成 ExcelDataConvertException 直接中断读取, + * 无法进入导入失败明细流程。口径见根工作区 docs/import-spec.md。 + * + * @author Chill + */ +public class LenientDateTimeStringConverter implements Converter { + + /** + * 数值日期序列号转文本的输出格式,与导入模板展示格式保持一致。 + */ + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getType() == CellDataTypeEnum.NUMBER) { + LocalDateTime dateTime = DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), + globalConfiguration.getUse1904windowing()); + return dateTime.format(DATE_TIME_FORMATTER); + } + return cellData.getStringValue(); + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value); + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java index 941b7eb..b59573d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java @@ -35,7 +35,6 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; -import java.time.LocalDate; /** * 币种汇率 Excel @@ -62,8 +61,8 @@ public class CurrencyExcel implements Serializable { @ExcelProperty("汇率") private BigDecimal exchangeRate; - @ExcelProperty("生效日期") - private LocalDate effectiveDate; + @ExcelProperty(value = "生效日期", converter = org.springblade.common.excel.LenientDateStringConverter.class) + private String effectiveDate; @ExcelProperty("状态") private String statusName; @@ -71,8 +70,8 @@ public class CurrencyExcel implements Serializable { @ExcelProperty("来源") private String dataSource; - @ExcelProperty("失效日期") - private LocalDate expiryDate; + @ExcelProperty(value = "失效日期", converter = org.springblade.common.excel.LenientDateStringConverter.class) + private String expiryDate; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/UserExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/UserExcel.java index 1dd71ac..306e144 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/UserExcel.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/UserExcel.java @@ -34,7 +34,6 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.util.Date; /** * UserExcel @@ -102,7 +101,7 @@ public class UserExcel implements Serializable { private String postName; @ColumnWidth(20) - @ExcelProperty("生日") - private Date birthday; + @ExcelProperty(value = "生日", converter = org.springblade.common.excel.LenientDateStringConverter.class) + private String birthday; } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml index 6a4dd47..70dd2e9 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml @@ -95,7 +95,7 @@