diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/ar/ArBankAccountController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/ar/ArBankAccountController.java index 568545a2..afda6589 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/ar/ArBankAccountController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/ar/ArBankAccountController.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.bms.ar.domain.ArBankAccount; -import com.bms.ar.mapper.ArBankAccountMapper; +import com.bms.ar.service.IArBillService; import com.bms.common.core.controller.BaseController; import com.bms.common.core.domain.AjaxResult; @@ -20,14 +20,14 @@ import com.bms.common.core.domain.AjaxResult; public class ArBankAccountController extends BaseController { @Autowired - private ArBankAccountMapper arBankAccountMapper; + private IArBillService billService; /** * 启用账户列表;可按币种过滤,默认账户优先。 */ @GetMapping("/options") public AjaxResult options(@RequestParam(value = "currency", required = false) String currency) { - List list = arBankAccountMapper.selectEnabledList(currency); + List list = billService.selectEnabledBankAccounts(currency); return AjaxResult.success(list); } } diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/service/IArBillService.java b/bms-back/bms-system/src/main/java/com/bms/ar/service/IArBillService.java index cfcecb94..35e3b5b7 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/service/IArBillService.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/service/IArBillService.java @@ -33,6 +33,8 @@ public interface IArBillService ArBankAccount getDefaultBankAccount(String currency); + List selectEnabledBankAccounts(String currency); + List previewBill(ArBillGenerateQuery query); BigDecimal sumPreviewBill(ArBillGenerateQuery query); diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillServiceImpl.java index 1e30f324..63fe9b5a 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillServiceImpl.java @@ -168,10 +168,14 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public List selectArBillList(ArBillQuery query) { + long startedAt = System.nanoTime(); List bills = billMapper.selectArBillList(query); for (ArBill bill : bills) { bill.setBankAccountNo(maskAccount(bill.getBankAccountNo())); } + log.debug("应收账单列表查询完成,billNo={}, customerId={}, status={}, resultCount={}, costMs={}", + safeLogValue(query.getBillNo()), query.getCustomerId(), safeLogValue(query.getStatus()), + bills.size(), elapsedMillis(startedAt)); return bills; } @@ -180,12 +184,16 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public ArBill selectArBillById(Long arBillId) { + long startedAt = System.nanoTime(); ArBill bill = billMapper.selectArBillById(arBillId); if (bill == null) { throw new ServiceException("应收账单不存在"); } bill.setBankAccountNo(maskAccount(bill.getBankAccountNo())); bill.setExports(exportMapper.selectByBillId(arBillId)); + log.debug("应收账单详情查询完成,arBillId={}, status={}, exportCount={}, costMs={}", + arBillId, bill.getStatus(), bill.getExports() == null ? 0 : bill.getExports().size(), + elapsedMillis(startedAt)); return bill; } @@ -194,7 +202,11 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public List selectArBillDetails(Long arBillId) { - return detailMapper.selectByBillId(arBillId); + long startedAt = System.nanoTime(); + List details = detailMapper.selectByBillId(arBillId); + log.debug("应收账单明细查询完成,arBillId={}, resultCount={}, costMs={}", + arBillId, details.size(), elapsedMillis(startedAt)); + return details; } /** @@ -202,7 +214,11 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public List selectArBillDetailFees(Long arBillId, Long detailId) { - return detailFeeMapper.selectByBillIdAndDetailId(arBillId, detailId); + long startedAt = System.nanoTime(); + List fees = detailFeeMapper.selectByBillIdAndDetailId(arBillId, detailId); + log.debug("应收账单费用明细查询完成,arBillId={}, detailId={}, resultCount={}, costMs={}", + arBillId, detailId, fees.size(), elapsedMillis(startedAt)); + return fees; } /** @@ -210,26 +226,45 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public ArBankAccount getDefaultBankAccount(String currency) { + long startedAt = System.nanoTime(); ArBankAccount account = bankAccountMapper.selectDefaultByCurrency( StringUtils.isEmpty(currency) ? "CNY" : currency); if (account != null) { account.setAccountNo(maskAccount(account.getAccountNo())); } + log.debug("应收默认收款账户查询完成,currency={}, found={}, costMs={}", + safeLogValue(StringUtils.isEmpty(currency) ? "CNY" : currency), account != null, + elapsedMillis(startedAt)); return account; } + /** 查询启用的收款账户选项,账户号和账户名称不写入日志。 */ + @Override + public List selectEnabledBankAccounts(String currency) { + long startedAt = System.nanoTime(); + List accounts = bankAccountMapper.selectEnabledList(currency); + log.debug("应收收款账户选项查询完成,currency={}, resultCount={}, costMs={}", + safeLogValue(currency), accounts.size(), elapsedMillis(startedAt)); + return accounts; + } + /** * 分页预览可生成订单;多个订单号、运单号或转单号按 OR 条件精确匹配。 */ @Override public List previewBill(ArBillGenerateQuery query) { + long startedAt = System.nanoTime(); validatePreviewQuery(query); List orderIdentifiers = normalizeOrderIdentifiers(query.getOrderOrTrackingNo()); List excludeOrderIdentifiers = normalizeOrderIdentifiers(query.getExcludeOrderOrTrackingNo()); - return sourceBillMapper.selectPrcOrderBillByCustomerPeriod(query.getCustomerId(), + List result = sourceBillMapper.selectPrcOrderBillByCustomerPeriod(query.getCustomerId(), query.getCustomerCode(), orderIdentifiers, excludeOrderIdentifiers, query.getBillPeriodEnd(), query.getCurrency(), eligibleOrderBillStatuses(), ArIncomeBillFeeStatus.UNBILLED.getCode(), null); + log.debug("应收账单生成范围预览查询完成,customerId={}, billPeriodEnd={}, currency={}, resultCount={}, costMs={}", + query.getCustomerId(), query.getBillPeriodEnd(), safeLogValue(query.getCurrency()), + result.size(), elapsedMillis(startedAt)); + return result; } /** @@ -237,13 +272,18 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public BigDecimal sumPreviewBill(ArBillGenerateQuery query) { + long startedAt = System.nanoTime(); validatePreviewQuery(query); List orderIdentifiers = normalizeOrderIdentifiers(query.getOrderOrTrackingNo()); List excludeOrderIdentifiers = normalizeOrderIdentifiers(query.getExcludeOrderOrTrackingNo()); - return nvl(sourceBillMapper.sumPrcOrderBillIncomeByCustomerPeriod(query.getCustomerId(), + BigDecimal total = nvl(sourceBillMapper.sumPrcOrderBillIncomeByCustomerPeriod(query.getCustomerId(), query.getCustomerCode(), orderIdentifiers, excludeOrderIdentifiers, query.getBillPeriodEnd(), query.getCurrency(), eligibleOrderBillStatuses(), ArIncomeBillFeeStatus.UNBILLED.getCode())); + log.debug("应收账单生成范围金额汇总完成,customerId={}, billPeriodEnd={}, currency={}, costMs={}", + query.getCustomerId(), query.getBillPeriodEnd(), safeLogValue(query.getCurrency()), + elapsedMillis(startedAt)); + return total; } /** @@ -254,9 +294,13 @@ public class ArBillServiceImpl implements IArBillService { @Override @Transactional(rollbackFor = Exception.class) public ArBillGenerateTask generateBill(ArBillGenerateRequest request, String operator, String requestId) { + long startedAt = System.nanoTime(); validatePeriod(request); ArBillGenerateTask existed = generateTaskMapper.selectByRequestToken(requestId); if (existed != null) { + log.info("应收账单生成请求命中幂等记录,operator={}, requestId={}, taskId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), existed.getTaskId(), existed.getStatus(), + elapsedMillis(startedAt)); return existed; } @@ -341,6 +385,10 @@ public class ArBillServiceImpl implements IArBillService { task.setCreateBy(operator); generateTaskMapper.insert(task); eventPublisher.publishEvent(new ArBillGenerateTaskEvent(task.getTaskId())); + log.info("应收账单生成任务已创建,operator={}, requestId={}, customerId={}, billPeriodEnd={}, selectionMode={}, selectedCount={}, excludedCount={}, taskId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), request.getCustomerId(), request.getBillPeriodEnd(), + safeLogValue(selectionMode), sourceBillIds.size(), excludedSourceBillIds.size(), + task.getTaskId(), task.getStatus(), elapsedMillis(startedAt)); return task; } @@ -349,10 +397,13 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public ArBillGenerateTask selectGenerateTask(Long taskId) { + long startedAt = System.nanoTime(); ArBillGenerateTask task = generateTaskMapper.selectById(taskId); if (task == null) { throw new ServiceException("账单生成任务不存在"); } + log.debug("应收账单生成任务状态查询,taskId={}, status={}, processedCount={}, totalCount={}, costMs={}", + taskId, task.getStatus(), task.getProcessedCount(), task.getTotalCount(), elapsedMillis(startedAt)); return task; } @@ -361,7 +412,11 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public List selectActiveGenerateTasks(String operator) { - return generateTaskMapper.selectActiveByCreateBy(operator); + long startedAt = System.nanoTime(); + List tasks = generateTaskMapper.selectActiveByCreateBy(operator); + log.debug("当前用户应收账单生成任务查询,operator={}, taskCount={}, costMs={}", + safeLogValue(operator), tasks.size(), elapsedMillis(startedAt)); + return tasks; } /** @@ -370,6 +425,7 @@ public class ArBillServiceImpl implements IArBillService { @Override @Transactional(rollbackFor = Exception.class) public ArBillGenerateTask cancelGenerateTask(Long taskId, String operator, String requestId) { + long startedAt = System.nanoTime(); ArBillGenerateTask task = selectGenerateTask(taskId); if (!operator.equals(task.getCreateBy())) { throw new ServiceException("只能取消自己创建的账单生成任务"); @@ -377,6 +433,8 @@ public class ArBillServiceImpl implements IArBillService { if (ArBillGenerateTaskStatus.SUCCESS.equals(task.getStatus()) || ArBillGenerateTaskStatus.FAILED.equals(task.getStatus()) || ArBillGenerateTaskStatus.CANCELED.equals(task.getStatus())) { + log.info("应收账单生成任务取消请求已处理,operator={}, requestId={}, taskId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), taskId, task.getStatus(), elapsedMillis(startedAt)); return task; } if (ArBillGenerateTaskStatus.WAITING.equals(task.getStatus())) { @@ -384,7 +442,10 @@ public class ArBillServiceImpl implements IArBillService { } else { generateTaskMapper.markCancelRequested(taskId); } - return selectGenerateTask(taskId); + ArBillGenerateTask result = selectGenerateTask(taskId); + log.info("应收账单生成任务取消请求已处理,operator={}, requestId={}, taskId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), taskId, result.getStatus(), elapsedMillis(startedAt)); + return result; } /** @@ -393,6 +454,7 @@ public class ArBillServiceImpl implements IArBillService { @Override @Transactional(rollbackFor = Exception.class) public ArBillGenerateTask retryGenerateTask(Long taskId, String operator, String requestId) { + long startedAt = System.nanoTime(); ArBillGenerateTask task = selectGenerateTask(taskId); if (!operator.equals(task.getCreateBy())) { throw new ServiceException("只能重新生成自己创建的账单任务"); @@ -402,7 +464,11 @@ public class ArBillServiceImpl implements IArBillService { } ArBillGenerateRequest request = JSON.parseObject(task.getRequestPayload(), ArBillGenerateRequest.class); request.setRequestId(requestId); - return generateBill(request, operator, requestId); + ArBillGenerateTask retried = generateBill(request, operator, requestId); + log.info("应收账单生成任务重试请求已处理,operator={}, requestId={}, sourceTaskId={}, newTaskId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), taskId, retried.getTaskId(), retried.getStatus(), + elapsedMillis(startedAt)); + return retried; } /** @@ -757,6 +823,7 @@ public class ArBillServiceImpl implements IArBillService { @Override @Transactional(rollbackFor = Exception.class) public ArBill confirmBill(Long arBillId, ArBillActionRequest request, String operator, String requestId) { + long startedAt = System.nanoTime(); ArBill bill = billMapper.selectArBillByIdForUpdate(arBillId); if (bill == null) { throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.CONFIRM, @@ -787,7 +854,10 @@ public class ArBillServiceImpl implements IArBillService { // 应收账单只负责形成应收,不参与客户余额或授信额度变更。 recordSuccess(bill, ArLogObjectType.BILL, ArLogOperationType.CONFIRM, "确认应收账单", ArBillStatus.DRAFT.getCode(), ArBillStatus.CONFIRMED.getCode(), requestId, operator); - return selectArBillById(arBillId); + ArBill result = selectArBillById(arBillId); + log.info("应收账单确认处理完成,operator={}, requestId={}, arBillId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), arBillId, result.getStatus(), elapsedMillis(startedAt)); + return result; } /** @@ -796,6 +866,7 @@ public class ArBillServiceImpl implements IArBillService { @Override @Transactional(rollbackFor = Exception.class) public ArBill voidBill(Long arBillId, ArBillActionRequest request, String operator, String requestId) { + long startedAt = System.nanoTime(); String remark = request == null ? null : request.getRemark(); if (StringUtils.isEmpty(remark)) { throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID, @@ -826,7 +897,10 @@ public class ArBillServiceImpl implements IArBillService { sourceBillMapper.refreshArBillStatusByArBillId(arBillId); recordSuccess(bill, ArLogObjectType.BILL, ArLogOperationType.VOID, "作废应收账单:" + remark, bill.getStatus(), ArBillStatus.VOID.getCode(), requestId, operator); - return selectArBillById(arBillId); + ArBill result = selectArBillById(arBillId); + log.info("应收账单作废处理完成,operator={}, requestId={}, arBillId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), arBillId, result.getStatus(), elapsedMillis(startedAt)); + return result; } /** @@ -835,11 +909,15 @@ public class ArBillServiceImpl implements IArBillService { @Override @Transactional(rollbackFor = Exception.class) public ArBillExport exportBill(Long arBillId, String operator, String requestId) { + long startedAt = System.nanoTime(); ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, ArLogObjectType.EXPORT.getCode(), ArLogResult.SUCCESS.getCode()); if (previous != null && isNumber(previous.getObjectId())) { ArBillExport previousExport = exportMapper.selectById(Long.valueOf(previous.getObjectId())); if (previousExport != null) { + log.info("应收账单导出请求命中幂等记录,operator={}, requestId={}, arBillId={}, exportId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), arBillId, previousExport.getExportId(), + previousExport.getExportStatus(), elapsedMillis(startedAt)); return previousExport; } } @@ -859,6 +937,9 @@ public class ArBillServiceImpl implements IArBillService { } ArBillExport processing = exportMapper.selectProcessingByBillId(arBillId); if (processing != null) { + log.info("应收账单导出任务已存在,operator={}, requestId={}, arBillId={}, exportId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), arBillId, processing.getExportId(), + processing.getExportStatus(), elapsedMillis(startedAt)); return processing; } ArExportTemplate template = exportTemplateMapper.selectDefaultEnabled(EXPORT_TEMPLATE_BUSINESS_TYPE, @@ -887,6 +968,9 @@ public class ArBillServiceImpl implements IArBillService { recordSuccess(bill, ArLogObjectType.EXPORT, ArLogOperationType.EXPORT, "已提交账单导出任务", bill.getStatus(), bill.getStatus(), requestId, operator, export.getExportId()); eventPublisher.publishEvent(new ArBillExportTaskEvent(export.getExportId())); + log.info("应收账单导出任务已创建,operator={}, requestId={}, arBillId={}, exportId={}, status={}, costMs={}", + safeLogValue(operator), safeLogValue(requestId), arBillId, export.getExportId(), + export.getExportStatus(), elapsedMillis(startedAt)); return export; } @@ -895,10 +979,13 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public ArBillExport selectExport(Long exportId) { + long startedAt = System.nanoTime(); ArBillExport export = exportMapper.selectById(exportId); if (export == null) { throw new ServiceException("导出任务不存在"); } + log.debug("应收账单导出任务状态查询,exportId={}, status={}, costMs={}", + exportId, export.getExportStatus(), elapsedMillis(startedAt)); return export; } @@ -909,9 +996,13 @@ public class ArBillServiceImpl implements IArBillService { public void processExportTask(Long exportId) { ArBillExport export = exportMapper.selectById(exportId); if (export == null || !ArExportStatus.PROCESSING.getCode().equals(export.getExportStatus())) { + log.debug("应收账单导出任务跳过,exportId={}, status={}", + exportId, export == null ? null : export.getExportStatus()); return; } + long startedAt = System.nanoTime(); + log.info("开始执行应收账单导出任务,exportId={}, arBillId={}", exportId, export.getArBillId()); ArBill bill = null; try { bill = billMapper.selectArBillById(export.getArBillId()); @@ -940,11 +1031,20 @@ public class ArBillServiceImpl implements IArBillService { export.setStorageKey(FileUploadUtils.getPathFileName(downloadDir, relative)); export.setFileSize(Files.size(target)); export.setFileHash(sha256(target)); - if (exportMapper.markSuccess(export) == 0) { + int updated = exportMapper.markSuccess(export); + if (updated == 0) { Files.deleteIfExists(target); + log.warn("应收账单导出文件已生成但任务状态未更新,exportId={}, arBillId={}", + exportId, export.getArBillId()); + } else { + log.info("应收账单导出任务执行完成,exportId={}, arBillId={}, rowCount={}, fileSize={}, costMs={}", + exportId, export.getArBillId(), export.getRowCount(), export.getFileSize(), + elapsedMillis(startedAt)); } } catch (Exception e) { String message = exportFailureMessage(e); + log.error("应收账单导出任务执行失败,exportId={}, arBillId={}, message={}, costMs={}", + exportId, export.getArBillId(), safeLogValue(message), elapsedMillis(startedAt), e); if (exportMapper.markFailed(exportId, message) > 0) { logService.recordFailure(export.getArBillId(), ArLogObjectType.EXPORT, String.valueOf(exportId), ArLogOperationType.EXPORT, bill == null ? null : bill.getStatus(), message, export.getExportBy(), null); @@ -964,11 +1064,16 @@ public class ArBillServiceImpl implements IArBillService { @Override public List selectExports(Long arBillId) { - return exportMapper.selectByBillId(arBillId); + long startedAt = System.nanoTime(); + List exports = exportMapper.selectByBillId(arBillId); + log.debug("应收账单导出记录查询,arBillId={}, exportCount={}, costMs={}", + arBillId, exports.size(), elapsedMillis(startedAt)); + return exports; } @Override public void downloadExport(Long exportId, HttpServletResponse response) throws Exception { + long startedAt = System.nanoTime(); ArBillExport export = exportMapper.selectById(exportId); if (export == null || !ArExportStatus.SUCCESS.getCode().equals(export.getExportStatus()) || StringUtils.isEmpty(export.getStorageKey())) { throw new ServiceException("导出文件不存在或尚未生成成功"); @@ -980,11 +1085,18 @@ public class ArBillServiceImpl implements IArBillService { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); FileUtils.setAttachmentResponseHeader(response, export.getFileName()); FileUtils.writeBytes(BmsConfig.getProfile() + FileUtils.stripPrefix(resource), response.getOutputStream()); + log.debug("应收账单导出文件下载完成,exportId={}, fileSize={}, costMs={}", + exportId, export.getFileSize(), elapsedMillis(startedAt)); } @Override public List selectLogList(ArBillLog query) { - return logService.selectLogList(query); + long startedAt = System.nanoTime(); + List logs = logService.selectLogList(query); + log.debug("应收账单操作日志查询完成,arBillId={}, objectType={}, operationType={}, resultCount={}, costMs={}", + query.getArBillId(), safeLogValue(query.getObjectType()), safeLogValue(query.getOperationType()), + logs.size(), elapsedMillis(startedAt)); + return logs; } private String exportFailureMessage(Exception e) { @@ -1738,4 +1850,17 @@ public class ArBillServiceImpl implements IArBillService { } return new ArrayList<>(uniqueIdentifiers); } + + private long elapsedMillis(long startedAt) { + return (System.nanoTime() - startedAt) / 1_000_000L; + } + + /** 防止请求参数中的换行控制字符伪造多行日志,并限制日志字段长度。 */ + private String safeLogValue(String value) { + if (StringUtils.isEmpty(value)) { + return value; + } + String sanitized = value.replaceAll("[\\p{Cntrl}]", "_"); + return sanitized.length() > 128 ? sanitized.substring(0, 128) : sanitized; + } }