From 078f3244947d37da84765df442e75db4bcfa7939 Mon Sep 17 00:00:00 2001
From: "jiazheng.zhao" <2624533894@qq.com>
Date: Tue, 11 Aug 2026 10:10:05 +0800
Subject: [PATCH] =?UTF-8?q?feat:=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?=
=?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 12 +
.../application/ConfirmationStatusResult.java | 17 ++
.../DenominationDecompositionResult.java | 24 ++
.../application/OperationBooleanResult.java | 17 ++
.../application/OperationIdStatusResult.java | 24 ++
.../application/OperationMessageResult.java | 17 ++
.../application/OperationStatusResult.java | 17 ++
.../application/OperationValueResult.java | 17 ++
.../StandardCurrencyOperationResult.java | 22 ++
...alBankInstitutionIdentifierController.java | 7 +-
...rencyGenerationVerificationController.java | 29 ++-
.../rest/StandardCurrencyController.java | 25 +-
.../digitalrmb/shared/config/IFileUtil.java | 89 +++++++
.../shared/config/LocalFileUtil.java | 178 ++++++++++++++
.../shared/config/ResultDataEntity.java | 65 +++++
.../shared/config/ResultEntity.java | 44 ++++
.../interfaces/FileManagementController.java | 225 ++++++++++++++++++
src/main/resources/application-dev.yml | 4 +
...generation_verification_unique_upgrade.sql | 9 +
.../institution_identifier_training.sql | 1 +
...onInformationIdentifierControllerTest.java | 55 ++---
21 files changed, 842 insertions(+), 56 deletions(-)
create mode 100644 src/main/java/com/yau/digitalrmb/institutionidentity/application/ConfirmationStatusResult.java
create mode 100644 src/main/java/com/yau/digitalrmb/institutionidentity/application/DenominationDecompositionResult.java
create mode 100644 src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationBooleanResult.java
create mode 100644 src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationIdStatusResult.java
create mode 100644 src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationMessageResult.java
create mode 100644 src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationStatusResult.java
create mode 100644 src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationValueResult.java
create mode 100644 src/main/java/com/yau/digitalrmb/institutionidentity/application/StandardCurrencyOperationResult.java
create mode 100644 src/main/java/com/yau/digitalrmb/shared/config/IFileUtil.java
create mode 100644 src/main/java/com/yau/digitalrmb/shared/config/LocalFileUtil.java
create mode 100644 src/main/java/com/yau/digitalrmb/shared/config/ResultDataEntity.java
create mode 100644 src/main/java/com/yau/digitalrmb/shared/config/ResultEntity.java
create mode 100644 src/main/java/com/yau/digitalrmb/shared/interfaces/FileManagementController.java
create mode 100644 src/main/resources/db/manual/currency_generation_verification_unique_upgrade.sql
diff --git a/pom.xml b/pom.xml
index aec2eca..a325a7b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,13 @@
org.springframework.boot
spring-boot-starter-validation
+
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.16
org.springframework.boot
@@ -104,6 +111,11 @@
spring-security-test
test
+
+ io.projectreactor
+ reactor-core
+ 3.8.5
+
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/application/ConfirmationStatusResult.java b/src/main/java/com/yau/digitalrmb/institutionidentity/application/ConfirmationStatusResult.java
new file mode 100644
index 0000000..6ba0f62
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/application/ConfirmationStatusResult.java
@@ -0,0 +1,17 @@
+package com.yau.digitalrmb.institutionidentity.application;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "报文确认状态")
+public class ConfirmationStatusResult {
+ @Schema(description = "报文确认状态", allowableValues = {"待确认", "已确认"})
+ private final String confirmationStatus;
+
+ public ConfirmationStatusResult(String confirmationStatus) {
+ this.confirmationStatus = confirmationStatus;
+ }
+
+ public String getConfirmationStatus() {
+ return confirmationStatus;
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/application/DenominationDecompositionResult.java b/src/main/java/com/yau/digitalrmb/institutionidentity/application/DenominationDecompositionResult.java
new file mode 100644
index 0000000..b57ac3e
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/application/DenominationDecompositionResult.java
@@ -0,0 +1,24 @@
+package com.yau.digitalrmb.institutionidentity.application;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.List;
+
+@Schema(description = "面额拆解结果")
+public class DenominationDecompositionResult {
+ private final Integer totalCount;
+ private final List breakdown;
+
+ public DenominationDecompositionResult(Integer totalCount, List breakdown) {
+ this.totalCount = totalCount;
+ this.breakdown = breakdown;
+ }
+
+ public Integer getTotalCount() {
+ return totalCount;
+ }
+
+ public List getBreakdown() {
+ return breakdown;
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationBooleanResult.java b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationBooleanResult.java
new file mode 100644
index 0000000..e5aa03f
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationBooleanResult.java
@@ -0,0 +1,17 @@
+package com.yau.digitalrmb.institutionidentity.application;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "操作校验结果")
+public class OperationBooleanResult {
+ @Schema(description = "本次校验是否通过")
+ private final boolean valid;
+
+ public OperationBooleanResult(boolean valid) {
+ this.valid = valid;
+ }
+
+ public boolean isValid() {
+ return valid;
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationIdStatusResult.java b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationIdStatusResult.java
new file mode 100644
index 0000000..2265b71
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationIdStatusResult.java
@@ -0,0 +1,24 @@
+package com.yau.digitalrmb.institutionidentity.application;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "创建或接收操作结果")
+public class OperationIdStatusResult {
+ @Schema(description = "后续操作所需的业务记录编号")
+ private final Long verificationId;
+ @Schema(description = "本次操作完成后的状态")
+ private final String status;
+
+ public OperationIdStatusResult(Long id, String status) {
+ this.verificationId = id;
+ this.status = status;
+ }
+
+ public Long getVerificationId() {
+ return verificationId;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationMessageResult.java b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationMessageResult.java
new file mode 100644
index 0000000..2fcc18c
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationMessageResult.java
@@ -0,0 +1,17 @@
+package com.yau.digitalrmb.institutionidentity.application;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "操作生成的报文")
+public class OperationMessageResult {
+ @Schema(description = "本次操作生成的报文")
+ private final String message;
+
+ public OperationMessageResult(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationStatusResult.java b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationStatusResult.java
new file mode 100644
index 0000000..80d6bc5
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationStatusResult.java
@@ -0,0 +1,17 @@
+package com.yau.digitalrmb.institutionidentity.application;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "操作状态")
+public class OperationStatusResult {
+ @Schema(description = "本次操作完成后的状态")
+ private final String status;
+
+ public OperationStatusResult(String status) {
+ this.status = status;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationValueResult.java b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationValueResult.java
new file mode 100644
index 0000000..9182bb6
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/application/OperationValueResult.java
@@ -0,0 +1,17 @@
+package com.yau.digitalrmb.institutionidentity.application;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "操作生成值")
+public class OperationValueResult {
+ @Schema(description = "本次操作生成的值")
+ private final String value;
+
+ public OperationValueResult(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/application/StandardCurrencyOperationResult.java b/src/main/java/com/yau/digitalrmb/institutionidentity/application/StandardCurrencyOperationResult.java
new file mode 100644
index 0000000..d72461f
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/application/StandardCurrencyOperationResult.java
@@ -0,0 +1,22 @@
+package com.yau.digitalrmb.institutionidentity.application;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "标准币串步骤操作结果")
+public class StandardCurrencyOperationResult {
+ private final String status;
+ private final Integer totalCount;
+
+ public StandardCurrencyOperationResult(String status, Integer totalCount) {
+ this.status = status;
+ this.totalCount = totalCount;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public Integer getTotalCount() {
+ return totalCount;
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/CentralBankInstitutionIdentifierController.java b/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/CentralBankInstitutionIdentifierController.java
index 6453e76..f8f6618 100644
--- a/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/CentralBankInstitutionIdentifierController.java
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/CentralBankInstitutionIdentifierController.java
@@ -11,6 +11,7 @@ import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyPairResu
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService;
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.application.InstitutionVerificationProcessResult;
+import com.yau.digitalrmb.institutionidentity.application.OperationStatusResult;
import com.yau.digitalrmb.institutionidentity.application.SignatureResult;
import com.yau.digitalrmb.institutionidentity.interfaces.dto.ConfirmInstitutionIdentifierRequest;
import com.yau.digitalrmb.institutionidentity.interfaces.dto.VerifyInstitutionApplicationRequest;
@@ -102,10 +103,10 @@ public class CentralBankInstitutionIdentifierController {
@PostMapping("/applications/feedback")
@Operation(summary = "央行信息反馈",
description = "将已确权的最终机构标识同步到商业银行端,商业银行状态由1变为2。")
- public ApiResponse feedback() {
+ public ApiResponse feedback() {
JwtUser jwtUser = AuthContextHolder.get();
- return ApiResponse.success(feedbackService.feedback(
- InstitutionKeySubject.from(jwtUser), jwtUser.getUsername()), traceId());
+ feedbackService.feedback(InstitutionKeySubject.from(jwtUser), jwtUser.getUsername());
+ return ApiResponse.success(new OperationStatusResult("FEEDBACK_SENT"), traceId());
}
private String traceId() {
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/CurrencyGenerationVerificationController.java b/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/CurrencyGenerationVerificationController.java
index 1f4909f..b2e87a7 100644
--- a/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/CurrencyGenerationVerificationController.java
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/CurrencyGenerationVerificationController.java
@@ -1,5 +1,6 @@
package com.yau.digitalrmb.institutionidentity.interfaces.rest;
+import com.yau.digitalrmb.institutionidentity.application.ConfirmationStatusResult;
import com.yau.digitalrmb.institutionidentity.application.CurrencyConfirmationMessageResult;
import com.yau.digitalrmb.institutionidentity.application.CurrencyConfirmationSignatureResult;
import com.yau.digitalrmb.institutionidentity.application.CurrencyDigestResult;
@@ -8,6 +9,7 @@ import com.yau.digitalrmb.institutionidentity.application.CurrencyQuotaVerificat
import com.yau.digitalrmb.institutionidentity.application.CurrencyGenerationVerificationService;
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.application.OperationBooleanResult;
+import com.yau.digitalrmb.institutionidentity.application.OperationIdStatusResult;
import com.yau.digitalrmb.institutionidentity.application.OperationMessageResult;
import com.yau.digitalrmb.institutionidentity.application.OperationStatusResult;
import com.yau.digitalrmb.institutionidentity.application.SignatureResult;
@@ -36,12 +38,13 @@ public class CurrencyGenerationVerificationController {
@PostMapping("/steps/receive")
@Operation(summary = "接收步骤二请求报文",
description = "对应页面“接收步骤二请求报文”按钮。步骤二必须达到RECEIVED;重复点击时复用verificationId,并将步骤三恢复到RECEIVED,清空后续按钮产生的数据。")
- public ApiResponse receive(
+ public ApiResponse receive(
@Valid @RequestBody ReceiveCurrencyVerificationRequest request) {
JwtUser user = AuthContextHolder.get();
CurrencyGenerationVerificationResult result = service.receive(request.getRequestId(),
InstitutionKeySubject.from(user), user.getUsername());
- return ApiResponse.success(new OperationStatusResult(result.getStatus()), MDC.get(TraceIdFilter.MDC_KEY));
+ return ApiResponse.success(new OperationIdStatusResult(result.getVerificationId(), result.getStatus()),
+ MDC.get(TraceIdFilter.MDC_KEY));
}
@PostMapping("/steps/verify-signature")
@@ -103,27 +106,33 @@ public class CurrencyGenerationVerificationController {
@PostMapping("/steps/package-response")
@Operation(summary = "打包确认报文", description = "对应页面“打包报文”按钮,只组装确认报文,不执行返回。")
- public ApiResponse packageResponse(
+ public ApiResponse packageResponse(
@Valid @RequestBody CurrencyVerificationStepRequest request) {
JwtUser user = AuthContextHolder.get();
- return ok(service.packageResponse(request.getVerificationId(),
- InstitutionKeySubject.from(user), user.getUsername()));
+ CurrencyGenerationVerificationResult result = service.packageResponse(request.getVerificationId(),
+ InstitutionKeySubject.from(user), user.getUsername());
+ return ApiResponse.success(new OperationMessageResult(result.getConfirmationMessage()),
+ MDC.get(TraceIdFilter.MDC_KEY));
}
@PostMapping("/steps/return-response")
@Operation(summary = "返回确认报文", description = "对应页面“返回确认报文”按钮,只返回已打包完成的确认报文。")
- public ApiResponse returnResponse(
+ public ApiResponse returnResponse(
@Valid @RequestBody CurrencyVerificationStepRequest request) {
JwtUser user = AuthContextHolder.get();
- return ok(service.returnResponse(request.getVerificationId(), InstitutionKeySubject.from(user), user.getUsername()));
+ CurrencyGenerationVerificationResult result = service.returnResponse(request.getVerificationId(),
+ InstitutionKeySubject.from(user), user.getUsername());
+ return ApiResponse.success(new OperationStatusResult(result.getStatus()), MDC.get(TraceIdFilter.MDC_KEY));
}
@PostMapping("/steps/receive-confirmation")
@Operation(summary = "商业银行接收报文确认信息",
description = "对应商业银行端“接收报文确认信息”按钮。根据当前Token查询本次实训数据,confirmationStatus在央行未返回时为“待确认”,已返回时为“已确认”;查询不改变流程状态。")
- public ApiResponse receiveConfirmation() {
- return ApiResponse.success(service.receiveConfirmation(
- InstitutionKeySubject.from(AuthContextHolder.get())), MDC.get(TraceIdFilter.MDC_KEY));
+ public ApiResponse receiveConfirmation() {
+ CurrencyConfirmationMessageResult result = service.receiveConfirmation(
+ InstitutionKeySubject.from(AuthContextHolder.get()));
+ return ApiResponse.success(new ConfirmationStatusResult(result.getConfirmationStatus()),
+ MDC.get(TraceIdFilter.MDC_KEY));
}
@GetMapping("/current")
diff --git a/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/StandardCurrencyController.java b/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/StandardCurrencyController.java
index d930468..22f95fc 100644
--- a/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/StandardCurrencyController.java
+++ b/src/main/java/com/yau/digitalrmb/institutionidentity/interfaces/rest/StandardCurrencyController.java
@@ -1,8 +1,10 @@
package com.yau.digitalrmb.institutionidentity.interfaces.rest;
+import com.yau.digitalrmb.institutionidentity.application.DenominationDecompositionResult;
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.application.StandardCurrencyBatchResult;
import com.yau.digitalrmb.institutionidentity.application.StandardCurrencyListItemResult;
+import com.yau.digitalrmb.institutionidentity.application.StandardCurrencyOperationResult;
import com.yau.digitalrmb.institutionidentity.application.StandardCurrencyService;
import com.yau.digitalrmb.institutionidentity.interfaces.dto.CurrencyRuleRequest;
import com.yau.digitalrmb.shared.api.ApiResponse;
@@ -33,25 +35,32 @@ public class StandardCurrencyController {
@PostMapping("/steps/decompose")
@Operation(summary = "执行面额拆解",
description = "无需请求体。后端按当前JWT主体查询步骤八已确认额度控制位金额,并按100、50、20、10、5、1、0.5、0.2、0.1、0.05、0.01从大到小拆解。")
- public ApiResponse decompose() {
+ public ApiResponse decompose() {
JwtUser user = AuthContextHolder.get();
- return ApiResponse.success(service.decompose(
- InstitutionKeySubject.from(user), user.getUsername()), traceId());
+ StandardCurrencyBatchResult result = service.decompose(
+ InstitutionKeySubject.from(user), user.getUsername());
+ return ApiResponse.success(new DenominationDecompositionResult(
+ result.getTotalCount(), result.getBreakdown()), traceId());
}
@PostMapping("/steps/confirm-rule")
@Operation(summary = "确认币串生成规则", description = "校验DC前缀、币串ID规则和待生效初始状态。")
- public ApiResponse confirmRule(@Valid @RequestBody CurrencyRuleRequest request) {
+ public ApiResponse confirmRule(@Valid @RequestBody CurrencyRuleRequest request) {
JwtUser user = AuthContextHolder.get();
- return ApiResponse.success(service.confirmRule(request.getPrefix(), request.getIdRule(),
- request.getCurrencyStatus(), InstitutionKeySubject.from(user), user.getUsername()), traceId());
+ StandardCurrencyBatchResult result = service.confirmRule(request.getPrefix(), request.getIdRule(),
+ request.getCurrencyStatus(), InstitutionKeySubject.from(user), user.getUsername());
+ return ApiResponse.success(new StandardCurrencyOperationResult(
+ result.getStatus(), result.getTotalCount()), traceId());
}
@PostMapping("/steps/generate")
@Operation(summary = "逐枚生成标准面额币串", description = "根据拆解清单为每一枚标准面额生成唯一币串。")
- public ApiResponse generate() {
+ public ApiResponse generate() {
JwtUser user = AuthContextHolder.get();
- return ApiResponse.success(service.generate(InstitutionKeySubject.from(user), user.getUsername()), traceId());
+ StandardCurrencyBatchResult result = service.generate(
+ InstitutionKeySubject.from(user), user.getUsername());
+ return ApiResponse.success(new StandardCurrencyOperationResult(
+ result.getStatus(), result.getTotalCount()), traceId());
}
@GetMapping("/currencies")
diff --git a/src/main/java/com/yau/digitalrmb/shared/config/IFileUtil.java b/src/main/java/com/yau/digitalrmb/shared/config/IFileUtil.java
new file mode 100644
index 0000000..bee99cf
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/shared/config/IFileUtil.java
@@ -0,0 +1,89 @@
+package com.yau.digitalrmb.shared.config;
+
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+
+/**
+ * 文件上传下载接口定义
+ *
+ * @author 陈沅
+ */
+
+public interface IFileUtil {
+
+
+
+ /**
+ * 从http请求上传文件
+ *
+ * @param file form传参http文件对象
+ * @return 返回文件在服务器上保存的相对路径
+ */
+ String upload(MultipartFile file);
+
+ /**
+ * 根据文件流上传文件
+ *
+ * @param objectName 指定文件名(带扩展名)
+ * @param fileIn 文件流
+ * @return 返回文件在服务器上保存的相对路径
+ */
+ String upload(String objectName, InputStream fileIn);
+
+ /**
+ * 删除服务器上的一个文件
+ *
+ * @param path 文件在服务器上保存的相对路径
+ * @return 返回true则表示删除成功
+ */
+ boolean remove(String path);
+
+ /**
+ * 下载文件流,指定文件名
+ *
+ * @param response
+ * @param fileName 指定下载文件名称
+ * @param filePath 文件保存的相对路径
+ */
+ void download(HttpServletResponse response, String fileName, String filePath);
+
+ /**
+ * 下载文件流,不指定文件名
+ *
+ * @param response
+ * @param filePath 文件保存的相对路径
+ */
+ void download(HttpServletResponse response, String filePath);
+
+ /**
+ * 相对路径
+ *
+ * @param relativePath 文件保存的相对路径
+ * @return 文件保存的绝对路径
+ */
+ String getFullPath(String relativePath);
+
+
+ /**
+ * 获取文件后缀名
+ *
+ * @param fileName 文件名称
+ * @return suffix
+ */
+ String getSuffix(String fileName);
+
+
+ /**
+ * 获取文件存储至磁盘的相对路径
+ *
+ * @param fileName 文件名
+ * @param suffix 动态拼接的文件后缀,若该值为空则不拼接
+ * @return 相对路径
+ */
+ String getDiskRelativePath(String fileName, String suffix);
+
+}
diff --git a/src/main/java/com/yau/digitalrmb/shared/config/LocalFileUtil.java b/src/main/java/com/yau/digitalrmb/shared/config/LocalFileUtil.java
new file mode 100644
index 0000000..3ee725a
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/shared/config/LocalFileUtil.java
@@ -0,0 +1,178 @@
+package com.yau.digitalrmb.shared.config;
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.IdUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+@Component
+public class LocalFileUtil implements IFileUtil{
+
+ private final static List excludeSp = Arrays.asList("exe", "bin", "sh");
+
+ private final String localPath;
+
+ public LocalFileUtil(@Value("${file.path}")String localPath) {
+ this.localPath = localPath;
+ }
+
+ @Override
+ public String upload(MultipartFile file) {
+ return upload(null, file);
+ }
+
+ @Override
+ public String upload(String objectName, InputStream fileIn) {
+ Assert.notNull(fileIn, "文件不能为空");
+ Assert.hasText(objectName, "文件名为空");
+ Assert.isTrue(objectName.lastIndexOf(".") > 0, "文件名称需携带扩展后缀");
+ FileOutputStream out = null;
+ try {
+ String relativePath = getDiskRelativePath(objectName, null);
+ File file = new File(getFullPath(relativePath));
+ file.getParentFile().mkdirs();
+ out = new FileOutputStream(file);
+ byte[] buff = new byte[4096];
+ int len;
+ while ((len = fileIn.read(buff)) != -1) {
+ out.write(buff, 0, len);
+ }
+ out.flush();
+ return relativePath;
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new IllegalArgumentException("上传文件失败,IO错误");
+ } finally {
+ try {
+ fileIn.close();
+ if (out != null) out.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public boolean remove(String path) {
+ if (!StringUtils.hasText(path)) return false;
+ File file = new File(getFullPath(path));
+ if (!file.exists()) return false;
+ return file.delete();
+ }
+
+ @Override
+ public void download(HttpServletResponse response, String fileName, String relativePath) {
+ Assert.hasText(relativePath, "路径为空");
+ System.out.println("------------------------------------" + getFullPath(relativePath));
+ File file = new File(getFullPath(relativePath));
+ Assert.isTrue(file.exists(), "附件不存在");
+ String sp = relativePath.substring(relativePath.lastIndexOf(".") + 1);
+ Assert.isTrue(!excludeSp.contains(sp.toLowerCase()), "文件类型错误");
+ if (!StringUtils.hasText(fileName)) {
+ fileName = file.getName().substring(0, file.getName().lastIndexOf("."));
+ }
+ InputStream fis = null;
+ try {
+ response.setContentType("application/octet-stream");
+ response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode((fileName + "." + sp), CharsetUtil.UTF_8));
+ fis = new BufferedInputStream(Files.newInputStream(file.toPath()));
+ response.addHeader("Content-Length", String.valueOf(fis.available()));
+ byte[] buff = new byte[4096];
+ int len;
+ while ((len = fis.read(buff)) != -1) {
+ response.getOutputStream().write(buff, 0, len);
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("下载文件失败: " + e.getMessage());
+ } finally {
+ try {
+ if (fis != null) fis.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void download(HttpServletResponse response, String relativePath) {
+ download(response, null, relativePath);
+ }
+
+ @Override
+ public String getFullPath(String relativePath) {
+ if (!relativePath.startsWith("/")) {
+ relativePath = "/" + relativePath;
+ }
+ return localPath + relativePath;
+ }
+
+ @Override
+ public String getSuffix(String fileName) {
+ Assert.hasText(fileName, "文件名不存在!");
+ if (fileName.lastIndexOf(".") < 0) {
+ return null;
+ }
+ return fileName.substring(fileName.lastIndexOf(".") + 1);
+ }
+
+ public String upload(String fileName, MultipartFile file) {
+ return upload(fileName, file, null);
+ }
+
+
+ @Override
+ public String getDiskRelativePath(String fileName, String suffix) {
+ Calendar c = Calendar.getInstance();
+ Date d = new Date();
+ c.setTime(d);
+ String year = Integer.toString(c.get(Calendar.YEAR));
+ String month = Integer.toString(c.get(Calendar.MONTH) + 1);
+ String day = Integer.toString(c.get(Calendar.DATE));
+ StringBuilder path = new StringBuilder();
+ path.append("/").append(year)
+ .append("/").append(month)
+ .append("/").append(day)
+ .append("/").append(fileName);
+ if (StringUtils.hasText(suffix)) path.append(".").append(suffix);
+ return path.toString();
+ }
+
+
+ private String upload(String fileName, MultipartFile file, String relativePath) {
+ Assert.isTrue(!file.isEmpty(), "文件不存在");
+ String originalFilename = file.getOriginalFilename();
+ if (fileName == null) fileName = IdUtil.simpleUUID();
+ String sp = getSuffix(originalFilename);
+ Assert.notNull(sp, "文件类型错误");
+ Assert.isTrue(!excludeSp.contains(sp.toLowerCase()), "文件类型错误");
+ try {
+ String filePath;
+ if (relativePath == null) {
+ filePath = getDiskRelativePath(fileName, sp);
+ } else {
+ relativePath = relativePath.endsWith("/") ? relativePath : relativePath + "/";
+ filePath = relativePath + fileName + "." + sp;
+ }
+
+ File destFile = new File(getFullPath(filePath));
+ destFile.getParentFile().mkdirs();
+ file.transferTo(destFile);
+ return filePath;
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("上传文件失败,FileNotFound错误");
+ } catch (IOException e) {
+ throw new IllegalArgumentException("上传文件失败,IO错误");
+ }
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/shared/config/ResultDataEntity.java b/src/main/java/com/yau/digitalrmb/shared/config/ResultDataEntity.java
new file mode 100644
index 0000000..f95ef60
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/shared/config/ResultDataEntity.java
@@ -0,0 +1,65 @@
+package com.yau.digitalrmb.shared.config;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+import reactor.core.publisher.Flux;
+
+
+/**
+ * 出参对象统一封装
+ */
+@Getter
+public class ResultDataEntity {
+ /**
+ * 状态码
+ */
+ private final Integer code;
+ /**
+ * 异常提示
+ */
+// @JsonInclude(JsonInclude.Include.NON_NULL)
+ private String msg = null;
+ /**
+ * 返回体对象
+ */
+// @JsonInclude(JsonInclude.Include.NON_NULL)
+ private T data = null;
+
+
+ private Flux flow;
+
+ public ResultDataEntity(T data) {
+ this.code = HttpStatus.OK.value();
+ this.data = data;
+ }
+
+ public ResultDataEntity(HttpStatus status, T data) {
+ this.code = status.value();
+ this.data = data;
+ }
+
+ public ResultDataEntity(HttpStatus status, Flux data) {
+ this.code = status.value();
+ this.flow = data;
+ }
+
+ public ResultDataEntity(HttpStatus status, String msg, T data) {
+ this.code = status.value();
+ this.msg = msg;
+ this.data = data;
+ }
+
+ public ResultDataEntity(String msg) {
+ this.code = HttpStatus.OK.value();
+ this.msg = msg;
+ }
+
+ public ResultDataEntity(HttpStatus status, String msg) {
+ this.code = status.value();
+ this.msg = msg;
+ }
+
+ public ResultDataEntity(HttpStatus status) {
+ this.code = status.value();
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/shared/config/ResultEntity.java b/src/main/java/com/yau/digitalrmb/shared/config/ResultEntity.java
new file mode 100644
index 0000000..6c585d9
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/shared/config/ResultEntity.java
@@ -0,0 +1,44 @@
+package com.yau.digitalrmb.shared.config;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import reactor.core.publisher.Flux;
+
+
+/**
+ * 对ResponseEntity做了简单包装
+ *
+ */
+@ApiModel("API返回对象")
+@Getter
+public class ResultEntity extends ResponseEntity> {
+ public ResultEntity(T data) {
+ super(new ResultDataEntity<>(HttpStatus.OK, data), HttpStatus.OK);
+ }
+
+ public ResultEntity(HttpStatus status, T data) {
+ super(new ResultDataEntity<>(status, data), status);
+ }
+
+ public ResultEntity(HttpStatus status, String msg, T data) {
+ super(new ResultDataEntity<>(status, msg, data), status);
+ }
+
+ public ResultEntity(String msg) {
+ super(new ResultDataEntity<>(HttpStatus.OK, msg), HttpStatus.OK);
+ }
+
+ public ResultEntity(HttpStatus status, String msg) {
+ super(new ResultDataEntity<>(status, msg), status);
+ }
+
+ public ResultEntity(HttpStatus status) {
+ super(new ResultDataEntity<>(status), status);
+ }
+
+ public ResultEntity(HttpStatus status, Flux flow) {
+ super(new ResultDataEntity<>(status, flow), status);
+ }
+}
diff --git a/src/main/java/com/yau/digitalrmb/shared/interfaces/FileManagementController.java b/src/main/java/com/yau/digitalrmb/shared/interfaces/FileManagementController.java
new file mode 100644
index 0000000..39c0f9d
--- /dev/null
+++ b/src/main/java/com/yau/digitalrmb/shared/interfaces/FileManagementController.java
@@ -0,0 +1,225 @@
+package com.yau.digitalrmb.shared.interfaces;
+
+
+import com.yau.digitalrmb.shared.config.IFileUtil;
+import com.yau.digitalrmb.shared.config.ResultEntity;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 文件管理控制器
+ * 提供文件上传、下载、删除等基础功能
+ */
+@Slf4j
+@Tag(name = "文件管理")
+@RestController
+@Validated
+public class FileManagementController {
+
+ @Autowired
+ private IFileUtil fileUtil;
+
+ /**
+ * 上传文件
+ * 支持可重复调用,每次上传都会生成新的文件路径
+ */
+ @Operation(summary = "上传文件")
+ @PostMapping("api/file/upload")
+
+ public ResultEntity