feat:文件上传的功能开发

master
jiazheng.zhao 3 weeks ago
parent 56edda8b97
commit 078f324494

@ -36,6 +36,13 @@
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version> <!-- 建议使用最新版本 -->
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -104,6 +111,11 @@
<artifactId>spring-security-test</artifactId> <artifactId>spring-security-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.8.5</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -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;
}
}

@ -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<DenominationBreakdownResult> breakdown;
public DenominationDecompositionResult(Integer totalCount, List<DenominationBreakdownResult> breakdown) {
this.totalCount = totalCount;
this.breakdown = breakdown;
}
public Integer getTotalCount() {
return totalCount;
}
public List<DenominationBreakdownResult> getBreakdown() {
return breakdown;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -11,6 +11,7 @@ import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyPairResu
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService; import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService;
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject; import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.application.InstitutionVerificationProcessResult; 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.application.SignatureResult;
import com.yau.digitalrmb.institutionidentity.interfaces.dto.ConfirmInstitutionIdentifierRequest; import com.yau.digitalrmb.institutionidentity.interfaces.dto.ConfirmInstitutionIdentifierRequest;
import com.yau.digitalrmb.institutionidentity.interfaces.dto.VerifyInstitutionApplicationRequest; import com.yau.digitalrmb.institutionidentity.interfaces.dto.VerifyInstitutionApplicationRequest;
@ -102,10 +103,10 @@ public class CentralBankInstitutionIdentifierController {
@PostMapping("/applications/feedback") @PostMapping("/applications/feedback")
@Operation(summary = "央行信息反馈", @Operation(summary = "央行信息反馈",
description = "将已确权的最终机构标识同步到商业银行端商业银行状态由1变为2。") description = "将已确权的最终机构标识同步到商业银行端商业银行状态由1变为2。")
public ApiResponse<InstitutionIdentifierFinalResult> feedback() { public ApiResponse<OperationStatusResult> feedback() {
JwtUser jwtUser = AuthContextHolder.get(); JwtUser jwtUser = AuthContextHolder.get();
return ApiResponse.success(feedbackService.feedback( feedbackService.feedback(InstitutionKeySubject.from(jwtUser), jwtUser.getUsername());
InstitutionKeySubject.from(jwtUser), jwtUser.getUsername()), traceId()); return ApiResponse.success(new OperationStatusResult("FEEDBACK_SENT"), traceId());
} }
private String traceId() { private String traceId() {

@ -1,5 +1,6 @@
package com.yau.digitalrmb.institutionidentity.interfaces.rest; 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.CurrencyConfirmationMessageResult;
import com.yau.digitalrmb.institutionidentity.application.CurrencyConfirmationSignatureResult; import com.yau.digitalrmb.institutionidentity.application.CurrencyConfirmationSignatureResult;
import com.yau.digitalrmb.institutionidentity.application.CurrencyDigestResult; 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.CurrencyGenerationVerificationService;
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject; import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.application.OperationBooleanResult; 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.OperationMessageResult;
import com.yau.digitalrmb.institutionidentity.application.OperationStatusResult; import com.yau.digitalrmb.institutionidentity.application.OperationStatusResult;
import com.yau.digitalrmb.institutionidentity.application.SignatureResult; import com.yau.digitalrmb.institutionidentity.application.SignatureResult;
@ -36,12 +38,13 @@ public class CurrencyGenerationVerificationController {
@PostMapping("/steps/receive") @PostMapping("/steps/receive")
@Operation(summary = "接收步骤二请求报文", @Operation(summary = "接收步骤二请求报文",
description = "对应页面“接收步骤二请求报文”按钮。步骤二必须达到RECEIVED重复点击时复用verificationId并将步骤三恢复到RECEIVED清空后续按钮产生的数据。") description = "对应页面“接收步骤二请求报文”按钮。步骤二必须达到RECEIVED重复点击时复用verificationId并将步骤三恢复到RECEIVED清空后续按钮产生的数据。")
public ApiResponse<OperationStatusResult> receive( public ApiResponse<OperationIdStatusResult> receive(
@Valid @RequestBody ReceiveCurrencyVerificationRequest request) { @Valid @RequestBody ReceiveCurrencyVerificationRequest request) {
JwtUser user = AuthContextHolder.get(); JwtUser user = AuthContextHolder.get();
CurrencyGenerationVerificationResult result = service.receive(request.getRequestId(), CurrencyGenerationVerificationResult result = service.receive(request.getRequestId(),
InstitutionKeySubject.from(user), user.getUsername()); 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") @PostMapping("/steps/verify-signature")
@ -103,27 +106,33 @@ public class CurrencyGenerationVerificationController {
@PostMapping("/steps/package-response") @PostMapping("/steps/package-response")
@Operation(summary = "打包确认报文", description = "对应页面“打包报文”按钮,只组装确认报文,不执行返回。") @Operation(summary = "打包确认报文", description = "对应页面“打包报文”按钮,只组装确认报文,不执行返回。")
public ApiResponse<CurrencyGenerationVerificationResult> packageResponse( public ApiResponse<OperationMessageResult> packageResponse(
@Valid @RequestBody CurrencyVerificationStepRequest request) { @Valid @RequestBody CurrencyVerificationStepRequest request) {
JwtUser user = AuthContextHolder.get(); JwtUser user = AuthContextHolder.get();
return ok(service.packageResponse(request.getVerificationId(), CurrencyGenerationVerificationResult result = service.packageResponse(request.getVerificationId(),
InstitutionKeySubject.from(user), user.getUsername())); InstitutionKeySubject.from(user), user.getUsername());
return ApiResponse.success(new OperationMessageResult(result.getConfirmationMessage()),
MDC.get(TraceIdFilter.MDC_KEY));
} }
@PostMapping("/steps/return-response") @PostMapping("/steps/return-response")
@Operation(summary = "返回确认报文", description = "对应页面“返回确认报文”按钮,只返回已打包完成的确认报文。") @Operation(summary = "返回确认报文", description = "对应页面“返回确认报文”按钮,只返回已打包完成的确认报文。")
public ApiResponse<CurrencyGenerationVerificationResult> returnResponse( public ApiResponse<OperationStatusResult> returnResponse(
@Valid @RequestBody CurrencyVerificationStepRequest request) { @Valid @RequestBody CurrencyVerificationStepRequest request) {
JwtUser user = AuthContextHolder.get(); 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") @PostMapping("/steps/receive-confirmation")
@Operation(summary = "商业银行接收报文确认信息", @Operation(summary = "商业银行接收报文确认信息",
description = "对应商业银行端“接收报文确认信息”按钮。根据当前Token查询本次实训数据confirmationStatus在央行未返回时为“待确认”已返回时为“已确认”查询不改变流程状态。") description = "对应商业银行端“接收报文确认信息”按钮。根据当前Token查询本次实训数据confirmationStatus在央行未返回时为“待确认”已返回时为“已确认”查询不改变流程状态。")
public ApiResponse<CurrencyConfirmationMessageResult> receiveConfirmation() { public ApiResponse<ConfirmationStatusResult> receiveConfirmation() {
return ApiResponse.success(service.receiveConfirmation( CurrencyConfirmationMessageResult result = service.receiveConfirmation(
InstitutionKeySubject.from(AuthContextHolder.get())), MDC.get(TraceIdFilter.MDC_KEY)); InstitutionKeySubject.from(AuthContextHolder.get()));
return ApiResponse.success(new ConfirmationStatusResult(result.getConfirmationStatus()),
MDC.get(TraceIdFilter.MDC_KEY));
} }
@GetMapping("/current") @GetMapping("/current")

@ -1,8 +1,10 @@
package com.yau.digitalrmb.institutionidentity.interfaces.rest; 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.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.application.StandardCurrencyBatchResult; import com.yau.digitalrmb.institutionidentity.application.StandardCurrencyBatchResult;
import com.yau.digitalrmb.institutionidentity.application.StandardCurrencyListItemResult; 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.application.StandardCurrencyService;
import com.yau.digitalrmb.institutionidentity.interfaces.dto.CurrencyRuleRequest; import com.yau.digitalrmb.institutionidentity.interfaces.dto.CurrencyRuleRequest;
import com.yau.digitalrmb.shared.api.ApiResponse; import com.yau.digitalrmb.shared.api.ApiResponse;
@ -33,25 +35,32 @@ public class StandardCurrencyController {
@PostMapping("/steps/decompose") @PostMapping("/steps/decompose")
@Operation(summary = "执行面额拆解", @Operation(summary = "执行面额拆解",
description = "无需请求体。后端按当前JWT主体查询步骤八已确认额度控制位金额并按100、50、20、10、5、1、0.5、0.2、0.1、0.05、0.01从大到小拆解。") description = "无需请求体。后端按当前JWT主体查询步骤八已确认额度控制位金额并按100、50、20、10、5、1、0.5、0.2、0.1、0.05、0.01从大到小拆解。")
public ApiResponse<StandardCurrencyBatchResult> decompose() { public ApiResponse<DenominationDecompositionResult> decompose() {
JwtUser user = AuthContextHolder.get(); JwtUser user = AuthContextHolder.get();
return ApiResponse.success(service.decompose( StandardCurrencyBatchResult result = service.decompose(
InstitutionKeySubject.from(user), user.getUsername()), traceId()); InstitutionKeySubject.from(user), user.getUsername());
return ApiResponse.success(new DenominationDecompositionResult(
result.getTotalCount(), result.getBreakdown()), traceId());
} }
@PostMapping("/steps/confirm-rule") @PostMapping("/steps/confirm-rule")
@Operation(summary = "确认币串生成规则", description = "校验DC前缀、币串ID规则和待生效初始状态。") @Operation(summary = "确认币串生成规则", description = "校验DC前缀、币串ID规则和待生效初始状态。")
public ApiResponse<StandardCurrencyBatchResult> confirmRule(@Valid @RequestBody CurrencyRuleRequest request) { public ApiResponse<StandardCurrencyOperationResult> confirmRule(@Valid @RequestBody CurrencyRuleRequest request) {
JwtUser user = AuthContextHolder.get(); JwtUser user = AuthContextHolder.get();
return ApiResponse.success(service.confirmRule(request.getPrefix(), request.getIdRule(), StandardCurrencyBatchResult result = service.confirmRule(request.getPrefix(), request.getIdRule(),
request.getCurrencyStatus(), InstitutionKeySubject.from(user), user.getUsername()), traceId()); request.getCurrencyStatus(), InstitutionKeySubject.from(user), user.getUsername());
return ApiResponse.success(new StandardCurrencyOperationResult(
result.getStatus(), result.getTotalCount()), traceId());
} }
@PostMapping("/steps/generate") @PostMapping("/steps/generate")
@Operation(summary = "逐枚生成标准面额币串", description = "根据拆解清单为每一枚标准面额生成唯一币串。") @Operation(summary = "逐枚生成标准面额币串", description = "根据拆解清单为每一枚标准面额生成唯一币串。")
public ApiResponse<StandardCurrencyBatchResult> generate() { public ApiResponse<StandardCurrencyOperationResult> generate() {
JwtUser user = AuthContextHolder.get(); 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") @GetMapping("/currencies")

@ -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 formhttp
* @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);
}

@ -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<String> 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错误");
}
}
}

@ -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<T> {
/**
*
*/
private final Integer code;
/**
*
*/
// @JsonInclude(JsonInclude.Include.NON_NULL)
private String msg = null;
/**
*
*/
// @JsonInclude(JsonInclude.Include.NON_NULL)
private T data = null;
private Flux<String> 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<String> 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();
}
}

@ -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<T> extends ResponseEntity<ResultDataEntity<T>> {
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<String> flow) {
super(new ResultDataEntity<>(status, flow), status);
}
}

@ -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<Map<String, String>> uploadFile(
@ApiParam("文件对象") @RequestPart MultipartFile file) {
try {
if (file == null || file.isEmpty()) {
return new ResultEntity<>(HttpStatus.BAD_REQUEST, "文件不能为空");
}
String originalFilename = file.getOriginalFilename();
if (!StringUtils.hasText(originalFilename)) {
return new ResultEntity<>(HttpStatus.BAD_REQUEST, "文件名不能为空");
}
String suffix = fileUtil.getSuffix(originalFilename);
if (!StringUtils.hasText(suffix)) {
return new ResultEntity<>(HttpStatus.BAD_REQUEST, "文件类型错误,文件必须包含扩展名");
}
String relativePath = fileUtil.upload(file);
Map<String, String> result = new HashMap<>();
result.put("relativePath", relativePath);
result.put("originalFilename", originalFilename);
result.put("fileSize", String.valueOf(file.getSize()));
result.put("suffix", suffix);
log.info("文件上传成功: 原始文件名={}, 保存路径={}, 文件大小={}",
originalFilename, relativePath, file.getSize());
return new ResultEntity<>(result);
} catch (IllegalArgumentException e) {
log.error("文件上传失败: {}", e.getMessage());
return new ResultEntity<>(HttpStatus.BAD_REQUEST, e.getMessage());
} catch (Exception e) {
log.error("文件上传异常", e);
return new ResultEntity<>(HttpStatus.INTERNAL_SERVER_ERROR, "文件上传失败: " + e.getMessage());
}
}
/**
*
* 使
*/
@Operation(summary = "下载文件(使用原始文件名)")
@GetMapping("api/file/download")
public void downloadFile(
@ApiParam("文件相对路径") @RequestParam String relativePath,
HttpServletResponse response) {
try {
if (!StringUtils.hasText(relativePath)) {
throw new IllegalArgumentException("文件路径不能为空");
}
log.info("文件下载请求: 路径={}", relativePath);
fileUtil.download(response, relativePath);
} catch (IllegalArgumentException e) {
log.error("文件下载失败: {}", e.getMessage());
try {
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"message\":\"" + e.getMessage() + "\"}");
} catch (Exception ex) {
log.error("写入错误响应失败", ex);
}
} catch (Exception e) {
log.error("文件下载异常", e);
try {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"message\":\"文件下载失败: " + e.getMessage() + "\"}");
} catch (Exception ex) {
log.error("写入错误响应失败", ex);
}
}
}
/**
*
*
*/
@Operation(summary = "下载文件(自定义文件名)")
@GetMapping("api/file/download/custom")
public void downloadFileWithCustomName(
@ApiParam("文件相对路径") @RequestParam String relativePath,
@ApiParam("自定义文件名(不含扩展名)") @RequestParam String fileName,
HttpServletResponse response) {
try {
if (!StringUtils.hasText(relativePath)) {
throw new IllegalArgumentException("文件路径不能为空");
}
if (!StringUtils.hasText(fileName)) {
throw new IllegalArgumentException("文件名不能为空");
}
log.info("文件下载请求: 路径={}, 自定义文件名={}", relativePath, fileName);
fileUtil.download(response, fileName, relativePath);
} catch (IllegalArgumentException e) {
log.error("文件下载失败: {}", e.getMessage());
try {
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"message\":\"" + e.getMessage() + "\"}");
} catch (Exception ex) {
log.error("写入错误响应失败", ex);
}
} catch (Exception e) {
log.error("文件下载异常", e);
try {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"message\":\"文件下载失败: " + e.getMessage() + "\"}");
} catch (Exception ex) {
log.error("写入错误响应失败", ex);
}
}
}
/**
*
*
*/
@Operation(summary = "删除文件")
@PostMapping("api/file/delete")
public ResultEntity<Map<String, Object>> deleteFile(
@ApiParam("文件相对路径") @RequestParam String relativePath) {
try {
if (!StringUtils.hasText(relativePath)) {
return new ResultEntity<>(HttpStatus.BAD_REQUEST, "文件路径不能为空");
}
boolean deleted = fileUtil.remove(relativePath);
Map<String, Object> result = new HashMap<>();
result.put("relativePath", relativePath);
result.put("deleted", deleted);
if (deleted) {
log.info("文件删除成功: 路径={}", relativePath);
result.put("message", "文件删除成功");
} else {
log.info("文件不存在或已被删除: 路径={}", relativePath);
result.put("message", "文件不存在或已被删除");
}
return new ResultEntity<>(result);
} catch (Exception e) {
log.error("文件删除异常: 路径={}", relativePath, e);
return new ResultEntity<>(HttpStatus.INTERNAL_SERVER_ERROR, "文件删除失败: " + e.getMessage());
}
}
/**
*
*
*/
@Operation(summary = "获取文件完整路径")
@GetMapping("api/file/fullPath")
public ResultEntity<Map<String, String>> getFullPath(
@ApiParam("文件相对路径") @RequestParam String relativePath) {
try {
if (!StringUtils.hasText(relativePath)) {
return new ResultEntity<>(HttpStatus.BAD_REQUEST, "文件路径不能为空");
}
String fullPath = fileUtil.getFullPath(relativePath);
Map<String, String> result = new HashMap<>();
result.put("relativePath", relativePath);
result.put("fullPath", fullPath);
return new ResultEntity<>(result);
} catch (Exception e) {
log.error("获取文件完整路径失败", e);
return new ResultEntity<>(HttpStatus.INTERNAL_SERVER_ERROR, "获取路径失败: " + e.getMessage());
}
}
}

@ -19,3 +19,7 @@ platform-integration:
security: security:
jwt: jwt:
secret: 0123456789012345678901234567890123456789012345678901234567890123 secret: 0123456789012345678901234567890123456789012345678901234567890123
file:
type: local
path: D:\新建文件夹

@ -0,0 +1,9 @@
-- Execute the duplicate check first. It must return no rows before adding the constraint.
SELECT request_id, COUNT(*) AS duplicate_count
FROM currency_generation_verification
GROUP BY request_id
HAVING COUNT(*) > 1;
-- MySQL 8+: add the business uniqueness constraint to an existing table.
ALTER TABLE currency_generation_verification
ADD CONSTRAINT uk_currency_verification_request UNIQUE (request_id);

@ -187,6 +187,7 @@ CREATE TABLE IF NOT EXISTS currency_generation_verification (
created_by VARCHAR(64) NOT NULL, created_by VARCHAR(64) NOT NULL,
updated_by VARCHAR(64) NOT NULL, updated_by VARCHAR(64) NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE, deleted BOOLEAN NOT NULL DEFAULT FALSE,
CONSTRAINT uk_currency_verification_request UNIQUE (request_id),
CONSTRAINT fk_currency_verification_request CONSTRAINT fk_currency_verification_request
FOREIGN KEY (request_id) REFERENCES currency_generation_request(id) FOREIGN KEY (request_id) REFERENCES currency_generation_request(id)
); );

@ -273,30 +273,20 @@ class TransactionInformationIdentifierControllerTest {
assertThat(transactionMapper.selectList(null).get(0).getId()).isEqualTo(stepFiveRecordId); assertThat(transactionMapper.selectList(null).get(0).getId()).isEqualTo(stepFiveRecordId);
mvc.perform(post(QUOTA_BASE_PATH + "/steps/package").header("Authorization", authorization)) mvc.perform(post(QUOTA_BASE_PATH + "/steps/package").header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.length()").value(6)) .andExpect(jsonPath("$.data.length()").value(1))
.andExpect(jsonPath("$.data.quotaRequestId").isNotEmpty()) .andExpect(jsonPath("$.data.message").value(org.hamcrest.Matchers.allOf(
.andExpect(jsonPath("$.data.amount").value(1589.85)) org.hamcrest.Matchers.containsString("\"quotaRequestId\""),
.andExpect(jsonPath("$.data.orgId").value("ORG_3A4B5C6D7E8F")) org.hamcrest.Matchers.containsString("\"amount\":1589.85"),
.andExpect(jsonPath("$.data.txnId").value(org.hamcrest.Matchers.startsWith("TXN_"))) org.hamcrest.Matchers.containsString("\"orgId\":\"ORG_3A4B5C6D7E8F\""),
.andExpect(jsonPath("$.data.digest").value(org.hamcrest.Matchers.matchesPattern("[0-9A-F]{64}"))) org.hamcrest.Matchers.containsString("\"txnId\":\"TXN_"))));
.andExpect(jsonPath("$.data.signature").isNotEmpty())
.andExpect(jsonPath("$.data.status").doesNotExist())
.andExpect(jsonPath("$.data.quotaRequestMessage").doesNotExist());
mvc.perform(get(CENTRAL_QUOTA_REQUEST_BASE_PATH).header("Authorization", authorization)) mvc.perform(get(CENTRAL_QUOTA_REQUEST_BASE_PATH).header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.status").value(1)) .andExpect(jsonPath("$.data.status").value(1))
.andExpect(jsonPath("$.data.length()").value(7)); .andExpect(jsonPath("$.data.length()").value(7));
mvc.perform(post(QUOTA_BASE_PATH + "/steps/send").header("Authorization", authorization)) mvc.perform(post(QUOTA_BASE_PATH + "/steps/send").header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.length()").value(6)) .andExpect(jsonPath("$.data.length()").value(1))
.andExpect(jsonPath("$.data.quotaRequestId").isNotEmpty()) .andExpect(jsonPath("$.data.status").value("SENT"));
.andExpect(jsonPath("$.data.amount").value(1589.85))
.andExpect(jsonPath("$.data.orgId").value("ORG_3A4B5C6D7E8F"))
.andExpect(jsonPath("$.data.txnId").value(org.hamcrest.Matchers.startsWith("TXN_")))
.andExpect(jsonPath("$.data.digest").value(org.hamcrest.Matchers.matchesPattern("[0-9A-F]{64}")))
.andExpect(jsonPath("$.data.signature").isNotEmpty())
.andExpect(jsonPath("$.data.status").doesNotExist())
.andExpect(jsonPath("$.data.centralBankReceiveStatus").doesNotExist());
mvc.perform(get(QUOTA_BASE_PATH).header("Authorization", authorization)) mvc.perform(get(QUOTA_BASE_PATH).header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.length()").value(6)); .andExpect(jsonPath("$.data.length()").value(6));
@ -469,8 +459,8 @@ class TransactionInformationIdentifierControllerTest {
.andExpect(jsonPath("$.data.length()").value(1)); .andExpect(jsonPath("$.data.length()").value(1));
mvc.perform(post(CONTROL_SIGNATURE_BASE_PATH + "/steps/package").header("Authorization", authorization)) mvc.perform(post(CONTROL_SIGNATURE_BASE_PATH + "/steps/package").header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.status").value("PACKAGED")) .andExpect(jsonPath("$.data.length()").value(1))
.andExpect(jsonPath("$.data.responseMessage").value(org.hamcrest.Matchers.containsString( .andExpect(jsonPath("$.data.message").value(org.hamcrest.Matchers.containsString(
"\"cbCtrlSignature\""))); "\"cbCtrlSignature\"")));
mvc.perform(get(COMMERCIAL_BANK_CONTROL_SIGNATURE_BASE_PATH).header("Authorization", authorization)) mvc.perform(get(COMMERCIAL_BANK_CONTROL_SIGNATURE_BASE_PATH).header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -619,8 +609,8 @@ class TransactionInformationIdentifierControllerTest {
assertThat(quotaControlBitMapper.selectList(null).get(0).getCompleteControlBit()) assertThat(quotaControlBitMapper.selectList(null).get(0).getCompleteControlBit())
.isEqualTo(completeControlBit); .isEqualTo(completeControlBit);
mvc.perform(post(QUOTA_CONTROL_BASE_PATH + "/steps/package").header("Authorization", authorization)) mvc.perform(post(QUOTA_CONTROL_BASE_PATH + "/steps/package").header("Authorization", authorization))
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("PACKAGED")) .andExpect(status().isOk()).andExpect(jsonPath("$.data.length()").value(1))
.andExpect(jsonPath("$.data.requestMessage").value(org.hamcrest.Matchers.containsString("\"quotaId\""))); .andExpect(jsonPath("$.data.message").value(org.hamcrest.Matchers.containsString("\"quotaId\"")));
mvc.perform(post(QUOTA_CONTROL_BASE_PATH + "/steps/static-segment") mvc.perform(post(QUOTA_CONTROL_BASE_PATH + "/steps/static-segment")
.header("Authorization", authorization)) .header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -649,8 +639,8 @@ class TransactionInformationIdentifierControllerTest {
mvc.perform(post(QUOTA_CONTROL_BASE_PATH + "/steps/package").header("Authorization", authorization)) mvc.perform(post(QUOTA_CONTROL_BASE_PATH + "/steps/package").header("Authorization", authorization))
.andExpect(status().isOk()); .andExpect(status().isOk());
mvc.perform(post(QUOTA_CONTROL_BASE_PATH + "/steps/send").header("Authorization", authorization)) mvc.perform(post(QUOTA_CONTROL_BASE_PATH + "/steps/send").header("Authorization", authorization))
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("RECEIVED")) .andExpect(status().isOk()).andExpect(jsonPath("$.data.length()").value(1))
.andExpect(jsonPath("$.data.receiveStatus").value(2)); .andExpect(jsonPath("$.data.status").value("RECEIVED"));
mvc.perform(get(CENTRAL_QUOTA_CONTROL_BASE_PATH).header("Authorization", authorization)) mvc.perform(get(CENTRAL_QUOTA_CONTROL_BASE_PATH).header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.status").value(2)) .andExpect(jsonPath("$.data.status").value(2))
@ -686,7 +676,7 @@ class TransactionInformationIdentifierControllerTest {
mvc.perform(post(STANDARD_CURRENCY_BASE_PATH + "/steps/decompose") mvc.perform(post(STANDARD_CURRENCY_BASE_PATH + "/steps/decompose")
.header("Authorization", authorization)) .header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.status").value("DECOMPOSED")) .andExpect(jsonPath("$.data.length()").value(2))
.andExpect(jsonPath("$.data.totalCount").value(27)) .andExpect(jsonPath("$.data.totalCount").value(27))
.andExpect(jsonPath("$.data.breakdown[0].denomination").value(100.0)) .andExpect(jsonPath("$.data.breakdown[0].denomination").value(100.0))
.andExpect(jsonPath("$.data.breakdown[0].count").value(15)) .andExpect(jsonPath("$.data.breakdown[0].count").value(15))
@ -706,16 +696,10 @@ class TransactionInformationIdentifierControllerTest {
mvc.perform(post(STANDARD_CURRENCY_BASE_PATH + "/steps/generate") mvc.perform(post(STANDARD_CURRENCY_BASE_PATH + "/steps/generate")
.header("Authorization", authorization)) .header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.length()").value(2))
.andExpect(jsonPath("$.data.status").value("GENERATED")) .andExpect(jsonPath("$.data.status").value("GENERATED"))
.andExpect(jsonPath("$.data.currencies.length()").value(27)) .andExpect(jsonPath("$.data.totalCount").value(27))
.andExpect(jsonPath("$.data.currencies[0].sequenceNumber").value(1)) .andExpect(jsonPath("$.data.currencies").doesNotExist());
.andExpect(jsonPath("$.data.currencies[0].denomination").value(100.0))
.andExpect(jsonPath("$.data.currencies[0].currencyId").value(
org.hamcrest.Matchers.matchesPattern("DC_[0-9]{8}_[0-9]{3}_001_[0-9A-Z]{6}")))
.andExpect(jsonPath("$.data.currencies[0].completeCurrency").value(
org.hamcrest.Matchers.containsString("_ORG_3A4B5C6D7E8F_100.00_")))
.andExpect(jsonPath("$.data.currencies[26].denomination").value(0.05))
.andExpect(jsonPath("$.data.currencies[26].status").value("待生效"));
mvc.perform(get(STANDARD_CURRENCY_BASE_PATH + "/currencies") mvc.perform(get(STANDARD_CURRENCY_BASE_PATH + "/currencies")
.header("Authorization", authorization)) .header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -753,7 +737,8 @@ class TransactionInformationIdentifierControllerTest {
mvc.perform(post(STANDARD_CURRENCY_BASE_PATH + "/steps/generate").header("Authorization", authorization)) mvc.perform(post(STANDARD_CURRENCY_BASE_PATH + "/steps/generate").header("Authorization", authorization))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.status").value("GENERATED")) .andExpect(jsonPath("$.data.status").value("GENERATED"))
.andExpect(jsonPath("$.data.currencies.length()").value(27)); .andExpect(jsonPath("$.data.totalCount").value(27))
.andExpect(jsonPath("$.data.currencies").doesNotExist());
assertThat(standardCurrencyBatchMapper.selectList(null)).hasSize(1); assertThat(standardCurrencyBatchMapper.selectList(null)).hasSize(1);
assertThat(standardCurrencyBatchMapper.selectList(null).get(0).getId()).isEqualTo(batchId); assertThat(standardCurrencyBatchMapper.selectList(null).get(0).getId()).isEqualTo(batchId);
assertThat(standardCurrencyMapper.selectList(null)).hasSize(27); assertThat(standardCurrencyMapper.selectList(null)).hasSize(27);

Loading…
Cancel
Save