diff --git a/airpower-core/pom.xml b/airpower-core/pom.xml
index 4f3393d2801864db09bd7d6ce650ec91d67cb5e1..6476c0dc6d42cacaa0fdbf1383b14dbdcd82e218 100644
--- a/airpower-core/pom.xml
+++ b/airpower-core/pom.xml
@@ -5,10 +5,10 @@
cn.hamm
airpower
- 2.1.0
+ 2.1.1
airpower-core
- 2.1.0
+ 2.1.1
airpower-core
AirPower is a fast backend development tool based on SpringBoot3 and JPA. It's the core of AirPower.
diff --git a/airpower-core/src/main/java/cn/hamm/airpower/annotation/ExcelColumn.java b/airpower-core/src/main/java/cn/hamm/airpower/annotation/ExcelColumn.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b1a35a4609219be6be50323bb7b0a71aea1e75a
--- /dev/null
+++ b/airpower-core/src/main/java/cn/hamm/airpower/annotation/ExcelColumn.java
@@ -0,0 +1,48 @@
+package cn.hamm.airpower.annotation;
+
+import cn.hamm.airpower.validate.dictionary.Dictionary;
+import java.lang.annotation.*;
+
+/**
+ *
Excel导出列
+ *
+ * @author Hamm.cn
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface ExcelColumn {
+ /**
+ * 类型
+ */
+ Type value() default Type.TEXT;
+
+ enum Type {
+ /**
+ * 普通文本
+ */
+ TEXT,
+
+ /**
+ * 时间日期
+ */
+ DATETIME,
+
+ /**
+ * 数字
+ */
+ NUMBER,
+
+ /**
+ * 字典
+ * @apiNote 请确保同时标记了 @{@link Dictionary}
+ */
+ DICTIONARY,
+
+ /**
+ * 布尔值
+ */
+ BOOLEAN
+ }
+}
diff --git a/airpower-core/src/main/java/cn/hamm/airpower/config/Constant.java b/airpower-core/src/main/java/cn/hamm/airpower/config/Constant.java
index ded1a3eabfbfa6be4f0f011527d47fbd67c05775..8babbe707092514c24e04c3f92e580a585612186 100644
--- a/airpower-core/src/main/java/cn/hamm/airpower/config/Constant.java
+++ b/airpower-core/src/main/java/cn/hamm/airpower/config/Constant.java
@@ -237,4 +237,24 @@ public class Constant {
* 毫秒转秒
*/
public static final int MILLISECONDS_PER_SECOND = 1000;
+
+ /**
+ * 换行
+ */
+ public static final String LINE_BREAK = "\n";
+
+ /**
+ * TAB
+ */
+ public static final String TAB = "\t";
+
+ /**
+ * 是
+ */
+ public static final String YES = "是";
+
+ /**
+ * 否
+ */
+ public static final String NO = "否";
}
diff --git a/airpower-core/src/main/java/cn/hamm/airpower/config/ServiceConfig.java b/airpower-core/src/main/java/cn/hamm/airpower/config/ServiceConfig.java
index 802e9c711ebdb88bc8d01a1681ba4f9c0dab25bc..f281ff5c9ea88c9bdc6985d76813c6169805c2a2 100644
--- a/airpower-core/src/main/java/cn/hamm/airpower/config/ServiceConfig.java
+++ b/airpower-core/src/main/java/cn/hamm/airpower/config/ServiceConfig.java
@@ -83,6 +83,13 @@ public class ServiceConfig {
*/
private String tenantHeader = "tenant-code";
+ /**
+ * 导出文件的目录
+ *
+ * @apiNote 请不要
使用 /
结尾
+ */
+ private String exportFilePath = "";
+
/**
* 是否开启调试模式
*
diff --git a/airpower-core/src/main/java/cn/hamm/airpower/enums/DateTimeFormatter.java b/airpower-core/src/main/java/cn/hamm/airpower/enums/DateTimeFormatter.java
new file mode 100644
index 0000000000000000000000000000000000000000..0049772d749d1e2505ec4b555d866b4d3ebaabf0
--- /dev/null
+++ b/airpower-core/src/main/java/cn/hamm/airpower/enums/DateTimeFormatter.java
@@ -0,0 +1,66 @@
+package cn.hamm.airpower.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 格式化模板
+ *
+ * @author Hamm.cn
+ */
+@Getter
+@AllArgsConstructor
+public enum DateTimeFormatter {
+ /**
+ * 年
+ */
+ YEAR("yyyy"),
+
+ /**
+ * 月
+ */
+ MONTH("MM"),
+
+ /**
+ * 日
+ */
+ DAY("dd"),
+
+ /**
+ * 时
+ */
+ HOUR("HH"),
+
+ /**
+ * 分
+ */
+ MINUTE("mm"),
+
+ /**
+ * 秒
+ */
+ SECOND("ss"),
+
+ /**
+ * 年月日
+ */
+ FULL_DATE("yyyy-MM-dd"),
+
+ /**
+ * 时分秒
+ */
+ FULL_TIME("HH:mm:ss"),
+
+ /**
+ * 年月日时分秒
+ */
+ FULL_DATETIME("yyyy-MM-dd HH:mm:ss"),
+
+ /**
+ * 月日时分
+ */
+ SHORT_DATETIME("MM-dd HH:mm"),
+ ;
+
+ private final String value;
+}
diff --git a/airpower-core/src/main/java/cn/hamm/airpower/model/query/QueryExport.java b/airpower-core/src/main/java/cn/hamm/airpower/model/query/QueryExport.java
new file mode 100644
index 0000000000000000000000000000000000000000..ea8c82b0b9e7fc2b794fb7617bfe68e89f67c322
--- /dev/null
+++ b/airpower-core/src/main/java/cn/hamm/airpower/model/query/QueryExport.java
@@ -0,0 +1,18 @@
+package cn.hamm.airpower.model.query;
+
+import cn.hamm.airpower.root.RootModel;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 查询导出结果模型
+ *
+ * @author Hamm.cn
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QueryExport extends RootModel {
+ @NotBlank(message = "文件Code不能为空")
+ private String fileCode;
+}
diff --git a/airpower-core/src/main/java/cn/hamm/airpower/root/RootEntity.java b/airpower-core/src/main/java/cn/hamm/airpower/root/RootEntity.java
index 3e04e7cc87a9aafb9d30c4338be76a2cbbfd8e1b..d4c59b615b12745256cd23efae6907d670121841 100644
--- a/airpower-core/src/main/java/cn/hamm/airpower/root/RootEntity.java
+++ b/airpower-core/src/main/java/cn/hamm/airpower/root/RootEntity.java
@@ -45,6 +45,7 @@ public class RootEntity> extends RootModel
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false, columnDefinition = "bigint UNSIGNED comment 'ID'")
@Min(value = 0, message = "ID必须大于{value}")
+ @ExcelColumn(ExcelColumn.Type.NUMBER)
@NotNull(groups = {WhenUpdate.class, WhenIdRequired.class}, message = "ID不能为空")
private Long id;
@@ -53,6 +54,7 @@ public class RootEntity> extends RootModel
@Column(columnDefinition = "text comment '备注'")
@Length(max = 1000, message = "备注最多允许{max}个字符")
@Exclude(filters = {WhenPayLoad.class})
+ @ExcelColumn
private String remark;
@Description("是否禁用")
@@ -60,30 +62,35 @@ public class RootEntity> extends RootModel
@Search(Search.Mode.EQUALS)
@Column(columnDefinition = "tinyint UNSIGNED default 0 comment '是否禁用'")
@Exclude(filters = {WhenPayLoad.class})
+ @ExcelColumn(ExcelColumn.Type.BOOLEAN)
private Boolean isDisabled;
@Description("创建时间")
@ReadOnly
@Column(columnDefinition = "bigint UNSIGNED default 0 comment '创建时间'")
@Exclude(filters = {WhenPayLoad.class})
+ @ExcelColumn(ExcelColumn.Type.DATETIME)
private Long createTime;
@Description("创建人ID")
@ReadOnly
@Column(columnDefinition = "bigint UNSIGNED default 0 comment '创建人ID'")
@Exclude(filters = {WhenPayLoad.class})
+ @ExcelColumn(ExcelColumn.Type.NUMBER)
private Long createUserId;
@Description("修改人ID")
@ReadOnly
@Column(columnDefinition = "bigint UNSIGNED default 0 comment '修改人ID'")
@Exclude(filters = {WhenPayLoad.class})
+ @ExcelColumn(ExcelColumn.Type.NUMBER)
private Long updateUserId;
@Description("修改时间")
@ReadOnly
@Column(columnDefinition = "bigint UNSIGNED default 0 comment '修改时间'")
@Exclude(filters = {WhenPayLoad.class})
+ @ExcelColumn(ExcelColumn.Type.DATETIME)
private Long updateTime;
@Transient
diff --git a/airpower-core/src/main/java/cn/hamm/airpower/root/RootEntityController.java b/airpower-core/src/main/java/cn/hamm/airpower/root/RootEntityController.java
index e987ad8c71b1ca71935a345b44314d9b08a72941..841c3ecdbed1f3fb626f5389f1b59d96fe588356 100644
--- a/airpower-core/src/main/java/cn/hamm/airpower/root/RootEntityController.java
+++ b/airpower-core/src/main/java/cn/hamm/airpower/root/RootEntityController.java
@@ -10,6 +10,7 @@ import cn.hamm.airpower.enums.ServiceError;
import cn.hamm.airpower.exception.ServiceException;
import cn.hamm.airpower.interfaces.IEntityAction;
import cn.hamm.airpower.model.Json;
+import cn.hamm.airpower.model.query.QueryExport;
import cn.hamm.airpower.model.query.QueryPageRequest;
import cn.hamm.airpower.model.query.QueryPageResponse;
import cn.hamm.airpower.model.query.QueryRequest;
@@ -42,6 +43,25 @@ public class RootEntityController<
@Autowired
protected S service;
+ /**
+ * 创建导出任务
+ */
+ @Description("创建导出任务")
+ @RequestMapping("export")
+ public Json export(@RequestBody QueryRequest queryRequest) {
+ return Json.data(service.createExportTask(queryRequest), "导出任务创建成功");
+ }
+
+ /**
+ * 查询异步导出结果
+ */
+ @Description("查询异步导出结果")
+ @RequestMapping("queryExport")
+ @Permission(authorize = false)
+ public Json queryExport(@RequestBody @Validated QueryExport queryExport) {
+ return Json.data(service.queryExport(queryExport), "请下载导出的文件");
+ }
+
/**
* 添加一条新数据接口
*
diff --git a/airpower-core/src/main/java/cn/hamm/airpower/root/RootService.java b/airpower-core/src/main/java/cn/hamm/airpower/root/RootService.java
index 43ecc0e2f7a1bbe151043f456dda0f11b84b4388..dbbd417934dd9055fc8f70d8da72a7decc62e82e 100644
--- a/airpower-core/src/main/java/cn/hamm/airpower/root/RootService.java
+++ b/airpower-core/src/main/java/cn/hamm/airpower/root/RootService.java
@@ -1,17 +1,25 @@
package cn.hamm.airpower.root;
+import cn.hamm.airpower.annotation.ExcelColumn;
import cn.hamm.airpower.annotation.Search;
import cn.hamm.airpower.config.Configs;
import cn.hamm.airpower.config.Constant;
import cn.hamm.airpower.config.MessageConstant;
+import cn.hamm.airpower.enums.DateTimeFormatter;
import cn.hamm.airpower.enums.ServiceError;
import cn.hamm.airpower.exception.ServiceException;
+import cn.hamm.airpower.interfaces.IDictionary;
+import cn.hamm.airpower.model.Json;
import cn.hamm.airpower.model.Page;
import cn.hamm.airpower.model.Sort;
+import cn.hamm.airpower.model.query.QueryExport;
import cn.hamm.airpower.model.query.QueryPageRequest;
import cn.hamm.airpower.model.query.QueryPageResponse;
import cn.hamm.airpower.model.query.QueryRequest;
+import cn.hamm.airpower.util.DateTimeUtil;
+import cn.hamm.airpower.util.ReflectUtil;
import cn.hamm.airpower.util.Utils;
+import cn.hamm.airpower.validate.dictionary.Dictionary;
import jakarta.persistence.Column;
import jakarta.persistence.criteria.*;
import lombok.extern.slf4j.Slf4j;
@@ -29,8 +37,12 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import java.beans.PropertyDescriptor;
+import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.*;
import java.util.function.BiFunction;
@@ -44,12 +56,217 @@ import java.util.function.BiFunction;
@SuppressWarnings({"unchecked", "SpringJavaInjectionPointsAutowiringInspection"})
@Slf4j
public class RootService, R extends RootRepository> {
+
/**
* 数据源
*/
@Autowired
protected R repository;
+ /**
+ * 导出文件前缀
+ */
+ public static final String EXPORT_FILE_PREFIX = "export_file_";
+
+ /**
+ * 导出文件后缀
+ */
+ public static final String EXPORT_FILE_CSV = ".csv";
+
+ /**
+ * 创建导出任务
+ *
+ * @param queryRequest 请求查询的参数
+ * @return 导出任务ID
+ */
+ public final String createExportTask(QueryRequest queryRequest) {
+ String fileCode = Utils.getRandomUtil().randomString().toLowerCase();
+ final String fileCacheKey = EXPORT_FILE_PREFIX + fileCode;
+ Object object = Utils.getRedisUtil().get(fileCacheKey);
+ if (Objects.nonNull(object)) {
+ return createExportTask(queryRequest);
+ }
+ Utils.getRedisUtil().set(fileCacheKey, "");
+ Utils.getTaskUtil().runAsync(() -> {
+ // 查数据 写文件
+ List list = getList(queryRequest);
+ list = beforeExport(list);
+ String url = saveExportFile(list);
+ Utils.getRedisUtil().set(fileCacheKey, url);
+ });
+ return fileCode;
+ }
+
+ /**
+ * 保存导出的数据到文件
+ *
+ * @param exportList 导出的数据
+ * @return 存储的文件地址
+ * @apiNote 支持完全重写导出逻辑
+ *
+ *
+ * - 默认导出为
CSV
表格,如需自定义导出方式或格式,可直接重写此方法
+ * - 如仅需
自定义导出存储位置
,可重写 {@link #afterExport(String)}
+ *
+ */
+ protected String saveExportFile(List exportList) {
+ // 导出到csv并存储文件
+ ReflectUtil reflectUtil = Utils.getReflectUtil();
+ List fieldNameList = new ArrayList<>();
+ List fieldList = new ArrayList<>();
+
+ List headerList = new ArrayList<>();
+ Class entityClass = getEntityClass();
+ for (Field field : reflectUtil.getFieldList(entityClass)) {
+ ExcelColumn excelColumn = reflectUtil.getAnnotation(ExcelColumn.class, field);
+ if (Objects.isNull(excelColumn)) {
+ continue;
+ }
+ fieldList.add(field);
+ fieldNameList.add(field.getName());
+ String fieldName = reflectUtil.getDescription(field);
+ headerList.add(fieldName);
+ }
+
+ List rowList = new ArrayList<>();
+ // 添加表头
+ rowList.add(String.join(Constant.COMMA, headerList));
+
+ String json = Json.toString(exportList);
+ List