Просмотр исходного кода

feat: 添加异步导入导出

wanggaokun 6 месяцев назад
Родитель
Сommit
b85249173d
28 измененных файлов с 1277 добавлено и 180 удалено
  1. 24 4
      pom.xml
  2. 45 0
      script/sql/postgresql/sys_import_export_t-1021-ddl.sql
  3. 19 0
      taais-common/taais-common-core/src/main/java/com/taais/common/core/core/domain/UploadRes.java
  4. 4 0
      taais-common/taais-common-core/src/main/java/com/taais/common/core/service/OssService.java
  5. 2 0
      taais-common/taais-common-core/src/main/java/com/taais/common/core/utils/StringUtils.java
  6. 5 4
      taais-common/taais-common-excel/src/main/java/com/taais/common/excel/core/DefaultExcelResult.java
  7. 3 1
      taais-common/taais-common-excel/src/main/java/com/taais/common/excel/core/ExcelResult.java
  8. 19 0
      taais-common/taais-common-excel/src/main/java/com/taais/common/excel/entity/ExcelResultRes.java
  9. 35 0
      taais-common/taais-common-excel/src/main/java/com/taais/common/excel/service/IExcelService.java
  10. 65 0
      taais-common/taais-common-excel/src/main/java/com/taais/common/excel/service/impl/ExcelService.java
  11. 23 0
      taais-common/taais-common-excel/src/main/java/com/taais/common/excel/utils/ExcelUtil.java
  12. 41 2
      taais-common/taais-common-oss/pom.xml
  13. 433 141
      taais-common/taais-common-oss/src/main/java/com/taais/common/oss/core/OssClient.java
  14. 5 0
      taais-common/taais-common-oss/src/main/java/com/taais/common/oss/entity/UploadResult.java
  15. 12 7
      taais-common/taais-common-oss/src/main/java/com/taais/common/oss/enumd/AccessPolicyType.java
  16. 49 0
      taais-modules/taais-system/src/main/java/com/taais/system/controller/system/ImportExportController.java
  17. 7 4
      taais-modules/taais-system/src/main/java/com/taais/system/controller/system/SysUserController.java
  18. 61 0
      taais-modules/taais-system/src/main/java/com/taais/system/domain/ImportExport.java
  19. 55 0
      taais-modules/taais-system/src/main/java/com/taais/system/domain/bo/ImportExportBo.java
  20. 75 0
      taais-modules/taais-system/src/main/java/com/taais/system/domain/vo/ImportExportVo.java
  21. 24 13
      taais-modules/taais-system/src/main/java/com/taais/system/listener/SysUserImportListener.java
  22. 16 0
      taais-modules/taais-system/src/main/java/com/taais/system/mapper/ImportExportMapper.java
  23. 62 0
      taais-modules/taais-system/src/main/java/com/taais/system/service/IImportExportService.java
  24. 24 0
      taais-modules/taais-system/src/main/java/com/taais/system/service/ISysUserService.java
  25. 105 0
      taais-modules/taais-system/src/main/java/com/taais/system/service/impl/ImportExportServiceImpl.java
  26. 15 4
      taais-modules/taais-system/src/main/java/com/taais/system/service/impl/SysOssServiceImpl.java
  27. 42 0
      taais-modules/taais-system/src/main/java/com/taais/system/service/impl/SysUserServiceImpl.java
  28. 7 0
      taais-modules/taais-system/src/main/resources/mapper/system/ImportExportMapper.xml

+ 24 - 4
pom.xml

@@ -50,7 +50,9 @@
         <!-- 离线IP地址定位库 -->
         <ip2region.version>2.7.0</ip2region.version>
         <!-- OSS 配置 -->
-        <aws-java-sdk-s3.version>1.12.600</aws-java-sdk-s3.version>
+<!--        <aws-java-sdk-s3.version>1.12.600</aws-java-sdk-s3.version>-->
+        <aws.sdk.version>2.25.15</aws.sdk.version>
+        <aws.crt.version>0.29.13</aws.crt.version>
         <!-- 加解密依赖库 -->
         <bcprov-jdk.version>1.77</bcprov-jdk.version>
         <!-- SMS 配置 -->
@@ -323,10 +325,28 @@
             </dependency>
 
             <!-- OSS 配置 -->
+<!--            <dependency>-->
+<!--                <groupId>com.amazonaws</groupId>-->
+<!--                <artifactId>aws-java-sdk-s3</artifactId>-->
+<!--                <version>${aws-java-sdk-s3.version}</version>-->
+<!--            </dependency>-->
+            <!--  AWS SDK for Java 2.x  -->
             <dependency>
-                <groupId>com.amazonaws</groupId>
-                <artifactId>aws-java-sdk-s3</artifactId>
-                <version>${aws-java-sdk-s3.version}</version>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>s3</artifactId>
+                <version>${aws.sdk.version}</version>
+            </dependency>
+            <!-- 使用AWS基于 CRT 的 S3 客户端 -->
+            <dependency>
+                <groupId>software.amazon.awssdk.crt</groupId>
+                <artifactId>aws-crt</artifactId>
+                <version>${aws.crt.version}</version>
+            </dependency>
+            <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>s3-transfer-manager</artifactId>
+                <version>${aws.sdk.version}</version>
             </dependency>
 
             <!-- 加解密依赖库 -->

+ 45 - 0
script/sql/postgresql/sys_import_export_t-1021-ddl.sql

@@ -0,0 +1,45 @@
+
+create table sys_import_export_t
+(
+    id          bigint                      not null,
+    name        varchar(255),
+    url         varchar(500),
+    oss_id      bigint,
+    log_info    text,
+    status      char    default '1'::bpchar not null,
+    tenant_id   bigint  default '0'::bigint not null,
+    version     integer default 0           not null,
+    create_by   bigint,
+    create_time timestamp,
+    update_by   bigint,
+    update_time timestamp,
+    constraint sys_import_export_pk
+        primary key (id)
+);
+
+comment on table sys_import_export_t is '导入导出记录';
+
+comment on column sys_import_export_t.id is '唯一编码';
+
+comment on column sys_import_export_t.name is '文件名称';
+
+comment on column sys_import_export_t.url is '文件地址';
+
+comment on column sys_import_export_t.oss_id is '文件Id';
+
+comment on column sys_import_export_t.log_info is '日志信息';
+
+comment on column sys_import_export_t.status is '状态(1正常  0异常 2部分正常)';
+
+comment on column sys_import_export_t.tenant_id is '租户id';
+
+comment on column sys_import_export_t.version is '乐观锁';
+
+comment on column sys_import_export_t.create_by is '创建人';
+
+comment on column sys_import_export_t.create_time is '创建时间';
+
+comment on column sys_import_export_t.update_by is '更新人';
+
+comment on column sys_import_export_t.update_time is '更新时间';
+

+ 19 - 0
taais-common/taais-common-core/src/main/java/com/taais/common/core/core/domain/UploadRes.java

@@ -0,0 +1,19 @@
+package com.taais.common.core.core.domain;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @Description: UploadRes
+ * @Author: GaoKun Wang
+ * @Date: 2024/10/21
+ */
+@Data
+@Builder
+public class UploadRes {
+    private String url;
+    private String name;
+    private Long ossId;
+
+
+}

+ 4 - 0
taais-common/taais-common-core/src/main/java/com/taais/common/core/service/OssService.java

@@ -1,5 +1,7 @@
 package com.taais.common.core.service;
 
+import com.taais.common.core.core.domain.UploadRes;
+
 /**
  * 通用 OSS服务
  *
@@ -15,4 +17,6 @@ public interface OssService {
      */
     String selectUrlByIds(String ossIds);
 
+    UploadRes upload(byte[] file, String name);
+
 }

+ 2 - 0
taais-common/taais-common-core/src/main/java/com/taais/common/core/utils/StringUtils.java

@@ -26,6 +26,8 @@ import java.util.stream.Collectors;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class StringUtils extends org.apache.commons.lang3.StringUtils {
     public static final String SEPARATOR = ",";
+    public static final String SLASH = "/";
+
 
     /**
      * 空字符串

+ 5 - 4
taais-common/taais-common-excel/src/main/java/com/taais/common/excel/core/DefaultExcelResult.java

@@ -1,6 +1,7 @@
 package com.taais.common.excel.core;
 
 import cn.hutool.core.util.StrUtil;
+import com.taais.common.excel.entity.ExcelResultRes;
 import lombok.Setter;
 
 import java.util.ArrayList;
@@ -57,16 +58,16 @@ public class DefaultExcelResult<T> implements ExcelResult<T> {
      * @return 导入回执
      */
     @Override
-    public String getAnalysis() {
+    public ExcelResultRes getAnalysis() {
         int successCount = list.size();
         int errorCount = errorList.size();
         if (successCount == 0) {
-            return "读取失败,未解析到数据";
+            return ExcelResultRes.builder().logInfo("读取失败,未解析到数据").status("0").build();
         } else {
             if (errorCount == 0) {
-                return StrUtil.format("恭喜您,全部读取成功!共{}条", successCount);
+                return ExcelResultRes.builder().logInfo(StrUtil.format("恭喜您,全部读取成功!共{}条", successCount)).status("1").build();
             } else {
-                return "";
+                return ExcelResultRes.builder().build();
             }
         }
     }

+ 3 - 1
taais-common/taais-common-excel/src/main/java/com/taais/common/excel/core/ExcelResult.java

@@ -1,5 +1,7 @@
 package com.taais.common.excel.core;
 
+import com.taais.common.excel.entity.ExcelResultRes;
+
 import java.util.List;
 
 /**
@@ -22,5 +24,5 @@ public interface ExcelResult<T> {
     /**
      * 导入回执
      */
-    String getAnalysis();
+    ExcelResultRes getAnalysis();
 }

+ 19 - 0
taais-common/taais-common-excel/src/main/java/com/taais/common/excel/entity/ExcelResultRes.java

@@ -0,0 +1,19 @@
+package com.taais.common.excel.entity;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @Description: ExcelResult 导入导出结果
+ * @Author: GaoKun Wang
+ * @Date: 2024/6/28
+ */
+@Data
+@Builder
+public class ExcelResultRes {
+    private String status;
+    private String logInfo;
+    private String url;
+    private String name;
+    private Long ossId;
+}

+ 35 - 0
taais-common/taais-common-excel/src/main/java/com/taais/common/excel/service/IExcelService.java

@@ -0,0 +1,35 @@
+package com.taais.common.excel.service;
+
+import com.taais.common.excel.core.ExcelListener;
+import com.taais.common.excel.entity.ExcelResultRes;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * @Description: IExcelService 接口
+ * @Author: GaoKun Wang
+ * @Date: 2024/6/26
+ */
+public interface IExcelService {
+
+    /**
+     * 使用自定义监听器 异步导入 自定义返回
+     *
+     * @param inputStream 输入流文件
+     * @param name        文件名
+     * @param clazz       对象类型
+     * @param listener    自定义监听器
+     */
+    <T> ExcelResultRes importExcel(InputStream inputStream, String name, Class<T> clazz, ExcelListener<T> listener);
+
+
+    /**
+     * 导出excel
+     *
+     * @param list      导出数据集合
+     * @param sheetName 工作表的名称
+     * @param clazz     实体类
+     */
+    <T> ExcelResultRes exportExcel(List<T> list, String sheetName, Class<T> clazz);
+}

+ 65 - 0
taais-common/taais-common-excel/src/main/java/com/taais/common/excel/service/impl/ExcelService.java

@@ -0,0 +1,65 @@
+package com.taais.common.excel.service.impl;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import com.alibaba.excel.EasyExcel;
+import com.taais.common.core.core.domain.UploadRes;
+import com.taais.common.core.service.OssService;
+import com.taais.common.excel.core.ExcelListener;
+import com.taais.common.excel.entity.ExcelResultRes;
+import com.taais.common.excel.service.IExcelService;
+import com.taais.common.excel.utils.ExcelUtil;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * @Description: ExcelService 实现类
+ * @Author: GaoKun Wang
+ * @Date: 2024/6/26
+ */
+@Service
+@Slf4j
+public class ExcelService implements IExcelService {
+
+    @Resource
+    private OssService ossService;
+
+    @Override
+    public <T> ExcelResultRes importExcel(InputStream inputStream, String name, Class<T> clazz, ExcelListener<T> listener) {
+        log.info("开始异步导入");
+        byte[] bytes = IoUtil.readBytes(inputStream);
+        InputStream copyInputStream = new ByteArrayInputStream(bytes);
+        EasyExcel.read(copyInputStream, clazz, listener).sheet().doReadSync();
+        UploadRes uploadRes = ossService.upload(bytes, name);
+        ExcelResultRes res = listener.getExcelResult().getAnalysis();
+        res.setName(uploadRes.getName());
+        res.setUrl(uploadRes.getUrl());
+        res.setOssId(uploadRes.getOssId());
+        log.info("错误信息:{}", res.getLogInfo());
+        log.info("状态:{}", res.getStatus());
+        log.info("异步导入结束");
+        return res;
+    }
+
+    @Override
+    public <T> ExcelResultRes exportExcel(List<T> list, String sheetName, Class<T> clazz) {
+        log.info("开始异步导出");
+        try {
+            String fileName = sheetName + "_" + IdUtil.fastSimpleUUID() + ".xlsx";
+            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+            byte[] bytes = ExcelUtil.exportExcel(list, sheetName, clazz, false, byteArrayOutputStream, null);
+            UploadRes uploadRes = ossService.upload(bytes, fileName);
+            log.info("异步导出结束");
+            return ExcelResultRes.builder().name(uploadRes.getName()).url(uploadRes.getUrl()).status("1").ossId(uploadRes.getOssId()).logInfo("导出成功").build();
+        } catch (Exception e) {
+            log.error("异步导出失败:{}", e.getMessage());
+            return ExcelResultRes.builder().status("0").logInfo("导出失败").build();
+        }
+    }
+}

+ 23 - 0
taais-common/taais-common-excel/src/main/java/com/taais/common/excel/utils/ExcelUtil.java

@@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletResponse;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -166,6 +167,28 @@ public class ExcelUtil {
         exportExcel(list, sheetName, clazz, false, os, null);
     }
 
+    public static <T> byte[] exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
+                                         ByteArrayOutputStream os, List<DropDownOptions> options) {
+        excelWriterSheetBuilder(list, sheetName, clazz, merge, os, options);
+        return os.toByteArray();
+    }
+
+    private static <T> void excelWriterSheetBuilder(List<T> list, String sheetName, Class<T> clazz, boolean merge, ByteArrayOutputStream os, List<DropDownOptions> options) {
+        ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
+                .autoCloseStream(false)
+                // 自动适配
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                // 大数值自动转换 防止失真
+                .registerConverter(new ExcelBigNumberConvert())
+                .sheet(sheetName);
+        if (merge) {
+            // 合并处理器
+            builder.registerWriteHandler(new CellMergeStrategy(list, true));
+        }
+        // 添加下拉框操作
+        builder.registerWriteHandler(new ExcelDownHandler(options));
+        builder.doWrite(list);
+    }
     /**
      * 导出excel
      *

+ 41 - 2
taais-common/taais-common-oss/pom.xml

@@ -31,9 +31,48 @@
             <artifactId>taais-common-redis</artifactId>
         </dependency>
 
+<!--        <dependency>-->
+<!--            <groupId>com.amazonaws</groupId>-->
+<!--            <artifactId>aws-java-sdk-s3</artifactId>-->
+<!--        </dependency>-->
+        <!--  AWS SDK for Java 2.x  -->
         <dependency>
-            <groupId>com.amazonaws</groupId>
-            <artifactId>aws-java-sdk-s3</artifactId>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>s3</artifactId>
+            <exclusions>
+                <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>netty-nio-client</artifactId>
+                </exclusion>
+                <!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>aws-crt-client</artifactId>
+                </exclusion>
+                <!-- 将基于 Apache 的 HTTP 客户端从类路径中移除 -->
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>apache-client</artifactId>
+                </exclusion>
+                <!-- 将配置基于 URL 连接的 HTTP 客户端从类路径中移除 -->
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>url-connection-client</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- 使用AWS基于 CRT 的 S3 客户端 -->
+        <dependency>
+            <groupId>software.amazon.awssdk.crt</groupId>
+            <artifactId>aws-crt</artifactId>
+        </dependency>
+
+        <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>s3-transfer-manager</artifactId>
         </dependency>
     </dependencies>
 

+ 433 - 141
taais-common/taais-common-oss/src/main/java/com/taais/common/oss/core/OssClient.java

@@ -2,35 +2,44 @@ package com.taais.common.oss.core;
 
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.IdUtil;
-import com.amazonaws.ClientConfiguration;
-import com.amazonaws.HttpMethod;
-import com.amazonaws.Protocol;
-import com.amazonaws.auth.AWSCredentials;
-import com.amazonaws.auth.AWSCredentialsProvider;
-import com.amazonaws.auth.AWSStaticCredentialsProvider;
-import com.amazonaws.auth.BasicAWSCredentials;
-import com.amazonaws.client.builder.AwsClientBuilder;
-import com.amazonaws.services.s3.AmazonS3;
-import com.amazonaws.services.s3.AmazonS3Client;
-import com.amazonaws.services.s3.AmazonS3ClientBuilder;
-import com.amazonaws.services.s3.model.CreateBucketRequest;
-import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
-import com.amazonaws.services.s3.model.ObjectMetadata;
-import com.amazonaws.services.s3.model.PutObjectRequest;
-import com.amazonaws.services.s3.model.S3Object;
+import com.taais.common.core.constant.Constants;
 import com.taais.common.core.utils.DateUtils;
 import com.taais.common.core.utils.StringUtils;
+import com.taais.common.core.utils.file.FileUtils;
 import com.taais.common.oss.constant.OssConstant;
 import com.taais.common.oss.entity.UploadResult;
 import com.taais.common.oss.enumd.AccessPolicyType;
 import com.taais.common.oss.enumd.PolicyType;
 import com.taais.common.oss.exception.OssException;
 import com.taais.common.oss.properties.OssProperties;
+import lombok.Getter;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.async.AsyncRequestBody;
+import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3AsyncClient;
+import software.amazon.awssdk.services.s3.S3Configuration;
+import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.transfer.s3.S3TransferManager;
+import software.amazon.awssdk.transfer.s3.model.CompletedFileUpload;
+import software.amazon.awssdk.transfer.s3.model.CompletedUpload;
+import software.amazon.awssdk.transfer.s3.model.FileDownload;
+import software.amazon.awssdk.transfer.s3.model.FileUpload;
+import software.amazon.awssdk.transfer.s3.model.Upload;
+import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
+import java.net.URI;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
 import java.util.Date;
 
 /**
@@ -40,39 +49,79 @@ import java.util.Date;
  * @author wgk
  */
 public class OssClient {
+    /**
+     * 服务商
+     * -- GETTER --
+     *  服务商
 
+     */
+    @Getter
     private final String configKey;
 
+    /**
+     * 配置属性
+     */
     private final OssProperties properties;
 
-    private final AmazonS3 client;
+    /**
+     * Amazon S3 异步客户端
+     */
+    private final S3AsyncClient client;
 
+    /**
+     * 用于管理 S3 数据传输的高级工具
+     */
+    private final S3TransferManager transferManager;
+
+    /**
+     * AWS S3 预签名 URL 的生成器
+     */
+    private final S3Presigner presigner;
+
+    /**
+     * 构造方法
+     *
+     * @param configKey     配置键
+     * @param ossProperties Oss配置属性
+     */
     public OssClient(String configKey, OssProperties ossProperties) {
         this.configKey = configKey;
         this.properties = ossProperties;
         try {
-            AwsClientBuilder.EndpointConfiguration endpointConfig =
-                new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
-
-            AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
-            AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
-            ClientConfiguration clientConfig = new ClientConfiguration();
-            if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
-                clientConfig.setProtocol(Protocol.HTTPS);
-            } else {
-                clientConfig.setProtocol(Protocol.HTTP);
-            }
-            AmazonS3ClientBuilder build = AmazonS3Client.builder()
-                .withEndpointConfiguration(endpointConfig)
-                .withClientConfiguration(clientConfig)
-                .withCredentials(credentialsProvider)
-                .disableChunkedEncoding();
-            if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
-                // minio 使用https限制使用域名访问 需要此配置 站点填域名
-                build.enablePathStyleAccess();
-            }
-            this.client = build.build();
+            // 创建 AWS 认证信息
+            StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
+                    AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
+
+            //MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
+            boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
+
+            //创建AWS基于 CRT 的 S3 客户端
+            this.client = S3AsyncClient.crtBuilder()
+                    .credentialsProvider(credentialsProvider)
+                    .endpointOverride(URI.create(getEndpoint()))
+                    .region(of())
+                    .targetThroughputInGbps(20.0)
+                    .minimumPartSizeInBytes(10 * 1025 * 1024L)
+                    .checksumValidationEnabled(false)
+                    .forcePathStyle(isStyle)
+                    .build();
+
+            //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
+            this.transferManager = S3TransferManager.builder().s3Client(this.client).build();
+
+            // 创建 S3 配置对象
+            S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false)
+                    .pathStyleAccessEnabled(isStyle).build();
+
+            // 创建 预签名 URL 的生成器 实例,用于生成 S3 预签名 URL
+            this.presigner = S3Presigner.builder()
+                    .region(of())
+                    .credentialsProvider(credentialsProvider)
+                    .endpointOverride(URI.create(getDomain()))
+                    .serviceConfiguration(config)
+                    .build();
 
+            // 创建存储桶
             createBucket();
         } catch (Exception e) {
             if (e instanceof OssException) {
@@ -82,170 +131,341 @@ public class OssClient {
         }
     }
 
-    private static String getPolicy(String bucketName, PolicyType policyType) {
-        StringBuilder builder = new StringBuilder();
-        builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
-        builder.append(switch (policyType) {
-            case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n";
-            case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n";
-            default -> "\"s3:GetBucketLocation\"\n";
-        });
-        builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
-        builder.append(bucketName);
-        builder.append("\"\n},\n");
-        if (policyType == PolicyType.READ) {
-            builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
-            builder.append(bucketName);
-            builder.append("\"\n},\n");
-        }
-        builder.append("{\n\"Action\": ");
-        builder.append(switch (policyType) {
-            case WRITE ->
-                "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
-            case READ_WRITE ->
-                "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
-            default -> "\"s3:GetObject\",\n";
-        });
-        builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
-        builder.append(bucketName);
-        builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
-        return builder.toString();
-    }
-
+    /**
+     * 同步创建存储桶
+     * 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作
+     *
+     * @throws OssException 当创建存储桶时发生异常时抛出
+     */
     public void createBucket() {
+        String bucketName = properties.getBucketName();
         try {
-            String bucketName = properties.getBucketName();
-            if (client.doesBucketExistV2(bucketName)) {
-                return;
+            // 尝试获取存储桶的信息
+            client.headBucket(
+                            x -> x.bucket(bucketName)
+                                    .build())
+                    .join();
+        } catch (Exception ex) {
+            if (ex.getCause() instanceof NoSuchBucketException) {
+                try {
+                    // 存储桶不存在,尝试创建存储桶
+                    client.createBucket(
+                                    x -> x.bucket(bucketName))
+                            .join();
+
+                    // 设置存储桶的访问策略(Bucket Policy)
+                    client.putBucketPolicy(
+                                    x -> x.bucket(bucketName)
+                                            .policy(getPolicy(bucketName, getAccessPolicy().getPolicyType())))
+                            .join();
+                } catch (S3Exception e) {
+                    // 存储桶创建或策略设置失败
+                    throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
+                }
+            } else {
+                throw new OssException("判断Bucket是否存在失败,请核对配置信息:[" + ex.getMessage() + "]");
             }
-            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
-            AccessPolicyType accessPolicy = getAccessPolicy();
-            createBucketRequest.setCannedAcl(accessPolicy.getAcl());
-            client.createBucket(createBucketRequest);
-            client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
-        } catch (Exception e) {
-            throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
         }
     }
 
-    public UploadResult upload(byte[] data, String path, String contentType) {
-        return upload(new ByteArrayInputStream(data), path, contentType);
+    /**
+     * 上传文件到 Amazon S3,并返回上传结果
+     *
+     * @param filePath  本地文件路径
+     * @param key       在 Amazon S3 中的对象键
+     * @param md5Digest 本地文件的 MD5 哈希值(可选)
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult upload(Path filePath, String key, String md5Digest) {
+        try {
+            // 构建上传请求对象
+            FileUpload fileUpload = transferManager.uploadFile(
+                    x -> x.putObjectRequest(
+                                    y -> y.bucket(properties.getBucketName())
+                                            .key(key)
+                                            .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
+                                            .build())
+                            .addTransferListener(LoggingTransferListener.create())
+                            .source(filePath).build());
+
+            // 等待上传完成并获取上传结果
+            CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
+            String eTag = uploadResult.response().eTag();
+
+            // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
+            return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
+        } catch (Exception e) {
+            // 捕获异常并抛出自定义异常
+            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
+        } finally {
+            // 无论上传是否成功,最终都会删除临时文件
+            FileUtils.del(filePath);
+        }
     }
 
-    public UploadResult upload(InputStream inputStream, String path, String contentType) {
+    /**
+     * 上传 InputStream 到 Amazon S3
+     *
+     * @param inputStream 要上传的输入流
+     * @param key         在 Amazon S3 中的对象键
+     * @param length      输入流的长度
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult upload(InputStream inputStream, String key, Long length) {
+        // 如果输入流不是 ByteArrayInputStream,则将其读取为字节数组再创建 ByteArrayInputStream
         if (!(inputStream instanceof ByteArrayInputStream)) {
             inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
         }
         try {
-            ObjectMetadata metadata = new ObjectMetadata();
-            metadata.setContentType(contentType);
-            metadata.setContentLength(inputStream.available());
-            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata);
-            // 设置上传对象的 Acl 为公共读
-            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
-            client.putObject(putObjectRequest);
+            // 创建异步请求体(length如果为空会报错)
+            BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length);
+
+            // 使用 transferManager 进行上传
+            Upload upload = transferManager.upload(
+                    x -> x.requestBody(body)
+                            .putObjectRequest(
+                                    y -> y.bucket(properties.getBucketName())
+                                            .key(key)
+                                            .build())
+                            .build());
+
+            // 将输入流写入请求体
+            body.writeInputStream(inputStream);
+
+            // 等待文件上传操作完成
+            CompletedUpload uploadResult = upload.completionFuture().join();
+            String eTag = uploadResult.response().eTag();
+
+            // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
+            return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
         } catch (Exception e) {
             throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
         }
-        return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
     }
 
-    public UploadResult upload(File file, String path) {
-        try {
-            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
-            // 设置上传对象的 Acl 为公共读
-            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
-            client.putObject(putObjectRequest);
-        } catch (Exception e) {
-            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
-        }
-        return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
+    /**
+     * 下载文件从 Amazon S3 到临时目录
+     *
+     * @param path 文件在 Amazon S3 中的对象键
+     * @return 下载后的文件在本地的临时路径
+     * @throws OssException 如果下载失败,抛出自定义异常
+     */
+    public Path fileDownload(String path) {
+        // 构建临时文件
+        Path tempFilePath = FileUtils.createTempFile().toPath();
+        // 使用 S3TransferManager 下载文件
+        FileDownload downloadFile = transferManager.downloadFile(
+                x -> x.getObjectRequest(
+                                y -> y.bucket(properties.getBucketName())
+                                        .key(removeBaseUrl(path))
+                                        .build())
+                        .addTransferListener(LoggingTransferListener.create())
+                        .destination(tempFilePath)
+                        .build());
+        // 等待文件下载操作完成
+        downloadFile.completionFuture().join();
+        return tempFilePath;
     }
 
+    /**
+     * 删除云存储服务中指定路径下文件
+     *
+     * @param path 指定路径
+     */
     public void delete(String path) {
-        path = path.replace(getUrl() + "/", "");
         try {
-            client.deleteObject(properties.getBucketName(), path);
+            client.deleteObject(
+                    x -> x.bucket(properties.getBucketName())
+                            .key(removeBaseUrl(path))
+                            .build());
         } catch (Exception e) {
             throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
         }
     }
 
-    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
-        return upload(data, getPath(properties.getPrefix(), suffix), contentType);
+    /**
+     * 获取私有URL链接
+     *
+     * @param objectKey 对象KEY
+     * @param second    授权时间
+     */
+    public String getPrivateUrl(String objectKey, Integer second) {
+        // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
+        URL url = presigner.presignGetObject(
+                        x -> x.signatureDuration(Duration.ofSeconds(second))
+                                .getObjectRequest(
+                                        y -> y.bucket(properties.getBucketName())
+                                                .key(objectKey)
+                                                .build())
+                                .build())
+                .url();
+        return url.toString();
+    }
+
+    /**
+     * 上传 byte[] 数据到 Amazon S3,使用指定的后缀构造对象键。
+     *
+     * @param data   要上传的 byte[] 数据
+     * @param suffix 对象键的后缀
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult uploadSuffix(byte[] data, String suffix) {
+        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length));
     }
 
-    public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
-        return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
+    /**
+     * 上传 InputStream 到 Amazon S3,使用指定的后缀构造对象键。
+     *
+     * @param inputStream 要上传的输入流
+     * @param suffix      对象键的后缀
+     * @param length      输入流的长度
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
+        return upload(inputStream, getPath(properties.getPrefix(), suffix), length);
     }
 
+    /**
+     * 上传文件到 Amazon S3,使用指定的后缀构造对象键
+     *
+     * @param file   要上传的文件
+     * @param suffix 对象键的后缀
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
     public UploadResult uploadSuffix(File file, String suffix) {
-        return upload(file, getPath(properties.getPrefix(), suffix));
+        return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null);
     }
 
     /**
-     * 获取文件元数据
+     * 获取文件输入流
      *
      * @param path 完整文件路径
+     * @return 输入流
+     */
+    public InputStream getObjectContent(String path) throws IOException {
+        // 下载文件到临时目录
+        Path tempFilePath = fileDownload(path);
+        // 创建输入流
+        InputStream inputStream = Files.newInputStream(tempFilePath);
+        // 删除临时文件
+        FileUtils.del(tempFilePath);
+        // 返回对象内容的输入流
+        return inputStream;
+    }
+
+    /**
+     * 获取 S3 客户端的终端点 URL
+     *
+     * @return 终端点 URL
      */
-    public ObjectMetadata getObjectMetadata(String path) {
-        path = path.replace(getUrl() + "/", "");
-        S3Object object = client.getObject(properties.getBucketName(), path);
-        return object.getObjectMetadata();
+    public String getEndpoint() {
+        // 根据配置文件中的是否使用 HTTPS,设置协议头部
+        String header = getIsHttps();
+        // 拼接协议头部和终端点,得到完整的终端点 URL
+        return header + properties.getEndpoint();
+    }
+
+    /**
+     * 获取 S3 客户端的终端点 URL(自定义域名)
+     *
+     * @return 终端点 URL
+     */
+    public String getDomain() {
+        // 从配置中获取域名、终端点、是否使用 HTTPS 等信息
+        String domain = properties.getDomain();
+        String endpoint = properties.getEndpoint();
+        String header = getIsHttps();
+
+        // 如果是云服务商,直接返回域名或终端点
+        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+            return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint;
+        }
+
+        // 如果是 MinIO,处理域名并返回
+        if (StringUtils.isNotEmpty(domain)) {
+            return domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP) ? domain : header + domain;
+        }
+
+        // 返回终端点
+        return header + endpoint;
     }
 
-    public InputStream getObjectContent(String path) {
-        path = path.replace(getUrl() + "/", "");
-        S3Object object = client.getObject(properties.getBucketName(), path);
-        return object.getObjectContent();
+    /**
+     * 根据传入的 region 参数返回相应的 AWS 区域
+     * 如果 region 参数非空,使用 Region.of 方法创建并返回对应的 AWS 区域对象
+     * 如果 region 参数为空,返回一个默认的 AWS 区域(例如,us-east-1),作为广泛支持的区域
+     *
+     * @return 对应的 AWS 区域对象,或者默认的广泛支持的区域(us-east-1)
+     */
+    public Region of() {
+        //AWS 区域字符串
+        String region = properties.getRegion();
+        // 如果 region 参数非空,使用 Region.of 方法创建对应的 AWS 区域对象,否则返回默认区域
+        return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
     }
 
+    /**
+     * 获取云存储服务的URL
+     *
+     * @return 文件路径
+     */
     public String getUrl() {
         String domain = properties.getDomain();
         String endpoint = properties.getEndpoint();
-        String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
+        String header = getIsHttps();
         // 云服务商直接返回
         if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
-            if (StringUtils.isNotBlank(domain)) {
-                return header + domain;
-            }
-            return header + properties.getBucketName() + "." + endpoint;
+            return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint);
         }
-        // minio 单独处理
-        if (StringUtils.isNotBlank(domain)) {
-            return header + domain + "/" + properties.getBucketName();
+        // MinIO 单独处理
+        if (StringUtils.isNotEmpty(domain)) {
+            // 如果 domain 以 "https://" 或 "http://" 开头
+            return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ?
+                    domain + StringUtils.SLASH + properties.getBucketName() : header + domain + StringUtils.SLASH + properties.getBucketName();
         }
-        return header + endpoint + "/" + properties.getBucketName();
+        return header + endpoint + StringUtils.SLASH + properties.getBucketName();
     }
 
+    /**
+     * 生成一个符合特定规则的、唯一的文件路径。通过使用日期、UUID、前缀和后缀等元素的组合,确保了文件路径的独一无二性
+     *
+     * @param prefix 前缀
+     * @param suffix 后缀
+     * @return 文件路径
+     */
     public String getPath(String prefix, String suffix) {
         // 生成uuid
         String uuid = IdUtil.fastSimpleUUID();
-        // 文件路径
-        String path = DateUtils.datePath() + "/" + uuid;
-        if (StringUtils.isNotBlank(prefix)) {
-            path = prefix + "/" + path;
-        }
+        // 生成日期路径
+        String datePath = DateUtils.datePath();
+        // 拼接路径
+        String path = StringUtils.isNotEmpty(prefix) ?
+                prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
         return path + suffix;
     }
 
-    public String getConfigKey() {
-        return configKey;
+    /**
+     * 移除路径中的基础URL部分,得到相对路径
+     *
+     * @param path 完整的路径,包括基础URL和相对路径
+     * @return 去除基础URL后的相对路径
+     */
+    public String removeBaseUrl(String path) {
+        return path.replace(getUrl() + StringUtils.SLASH, "");
     }
 
     /**
-     * 获取私有URL链接
+     * 获取是否使用 HTTPS 的配置,并返回相应的协议头部。
      *
-     * @param objectKey 对象KEY
-     * @param second    授权时间
+     * @return 协议头部,根据是否使用 HTTPS 返回 "https://" 或 "http://"
      */
-    public String getPrivateUrl(String objectKey, Integer second) {
-        GeneratePresignedUrlRequest generatePresignedUrlRequest =
-            new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
-                .withMethod(HttpMethod.GET)
-                .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
-        URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
-        return url.toString();
+    public String getIsHttps() {
+        return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP;
     }
 
     /**
@@ -264,4 +484,76 @@ public class OssClient {
         return AccessPolicyType.getByType(properties.getAccessPolicy());
     }
 
+    /**
+     * 生成 AWS S3 存储桶访问策略
+     *
+     * @param bucketName 存储桶
+     * @param policyType 桶策略类型
+     * @return 符合 AWS S3 存储桶访问策略格式的字符串
+     */
+    private static String getPolicy(String bucketName, PolicyType policyType) {
+        String policy = switch (policyType) {
+            case WRITE -> """
+                {
+                  "Version": "2012-10-17",
+                  "Statement": []
+                }
+                """;
+            case READ_WRITE -> """
+                {
+                  "Version": "2012-10-17",
+                  "Statement": [
+                    {
+                      "Effect": "Allow",
+                      "Principal": "*",
+                      "Action": [
+                        "s3:GetBucketLocation",
+                        "s3:ListBucket",
+                        "s3:ListBucketMultipartUploads"
+                      ],
+                      "Resource": "arn:aws:s3:::bucketName"
+                    },
+                    {
+                      "Effect": "Allow",
+                      "Principal": "*",
+                      "Action": [
+                        "s3:AbortMultipartUpload",
+                        "s3:DeleteObject",
+                        "s3:GetObject",
+                        "s3:ListMultipartUploadParts",
+                        "s3:PutObject"
+                      ],
+                      "Resource": "arn:aws:s3:::bucketName/*"
+                    }
+                  ]
+                }
+                """;
+            case READ -> """
+                {
+                  "Version": "2012-10-17",
+                  "Statement": [
+                    {
+                      "Effect": "Allow",
+                      "Principal": "*",
+                      "Action": ["s3:GetBucketLocation"],
+                      "Resource": "arn:aws:s3:::bucketName"
+                    },
+                    {
+                      "Effect": "Deny",
+                      "Principal": "*",
+                      "Action": ["s3:ListBucket"],
+                      "Resource": "arn:aws:s3:::bucketName"
+                    },
+                    {
+                      "Effect": "Allow",
+                      "Principal": "*",
+                      "Action": "s3:GetObject",
+                      "Resource": "arn:aws:s3:::bucketName/*"
+                    }
+                  ]
+                }
+                """;
+        };
+        return policy.replaceAll("bucketName", bucketName);
+    }
 }

+ 5 - 0
taais-common/taais-common-oss/src/main/java/com/taais/common/oss/entity/UploadResult.java

@@ -21,4 +21,9 @@ public class UploadResult {
      * 文件名
      */
     private String filename;
+
+    /**
+     * 已上传对象的实体标记(用来校验文件)
+     */
+    private String eTag;
 }

+ 12 - 7
taais-common/taais-common-oss/src/main/java/com/taais/common/oss/enumd/AccessPolicyType.java

@@ -1,8 +1,8 @@
 package com.taais.common.oss.enumd;
-
-import com.amazonaws.services.s3.model.CannedAccessControlList;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import software.amazon.awssdk.services.s3.model.BucketCannedACL;
+import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
 
 /**
  * 桶访问策略配置
@@ -16,27 +16,32 @@ public enum AccessPolicyType {
     /**
      * private
      */
-    PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE),
+    PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE),
 
     /**
      * public
      */
-    PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ),
+    PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE),
 
     /**
      * custom
      */
-    CUSTOM("2", CannedAccessControlList.PublicRead, PolicyType.READ);
+    CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ);
 
     /**
-     * 桶 权限类型
+     * 桶 权限类型(数据库值)
      */
     private final String type;
 
+    /**
+     * 桶 权限类型
+     */
+    private final BucketCannedACL bucketCannedACL;
+
     /**
      * 文件对象 权限类型
      */
-    private final CannedAccessControlList acl;
+    private final ObjectCannedACL objectCannedACL;
 
     /**
      * 桶策略类型

+ 49 - 0
taais-modules/taais-system/src/main/java/com/taais/system/controller/system/ImportExportController.java

@@ -0,0 +1,49 @@
+package com.taais.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.taais.common.core.core.domain.CommonResult;
+import com.taais.common.core.core.page.PageResult;
+import com.taais.common.web.core.BaseController;
+import com.taais.system.domain.bo.ImportExportBo;
+import com.taais.system.domain.vo.ImportExportVo;
+import com.taais.system.service.IImportExportService;
+import jakarta.annotation.Resource;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 导入导出日志Controller
+ *
+ * @author wgk
+ * @date 2024-06-24
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/importExport")
+public class ImportExportController extends BaseController {
+    @Resource
+    private IImportExportService importExportService;
+
+    /**
+     * 查询导入导出日志列表
+     */
+    @SaCheckPermission("system:importExport:list")
+    @GetMapping("/list")
+    public CommonResult<PageResult<ImportExportVo>> list(ImportExportBo importExportBo) {
+        return CommonResult.success(importExportService.selectPage(importExportBo));
+    }
+
+    /**
+     * 获取导入导出日志详细信息
+     */
+    @SaCheckPermission("system:importExport:query")
+    @GetMapping(value = "/{id}")
+    public CommonResult<ImportExportVo> getInfo(@PathVariable Long id) {
+        return CommonResult.success(importExportService.selectById(id));
+    }
+}

+ 7 - 4
taais-modules/taais-system/src/main/java/com/taais/system/controller/system/SysUserController.java

@@ -91,10 +91,12 @@ public class SysUserController extends BaseController {
     @Log(title = "用户管理", businessType = BusinessType.EXPORT)
     @SaCheckPermission("system:user:export")
     @PostMapping("/export")
-    public void export(HttpServletResponse response, SysUserBo userBo) {
+    public CommonResult<Void> export(HttpServletResponse response, SysUserBo userBo) {
+        LoginUser loginUser = LoginHelper.getLoginUser();
         List<SysUserVo> list = userService.selectUserList(userBo);
         List<SysUserExportVo> listVo = MapstructUtils.convert(list, SysUserExportVo.class);
-        ExcelUtil.exportExcel(listVo, "用户数据", SysUserExportVo.class, response);
+        userService.asyncExport(listVo, "用户数据", loginUser);
+        return CommonResult.success();
     }
 
     /**
@@ -107,8 +109,9 @@ public class SysUserController extends BaseController {
     @SaCheckPermission("system:user:import")
     @PostMapping("/importData")
     public CommonResult<Void> importData(MultipartFile file, boolean updateSupport) throws Exception {
-        ExcelResult<SysUserImportVo> result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport));
-        return CommonResult.success(result.getAnalysis());
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        userService.asyncImportData(file, updateSupport, loginUser);
+        return CommonResult.success();
     }
 
     @PostMapping("/importTemplate")

+ 61 - 0
taais-modules/taais-system/src/main/java/com/taais/system/domain/ImportExport.java

@@ -0,0 +1,61 @@
+package com.taais.system.domain;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+import com.taais.common.orm.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 导入导出日志对象 sys_import_export_t
+ *
+ * @author wgk
+ * @date 2024-10-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Table(value = "sys_import_export_t")
+public class ImportExport extends BaseEntity {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 唯一编码
+     */
+    @Id
+    private Long id;
+
+    /**
+     * 类型(0导入 1导出)
+     */
+    private String type;
+
+    /**
+     * 文件名称
+     */
+    private String name;
+
+    /**
+     * 文件地址
+     */
+    private String url;
+
+    /**
+     * 状态
+     */
+    private Long ossId;
+
+    /**
+     * 日志信息
+     */
+    private String logInfo;
+
+    /**
+     * 状态
+     */
+    private String status;
+
+
+}

+ 55 - 0
taais-modules/taais-system/src/main/java/com/taais/system/domain/bo/ImportExportBo.java

@@ -0,0 +1,55 @@
+package com.taais.system.domain.bo;
+
+import com.taais.common.orm.core.domain.BaseEntity;
+import com.taais.system.domain.ImportExport;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 导入导出日志业务对象 sys_import_export_t
+ *
+ * @author wgk
+ * @date 2024-10-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ImportExport.class, reverseConvertGenerate = false)
+public class ImportExportBo extends BaseEntity {
+    /**
+     * 唯一编码
+     */
+    private Long id;
+
+    /**
+     * 类型(0导入 1导出)
+     */
+    private String type;
+
+    /**
+     * 文件名称
+     */
+    private String name;
+
+    /**
+     * 日志信息
+     */
+    private String logInfo;
+
+    /**
+     * 状态
+     */
+    private String status;
+
+    /**
+     * 状态
+     */
+    private String url;
+
+    /**
+     * 状态
+     */
+    private Long ossId;
+
+
+}

+ 75 - 0
taais-modules/taais-system/src/main/java/com/taais/system/domain/vo/ImportExportVo.java

@@ -0,0 +1,75 @@
+package com.taais.system.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.taais.system.domain.ImportExport;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 导入导出日志视图对象 sys_import_export_t
+ *
+ * @author wgk
+ * @date 2024-10-21
+ */
+@Data
+@AutoMapper(target = ImportExport.class)
+public class ImportExportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 唯一编码
+     */
+    private Long id;
+
+    /**
+     * 类型(0导入 1导出)
+     */
+    private String type;
+
+    /**
+     * 状态
+     */
+    private Long ossId;
+
+    /**
+     * 文件名称
+     */
+    private String name;
+
+    /**
+     * 文件url
+     */
+    private String url;
+
+    /**
+     * 日志信息
+     */
+    private String logInfo;
+
+    /**
+     * 状态
+     */
+    private String status;
+
+    /**
+     * 创建人名称
+     */
+    private String createByName;
+
+    /**
+     * 上传人
+     */
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

+ 24 - 13
taais-modules/taais-system/src/main/java/com/taais/system/listener/SysUserImportListener.java

@@ -5,13 +5,13 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.crypto.digest.BCrypt;
 import com.alibaba.excel.context.AnalysisContext;
 import com.alibaba.excel.event.AnalysisEventListener;
-import com.taais.common.core.exception.ServiceException;
+import com.taais.common.core.core.domain.model.LoginUser;
 import com.taais.common.core.utils.SpringUtils;
 import com.taais.common.core.utils.StringUtils;
 import com.taais.common.core.utils.ValidatorUtils;
 import com.taais.common.excel.core.ExcelListener;
 import com.taais.common.excel.core.ExcelResult;
-import com.taais.common.security.utils.LoginHelper;
+import com.taais.common.excel.entity.ExcelResultRes;
 import com.taais.system.domain.bo.SysUserBo;
 import com.taais.system.domain.vo.SysDeptVo;
 import com.taais.system.domain.vo.SysUserImportVo;
@@ -31,6 +31,7 @@ import java.util.List;
 @Slf4j
 public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo> implements ExcelListener<SysUserImportVo> {
 
+
     private final ISysUserService userService;
 
     private final ISysDeptService deptService;
@@ -40,22 +41,30 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
     private final Boolean isUpdateSupport;
 
     private final Long operateUserId;
+
+    private final LoginUser loginUser;
     private final StringBuilder successMsg = new StringBuilder();
     private final StringBuilder failureMsg = new StringBuilder();
     private int successNum = 0;
     private int failureNum = 0;
 
-    public SysUserImportListener(Boolean isUpdateSupport) {
+    public SysUserImportListener(Boolean isUpdateSupport, LoginUser loginUser) {
+        this.loginUser = loginUser;
         String initPassword = SpringUtils.getBean(ISysConfigService.class).selectConfigByKey("sys.user.initPassword").getConfigValue();
         this.userService = SpringUtils.getBean(ISysUserService.class);
         this.deptService = SpringUtils.getBean(ISysDeptService.class);
         this.password = BCrypt.hashpw(initPassword);
         this.isUpdateSupport = isUpdateSupport;
-        this.operateUserId = LoginHelper.getUserId();
+        this.operateUserId = loginUser.getUserId();
     }
 
     @Override
     public void invoke(SysUserImportVo userVo, AnalysisContext context) {
+        try {
+            Thread.sleep(3000L);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
         SysUserVo sysUser = this.userService.selectUserByUserName(userVo.getUserName());
         try {
             if (StringUtils.isNotEmpty(userVo.getDeptName())) {
@@ -70,6 +79,8 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
                             SysUserBo user = BeanUtil.toBean(userVo, SysUserBo.class);
                             user.setDeptId(dept.getDeptId());//获取用户部门ID
                             ValidatorUtils.validate(user);
+                            user.setTenantId(loginUser.getTenantId());
+                            user.setUpdateBy(operateUserId);
                             user.setPassword(password);
                             user.setCreateBy(operateUserId);
                             userService.insertUser(user);
@@ -84,6 +95,7 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
                             userService.checkUserAllowed(user.getUserId());
                             userService.checkUserDataScope(user.getUserId());
                             user.setUpdateBy(operateUserId);
+                            user.setVersion(sysUser.getVersion());
                             userService.updateUser(user);
                             successNum++;
                             successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUserName()).append(" 更新成功");
@@ -91,13 +103,11 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
                             failureNum++;
                             failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(sysUser.getUserName()).append(" 已存在");
                         }
-
                     } else {
                         failureNum++;
                         failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(sysUser.getUserName()).append(" 的部门名称 ").append(userVo.getDeptName()).append(" 在部门表中不存在,无法导入");
                     }
                 }
-
                 if (deptCount == 0) {
                     failureNum++;
                     failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(userVo.getUserName()).append(" 的部门名称 ").append(userVo.getDeptName()).append(" 在部门表中不存在,无法导入");
@@ -110,7 +120,6 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
                 failureNum++;
                 failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(userVo.getUserName()).append(" 的部门名称为空,无法导入");
             }
-
         } catch (Exception e) {
             failureNum++;
             String msg = "<br/>" + failureNum + "、账号 " + userVo.getUserName() + " 导入失败:";
@@ -121,22 +130,24 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
 
     @Override
     public void doAfterAllAnalysed(AnalysisContext context) {
-
+        log.info("解析完成");
     }
 
     @Override
     public ExcelResult<SysUserImportVo> getExcelResult() {
         return new ExcelResult<>() {
-
             @Override
-            public String getAnalysis() {
-                if (failureNum > 0) {
+            public ExcelResultRes getAnalysis() {
+                if (failureNum > 0 && successNum == 0) {
                     failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据没有成功导入,错误如下:");
-                    throw new ServiceException(failureMsg.toString());
+                    return ExcelResultRes.builder().logInfo(failureMsg.toString()).status("0").build();
+                } else if (failureNum > 0 && successNum > 0) {
+                    failureMsg.insert(0, "很抱歉,部分导入失败!共 " + failureNum + " 条数据没有成功导入,错误如下:");
+                    return ExcelResultRes.builder().logInfo(failureMsg.toString()).status("2").build();
                 } else {
                     successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+                    return ExcelResultRes.builder().logInfo(successMsg.toString()).status("1").build();
                 }
-                return successMsg.toString();
             }
 
             @Override

+ 16 - 0
taais-modules/taais-system/src/main/java/com/taais/system/mapper/ImportExportMapper.java

@@ -0,0 +1,16 @@
+package com.taais.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.taais.system.domain.ImportExport;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 导入导出日志Mapper接口
+ *
+ * @author wgk
+ * @date 2024-10-21
+ */
+@Mapper
+public interface ImportExportMapper extends BaseMapper<ImportExport> {
+
+}

+ 62 - 0
taais-modules/taais-system/src/main/java/com/taais/system/service/IImportExportService.java

@@ -0,0 +1,62 @@
+package com.taais.system.service;
+
+
+import com.taais.common.core.core.domain.model.LoginUser;
+import com.taais.common.core.core.page.PageResult;
+import com.taais.common.excel.entity.ExcelResultRes;
+import com.taais.common.orm.core.service.IBaseService;
+import com.taais.system.domain.ImportExport;
+import com.taais.system.domain.bo.ImportExportBo;
+import com.taais.system.domain.vo.ImportExportVo;
+
+import java.util.List;
+
+/**
+ * 导入导出日志Service接口
+ *
+ * @author wgk
+ * @date 2024-06-24
+ */
+public interface IImportExportService extends IBaseService<ImportExport> {
+    /**
+     * 查询导入导出日志
+     *
+     * @param id 导入导出日志主键
+     * @return 导入导出日志
+     */
+    ImportExportVo selectById(Long id);
+
+    /**
+     * 查询导入导出日志列表
+     *
+     * @param importExportBo 导入导出日志Bo
+     * @return 导入导出日志集合
+     */
+    List<ImportExportVo> selectList(ImportExportBo importExportBo);
+
+    /**
+     * 分页查询导入导出日志列表
+     *
+     * @param importExportBo 导入导出日志Bo
+     * @return 分页导入导出日志集合
+     */
+    PageResult<ImportExportVo> selectPage(ImportExportBo importExportBo);
+
+    /**
+     * 新增导入导出日志
+     *
+     * @param importExportBo 导入导出日志Bo
+     * @return 结果:true 操作成功,false 操作失败
+     */
+    boolean insert(ImportExportBo importExportBo);
+
+    /**
+     * 新增导入导出日志
+     *
+     * @param result 导入导出日志
+     * @param type   导入导出类型
+     * @return 结果:true 操作成功,false 操作失败
+     */
+    boolean saveInfo(ExcelResultRes result, LoginUser loginUser, String type);
+
+}

+ 24 - 0
taais-modules/taais-system/src/main/java/com/taais/system/service/ISysUserService.java

@@ -1,10 +1,14 @@
 package com.taais.system.service;
 
+import com.taais.common.core.core.domain.model.LoginUser;
 import com.taais.common.core.core.page.PageResult;
 import com.taais.common.orm.core.service.IBaseService;
 import com.taais.system.domain.SysUser;
 import com.taais.system.domain.bo.SysUserBo;
+import com.taais.system.domain.vo.SysUserExportVo;
 import com.taais.system.domain.vo.SysUserVo;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
 
@@ -240,4 +244,24 @@ public interface ISysUserService extends IBaseService<SysUser> {
      * @return 用户vo列表
      */
     List<SysUserVo> selectUserListByDept(Long deptId);
+
+    /**
+     * 异步导入
+     *
+     * @param file          导入的文件
+     * @param updateSupport 是否覆盖
+     * @param user          用户上下文信息
+     */
+    @Async
+    void asyncImportData(MultipartFile file, boolean updateSupport, LoginUser user);
+
+    /**
+     * asyncExport 异步导出
+     *
+     * @param listVo    数据列表
+     * @param sheetName 文件名称
+     * @param user      上下文
+     */
+    @Async
+    void asyncExport(List<SysUserExportVo> listVo, String sheetName, LoginUser user);
 }

+ 105 - 0
taais-modules/taais-system/src/main/java/com/taais/system/service/impl/ImportExportServiceImpl.java

@@ -0,0 +1,105 @@
+package com.taais.system.service.impl;
+
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.taais.common.core.core.domain.model.LoginUser;
+import com.taais.common.core.core.page.PageResult;
+import com.taais.common.core.utils.MapstructUtils;
+import com.taais.common.core.utils.bean.BeanUtils;
+import com.taais.common.excel.entity.ExcelResultRes;
+import com.taais.common.orm.core.page.PageQuery;
+import com.taais.common.orm.core.service.impl.BaseServiceImpl;
+import com.taais.system.domain.ImportExport;
+import com.taais.system.domain.bo.ImportExportBo;
+import com.taais.system.domain.vo.ImportExportVo;
+import com.taais.system.mapper.ImportExportMapper;
+import com.taais.system.service.IImportExportService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import static com.taais.system.domain.table.ImportExportTableDef.IMPORT_EXPORT;
+/**
+ * 导入导出日志Service业务层处理
+ *
+ * @author wgk
+ * @date 2024-06-24
+ */
+@Service
+public class ImportExportServiceImpl extends BaseServiceImpl<ImportExportMapper, ImportExport> implements IImportExportService {
+
+    @Override
+    public QueryWrapper query() {
+        return super.query().from(IMPORT_EXPORT);
+    }
+
+    private QueryWrapper buildQueryWrapper(ImportExportBo importExportBo) {
+        QueryWrapper queryWrapper = super.buildBaseQueryWrapper();
+        queryWrapper.and(IMPORT_EXPORT.NAME.like(importExportBo.getName()));
+        queryWrapper.and(IMPORT_EXPORT.LOG_INFO.eq(importExportBo.getLogInfo()));
+        queryWrapper.and(IMPORT_EXPORT.STATUS.eq(importExportBo.getStatus()));
+        queryWrapper.and(IMPORT_EXPORT.TYPE.eq(importExportBo.getType()));
+        queryWrapper.orderBy(IMPORT_EXPORT.UPDATE_TIME.desc());
+        return queryWrapper;
+    }
+
+    /**
+     * 查询导入导出日志
+     *
+     * @param id 导入导出日志主键
+     * @return 导入导出日志
+     */
+    @Override
+    public ImportExportVo selectById(Long id) {
+        return this.getOneAs(query().where(IMPORT_EXPORT.ID.eq(id)), ImportExportVo.class);
+
+    }
+
+    /**
+     * 查询导入导出日志列表
+     *
+     * @param importExportBo 导入导出日志Bo
+     * @return 导入导出日志集合
+     */
+    @Override
+    public List<ImportExportVo> selectList(ImportExportBo importExportBo) {
+        QueryWrapper queryWrapper = buildQueryWrapper(importExportBo);
+        return this.listAs(queryWrapper, ImportExportVo.class);
+    }
+
+    /**
+     * 分页查询导入导出日志列表
+     *
+     * @param importExportBo 导入导出日志Bo
+     * @return 分页导入导出日志集合
+     */
+    @Override
+    public PageResult<ImportExportVo> selectPage(ImportExportBo importExportBo) {
+        QueryWrapper queryWrapper = buildQueryWrapper(importExportBo);
+        Page<ImportExportVo> page = this.pageAs(PageQuery.build(), queryWrapper, ImportExportVo.class);
+        return PageResult.build(page);
+    }
+
+    /**
+     * 新增导入导出日志
+     *
+     * @param importExportBo 导入导出日志Bo
+     * @return 结果:true 操作成功,false 操作失败
+     */
+    @Override
+    public boolean insert(ImportExportBo importExportBo) {
+        ImportExport importExport = MapstructUtils.convert(importExportBo, ImportExport.class);
+        return this.save(importExport);//使用全局配置的雪花算法主键生成器生成ID值
+    }
+
+    @Override
+    public boolean saveInfo(ExcelResultRes result, LoginUser loginUser, String type) {
+        ImportExportBo bo = new ImportExportBo();
+        BeanUtils.copyProperties(result, bo);
+        bo.setUpdateBy(loginUser.getUserId());
+        bo.setCreateBy(loginUser.getUserId());
+        bo.setType(type);
+        ImportExport importExport = MapstructUtils.convert(bo, ImportExport.class);
+        return this.save(importExport);//使用全局配置的雪花算法主键生成器生成ID值
+    }
+
+}

+ 15 - 4
taais-modules/taais-system/src/main/java/com/taais/system/service/impl/SysOssServiceImpl.java

@@ -8,6 +8,7 @@ import cn.hutool.core.util.StrUtil;
 import com.taais.common.core.config.TaaisConfig;
 import com.taais.common.core.constant.CacheNames;
 import com.taais.common.core.constant.Constants;
+import com.taais.common.core.core.domain.UploadRes;
 import com.taais.common.core.core.page.PageResult;
 import com.taais.common.core.exception.ServiceException;
 import com.taais.common.core.service.OssService;
@@ -117,6 +118,15 @@ public class SysOssServiceImpl extends BaseServiceImpl<SysOssMapper, SysOss> imp
         return String.join(StringUtils.SEPARATOR, list);
     }
 
+    @Override
+    public UploadRes upload(byte[] file, String name) {
+        String suffix = StringUtils.substring(name, name.lastIndexOf("."), name.length());
+        OssClient storage = OssFactory.instance();
+        UploadResult uploadResult = storage.uploadSuffix(file, suffix);
+        SysOssVo ossVo = buildResultEntity(name, suffix, storage.getConfigKey(), uploadResult);
+        return UploadRes.builder().name(name).url(uploadResult.getUrl()).ossId(ossVo.getOssId()).build();
+    }
+
 
     @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
     @Override
@@ -145,17 +155,18 @@ public class SysOssServiceImpl extends BaseServiceImpl<SysOssMapper, SysOss> imp
 
     @Override
     public SysOssVo upload(MultipartFile file) {
-        String originalfileName = file.getOriginalFilename();
-        String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
+        String originalFilename = file.getOriginalFilename();
+        assert originalFilename != null;
+        String suffix = StringUtils.substring(originalFilename, originalFilename.lastIndexOf("."), originalFilename.length());
         OssClient storage = OssFactory.instance();
         UploadResult uploadResult;
         try {
-            uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
+            uploadResult = storage.uploadSuffix(file.getBytes(), suffix);
         } catch (IOException e) {
             throw new ServiceException(e.getMessage());
         }
         // 保存文件信息
-        return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult);
+        return buildResultEntity(originalFilename, suffix, storage.getConfigKey(), uploadResult);
     }
 
     @Override

+ 42 - 0
taais-modules/taais-system/src/main/java/com/taais/system/service/impl/SysUserServiceImpl.java

@@ -4,24 +4,33 @@ import cn.dev33.satoken.secure.BCrypt;
 import cn.hutool.core.util.ObjectUtil;
 import com.taais.common.core.constant.CacheNames;
 import com.taais.common.core.constant.UserConstants;
+import com.taais.common.core.core.domain.model.LoginUser;
 import com.taais.common.core.core.page.PageResult;
 import com.taais.common.core.exception.ServiceException;
 import com.taais.common.core.service.UserService;
 import com.taais.common.core.utils.MapstructUtils;
 import com.taais.common.core.utils.SpringUtils;
 import com.taais.common.core.utils.StringUtils;
+import com.taais.common.core.utils.bean.BeanUtils;
 import com.taais.common.core.utils.bean.BeanValidators;
+import com.taais.common.excel.entity.ExcelResultRes;
+import com.taais.common.excel.service.IExcelService;
 import com.taais.common.orm.core.page.PageQuery;
 import com.taais.common.orm.core.service.impl.BaseServiceImpl;
 import com.taais.common.security.utils.LoginHelper;
 import com.taais.common.tenant.helper.TenantHelper;
 import com.taais.system.domain.SysUser;
 import com.taais.system.domain.SysUserPost;
+import com.taais.system.domain.bo.ImportExportBo;
 import com.taais.system.domain.bo.SysUserBo;
 import com.taais.system.domain.vo.SysPostVo;
 import com.taais.system.domain.vo.SysRoleVo;
+import com.taais.system.domain.vo.SysUserExportVo;
+import com.taais.system.domain.vo.SysUserImportVo;
 import com.taais.system.domain.vo.SysUserVo;
+import com.taais.system.listener.SysUserImportListener;
 import com.taais.system.mapper.SysUserMapper;
+import com.taais.system.service.IImportExportService;
 import com.taais.system.service.ISysConfigService;
 import com.taais.system.service.ISysDataScopeService;
 import com.taais.system.service.ISysPostService;
@@ -40,7 +49,9 @@ import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -81,6 +92,12 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserMapper, SysUser>
     @Resource
     private ISysDataScopeService dataScopeService;
 
+    @Resource
+    private IExcelService excelService;
+
+    @Resource
+    private IImportExportService importExportService;
+
     @Override
     public QueryWrapper query() {
         return super.query().from(SYS_USER);
@@ -719,4 +736,29 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserMapper, SysUser>
         QueryWrapper queryWrapper = query().where(SYS_USER.DEPT_ID.eq(deptId)).and(SYS_USER.DEL_FLAG.eq(0));
         return this.listAs(queryWrapper, SysUserVo.class);
     }
+
+    @Override
+    public void asyncImportData(MultipartFile file, boolean updateSupport, LoginUser loginUser) {
+        ExcelResultRes result;
+        try {
+            String name = file.getOriginalFilename();
+            result = excelService.importExcel(file.getInputStream(), name, SysUserImportVo.class, new SysUserImportListener(updateSupport, loginUser));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        ImportExportBo bo = new ImportExportBo();
+        BeanUtils.copyProperties(result, bo);
+        bo.setUpdateBy(loginUser.getUserId());
+        bo.setCreateBy(loginUser.getUserId());
+        bo.setType("0");
+        boolean flag = importExportService.insert(bo);
+        if (flag) {
+            log.info("异步导入日志写入成功");
+        }
+    }
+
+    @Override
+    public void asyncExport(List<SysUserExportVo> listVo, String sheetName, LoginUser user) {
+
+    }
 }

+ 7 - 0
taais-modules/taais-system/src/main/resources/mapper/system/ImportExportMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.taais.system.mapper.ImportExportMapper">
+
+</mapper>