feat:文件上传的功能开发

master
jiazheng.zhao 1 week ago
parent c92d26bdce
commit 9a90eb1eee

@ -19,7 +19,9 @@ public class QuotaRequestSourceDataResult {
this.transactionIdentifier = value.getTransactionIdentifier();
}
public BigDecimal getAmount() { return amount; }
public String getInstitutionIdentifier() { return institutionIdentifier; }
public String getTransactionIdentifier() { return transactionIdentifier; }
}

@ -96,21 +96,11 @@ public class StandardCurrencyService {
}
@Transactional
public StandardCurrencyBatchResult confirmRule(String prefix, String idRule, String currencyStatus,
InstitutionKeySubject subject, String operator) {
public CurrencyIdRuleResult confirmIdRule(InstitutionKeySubject subject, String operator) {
StandardCurrencyBatchEntity batch = requireLatest(subject);
if (batch.getDenominationSummary() == null || batch.getTotalAmount() == null) {
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先执行面额拆解");
}
if (!"DC".equals(prefix == null ? null : prefix.trim().toUpperCase())) {
throw inputError(subject, "币串前缀必须为DC");
}
if (!ID_RULE.equals(idRule == null ? null : idRule.trim())) {
throw inputError(subject, "币串ID规则必须为“批次号+枚序号+随机校验码”");
}
if (!"待生效".equals(currencyStatus == null ? null : currencyStatus.trim())) {
throw inputError(subject, "币串初始状态必须为“待生效”");
}
int updated = batchMapper.update(null, new LambdaUpdateWrapper<StandardCurrencyBatchEntity>()
.eq(StandardCurrencyBatchEntity::getId, batch.getId())
.eq(StandardCurrencyBatchEntity::getStatus, batch.getStatus())
@ -122,7 +112,25 @@ public class StandardCurrencyService {
.set(StandardCurrencyBatchEntity::getUpdatedAt, LocalDateTime.now())
.set(StandardCurrencyBatchEntity::getUpdatedBy, operator));
if (updated != 1) throw stateChanged();
return result(requireLatest(subject));
StandardCurrencyBatchEntity latest = requireLatest(subject);
return new CurrencyIdRuleResult("DC", ID_RULE,
latest.getBatchNumber(), latest.getTotalCount(), latest.getStatus());
}
@Transactional
public StandardCurrencyRuleResult confirmCurrencyRule(InstitutionKeySubject subject, String operator) {
StandardCurrencyBatchEntity batch = requireLatest(subject);
if (batch.getPrefix() == null || batch.getIdRule() == null) {
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先确认币串ID生成规则");
}
// 从步骤八获取真实数据
QuotaControlBitEntity step8 = requireStepEight(subject);
List<DenominationBreakdownResult> breakdown = decode(batch.getDenominationSummary());
BigDecimal firstDenomination = breakdown.isEmpty() ? new BigDecimal("100.00") : breakdown.get(0).getDenomination();
return new StandardCurrencyRuleResult(batch.getPrefix(), batch.getCurrencyStatus(),
batch.getBatchNumber(), batch.getTotalCount(), batch.getStatus(),
firstDenomination, step8.getInstitutionIdentifier(),
step8.getBankSignature(), step8.getCentralBankSignatureSegment());
}
@Transactional

@ -143,21 +143,64 @@ public class TransactionInformationIdentifierService {
return new TransactionIdentifierAssemblyResult(value);
}
@Transactional
// @Transactional
// public QuotaRequestSourceDataResult quotaRequestSourceData(InstitutionKeySubject subject) {
// TransactionInformationIdentifier value = repository.findLatestBySubject(subject.getUserId(),
// subject.getSchoolId(), subject.getClassId()).orElseThrow(() ->
// new BusinessException(ErrorCode.VALIDATION_ERROR,
// "步骤四尚未完成交易信息标识组装,不能查询步骤五前序数据"));
// if (value.getStatus().ordinal() < TransactionInformationIdentifier.Status.ASSEMBLED.ordinal()
// || value.getTransactionIdentifier() == null
// || !value.getTransactionIdentifier().matches("TXN_[0-9A-F]{64}")) {
// throw new BusinessException(ErrorCode.VALIDATION_ERROR,
// "步骤四尚未生成交易信息标识,请先完成步骤四");
// }
// return new QuotaRequestSourceDataResult(value);
// }
//@Transactional(readOnly = true) // 既然只查询,用 readOnly 优化
//public QuotaRequestSourceDataResult quotaRequestSourceData(InstitutionKeySubject subject) {
// // 1. 尝试查询,查不到则直接返回空结果
// TransactionInformationIdentifier value = repository.findLatestBySubject(
// subject.getUserId(), subject.getSchoolId(), subject.getClassId())
// .orElse(null);
//
// // 2. 如果查不到,或者状态/标识不满足条件,返回一个“空”结果对象
// if (value == null ||
// value.getStatus().ordinal() < TransactionInformationIdentifier.Status.ASSEMBLED.ordinal() ||
// value.getTransactionIdentifier() == null ||
// !value.getTransactionIdentifier().matches("TXN_[0-9A-F]{64}")) {
//
// // 这里返回一个代表“未就绪”的空对象,前端通过检查关键字段(如 transactionIdentifier是否为 null 来判断
// return new QuotaRequestSourceDataResult(null); // 假设你在 Result 类里增加了 static empty() 方法
// }
//
// // 3. 条件满足,正常返回
// return new QuotaRequestSourceDataResult(value);
//}
@Transactional(readOnly = true)
public QuotaRequestSourceDataResult quotaRequestSourceData(InstitutionKeySubject subject) {
TransactionInformationIdentifier value = repository.findLatestBySubject(subject.getUserId(),
subject.getSchoolId(), subject.getClassId()).orElseThrow(() ->
new BusinessException(ErrorCode.VALIDATION_ERROR,
"步骤四尚未完成交易信息标识组装,不能查询步骤五前序数据"));
TransactionInformationIdentifier value = repository.findLatestBySubject(
subject.getUserId(),
subject.getSchoolId(),
subject.getClassId()
).orElse(null);
// 无记录 → 返回 null
if (value == null) {
return null;
}
// 状态或标识不满足条件 → 返回 null
if (value.getStatus().ordinal() < TransactionInformationIdentifier.Status.ASSEMBLED.ordinal()
|| value.getTransactionIdentifier() == null
|| !value.getTransactionIdentifier().matches("TXN_[0-9A-F]{64}")) {
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
"步骤四尚未生成交易信息标识,请先完成步骤四");
return null;
}
// 正常返回
return new QuotaRequestSourceDataResult(value);
}
@Transactional
public QuotaRequestDigestResult digestQuotaRequest(String originalText,
InstitutionKeySubject subject,
@ -315,17 +358,31 @@ public class TransactionInformationIdentifierService {
return new QuotaVerificationResult(value);
}
// @Transactional(readOnly = true)
// public QuotaRequestMessageResult quotaRequestMessageDetail(InstitutionKeySubject subject) {
// return new QuotaRequestMessageResult(requireLatest(subject));
// }
@Transactional(readOnly = true)
public QuotaRequestMessageResult quotaRequestMessageDetail(InstitutionKeySubject subject) {
return new QuotaRequestMessageResult(requireLatest(subject));
TransactionInformationIdentifier value = repository.findLatestBySubject(
subject.getUserId(),
subject.getSchoolId(),
subject.getClassId()
).orElse(null);
if (value == null) {
return null; // 返回 null前端收到 data: nullHTTP 200
}
return new QuotaRequestMessageResult(value);
}
@Transactional(readOnly = true)
public TransactionInformationIdentifierResult detail(InstitutionKeySubject subject) {
TransactionInformationIdentifier value = repository.findLatestBySubject(subject.getUserId(),
subject.getSchoolId(), subject.getClassId()).orElseThrow(() ->
new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前没有交易信息标识记录"));
return new TransactionInformationIdentifierResult(value);
TransactionInformationIdentifier value = repository.findLatestBySubject(
subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElse(null); // 查不到就返回 null
// 注意:这里 value 可能为 null
return value != null ? new TransactionInformationIdentifierResult(value) : null;
}
private CurrencyGenerationVerificationEntity requireCompletedVerification(InstitutionKeySubject subject) {
@ -378,18 +435,21 @@ public class TransactionInformationIdentifierService {
return value;
}
// private TransactionInformationIdentifier requireLatest(InstitutionKeySubject subject) {
// return repository.findLatestBySubject(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
// .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND,
// "当前没有额度请求记录"));
// }
private TransactionInformationIdentifier requireLatest(InstitutionKeySubject subject) {
return repository.findLatestBySubject(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND,
"当前没有额度请求记录"));
.orElse(null);
}
private TransactionInformationIdentifier requireStatus(InstitutionKeySubject subject,
TransactionInformationIdentifier.Status status,
String message) {
return repository.findLatestBySubjectAndStatus(subject.getUserId(), subject.getSchoolId(),
subject.getClassId(), status).orElseThrow(() ->
new BusinessException(ErrorCode.VALIDATION_ERROR, message));
subject.getClassId(), status).orElse(null);
}
private BusinessException keyError(InstitutionKeySubject subject, String message) {

@ -1,19 +0,0 @@
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
@Getter
@Setter
public class CurrencyRuleRequest {
@NotBlank(message = "币串前缀不能为空")
private String prefix;
@NotBlank(message = "币串ID规则不能为空")
private String idRule;
@NotBlank(message = "币串初始状态不能为空")
private String currencyStatus;
}

@ -1,12 +1,13 @@
package com.yau.digitalrmb.institutionidentity.interfaces.rest;
import com.yau.digitalrmb.institutionidentity.application.CurrencyIdRuleResult;
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.StandardCurrencyRuleResult;
import com.yau.digitalrmb.institutionidentity.application.StandardCurrencyService;
import com.yau.digitalrmb.institutionidentity.interfaces.dto.CurrencyRuleRequest;
import com.yau.digitalrmb.shared.api.ApiResponse;
import com.yau.digitalrmb.shared.web.TraceIdFilter;
import com.yau.digitalrmb.security.context.AuthContextHolder;
@ -17,12 +18,10 @@ import org.slf4j.MDC;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
@Validated
@ -43,14 +42,22 @@ public class StandardCurrencyController {
result.getTotalCount(), result.getBreakdown()), traceId());
}
@PostMapping("/steps/confirm-rule")
@Operation(summary = "确认币串生成规则", description = "校验DC前缀、币串ID规则和待生效初始状态。")
public ApiResponse<StandardCurrencyOperationResult> confirmRule(@Valid @RequestBody CurrencyRuleRequest request) {
@PostMapping("/steps/confirm-id-rule")
@Operation(summary = "确认币串ID生成规则", description = "无需请求体。后端自动设置DC前缀和批次号+枚序号+随机校验码ID规则返回ID规则明细表批次号、枚序号、随机校验码、完整币串ID。")
public ApiResponse<CurrencyIdRuleResult> confirmIdRule() {
JwtUser user = AuthContextHolder.get();
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());
CurrencyIdRuleResult result = service.confirmIdRule(
InstitutionKeySubject.from(user), user.getUsername());
return ApiResponse.success(result, traceId());
}
@PostMapping("/steps/confirm-currency-rule")
@Operation(summary = "确认标准面额币串规则", description = "无需请求体。返回标准面额币串规则明细表前缀、币串ID、标准面额、机构标识、银行签名段、央行签名段、币串状态数据从步骤七步骤八动态获取。")
public ApiResponse<StandardCurrencyRuleResult> confirmCurrencyRule() {
JwtUser user = AuthContextHolder.get();
StandardCurrencyRuleResult result = service.confirmCurrencyRule(
InstitutionKeySubject.from(user), user.getUsername());
return ApiResponse.success(result, traceId());
}
@PostMapping("/steps/generate")

@ -64,6 +64,7 @@ public class UserStepProgressNewService {
.eq(UserStepProgressNewEntity::getUserId, userId)
.eq(UserStepProgressNewEntity::getModuleCode, moduleCode)
.eq(UserStepProgressNewEntity::getStepCode, stepCode)
.eq(UserStepProgressNewEntity::getDeleted,0)
.last("LIMIT 1"));
}

@ -4,6 +4,7 @@ import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierApplicationEntity;
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierApplicationMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yau.digitalrmb.security.context.JwtUser;
import com.yau.digitalrmb.shared.api.ErrorCode;
import com.yau.digitalrmb.shared.exception.BusinessException;
import com.yau.digitalrmb.walletopening.domain.BankReceivedApplication;
@ -29,26 +30,45 @@ public class WalletOpeningService {
@Resource
private InstitutionIdentifierApplicationMapper institutionIdentifierMapper;
@Transactional
public UserWalletPageResult getPage(InstitutionKeySubject subject) {
WalletApplication app = findOrCreate(subject);
refreshCurrentUserInfo(subject, app);
@Transactional(readOnly = true)
public UserWalletPageResult getPage(InstitutionKeySubject subject, JwtUser jwtUser) {
WalletApplication app = walletRepository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseGet(() -> {
// 未创建记录时返回一个只包含JWT用户信息的临时对象不保存到数据库
WalletApplication temp = WalletApplication.create(
subject.getUserId(), subject.getSchoolId(), subject.getClassId(),
jwtUser.getUserName(), jwtUser.getPhone());
return temp;
});
return new UserWalletPageResult(app);
}
@Transactional
public FaceRecognitionResult updateFaceRecognition(InstitutionKeySubject subject, int status) {
WalletApplication app = findRequired(subject);
refreshCurrentUserInfo(subject, app);
public FaceRecognitionResult updateFaceRecognition(InstitutionKeySubject subject, JwtUser jwtUser, int status) {
WalletApplication app = walletRepository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseGet(() -> {
// 首次人脸识别时创建记录
WalletApplication newApp = WalletApplication.create(
subject.getUserId(), subject.getSchoolId(), subject.getClassId(),
jwtUser.getUserName(), jwtUser.getPhone());
return walletRepository.save(newApp);
});
app.updateFaceRecognition(status);
walletRepository.update(app);
return new FaceRecognitionResult(status);
}
@Transactional
public WalletApplicationMessageResult outputApplication(InstitutionKeySubject subject, WalletApplicationRequest request) {
WalletApplication app = findRequired(subject);
refreshCurrentUserInfo(subject, app);
public WalletApplicationMessageResult outputApplication(InstitutionKeySubject subject, JwtUser jwtUser, WalletApplicationRequest request) {
WalletApplication app = walletRepository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseGet(() -> {
// 首次输出申请报文时创建记录
WalletApplication newApp = WalletApplication.create(
subject.getUserId(), subject.getSchoolId(), subject.getClassId(),
jwtUser.getUserName(), jwtUser.getPhone());
return walletRepository.save(newApp);
});
if (app.getStatus() != null && app.getStatus().ordinal() >= WalletApplication.Status.OUTPUT.ordinal()) {
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "申请报文已输出,请勿重复操作");
}
@ -58,8 +78,11 @@ public class WalletOpeningService {
if (request.getWalletType() == null || request.getWalletType().trim().isEmpty()) {
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请选择数字钱包类型");
}
// 输出申请报文时才存储身份证号、银行卡号等敏感信息
app.fillForm(request.getSelectedBank(), request.getWalletType(), request.getInputPhone(),
request.getInputIdNumber(), request.getInputBankCardNumber(), request.getSignaturePhotoPath());
request.getInputIdNumber(), request.getInputBankCardNumber(), request.getSignaturePhotoPath(),
request.getIdNumber(), request.getAccountBank(), request.getBankCardNumber(), request.getAccountBalance());
String applyTime = LocalDateTime.now().format(TIMESTAMP_FORMAT);
String applicationId = "APP_" + applyTime;
@ -72,8 +95,9 @@ public class WalletOpeningService {
}
@Transactional
public WalletSendResult sendApplication(InstitutionKeySubject subject) {
WalletApplication app = findRequired(subject);
public WalletSendResult sendApplication(InstitutionKeySubject subject, JwtUser jwtUser) {
WalletApplication app = walletRepository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "钱包申请不存在,请先输出申请报文"));
if (app.getStatus() == null || app.getStatus().ordinal() < WalletApplication.Status.OUTPUT.ordinal()) {
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先输出申请报文");
}
@ -101,31 +125,6 @@ public class WalletOpeningService {
return new WalletSendResult("已提交");
}
private WalletApplication findOrCreate(InstitutionKeySubject subject) {
return walletRepository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseGet(() -> walletRepository.save(
WalletApplication.newDraft(subject.getUserId(), subject.getSchoolId(), subject.getClassId(),
subject.getUserName(), subject.getPhone())));
}
private WalletApplication findRequired(InstitutionKeySubject subject) {
return walletRepository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "钱包申请不存在,请先初始化页面"));
}
private void refreshCurrentUserInfo(InstitutionKeySubject subject, WalletApplication app) {
String beforeUserName = app.getUserName();
String beforePhone = app.getPhone();
app.refreshTokenUserInfo(subject.getUserName(), subject.getPhone());
if (!equals(beforeUserName, app.getUserName()) || !equals(beforePhone, app.getPhone())) {
walletRepository.update(app);
}
}
private boolean equals(String left, String right) {
return left == null ? right == null : left.equals(right);
}
private String resolveOrgCode(InstitutionKeySubject subject) {
InstitutionIdentifierApplicationEntity entity = institutionIdentifierMapper.selectOne(
new LambdaQueryWrapper<InstitutionIdentifierApplicationEntity>()

@ -33,30 +33,31 @@ public class WalletOpeningController {
@GetMapping("/page")
@Operation(summary = "查询用户端钱包开通申请页面数据",
description = "返回页面完整数据首次访问自动创建草稿申请预填用户信息。尚未完成的阶段字段返回null。")
description = "返回页面完整数据用户名和手机号从JWT token获取尚未产生的字段返回null。不会自动创建数据库记录。")
public ApiResponse<UserWalletPageResult> page() {
JwtUser jwtUser = AuthContextHolder.get();
return ApiResponse.success(walletOpeningService.getPage(
InstitutionKeySubject.from(AuthContextHolder.get())), traceId());
InstitutionKeySubject.from(jwtUser), jwtUser), traceId());
}
@PostMapping("/face-recognition")
@Operation(summary = "更新人脸识别状态",
description = "前端调用接口修改人脸识别状态。参数1=未成功2=已成功。")
description = "前端调用接口修改人脸识别状态。参数1=未成功2=已成功。首次调用时自动创建申请记录。")
public ApiResponse<FaceRecognitionResult> faceRecognition(
@Valid @RequestBody FaceRecognitionRequest request) {
JwtUser jwtUser = AuthContextHolder.get();
return ApiResponse.success(walletOpeningService.updateFaceRecognition(
InstitutionKeySubject.from(jwtUser), request.getStatus()), traceId());
InstitutionKeySubject.from(jwtUser), jwtUser, request.getStatus()), traceId());
}
@PostMapping("/output")
@Operation(summary = "输出申请报文",
description = "保存用户填写的表单数据拼接原始报文并生成JSON申请报文。")
description = "保存用户填写的表单数据(包括身份证号、银行卡号等敏感信息)拼接原始报文并生成JSON申请报文。")
public ApiResponse<WalletApplicationMessageResult> output(
@Valid @RequestBody WalletApplicationRequest request) {
JwtUser jwtUser = AuthContextHolder.get();
return ApiResponse.success(walletOpeningService.outputApplication(
InstitutionKeySubject.from(jwtUser), request), traceId());
InstitutionKeySubject.from(jwtUser), jwtUser, request), traceId());
}
@PostMapping("/send")
@ -65,7 +66,7 @@ public class WalletOpeningController {
public ApiResponse<WalletSendResult> send() {
JwtUser jwtUser = AuthContextHolder.get();
return ApiResponse.success(walletOpeningService.sendApplication(
InstitutionKeySubject.from(jwtUser)), traceId());
InstitutionKeySubject.from(jwtUser), jwtUser), traceId());
}
private String traceId() {

@ -5,6 +5,7 @@ import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.math.BigDecimal;
@Getter
@Setter
@ -23,4 +24,12 @@ public class WalletApplicationRequest {
private String inputBankCardNumber;
@Schema(description = "面签照片路径", example = "/2026/8/11/5cae82501de84bfba3693477ac55140c.pdf")
private String signaturePhotoPath;
@Schema(description = "身份证号(确认)", example = "110101199001011234")
private String idNumber;
@Schema(description = "开户银行", example = "中国银行北京分行")
private String accountBank;
@Schema(description = "银行卡号(确认)", example = "6216610100001234567")
private String bankCardNumber;
@Schema(description = "账户余额", example = "500000.00")
private BigDecimal accountBalance;
}

Loading…
Cancel
Save