This commit is contained in:
2026-09-09 15:27:10 +08:00
parent 5f671e164f
commit 2980308f4d
17 changed files with 292 additions and 17 deletions
@@ -0,0 +1,65 @@
package com.baiyue.finance.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.finance.client.FinanceFileClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.CacheControl;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.nio.charset.StandardCharsets;
@RestController
@RequestMapping("/api/platform/finance/v1/vouchers")
@PreAuthorize("hasAuthority('platform:finance:customer-arap:list')")
public class PlatformFinanceVoucherController {
private final FinanceFileClient files;
private final String token;
public PlatformFinanceVoucherController(FinanceFileClient files,
@Value("${baiyue.security.internal-service-token:}") String token) {
this.files = files;
this.token = token;
}
public record Voucher(String fileId, String fileName, String contentType, Long sizeBytes) {}
@GetMapping("/{fileId}")
public ApiResponse<Voucher> metadata(@PathVariable long fileId, @RequestParam long customerId) {
var file = scopedFile(customerId, fileId);
return FinanceResponses.success(new Voucher(String.valueOf(fileId), file.fileName(), file.contentType(), file.sizeBytes()));
}
@GetMapping("/{fileId}/content")
public ResponseEntity<byte[]> content(@PathVariable long fileId, @RequestParam long customerId) {
var file = scopedFile(customerId, fileId);
var response = files.download(token, customerId, customerId, fileId);
if (response == null || !response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "付款凭证读取失败,请稍后重试");
}
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
.header("X-Content-Type-Options", "nosniff")
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
.filename(file.fileName() == null ? "付款凭证" : file.fileName(), StandardCharsets.UTF_8).build().toString())
.contentType(MediaType.APPLICATION_OCTET_STREAM).body(response.getBody());
}
private FinanceFileClient.Metadata scopedFile(long customerId, long fileId) {
if (customerId <= 0 || fileId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "客户或付款凭证无效");
var response = files.metadata(token, customerId, customerId, fileId);
var file = response == null ? null : response.data();
if (file == null || !Long.valueOf(fileId).equals(file.fileId())
|| !Long.valueOf(customerId).equals(file.tenantId())
|| !"FINANCE_VOUCHER".equals(file.businessType())
|| !String.valueOf(customerId).equals(file.businessId())) {
throw new BizException(ResultCode.BAD_REQUEST, "付款凭证不存在或不属于当前客户");
}
return file;
}
}
@@ -115,6 +115,13 @@ public class BusinessFeeSourceEventService {
|| blank(value.feeItemName()) || !DIRECTIONS.contains(value.direction())
|| value.businessDate() == null || !"CNY".equals(value.currency())
|| value.totalAmount() == null || value.totalAmount().signum() <= 0
|| !Set.of("CNY", "USD").contains(value.originalCurrency())
|| value.originalAmount() == null || value.originalAmount().signum() <= 0
|| value.exchangeRate() == null || value.exchangeRate().signum() <= 0
|| blank(value.exchangeRateMonth())
|| (value.periodStart() == null) != (value.periodEnd() == null)
|| value.periodStart() != null && value.periodStart().isAfter(value.periodEnd())
|| (value.chargeableQuantity() == null) != blank(value.quantityUnit())
|| !"APPROVED".equals(value.reviewStatus()) || blank(value.sourceHash())) {
throw error("FIN-EVT-012", "跨区费用来源快照字段不完整");
}
@@ -148,7 +155,9 @@ public class BusinessFeeSourceEventService {
value.warehouseId(), value.feeItemCode(), value.feeItemName(), value.direction(), value.supplierId(),
value.supplierName(), value.supplierCategory(), value.settlementMode(), value.paidAt(), value.paymentMethod(),
value.paymentReferenceNo(), value.paymentVoucherFileIds(), value.businessDate(), value.currency(),
value.totalAmount(), value.reviewStatus(), "PUBLISHED", "ACTIVE", null);
value.totalAmount(), value.originalCurrency(), value.originalAmount(), value.exchangeRate(),
value.exchangeRateMonth(), value.chargeableQuantity(), value.quantityUnit(), value.unitPrice(),
value.periodStart(), value.periodEnd(), value.reviewStatus(), "PUBLISHED", "ACTIVE", null);
String expected = EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(objectMapper.valueToTree(unsigned)));
if (!expected.equals(value.sourceHash())) throw error("FIN-EVT-004", "费用来源快照哈希不一致");
}