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/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
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/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/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/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/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..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
@@ -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() {
@@ -116,6 +115,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList = itemMapper.selectBySalesOrderId(order.getId());
+ fillItemDisplayInfo(itemList);
for (GaoSalesOrderItem item : itemList) {
fillItemBalance(item);
}
@@ -139,23 +139,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 +222,11 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService cancelInternal(request));
+ return cancelInternal(request);
}
private GaoSalesOrder cancelInternal(GaoSalesOrderCancelRequest request) {
@@ -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/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);
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 @@
+