From c122c353769d2e437c938d6143797606d1808902 Mon Sep 17 00:00:00 2001 From: Timi Date: Wed, 9 Sep 2026 13:04:16 +0800 Subject: [PATCH 1/3] refactor LoggerService.excute --- .idea/data_source_mapping.xml | 6 - .../imyeyu/api/config/ThreadPoolConfig.java | 18 +++ .../common/controller/LoggerController.java | 7 +- .../modules/common/vo/ClientLogRequest.java | 18 +++ .../GaoCustomerPackageServiceImplement.java | 11 +- .../GaoOrderFulfillmentServiceImplement.java | 23 ++-- .../GaoOrderPaymentServiceImplement.java | 24 ++-- .../GaoPackageConfigServiceImplement.java | 27 ++-- .../GaoProductCategoryServiceImplement.java | 72 +++++----- .../implement/GaoProductServiceImplement.java | 85 ++++++------ .../GaoProductStockServiceImplement.java | 37 ++--- .../GaoSalesOrderServiceImplement.java | 26 ++-- .../gao/vo/GaoProductStockChangeRequest.java | 3 + .../modules/system/audit/AuditOperation.java | 66 +++++++++ .../system/audit/AuditPayloadSerializer.java | 128 ++++++++++++++++++ .../modules/system/audit/AuditRecorder.java | 17 +++ .../modules/system/audit/OperationAudit.java | 22 +++ .../system/audit/OperationAuditAspect.java | 90 ++++++++++++ .../modules/system/service/LoggerService.java | 47 ------- .../implement/LoggerServiceImplement.java | 56 +++----- 20 files changed, 533 insertions(+), 250 deletions(-) delete mode 100644 .idea/data_source_mapping.xml create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/ClientLogRequest.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/audit/AuditOperation.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/audit/AuditPayloadSerializer.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/audit/AuditRecorder.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/audit/OperationAudit.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/audit/OperationAuditAspect.java diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml deleted file mode 100644 index a381639..0000000 --- a/.idea/data_source_mapping.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/main/java/com/imyeyu/api/config/ThreadPoolConfig.java b/src/main/java/com/imyeyu/api/config/ThreadPoolConfig.java index 8bdaf41..3c5a97f 100644 --- a/src/main/java/com/imyeyu/api/config/ThreadPoolConfig.java +++ b/src/main/java/com/imyeyu/api/config/ThreadPoolConfig.java @@ -32,4 +32,22 @@ public class ThreadPoolConfig { executor.initialize(); return executor; } + + /// 创建操作审计线程池 + /// + /// @return 操作审计线程池 + @Bean(name = "auditTaskExecutor") + public ThreadPoolTaskExecutor auditTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(4); + executor.setMaxPoolSize(8); + executor.setQueueCapacity(2000); + executor.setKeepAliveSeconds(60); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(60); + executor.setThreadNamePrefix("audit-task-executor-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } } diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java b/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java index 4fc539f..aaa6e37 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java @@ -1,5 +1,6 @@ package com.imyeyu.api.modules.common.controller; +import com.imyeyu.api.modules.common.vo.ClientLogRequest; import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.spring.annotation.AOPLog; @@ -29,7 +30,9 @@ public class LoggerController { @AOPLog @RequiredToken @PostMapping("/create") - public void create(@Valid @RequestBody Logger logger) { + public void create(@Valid @RequestBody ClientLogRequest request) { + Logger logger = Logger.info(Logger.Module.COMMON, "CLIENT_LOG"); + logger.setContent(request.getContent()); service.create(logger); } -} \ No newline at end of file +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/ClientLogRequest.java b/src/main/java/com/imyeyu/api/modules/common/vo/ClientLogRequest.java new file mode 100644 index 0000000..e6b5750 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/ClientLogRequest.java @@ -0,0 +1,18 @@ +package com.imyeyu.api.modules.common.vo; + +import jakarta.validation.constraints.Size; +import lombok.Data; + +/// 客户端诊断日志请求 +/// +/// 客户端日志不是操作审计,操作编码、模块、级别、用户和 IP 均由服务端决定 +/// +/// @author Codex +/// @since 2026-09-09 +@Data +public class ClientLogRequest { + + /// 日志内容 + @Size(max = 32768, message = "日志内容不能超过 32 KB") + private String content; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java index 6e75900..d695043 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java @@ -24,8 +24,8 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePurchaseItem; import com.imyeyu.api.modules.gao.vo.GaoPackageOrderCreateData; import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.system.audit.AuditOperation; +import com.imyeyu.api.modules.system.audit.OperationAudit; import com.imyeyu.api.modules.user.service.PermissionChecker; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.java.TimiJava; @@ -69,7 +69,6 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService mapper() { @@ -128,10 +127,11 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService purchaseInternal(request)); + return purchaseInternal(request); } private GaoCustomerPackage purchaseInternal(GaoCustomerPackagePurchaseRequest request) { @@ -206,10 +206,11 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService cancelInternal(request)); + return cancelInternal(request); } private GaoCustomerPackage cancelInternal(GaoCustomerPackageCancelRequest request) { diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java index ce884f8..effca17 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java @@ -31,8 +31,8 @@ import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentPrepareStockRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.system.audit.AuditOperation; +import com.imyeyu.api.modules.system.audit.OperationAudit; import com.imyeyu.api.modules.user.service.RoleChecker; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.java.TimiJava; @@ -79,7 +79,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService mapper() { @@ -147,10 +146,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService createInternal(request)); + return createInternal(request); } private GaoOrderFulfillment createInternal(GaoOrderFulfillmentCreateRequest request) { @@ -183,10 +183,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService prepareStockInternal(request)); + return prepareStockInternal(request); } private GaoOrderFulfillment prepareStockInternal(GaoOrderFulfillmentPrepareStockRequest request) { @@ -217,10 +218,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService verifyInternal(request)); + return verifyInternal(request); } private GaoOrderFulfillment verifyInternal(GaoOrderFulfillmentActionRequest request) { @@ -254,16 +256,18 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService releaseInternal(request, GaoOrderFulfillment.Status.REJECTED)); + return releaseInternal(request, GaoOrderFulfillment.Status.REJECTED); } + @OperationAudit(AuditOperation.GAO_ORDER_FULFILLMENT_CANCEL) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoOrderFulfillment cancel(GaoOrderFulfillmentActionRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_CANCEL", request, () -> cancelInternal(request)); + return cancelInternal(request); } private GaoOrderFulfillment cancelInternal(GaoOrderFulfillmentActionRequest request) { @@ -306,10 +310,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService revokeInternal(request)); + return revokeInternal(request); } private GaoOrderFulfillment revokeInternal(GaoOrderFulfillmentRevokeRequest request) { diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderPaymentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderPaymentServiceImplement.java index 6ef23e3..4629573 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderPaymentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderPaymentServiceImplement.java @@ -16,8 +16,8 @@ import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentNoRefundRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentReceiveRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRefundRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.system.audit.AuditOperation; +import com.imyeyu.api.modules.system.audit.OperationAudit; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.PermissionChecker; import com.imyeyu.java.TimiJava; @@ -44,12 +44,12 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService { private final AttachmentService attachmentService; private final UserLoginService userLoginService; private final PermissionChecker permissionChecker; - private final LoggerService loggerService; + @OperationAudit(AuditOperation.GAO_SALES_ORDER_PAYMENT_RECEIVE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoFundRecord receive(GaoOrderPaymentReceiveRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_PAYMENT_RECEIVE", request, () -> receiveInternal(request)); + return receiveInternal(request); } private GaoFundRecord receiveInternal(GaoOrderPaymentReceiveRequest request) { @@ -80,11 +80,11 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService { return record; } + @OperationAudit(AuditOperation.GAO_SALES_ORDER_PAYMENT_VERIFY) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoFundRecord verify(String fundRecordId) { - return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_PAYMENT_VERIFY", fundRecordId, () -> { - GaoFundRecord record = lockOrderRecord(fundRecordId); + GaoFundRecord record = lockOrderRecord(fundRecordId); TimiException.requiredTrue(record.getOperationType() == GaoFundRecord.OperationType.RECEIVE && record.getStatus() == GaoFundRecord.Status.PENDING, "收款记录不是待核销状态"); GaoSalesOrder order = orderService.getForUpdate(record.getBizId()); Long paidAtBefore = order.getPaidAt(); @@ -102,7 +102,6 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService { pointTriggerService.onSalesOrderPaid(order); } return record; - }); } @Transactional(TimiServerDBConfig.ROLLBACKER) @@ -140,10 +139,11 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService { return record; } + @OperationAudit(AuditOperation.GAO_SALES_ORDER_REFUND) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoFundRecord refund(GaoOrderPaymentRefundRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_REFUND", request, () -> refundInternal(request)); + return refundInternal(request); } private GaoFundRecord refundInternal(GaoOrderPaymentRefundRequest request) { @@ -177,11 +177,11 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService { return record; } + @OperationAudit(AuditOperation.GAO_SALES_ORDER_CONFIRM_NO_REFUND) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoFundRecord confirmNoRefund(GaoOrderPaymentNoRefundRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_CONFIRM_NO_REFUND", request, () -> { - TimiException.required(request, "not found no refund request"); + TimiException.required(request, "not found no refund request"); GaoSalesOrder order = orderService.getForUpdate(request.getSalesOrderId()); String requestNo = requiredText(request.getRequestNo(), "not found requestNo"); String reason = requiredText(request.getReason(), "确认无需退款原因不能为空"); @@ -199,13 +199,13 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService { order.setRefundStatus(GaoSalesOrder.RefundStatus.REFUNDED); orderService.updatePayment(order); return record; - }); } + @OperationAudit(AuditOperation.GAO_SALES_ORDER_FUND_REVOKE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoFundRecord revoke(GaoOrderPaymentRevokeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_FUND_REVOKE", request, () -> revokeInternal(request)); + return revokeInternal(request); } private GaoFundRecord revokeInternal(GaoOrderPaymentRevokeRequest request) { diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoPackageConfigServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoPackageConfigServiceImplement.java index e36c6f9..d964acd 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoPackageConfigServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoPackageConfigServiceImplement.java @@ -6,8 +6,8 @@ import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem; import com.imyeyu.api.modules.gao.entity.GaoProduct; import com.imyeyu.api.modules.gao.entity.GaoProductSku; import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.system.audit.AuditOperation; +import com.imyeyu.api.modules.system.audit.OperationAudit; import com.imyeyu.api.modules.gao.mapper.GaoPackageConfigItemMapper; import com.imyeyu.api.modules.gao.mapper.GaoPackageConfigMapper; import com.imyeyu.api.modules.gao.service.GaoPackageConfigService; @@ -44,7 +44,6 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService mapper() { @@ -90,13 +89,11 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService { - createInternal(config); - return config; - }); + createInternal(config); } private void createInternal(GaoPackageConfig config) { @@ -109,13 +106,11 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService { - updateInternal(request); - return request; - }); + updateInternal(request); } private void updateInternal(GaoPackageConfig request) { @@ -141,15 +136,13 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService { - GaoPackageConfig config = get(id); - TimiException.required(config, "not found package config"); - super.delete(id); - return id; - }); + GaoPackageConfig config = get(id); + TimiException.required(config, "not found package config"); + super.delete(id); } /// 套餐配置只允许逻辑删除 diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductCategoryServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductCategoryServiceImplement.java index 63ae599..bac88c4 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductCategoryServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductCategoryServiceImplement.java @@ -6,8 +6,8 @@ import com.imyeyu.api.modules.gao.mapper.GaoProductCategoryMapper; import com.imyeyu.api.modules.gao.service.GaoProductCategoryService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.vo.GaoProductCategoryPage; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.system.audit.AuditOperation; +import com.imyeyu.api.modules.system.audit.OperationAudit; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiException; @@ -37,7 +37,6 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService mapper() { @@ -59,6 +58,7 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService idList) { @@ -66,58 +66,50 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService distinctIdList = new ArrayList<>(new LinkedHashSet<>(idList)); - loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_SORT", distinctIdList, () -> { - Map categoryMap = mapper.selectByIdList(new LinkedHashSet<>(distinctIdList)).stream() - .collect(Collectors.toMap(GaoProductCategory::getId, Function.identity())); - TimiException.requiredTrue(categoryMap.size() == distinctIdList.size(), "产品分类列表包含不存在的分类"); - for (GaoProductCategory category : categoryMap.values()) { - storeScopeService.checkStoreId(category.getStoreId()); - } - mapper.updateSort(distinctIdList, Time.now()); - }); + Map categoryMap = mapper.selectByIdList(new LinkedHashSet<>(distinctIdList)).stream() + .collect(Collectors.toMap(GaoProductCategory::getId, Function.identity())); + TimiException.requiredTrue(categoryMap.size() == distinctIdList.size(), "产品分类列表包含不存在的分类"); + for (GaoProductCategory category : categoryMap.values()) { + storeScopeService.checkStoreId(category.getStoreId()); + } + mapper.updateSort(distinctIdList, Time.now()); } + @OperationAudit(AuditOperation.GAO_PRODUCT_CATEGORY_CREATE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void create(GaoProductCategory category) { - loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_CREATE", category, () -> { - prepare(category, null); - super.create(category); - return category; - }); + prepare(category, null); + super.create(category); } + @OperationAudit(AuditOperation.GAO_PRODUCT_CATEGORY_UPDATE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void update(GaoProductCategory request) { - loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_UPDATE", request, () -> { - TimiException.required(request, "not found category"); - TimiException.required(request.getId(), "not found category.id"); - GaoProductCategory category = get(request.getId()); - TimiException.required(category, "not found category"); - storeScopeService.checkStoreId(category.getStoreId()); - category.setName(request.getName()); - category.setSort(request.getSort()); - category.setStatus(request.getStatus()); - prepare(category, category.getId()); - super.update(category); - return category; - }); + TimiException.required(request, "not found category"); + TimiException.required(request.getId(), "not found category.id"); + GaoProductCategory category = get(request.getId()); + TimiException.required(category, "not found category"); + storeScopeService.checkStoreId(category.getStoreId()); + category.setName(request.getName()); + category.setSort(request.getSort()); + category.setStatus(request.getStatus()); + prepare(category, category.getId()); + super.update(category); } + @OperationAudit(AuditOperation.GAO_PRODUCT_CATEGORY_DISABLE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void disable(String id) { - loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_DISABLE", id, () -> { - GaoProductCategory category = get(id); - TimiException.required(category, "not found category"); - storeScopeService.checkStoreId(category.getStoreId()); - category.setStatus(GaoProductCategory.Status.INACTIVE); - category.setUpdatedAt(Time.now()); - category.setSort(TimiJava.defaultIfNull(category.getSort(), 0)); - super.update(category); - return category; - }); + GaoProductCategory category = get(id); + TimiException.required(category, "not found category"); + storeScopeService.checkStoreId(category.getStoreId()); + category.setStatus(GaoProductCategory.Status.INACTIVE); + category.setUpdatedAt(Time.now()); + category.setSort(TimiJava.defaultIfNull(category.getSort(), 0)); + super.update(category); } @Override diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductServiceImplement.java index 75dd959..806dba3 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductServiceImplement.java @@ -14,8 +14,8 @@ import com.imyeyu.api.modules.gao.service.GaoProductService; import com.imyeyu.api.modules.gao.service.GaoProductSkuService; import com.imyeyu.api.modules.gao.service.GaoProductSpecService; import com.imyeyu.api.modules.gao.vo.GaoProductPage; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.system.audit.AuditOperation; +import com.imyeyu.api.modules.system.audit.OperationAudit; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiCode; @@ -52,7 +52,6 @@ public class GaoProductServiceImplement extends AbstractEntityService skuServiceProvider; private final ObjectProvider specServiceProvider; private final UserLoginService userLoginService; - private final LoggerService loggerService; @Override protected BaseMapper mapper() { @@ -100,62 +99,56 @@ public class GaoProductServiceImplement extends AbstractEntityService !product.getSkuList().isEmpty()).toList(); } + @OperationAudit(AuditOperation.GAO_PRODUCT_CREATE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void create(GaoProduct product) { - loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CREATE", product, () -> { - prepare(product, null); - super.create(product); - Map specValueIdMap = saveSpecs(product, product.getSpecList()); - normalizeSkuSpecValueIds(product.getSkuList(), specValueIdMap); - saveSkus(product, product.getSkuList()); - attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), product.getAttachmentList()); - return product; - }); + prepare(product, null); + super.create(product); + Map specValueIdMap = saveSpecs(product, product.getSpecList()); + normalizeSkuSpecValueIds(product.getSkuList(), specValueIdMap); + saveSkus(product, product.getSkuList()); + attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), product.getAttachmentList()); } + @OperationAudit(AuditOperation.GAO_PRODUCT_UPDATE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void update(GaoProduct request) { - loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_UPDATE", request, () -> { - TimiException.required(request, "not found product"); - TimiException.required(request.getId(), "not found product.id"); - GaoProduct product = get(request.getId()); - TimiException.required(product, "not found product"); - product.setCategoryId(request.getCategoryId()); - product.setCode(TimiJava.defaultIfNull(request.getCode(), product.getCode())); - product.setName(TimiJava.defaultIfNull(request.getName(), product.getName())); - product.setProductType(TimiJava.defaultIfNull(request.getProductType(), product.getProductType())); - product.setStockEnabled(TimiJava.defaultIfNull(request.getStockEnabled(), product.getStockEnabled())); - product.setStatus(TimiJava.defaultIfNull(request.getStatus(), product.getStatus())); - product.setSort(TimiJava.defaultIfNull(request.getSort(), product.getSort())); - product.setRemark(request.getRemark()); - product.setSkuList(request.getSkuList() == null ? skuServiceProvider.getObject().listByProductId(product.getId()) : request.getSkuList()); - prepare(product, product.getId()); - super.update(product); - if (request.getSkuList() != null) { - saveSkus(product, request.getSkuList()); - } - if (request.getAttachmentList() != null) { - attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), request.getAttachmentList()); - } - return product; - }); + TimiException.required(request, "not found product"); + TimiException.required(request.getId(), "not found product.id"); + GaoProduct product = get(request.getId()); + TimiException.required(product, "not found product"); + product.setCategoryId(request.getCategoryId()); + product.setCode(TimiJava.defaultIfNull(request.getCode(), product.getCode())); + product.setName(TimiJava.defaultIfNull(request.getName(), product.getName())); + product.setProductType(TimiJava.defaultIfNull(request.getProductType(), product.getProductType())); + product.setStockEnabled(TimiJava.defaultIfNull(request.getStockEnabled(), product.getStockEnabled())); + product.setStatus(TimiJava.defaultIfNull(request.getStatus(), product.getStatus())); + product.setSort(TimiJava.defaultIfNull(request.getSort(), product.getSort())); + product.setRemark(request.getRemark()); + product.setSkuList(request.getSkuList() == null ? skuServiceProvider.getObject().listByProductId(product.getId()) : request.getSkuList()); + prepare(product, product.getId()); + super.update(product); + if (request.getSkuList() != null) { + saveSkus(product, request.getSkuList()); + } + if (request.getAttachmentList() != null) { + attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), request.getAttachmentList()); + } } + @OperationAudit(AuditOperation.GAO_PRODUCT_DISABLE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void disable(String id) { - loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_DISABLE", id, () -> { - GaoProduct product = get(id); - TimiException.required(product, "not found product"); - TimiException.requiredTrue(skuServiceProvider.getObject().listByProductId(id).stream().noneMatch(sku -> sku.getStatus() == com.imyeyu.api.modules.gao.entity.GaoProductSku.Status.ACTIVE), "请先停用产品的全部 SKU"); - product.setStatus(GaoProduct.Status.INACTIVE); - product.setUpdatedBy(userLoginService.getRequireLoginUserId()); - product.setUpdatedAt(Time.now()); - super.update(product); - return product; - }); + GaoProduct product = get(id); + TimiException.required(product, "not found product"); + TimiException.requiredTrue(skuServiceProvider.getObject().listByProductId(id).stream().noneMatch(sku -> sku.getStatus() == com.imyeyu.api.modules.gao.entity.GaoProductSku.Status.ACTIVE), "请先停用产品的全部 SKU"); + product.setStatus(GaoProduct.Status.INACTIVE); + product.setUpdatedBy(userLoginService.getRequireLoginUserId()); + product.setUpdatedAt(Time.now()); + super.update(product); } @Override diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java index b2ff198..f2d0dae 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java @@ -4,24 +4,24 @@ import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.gao.entity.GaoProduct; -import com.imyeyu.api.modules.gao.entity.GaoProductStock; import com.imyeyu.api.modules.gao.entity.GaoProductSku; import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit; +import com.imyeyu.api.modules.gao.entity.GaoProductStock; import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord; import com.imyeyu.api.modules.gao.mapper.GaoProductStockMapper; import com.imyeyu.api.modules.gao.service.GaoProductService; -import com.imyeyu.api.modules.gao.service.GaoProductStockService; import com.imyeyu.api.modules.gao.service.GaoProductSkuService; import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService; +import com.imyeyu.api.modules.gao.service.GaoProductStockService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; +import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest; import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoProductStockPage; import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView; -import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.system.audit.AuditOperation; +import com.imyeyu.api.modules.system.audit.OperationAudit; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiCode; @@ -59,7 +59,6 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { private final GaoProductSkuService skuService; private final GaoStoreScopeService storeScopeService; private final UserLoginService userLoginService; - private final LoggerService loggerService; @Override public PageResult pageByQuery(GaoProductStockPage page) { @@ -112,40 +111,46 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { return stock; } + @OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_INITIAL_IN) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock initialIn(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_INITIAL_IN", request, () -> change(request, GaoProductStockRecord.OperationType.INITIAL_IN)); + return change(request, GaoProductStockRecord.OperationType.INITIAL_IN); } + @OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_MANUAL_IN) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock manualIn(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_IN", request, () -> change(request, GaoProductStockRecord.OperationType.MANUAL_IN)); + return change(request, GaoProductStockRecord.OperationType.MANUAL_IN); } + @OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_MANUAL_OUT) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock manualOut(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_OUT", request, () -> change(request, GaoProductStockRecord.OperationType.MANUAL_OUT)); + return change(request, GaoProductStockRecord.OperationType.MANUAL_OUT); } + @OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_MANUAL_REVOKE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock manualRevoke(GaoProductStockManualRevokeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_REVOKE", request, () -> manualRevokeInternal(request)); + return manualRevokeInternal(request); } + @OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_STOCKTAKE_GAIN) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock stocktakeGain(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_STOCKTAKE_GAIN", request, () -> change(request, GaoProductStockRecord.OperationType.STOCKTAKE_GAIN)); + return change(request, GaoProductStockRecord.OperationType.STOCKTAKE_GAIN); } + @OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_STOCKTAKE_LOSS) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock stocktakeLoss(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_STOCKTAKE_LOSS", request, () -> change(request, GaoProductStockRecord.OperationType.STOCKTAKE_LOSS)); + return change(request, GaoProductStockRecord.OperationType.STOCKTAKE_LOSS); } @Override @@ -216,7 +221,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { GaoStockChangeItem item = GaoStockChangeItem.of(product, sku, unit, quantity, baseQuantity); item.setSkuSpecSnapshot(skuService.getSkuSpecSnapshot(sku)); List result = executeInternal(storeId, sku.getId(), List.of(item), requestNo, - userLoginService.getRequireLoginUserId(), operationType, request.getRemark(), request.getAttachmentList()); + userLoginService.getRequireLoginUserId(), operationType, request.getOccurredAt(), request.getRemark(), request.getAttachmentList()); return result.getFirst(); } @@ -295,11 +300,11 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { private List executeInternal(String storeId, String bizId, List sourceList, String requestNo, String operatorUserId, GaoProductStockRecord.OperationType operationType, String remark) { - return executeInternal(storeId, bizId, sourceList, requestNo, operatorUserId, operationType, remark, null); + return executeInternal(storeId, bizId, sourceList, requestNo, operatorUserId, operationType, null, remark, null); } private List executeInternal(String storeId, String bizId, List sourceList, String requestNo, - String operatorUserId, GaoProductStockRecord.OperationType operationType, String remark, List attachmentList) { + String operatorUserId, GaoProductStockRecord.OperationType operationType, Long occurredAt, String remark, List attachmentList) { TimiException.required(storeId, "not found storeId"); TimiException.required(bizId, "not found stock bizId"); TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "库存变更至少需要一个产品"); @@ -360,7 +365,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { record.setSourceRecordId(item.getSourceRecordId()); record.setRequestNo(normalizedRequestNo); record.setOperatorUserId(operatorUserId); - record.setOccurredAt(Time.now()); + record.setOccurredAt(TimiJava.defaultIfNull(occurredAt, Time.now())); record.setRemark(remark); recordService.createOperationRecord(record, attachmentList); stock.setOnHandQuantity(onHandAfter); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java index cbaa636..c130b77 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java @@ -34,8 +34,8 @@ import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderItemRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.system.audit.AuditOperation; +import com.imyeyu.api.modules.system.audit.OperationAudit; import com.imyeyu.api.modules.user.service.PermissionChecker; import com.imyeyu.api.modules.user.service.RoleChecker; import com.imyeyu.api.modules.user.service.UserLoginService; @@ -91,7 +91,6 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService mapper() { @@ -139,23 +138,23 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService createDirectInternal(request)); + return createDirectInternal(request); } + @OperationAudit(AuditOperation.GAO_SALES_ORDER_UPDATE) @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoSalesOrder updateRemarkAndAttachments(GaoSalesOrderUpdateRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_UPDATE", request, () -> { - TimiException.required(request, "not found sales order update request"); - TimiException.required(request.getId(), "not found sales order.id"); - GaoSalesOrder order = getForUpdate(request.getId()); - TimiException.requiredTrue(1 == mapper.updateRemark(order.getId(), request.getRemark()), "订单备注更新失败"); - attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, order.getId(), request.getAttachmentList()); - return detail(order.getId()); - }); + TimiException.required(request, "not found sales order update request"); + TimiException.required(request.getId(), "not found sales order.id"); + GaoSalesOrder order = getForUpdate(request.getId()); + TimiException.requiredTrue(1 == mapper.updateRemark(order.getId(), request.getRemark()), "订单备注更新失败"); + attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, order.getId(), request.getAttachmentList()); + return detail(order.getId()); } private GaoSalesOrder createDirectInternal(GaoDirectProductOrderCreateRequest request) { @@ -222,10 +221,11 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService cancelInternal(request)); + return cancelInternal(request); } private GaoSalesOrder cancelInternal(GaoSalesOrderCancelRequest request) { diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoProductStockChangeRequest.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoProductStockChangeRequest.java index b161676..cc2d9fe 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoProductStockChangeRequest.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoProductStockChangeRequest.java @@ -28,6 +28,9 @@ public class GaoProductStockChangeRequest { /// 业务数量 private BigDecimal quantity; + /// 库存发生时间,毫秒时间戳 + private Long occurredAt; + /// 备注 private String remark; diff --git a/src/main/java/com/imyeyu/api/modules/system/audit/AuditOperation.java b/src/main/java/com/imyeyu/api/modules/system/audit/AuditOperation.java new file mode 100644 index 0000000..95ce596 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/audit/AuditOperation.java @@ -0,0 +1,66 @@ +package com.imyeyu.api.modules.system.audit; + +import com.imyeyu.api.modules.system.entity.Logger; + +/// 操作审计编码 +/// +/// @author Codex +/// @since 2026-09-09 +public enum AuditOperation { + + GAO_CUSTOMER_PACKAGE_PURCHASE(Logger.Module.GAO, "GAO_CUSTOMER_PACKAGE_PURCHASE"), + GAO_CUSTOMER_PACKAGE_CANCEL(Logger.Module.GAO, "GAO_CUSTOMER_PACKAGE_CANCEL"), + GAO_SALES_ORDER_PAYMENT_RECEIVE(Logger.Module.GAO, "GAO_SALES_ORDER_PAYMENT_RECEIVE"), + GAO_SALES_ORDER_PAYMENT_VERIFY(Logger.Module.GAO, "GAO_SALES_ORDER_PAYMENT_VERIFY"), + GAO_SALES_ORDER_REFUND(Logger.Module.GAO, "GAO_SALES_ORDER_REFUND"), + GAO_SALES_ORDER_CONFIRM_NO_REFUND(Logger.Module.GAO, "GAO_SALES_ORDER_CONFIRM_NO_REFUND"), + GAO_SALES_ORDER_FUND_REVOKE(Logger.Module.GAO, "GAO_SALES_ORDER_FUND_REVOKE"), + GAO_ORDER_FULFILLMENT_CREATE(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_CREATE"), + GAO_ORDER_FULFILLMENT_PREPARE_STOCK(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_PREPARE_STOCK"), + GAO_ORDER_FULFILLMENT_VERIFY(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_VERIFY"), + GAO_ORDER_FULFILLMENT_REJECT(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_REJECT"), + GAO_ORDER_FULFILLMENT_CANCEL(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_CANCEL"), + GAO_ORDER_FULFILLMENT_REVOKE(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_REVOKE"), + GAO_PACKAGE_CONFIG_CREATE(Logger.Module.GAO, "GAO_PACKAGE_CONFIG_CREATE"), + GAO_PACKAGE_CONFIG_UPDATE(Logger.Module.GAO, "GAO_PACKAGE_CONFIG_UPDATE"), + GAO_PACKAGE_CONFIG_DELETE(Logger.Module.GAO, "GAO_PACKAGE_CONFIG_DELETE"), + GAO_PRODUCT_CATEGORY_SORT(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_SORT"), + GAO_PRODUCT_CATEGORY_CREATE(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_CREATE"), + GAO_PRODUCT_CATEGORY_UPDATE(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_UPDATE"), + GAO_PRODUCT_CATEGORY_DISABLE(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_DISABLE"), + GAO_PRODUCT_CREATE(Logger.Module.GAO, "GAO_PRODUCT_CREATE"), + GAO_PRODUCT_UPDATE(Logger.Module.GAO, "GAO_PRODUCT_UPDATE"), + GAO_PRODUCT_DISABLE(Logger.Module.GAO, "GAO_PRODUCT_DISABLE"), + GAO_PRODUCT_STOCK_INITIAL_IN(Logger.Module.GAO, "GAO_PRODUCT_STOCK_INITIAL_IN"), + GAO_PRODUCT_STOCK_MANUAL_IN(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_IN"), + GAO_PRODUCT_STOCK_MANUAL_OUT(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_OUT"), + GAO_PRODUCT_STOCK_MANUAL_REVOKE(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_REVOKE"), + GAO_PRODUCT_STOCK_STOCKTAKE_GAIN(Logger.Module.GAO, "GAO_PRODUCT_STOCK_STOCKTAKE_GAIN"), + GAO_PRODUCT_STOCK_STOCKTAKE_LOSS(Logger.Module.GAO, "GAO_PRODUCT_STOCK_STOCKTAKE_LOSS"), + GAO_SALES_ORDER_DIRECT_PRODUCT_CREATE(Logger.Module.GAO, "GAO_SALES_ORDER_DIRECT_PRODUCT_CREATE"), + GAO_SALES_ORDER_UPDATE(Logger.Module.GAO, "GAO_SALES_ORDER_UPDATE"), + GAO_SALES_ORDER_CANCEL(Logger.Module.GAO, "GAO_SALES_ORDER_CANCEL"); + + private final Logger.Module module; + + private final String code; + + AuditOperation(Logger.Module module, String code) { + this.module = module; + this.code = code; + } + + /// 获取日志模块 + /// + /// @return 日志模块 + public Logger.Module getModule() { + return module; + } + + /// 获取操作编码 + /// + /// @return 操作编码 + public String getCode() { + return code; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/system/audit/AuditPayloadSerializer.java b/src/main/java/com/imyeyu/api/modules/system/audit/AuditPayloadSerializer.java new file mode 100644 index 0000000..9f92f55 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/audit/AuditPayloadSerializer.java @@ -0,0 +1,128 @@ +package com.imyeyu.api.modules.system.audit; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.imyeyu.spring.entity.IDEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Array; +import java.util.Locale; +import java.util.Set; + +/// 操作审计参数和结果序列化器 +/// +/// @author Codex +/// @since 2026-09-09 +@Component +@RequiredArgsConstructor +public class AuditPayloadSerializer { + + private static final int MAX_LENGTH = 32 * 1024; + + private static final Set SECRET_FIELD_SET = Set.of( + "password", + "passwordconfirm", + "token", + "accesstoken", + "refreshtoken", + "secret", + "credential", + "authorization" + ); + + private final ObjectMapper objectMapper; + + /// 序列化方法参数 + /// + /// @param arguments 方法参数 + /// @return 脱敏后的参数 JSON + public String serializeArguments(Object[] arguments) { + if (arguments == null || 0 == arguments.length) { + return null; + } + return serialize(arguments.length == 1 ? arguments[0] : arguments); + } + + /// 序列化业务结果 + /// + /// @param result 方法返回值 + /// @param arguments 方法参数 + /// @return 结果摘要 + public String serializeResult(Object result, Object[] arguments) { + if (result instanceof IDEntity entity && entity.getId() != null) { + return truncate(String.valueOf(entity.getId())); + } + if (result != null) { + return summarize(result); + } + if (arguments != null) { + for (Object argument : arguments) { + if (argument instanceof IDEntity entity && entity.getId() != null) { + return truncate(String.valueOf(entity.getId())); + } + } + if (1 == arguments.length && arguments[0] instanceof CharSequence value) { + return truncate(value.toString()); + } + } + return null; + } + + private String serialize(Object value) { + try { + JsonNode node = objectMapper.valueToTree(value); + mask(node); + return truncate(objectMapper.writeValueAsString(node)); + } catch (Exception e) { + return value == null ? null : truncate(value.getClass().getName()); + } + } + + private void mask(JsonNode node) { + if (node == null) { + return; + } + if (node.isObject()) { + ObjectNode objectNode = (ObjectNode) node; + objectNode.fieldNames().forEachRemaining(fieldName -> { + JsonNode field = objectNode.get(fieldName); + if (SECRET_FIELD_SET.contains(fieldName.toLowerCase(Locale.ROOT))) { + objectNode.put(fieldName, "***"); + } else { + mask(field); + } + }); + } else if (node.isArray()) { + node.forEach(this::mask); + } + } + + private String summarize(Object value) { + if (value instanceof CharSequence || value instanceof Number || value instanceof Boolean || value instanceof Enum) { + return truncate(String.valueOf(value)); + } + if (value instanceof Iterable iterable) { + int size = 0; + for (Object ignored : iterable) { + size++; + } + return "%s[%s]".formatted(value.getClass().getSimpleName(), size); + } + if (value instanceof java.util.Map map) { + return "%s[%s]".formatted(value.getClass().getSimpleName(), map.size()); + } + if (value.getClass().isArray()) { + return "%s[%s]".formatted(value.getClass().getComponentType().getSimpleName(), Array.getLength(value)); + } + return value.getClass().getSimpleName(); + } + + private String truncate(String value) { + if (value == null || value.length() <= MAX_LENGTH) { + return value; + } + return value.substring(0, MAX_LENGTH) + "..."; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/system/audit/AuditRecorder.java b/src/main/java/com/imyeyu/api/modules/system/audit/AuditRecorder.java new file mode 100644 index 0000000..6744723 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/audit/AuditRecorder.java @@ -0,0 +1,17 @@ +package com.imyeyu.api.modules.system.audit; + +import com.imyeyu.api.modules.system.entity.Logger; + +/// 操作审计记录器 +/// +/// 业务切面只依赖该写入端口,不直接依赖日志查询服务 +/// +/// @author Codex +/// @since 2026-09-09 +public interface AuditRecorder { + + /// 记录操作审计 + /// + /// @param logger 审计记录 + void record(Logger logger); +} diff --git a/src/main/java/com/imyeyu/api/modules/system/audit/OperationAudit.java b/src/main/java/com/imyeyu/api/modules/system/audit/OperationAudit.java new file mode 100644 index 0000000..66cd641 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/audit/OperationAudit.java @@ -0,0 +1,22 @@ +package com.imyeyu.api.modules.system.audit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// 标记需要记录操作审计的应用服务方法 +/// +/// 只应标记对外的业务操作入口,不要标记被入口调用的内部方法 +/// +/// @author Codex +/// @since 2026-09-09 +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperationAudit { + + /// 操作编码 + /// + /// @return 操作编码 + AuditOperation value(); +} diff --git a/src/main/java/com/imyeyu/api/modules/system/audit/OperationAuditAspect.java b/src/main/java/com/imyeyu/api/modules/system/audit/OperationAuditAspect.java new file mode 100644 index 0000000..a1e1431 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/audit/OperationAuditAspect.java @@ -0,0 +1,90 @@ +package com.imyeyu.api.modules.system.audit; + +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.java.bean.timi.TimiException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/// 操作审计切面 +/// +/// 该切面放在事务拦截器外层,确保成功日志只在事务提交成功后生成 +/// +/// @author Codex +/// @since 2026-09-09 +@Aspect +@Component +@Order(Ordered.LOWEST_PRECEDENCE - 100) +@RequiredArgsConstructor +@Slf4j +public class OperationAuditAspect { + + private final AuditRecorder auditRecorder; + + private final AuditPayloadSerializer payloadSerializer; + + /// 执行并记录操作审计 + /// + /// @param joinPoint 连接点 + /// @param operationAudit 审计配置 + /// @return 业务返回值 + /// @throws Throwable 业务异常 + @Around("@annotation(operationAudit)") + public Object around(ProceedingJoinPoint joinPoint, OperationAudit operationAudit) throws Throwable { + AuditOperation operation = operationAudit.value(); + Object[] arguments = joinPoint.getArgs(); + Logger logger = new Logger(operation.getModule(), operation.getCode()); + logger.setContent(payloadSerializer.serializeArguments(arguments)); + long startNanos = System.nanoTime(); + try { + Object result = joinPoint.proceed(); + logger.setLevel(Logger.Level.INFO); + logger.setResult(payloadSerializer.serializeResult(result, arguments)); + logger.setDuration(duration(startNanos)); + record(logger); + return result; + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + logger.setDuration(duration(startNanos)); + record(logger); + throw e; + } catch (Throwable e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(serializeThrowable(e)); + logger.setDuration(duration(startNanos)); + log.error("业务操作失败: {}", operation.getCode(), e); + record(logger); + throw e; + } + } + + private void record(Logger logger) { + try { + auditRecorder.record(logger); + } catch (Exception e) { + log.error("记录操作审计失败: {}", logger.getOperation(), e); + } + } + + private int duration(long startNanos) { + long millis = (System.nanoTime() - startNanos) / 1_000_000L; + if (Integer.MAX_VALUE < millis) { + return Integer.MAX_VALUE; + } + return (int) Math.max(0L, millis); + } + + private String serializeThrowable(Throwable throwable) { + java.io.StringWriter stringWriter = new java.io.StringWriter(); + try (java.io.PrintWriter printWriter = new java.io.PrintWriter(stringWriter)) { + throwable.printStackTrace(printWriter); + } + return stringWriter.toString(); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/system/service/LoggerService.java b/src/main/java/com/imyeyu/api/modules/system/service/LoggerService.java index 39f8afa..45e4463 100644 --- a/src/main/java/com/imyeyu/api/modules/system/service/LoggerService.java +++ b/src/main/java/com/imyeyu/api/modules/system/service/LoggerService.java @@ -6,8 +6,6 @@ import com.imyeyu.spring.service.DestroyableService; import com.imyeyu.spring.service.GettableService; import com.imyeyu.spring.service.PageableService; -import java.util.function.Supplier; - /** * 日志服务 * @@ -18,51 +16,6 @@ public interface LoggerService extends GettableService, Pageable void createSync(Logger logger); - /// 执行并记录有返回值的业务操作 - /// - /// @param logger 日志对象,调用方负责设置模块、操作和请求内容 - /// @param action 业务操作 - /// @param 返回值类型 - /// @return 业务操作返回值 - T execute(Logger logger, Supplier action); - - /// 执行并记录无返回值的业务操作 - /// - /// @param logger 日志对象,调用方负责设置模块、操作和请求内容 - /// @param action 业务操作 - default void execute(Logger logger, Runnable action) { - execute(logger, () -> { - action.run(); - return null; - }); - } - - /// 创建日志并执行有返回值的业务操作 - /// - /// @param module 日志模块 - /// @param operation 操作描述 - /// @param content 请求内容 - /// @param action 业务操作 - /// @param 返回值类型 - /// @return 业务操作返回值 - default T execute(Logger.Module module, String operation, Object content, Supplier action) { - Logger logger = new Logger(module, operation); - logger.setContent(content == null ? null : content.toString()); - return execute(logger, action); - } - - /// 创建日志并执行无返回值的业务操作 - /// - /// @param module 日志模块 - /// @param operation 操作描述 - /// @param content 请求内容 - /// @param action 业务操作 - default void execute(Logger.Module module, String operation, Object content, Runnable action) { - Logger logger = new Logger(module, operation); - logger.setContent(content == null ? null : content.toString()); - execute(logger, action); - } - void create(Logger.Module module, Logger.Level level, String operation, String content, String result); default void info(Logger.Module module, String operation) { diff --git a/src/main/java/com/imyeyu/api/modules/system/service/implement/LoggerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/system/service/implement/LoggerServiceImplement.java index dff254a..d9e9dac 100644 --- a/src/main/java/com/imyeyu/api/modules/system/service/implement/LoggerServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/system/service/implement/LoggerServiceImplement.java @@ -1,37 +1,41 @@ package com.imyeyu.api.modules.system.service.implement; import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.api.modules.system.audit.AuditRecorder; import com.imyeyu.api.modules.system.mapper.LoggerMapper; import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.entity.IDEntity; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; -import java.util.function.Supplier; - /** * 日志服务实现 * * @author 夜雨 * @since 2026-05-13 14:46 */ -@Slf4j @Service -@RequiredArgsConstructor -public class LoggerServiceImplement extends AbstractEntityService implements LoggerService { +public class LoggerServiceImplement extends AbstractEntityService implements LoggerService, AuditRecorder { private final UserLoginService userLoginService; private final LoggerMapper mapper; + private final ThreadPoolTaskExecutor auditTaskExecutor; + + public LoggerServiceImplement(UserLoginService userLoginService, LoggerMapper mapper, + @Qualifier("auditTaskExecutor") ThreadPoolTaskExecutor auditTaskExecutor) { + this.userLoginService = userLoginService; + this.mapper = mapper; + this.auditTaskExecutor = auditTaskExecutor; + } + @Override protected BaseMapper mapper() { return mapper; @@ -39,9 +43,13 @@ public class LoggerServiceImplement extends AbstractEntityService createSync(logger)); + auditTaskExecutor.execute(() -> createSync(logger)); + } + + @Override + public void record(Logger logger) { + create(logger); } @Override @@ -50,32 +58,6 @@ public class LoggerServiceImplement extends AbstractEntityService T execute(Logger logger, Supplier action) { - try { - T result = action.get(); - logger.setLevel(Logger.Level.INFO); - if (result instanceof IDEntity entity) { - logger.setResult(String.valueOf(entity.getId())); - } else if (result != null) { - logger.setResult(result.toString()); - } - create(logger); - return result; - } catch (TimiException e) { - logger.setLevel(Logger.Level.WARN); - logger.setException(e.getMessage()); - create(logger); - throw e; - } catch (RuntimeException e) { - logger.setLevel(Logger.Level.ERROR); - logger.setException(TimiJava.serializeThrowable(e)); - log.error("业务操作失败: {}", logger.getOperation(), e); - create(logger); - throw e; - } - } - public void create(Logger.Module module, Logger.Level level, String operation, String content, String result) { Logger log = new Logger(module, level, operation); log.setContent(content); -- 2.54.0 From 3cdac988322bdf7e1ce2a8c4237ddc2939f846b0 Mon Sep 17 00:00:00 2001 From: Timi Date: Wed, 9 Sep 2026 14:21:04 +0800 Subject: [PATCH 2/3] add allowDecimalQuantity --- .../modules/gao/entity/GaoSalesOrderItem.java | 8 +++++ .../gao/mapper/GaoProductSkuMapper.java | 6 ++++ .../gao/service/GaoProductSkuService.java | 6 ++++ .../GaoProductSkuServiceImplement.java | 10 ++++++ .../GaoSalesOrderServiceImplement.java | 32 +++++++++++++++++++ .../timi-server/gao/GaoProductSkuMapper.xml | 5 +++ 6 files changed, 67 insertions(+) diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java index eb66708..2235453 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java @@ -68,6 +68,14 @@ public class GaoSalesOrderItem extends UUIDEntity { /// 基础单位数量系数快照 private BigDecimal baseQuantityPerUnitSnapshot; + /// 当前 SKU 是否允许业务数量保留小数 + @Transient + private Boolean allowDecimalQuantity; + + /// 当前 SKU 基础单位名称 + @Transient + private String baseUnitName; + /// 购买数量 private BigDecimal quantity; diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuMapper.java index a14e681..5007e9b 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuMapper.java @@ -13,6 +13,12 @@ import java.util.List; /// @since 2026-09-06 public interface GaoProductSkuMapper extends BaseMapper { + /// 批量查询 SKU + /// + /// @param idList SKU ID 集合 + /// @return SKU 列表 + List selectByIdList(@Param("idList") Collection idList); + /// 按产品查询 SKU List selectByProductId(@Param("productId") String productId); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductSkuService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductSkuService.java index f7d4d21..c8de44d 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductSkuService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductSkuService.java @@ -15,6 +15,12 @@ import java.util.List; /// @since 2026-09-06 public interface GaoProductSkuService extends BaseService { + /// 批量查询 SKU + /// + /// @param idList SKU ID 集合 + /// @return SKU 列表,包含单位、规格和附件临时数据 + List listByIdList(Collection idList); + /// 查询 SKU 详情 GaoProductSku detail(String id); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductSkuServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductSkuServiceImplement.java index 749979f..8e85d17 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductSkuServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductSkuServiceImplement.java @@ -76,6 +76,16 @@ public class GaoProductSkuServiceImplement extends AbstractEntityService listByIdList(Collection idList) { + if (idList == null || idList.isEmpty()) { + return List.of(); + } + List result = mapper.selectByIdList(idList); + fillTransient(result); + return result; + } + @Override public List listByProductId(String productId) { TimiException.required(productId, "not found productId"); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java index c130b77..468656c 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java @@ -115,6 +115,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList = itemMapper.selectBySalesOrderId(order.getId()); + fillItemDisplayInfo(itemList); for (GaoSalesOrderItem item : itemList) { fillItemBalance(item); } @@ -562,6 +563,37 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList) { + if (itemList == null || itemList.isEmpty()) { + return; + } + List skuIdList = itemList.stream() + .map(GaoSalesOrderItem::getSkuId) + .filter(value -> value != null && !value.isBlank()) + .distinct() + .toList(); + if (skuIdList.isEmpty()) { + return; + } + Map skuMap = skuService.listByIdList(skuIdList).stream() + .collect(java.util.stream.Collectors.toMap(GaoProductSku::getId, java.util.function.Function.identity())); + for (GaoSalesOrderItem item : itemList) { + GaoProductSku sku = skuMap.get(item.getSkuId()); + if (sku == null) { + continue; + } + item.setAllowDecimalQuantity(sku.getAllowDecimalQuantity()); + item.setBaseUnitName(sku.getUnitList().stream() + .filter(unit -> Boolean.TRUE.equals(unit.getIsBase())) + .map(GaoProductSkuUnit::getName) + .findFirst() + .orElse(null)); + } + } + private void fillItemAttachments(List itemList) { if (itemList == null || itemList.isEmpty()) { return; diff --git a/src/main/resources/mapper/timi-server/gao/GaoProductSkuMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoProductSkuMapper.xml index f46e775..3af78e6 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoProductSkuMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoProductSkuMapper.xml @@ -1,6 +1,11 @@ + -- 2.54.0 From f93ec3cd4d2193a6a671d3cd48c15caacf66d58d Mon Sep 17 00:00:00 2001 From: Timi Date: Wed, 9 Sep 2026 14:21:18 +0800 Subject: [PATCH 3/3] v1.0.37 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0d4ae86..07b873a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.imyeyu.timiserverapi TimiServerAPI - 1.0.36 + 1.0.37 jar TimiServerAPI imyeyu.com API -- 2.54.0