Compare commits

..
14 Commits
Author SHA1 Message Date
timi 15d69f9361 Merge pull request 'v1.0.39' (#51) from dev into master
Reviewed-on: #51
2026-09-10 06:10:30 +00:00
Timi b000390a75 v1.0.39
CI / build-deploy (pull_request) Successful in 24s
CI / notify-on-failure (pull_request) Skipped
2026-09-10 14:09:49 +08:00
Timi 1b9f92caf5 add setting map 2026-09-10 14:09:38 +08:00
Timi 7b08641acc add customer order map 2026-09-10 14:09:30 +08:00
Timi 66c13062b2 fix notify fail 2026-09-10 12:47:29 +08:00
timi 2b4089ef59 Merge pull request 'v1.0.38' (#50) from dev into master
Reviewed-on: #50
2026-09-09 17:33:25 +00:00
Timi cdf8a426e9 v1.0.38
CI / build-deploy (pull_request) Successful in 25s
CI / notify-on-failure (pull_request) Skipped
2026-09-10 01:31:20 +08:00
Timi a2d69fe721 support gift 2026-09-10 01:31:06 +08:00
Timi 19db357b44 update article CRUD 2026-09-10 01:30:50 +08:00
Timi 07ffef3e2a add cancel operator 2026-09-09 17:14:29 +08:00
timi 20c3e1f422 Merge pull request 'v1.0.37' (#49) from dev into master
Reviewed-on: #49
2026-09-09 06:21:55 +00:00
Timi f93ec3cd4d v1.0.37
CI / build-deploy (pull_request) Successful in 25s
CI / notify-on-failure (pull_request) Skipped
2026-09-09 14:21:18 +08:00
Timi 3cdac98832 add allowDecimalQuantity 2026-09-09 14:21:04 +08:00
Timi c122c35376 refactor LoggerService.excute 2026-09-09 13:04:16 +08:00
49 changed files with 999 additions and 353 deletions
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$PROJECT_DIR$/src/main/java/com/imyeyu/api/modules/common/mapper/GroovyScriptVersionMapper.java" value="05742bdc-a11a-4147-9168-ef976a70258a" />
</component>
</project>
+1 -1
View File
@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId> <groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId> <artifactId>TimiServerAPI</artifactId>
<version>1.0.36</version> <version>1.0.39</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TimiServerAPI</name> <name>TimiServerAPI</name>
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
@@ -45,6 +45,11 @@ public enum CorePermissionCode implements BuiltinPermissionCode {
TAG_UPDATE("TAG:UPDATE", "标签修改", false, true), TAG_UPDATE("TAG:UPDATE", "标签修改", false, true),
TAG_DELETE("TAG:DELETE", "标签删除", false, true), TAG_DELETE("TAG:DELETE", "标签删除", false, true),
ARTICLE_CREATE("ARTICLE:CREATE", "文章创建", false, true),
ARTICLE_READ("ARTICLE:READ", "文章读取", false, true),
ARTICLE_UPDATE("ARTICLE:UPDATE", "文章修改", false, true),
ARTICLE_DELETE("ARTICLE:DELETE", "文章删除", false, true),
USER_CREATE("USER:CREATE", "用户创建", false, true), USER_CREATE("USER:CREATE", "用户创建", false, true),
USER_READ("USER:READ", "用户读取", false, true), USER_READ("USER:READ", "用户读取", false, true),
USER_UPDATE("USER:UPDATE", "用户修改", false, true), USER_UPDATE("USER:UPDATE", "用户修改", false, true),
@@ -32,4 +32,22 @@ public class ThreadPoolConfig {
executor.initialize(); executor.initialize();
return executor; 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;
}
} }
@@ -0,0 +1,95 @@
package com.imyeyu.api.modules.common.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.annotation.RequireCorePermission;
import com.imyeyu.api.bean.CorePermissionCode;
import com.imyeyu.api.modules.common.entity.Article;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.ArticleService;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.util.ResponseView;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/// 文章接口
///
/// @author Codex
/// @since 2026-09-09
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/article")
public class ArticleController {
private final ArticleService service;
private final AttachmentService attachmentService;
/// 查询文章列表
///
/// @param page 分页参数
/// @return 文章分页
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@PostMapping("/list")
public PageResult<Article> list(@RequestBody Page<Article> page) {
return service.page(page);
}
/// 查询文章详情
///
/// @param id 文章 ID
/// @return 文章详情
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@PostMapping("/detail")
public Article detail(@RequestParam String id) {
Article article = service.get(id);
article.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.ARTICLE, article.getId()));
return article;
}
/// 创建文章
///
/// @param article 文章
/// @return 创建后的文章
@AOPLog
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.ARTICLE_CREATE)
@PostMapping("/create")
public Article create(@RequestBody Article article) {
service.create(article);
return article;
}
/// 更新文章
///
/// @param article 文章
@AOPLog
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.ARTICLE_UPDATE)
@PostMapping("/update")
public void update(@RequestBody Article article) {
service.update(article);
}
/// 删除文章
///
/// @param id 文章 ID
@AOPLog
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.ARTICLE_DELETE)
@PostMapping("/delete")
public void delete(@RequestParam String id) {
service.delete(id);
}
}
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.common.controller; 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.entity.Logger;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.service.LoggerService;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
@@ -29,7 +30,9 @@ public class LoggerController {
@AOPLog @AOPLog
@RequiredToken @RequiredToken
@PostMapping("/create") @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); service.create(logger);
} }
} }
@@ -15,8 +15,12 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/** /**
* *
* *
@@ -26,11 +30,30 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/system/setting") @RequestMapping("/setting")
public class SettingController { public class SettingController {
private final SettingService service; private final SettingService service;
/**
* 查询公开配置映射
*
* @param bizType 业务类型
* @param bizId 业务 ID,可为空
* @param code 配置代码列表,可为空
* @return 公开配置映射
*/
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@RequestMapping("/map")
public Map<String, Setting> map(
@RequestParam String bizType,
@RequestParam(required = false) String bizId,
@RequestParam(required = false) List<String> code
) {
return service.mapPublic(bizType, bizId, code);
}
/** /**
* 查询配置列表 * 查询配置列表
* *
@@ -34,6 +34,8 @@ public class Article extends UUIDEntity implements SettingSupport {
/// GAO 门店日记 /// GAO 门店日记
GAO_STORE_DIARY, GAO_STORE_DIARY,
GAO_SYSTEM,
/// 反馈 /// 反馈
FEEDBACK FEEDBACK
} }
@@ -20,11 +20,11 @@ public class Notify extends UUIDEntity {
/** 业务类型 */ /** 业务类型 */
private String bizType; private String bizType;
/** 提醒业务类型 */ /** 业务提醒类型 */
private String bizRemindedType; private String bizRemindedType;
/** 提醒业务 ID */ /** 业务提醒查重键 */
private String bizRemindedId; private String bizRemindedKey;
/** 参数 */ /** 参数 */
private String args; private String args;
@@ -2,9 +2,8 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Notify; import com.imyeyu.api.modules.common.entity.Notify;
import com.imyeyu.api.modules.common.vo.NotifyManagementItem; import com.imyeyu.api.modules.common.vo.NotifyManagementItem;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
@@ -16,9 +15,8 @@ import java.util.List;
*/ */
public interface NotifyMapper extends BaseMapper<Notify, String> { public interface NotifyMapper extends BaseMapper<Notify, String> {
/// 查询业务提醒是否已创建 /// 原子创建业务提醒
@Select("SELECT EXISTS (SELECT 1 FROM `notify` WHERE `biz_type` = #{bizType} AND `biz_reminded_type` = #{bizRemindedType} AND `biz_reminded_id` = #{bizRemindedId} AND `deleted_at` IS NULL)") int insertIfAbsent(Notify notify);
boolean existsByBizReminded(@Param("bizType") String bizType, @Param("bizRemindedType") String bizRemindedType, @Param("bizRemindedId") String bizRemindedId);
/// 分页查询管理端通知 /// 分页查询管理端通知
/// ///
@@ -3,6 +3,8 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import java.util.List;
/** /**
* 系统配置 * 系统配置
* *
@@ -10,4 +12,6 @@ import com.imyeyu.spring.mapper.BaseMapper;
* @since 2021-07-20 22:26 * @since 2021-07-20 22:26
*/ */
public interface SettingMapper extends BaseMapper<Setting, String> { public interface SettingMapper extends BaseMapper<Setting, String> {
List<Setting> selectPublicByQuery(String bizType, String bizId, List<String> codeList);
} }
@@ -14,13 +14,10 @@ import com.imyeyu.spring.service.BaseService;
*/ */
public interface NotifyService extends BaseService<Notify, String> { public interface NotifyService extends BaseService<Notify, String> {
/// 查询业务提醒是否已创建 /// 原子创建业务提醒,重复查重键不会创建通知详情
/// ///
/// @param bizType 业务类型 /// @param notify 通知
/// @param bizRemindedType 被提醒业务类型 void createIfAbsent(Notify notify);
/// @param bizRemindedId 被提醒业务 ID
/// @return true 为已创建
boolean existsByBizReminded(String bizType, String bizRemindedType, String bizRemindedId);
/// 分页查询当前用户创建的通知 /// 分页查询当前用户创建的通知
/// ///
@@ -7,6 +7,7 @@ import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 配置服务 * 配置服务
@@ -24,6 +25,16 @@ public interface SettingService extends BaseService<Setting, String> {
List<Setting> listByBizId(String bizType, String bizId); List<Setting> listByBizId(String bizType, String bizId);
/**
* 查询公开配置映射
*
* @param bizType 业务类型
* @param bizId 业务 ID,可为空
* @param code 配置代码列表,可为空
* @return 公开配置映射
*/
Map<String, Setting> mapPublic(String bizType, String bizId, List<String> code);
default List<Setting> listBySettingSupport(SettingSupport settingSupport) { default List<Setting> listBySettingSupport(SettingSupport settingSupport) {
String bizType = settingSupport.getClass().getSimpleName().toUpperCase(); String bizType = settingSupport.getClass().getSimpleName().toUpperCase();
return listByBizId(bizType, settingSupport.getId()); return listByBizId(bizType, settingSupport.getId());
@@ -7,9 +7,8 @@ import com.imyeyu.api.modules.common.mapper.NotifyMapper;
import com.imyeyu.api.modules.common.service.NotifyDetailService; import com.imyeyu.api.modules.common.service.NotifyDetailService;
import com.imyeyu.api.modules.common.service.NotifyService; import com.imyeyu.api.modules.common.service.NotifyService;
import com.imyeyu.api.modules.common.vo.NotifyManagementItem; import com.imyeyu.api.modules.common.vo.NotifyManagementItem;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; 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.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
@@ -17,12 +16,13 @@ import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.UUID;
/** /**
* 通知服务实现 * 通知服务实现
@@ -30,13 +30,10 @@ import java.util.List;
* @author 夜雨 * @author 夜雨
* @since 2026-05-13 14:46 * @since 2026-05-13 14:46
*/ */
@Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class NotifyServiceImplement extends AbstractEntityService<Notify, String> implements NotifyService { public class NotifyServiceImplement extends AbstractEntityService<Notify, String> implements NotifyService {
private final LoggerService loggerService;
private final UserLoginService userLoginService;
private final NotifyDetailService detailService; private final NotifyDetailService detailService;
private final NotifyMapper mapper; private final NotifyMapper mapper;
@@ -46,44 +43,54 @@ public class NotifyServiceImplement extends AbstractEntityService<Notify, String
return mapper; return mapper;
} }
@Override
public boolean existsByBizReminded(String bizType, String bizRemindedType, String bizRemindedId) {
return mapper.existsByBizReminded(bizType, bizRemindedType, bizRemindedId);
}
/// 创建通知并级联创建通知详情 /// 创建通知并级联创建通知详情
/// ///
/// @param notify 通知 /// @param notify 通知
@OperationAudit(AuditOperation.COMMON_NOTIFY_CREATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void create(Notify notify) { public void create(Notify notify) {
Logger logger = new Logger(Logger.Module.COMMON, "COMMON_NOTIFY_CREATE"); prepareCreate(notify);
try { super.create(notify);
createDetails(notify);
}
/// 原子创建业务提醒并级联创建通知详情
///
/// @param notify 通知
@OperationAudit(AuditOperation.COMMON_NOTIFY_CREATE)
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void createIfAbsent(Notify notify) {
prepareCreate(notify);
if (mapper.insertIfAbsent(notify) == 0) {
return;
}
createDetails(notify);
}
/// 校验并补全通知创建字段
///
/// @param notify 通知
private void prepareCreate(Notify notify) {
TimiException.required(notify, "未找到通知"); TimiException.required(notify, "未找到通知");
TimiException.required(notify.getDetailList(), "未找到通知详情列表"); TimiException.required(notify.getDetailList(), "未找到通知详情列表");
notify.setId(TimiJava.defaultIfEmpty(notify.getId(), UUID.randomUUID().toString()));
notify.setCreatedBy(TimiJava.defaultIfEmpty(notify.getCreatedBy(), TimiJava.ZERO_UUID)); notify.setCreatedBy(TimiJava.defaultIfEmpty(notify.getCreatedBy(), TimiJava.ZERO_UUID));
super.create(notify); notify.setCreatedAt(TimiJava.defaultIfNull(notify.getCreatedAt(), Time.now()));
notify.setUpdatedAt(null);
notify.setDeletedAt(null);
}
/// 创建通知详情
///
/// @param notify 通知
private void createDetails(Notify notify) {
for (NotifyDetail detail : notify.getDetailList()) { for (NotifyDetail detail : notify.getDetailList()) {
detail.setNotifyId(notify.getId()); detail.setNotifyId(notify.getId());
detail.setSendFrom(TimiJava.defaultIfEmpty(detail.getSendFrom(), TimiJava.ZERO_UUID)); detail.setSendFrom(TimiJava.defaultIfEmpty(detail.getSendFrom(), TimiJava.ZERO_UUID));
detailService.create(detail); detailService.create(detail);
} }
logger.setLevel(Logger.Level.INFO);
logger.setResult(notify.getId());
} catch (TimiException e) {
logger.setLevel(Logger.Level.WARN);
logger.setException(e.getMessage());
throw e;
} catch (Exception e) {
logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e));
log.error("创建通知失败", e);
throw new TimiException(TimiCode.ERROR, "创建通知失败", e);
} finally {
loggerService.create(logger);
}
} }
/// 分页查询当前用户创建的通知 /// 分页查询当前用户创建的通知
@@ -120,6 +127,8 @@ public class NotifyServiceImplement extends AbstractEntityService<Notify, String
/// @param notifyId 通知 ID /// @param notifyId 通知 ID
/// @param userId 用户 ID /// @param userId 用户 ID
@Override @Override
@Transactional(TimiServerDBConfig.ROLLBACKER)
@OperationAudit(AuditOperation.COMMON_NOTIFY_DELETE)
public void deleteManagement(String notifyId, String userId) { public void deleteManagement(String notifyId, String userId) {
getManagement(notifyId, userId); getManagement(notifyId, userId);
delete(notifyId); delete(notifyId);
@@ -130,6 +139,7 @@ public class NotifyServiceImplement extends AbstractEntityService<Notify, String
/// @param notifyId 通知 ID /// @param notifyId 通知 ID
/// @param userId 用户 ID /// @param userId 用户 ID
@Override @Override
@Transactional(TimiServerDBConfig.ROLLBACKER)
public void cancelManagement(String notifyId, String userId) { public void cancelManagement(String notifyId, String userId) {
getManagement(notifyId, userId); getManagement(notifyId, userId);
cancel(notifyId); cancel(notifyId);
@@ -138,11 +148,10 @@ public class NotifyServiceImplement extends AbstractEntityService<Notify, String
/// 删除通知并级联软删除通知详情 /// 删除通知并级联软删除通知详情
/// ///
/// @param notifyId 通知 ID /// @param notifyId 通知 ID
@OperationAudit(AuditOperation.COMMON_NOTIFY_DELETE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void delete(String notifyId) { public void delete(String notifyId) {
Logger logger = new Logger(Logger.Module.COMMON, "COMMON_NOTIFY_DELETE");
try {
TimiException.required(notifyId, "未找到通知 ID"); TimiException.required(notifyId, "未找到通知 ID");
TimiException.required(get(notifyId), "未找到通知"); TimiException.required(get(notifyId), "未找到通知");
List<NotifyDetail> detailList = detailService.listByNotifyId(notifyId); List<NotifyDetail> detailList = detailService.listByNotifyId(notifyId);
@@ -150,20 +159,6 @@ public class NotifyServiceImplement extends AbstractEntityService<Notify, String
detailService.delete(detail.getId()); detailService.delete(detail.getId());
} }
super.delete(notifyId); super.delete(notifyId);
logger.setLevel(Logger.Level.INFO);
logger.setResult(notifyId);
} catch (TimiException e) {
logger.setLevel(Logger.Level.WARN);
logger.setException(e.getMessage());
throw e;
} catch (Exception e) {
logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e));
log.error("删除通知失败", e);
throw new TimiException(TimiCode.ERROR, "删除通知失败", e);
} finally {
loggerService.create(logger);
}
} }
/// 取消通知中尚未成功发送的通知详情 /// 取消通知中尚未成功发送的通知详情
@@ -17,6 +17,8 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/** /**
* 系统配置服务实现 * 系统配置服务实现
@@ -53,6 +55,12 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
return mapper.selectAllByExample(example); return mapper.selectAllByExample(example);
} }
@Override
public Map<String, Setting> mapPublic(String bizType, String bizId, List<String> code) {
TimiException.required(bizType, "not found bizType");
return mapper.selectPublicByQuery(bizType, bizId, code).stream().collect(Collectors.toMap(Setting::getCode, Function.identity()));
}
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void updateByBizId(String bizType, String bizId, List<Setting> list) { public void updateByBizId(String bizType, String bizId, List<Setting> list) {
@@ -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;
}
@@ -111,6 +111,8 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
CUSTOMER_PACKAGE_PRICE_OVERRIDE("CUSTOMER_PACKAGE:PRICE_OVERRIDE", "客户套餐成交价和权益配额调整", true, true), CUSTOMER_PACKAGE_PRICE_OVERRIDE("CUSTOMER_PACKAGE:PRICE_OVERRIDE", "客户套餐成交价和权益配额调整", true, true),
CUSTOMER_PACKAGE_GIFT_CREATE("CUSTOMER_PACKAGE:GIFT_CREATE", "客户套餐创建赠送", true, true),
FUND_RECORD_READ("FUND_RECORD:READ", "资金流水读取", true, true), FUND_RECORD_READ("FUND_RECORD:READ", "资金流水读取", true, true),
FUND_RECEIVE_CREATE("FUND_RECEIVE:CREATE", "线下收款创建", true, true), FUND_RECEIVE_CREATE("FUND_RECEIVE:CREATE", "线下收款创建", true, true),
@@ -0,0 +1,27 @@
package com.imyeyu.api.modules.gao.bean;
import com.imyeyu.api.annotation.SettingBizType;
import com.imyeyu.api.bean.SettingEnum;
import com.imyeyu.api.modules.common.entity.Setting;
import lombok.AllArgsConstructor;
import lombok.Getter;
///
///
/// @author 夜雨
/// @since 2026-09-09 16:11
@Getter
@SettingBizType("GAO_SYSTEM")
@AllArgsConstructor
public enum GaoSystemSetting implements SettingEnum {
SERVICE_LICENSE(null, Setting.ValueType.STRING),
PRIVACY_LICENSE(null, Setting.ValueType.STRING),
OPEN_SOURCE_LICENSE(null, Setting.ValueType.STRING);
final String defaultValue;
final Setting.ValueType valueType;
}
@@ -1,8 +1,8 @@
package com.imyeyu.api.modules.gao.controller; package com.imyeyu.api.modules.gao.controller;
import com.imyeyu.api.bean.PermissionMatchMode;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.bean.PermissionMatchMode;
import com.imyeyu.api.modules.gao.entity.GaoFundRecord; import com.imyeyu.api.modules.gao.entity.GaoFundRecord;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService; import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService;
@@ -17,6 +17,8 @@ import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
@@ -40,6 +42,7 @@ public class GaoSalesOrderController {
private final GaoSalesOrderService service; private final GaoSalesOrderService service;
private final GaoOrderPaymentService paymentService; private final GaoOrderPaymentService paymentService;
private final GaoStoreScopeService storeScopeService; private final GaoStoreScopeService storeScopeService;
private final UserService userService;
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@@ -56,7 +59,11 @@ public class GaoSalesOrderController {
@RequireGaoPermission(GaoPermissionCode.SALES_ORDER_READ) @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_READ)
@PostMapping("/detail") @PostMapping("/detail")
public GaoSalesOrder detail(@RequestParam String id) { public GaoSalesOrder detail(@RequestParam String id) {
return service.detail(id); GaoSalesOrder result = service.detail(id);
if (TimiJava.isNotEmpty(result.getCancelOperatorUserId())) {
result.setCancelOperator(userService.get(result.getCancelOperatorUserId()));
}
return result;
} }
@AOPLog @AOPLog
@@ -34,6 +34,9 @@ public class GaoSalesOrderItem extends UUIDEntity {
/// 订单项类型 /// 订单项类型
private ItemType itemType; private ItemType itemType;
/// true 为赠送权益
private Boolean gifted;
/// SKU ID /// SKU ID
private String skuId; private String skuId;
@@ -68,6 +71,14 @@ public class GaoSalesOrderItem extends UUIDEntity {
/// 基础单位数量系数快照 /// 基础单位数量系数快照
private BigDecimal baseQuantityPerUnitSnapshot; private BigDecimal baseQuantityPerUnitSnapshot;
/// 当前 SKU 是否允许业务数量保留小数
@Transient
private Boolean allowDecimalQuantity;
/// 当前 SKU 基础单位名称
@Transient
private String baseUnitName;
/// 购买数量 /// 购买数量
private BigDecimal quantity; private BigDecimal quantity;
@@ -55,5 +55,5 @@ public interface GaoCustomerMapper extends BaseMapper<GaoCustomer, String> {
/// @param customerId 客户 ID /// @param customerId 客户 ID
/// @param delta 变化量 /// @param delta 变化量
@Update("UPDATE `gao_customer` SET `invited_count` = GREATEST(IFNULL(`invited_count`, 0) + #{delta}, 0), `updated_at` = " + UNIX_TIME + " WHERE `id` = #{customerId}" + NOT_DELETE) @Update("UPDATE `gao_customer` SET `invited_count` = GREATEST(IFNULL(`invited_count`, 0) + #{delta}, 0), `updated_at` = " + UNIX_TIME + " WHERE `id` = #{customerId}" + NOT_DELETE)
void updateInvitedCountDelta(@Param("customerId") String customerId, @Param("delta") Long delta); void updateInvitedCountDelta(String customerId, Long delta);
} }
@@ -13,6 +13,12 @@ import java.util.List;
/// @since 2026-09-06 /// @since 2026-09-06
public interface GaoProductSkuMapper extends BaseMapper<GaoProductSku, String> { public interface GaoProductSkuMapper extends BaseMapper<GaoProductSku, String> {
/// 批量查询 SKU
///
/// @param idList SKU ID 集合
/// @return SKU 列表
List<GaoProductSku> selectByIdList(@Param("idList") Collection<String> idList);
/// 按产品查询 SKU /// 按产品查询 SKU
List<GaoProductSku> selectByProductId(@Param("productId") String productId); List<GaoProductSku> selectByProductId(@Param("productId") String productId);
@@ -15,6 +15,12 @@ import java.util.List;
/// @since 2026-09-06 /// @since 2026-09-06
public interface GaoProductSkuService extends BaseService<GaoProductSku, String> { public interface GaoProductSkuService extends BaseService<GaoProductSku, String> {
/// 批量查询 SKU
///
/// @param idList SKU ID 集合
/// @return SKU 列表,包含单位、规格和附件临时数据
List<GaoProductSku> listByIdList(Collection<String> idList);
/// 查询 SKU 详情 /// 查询 SKU 详情
GaoProductSku detail(String id); GaoProductSku detail(String id);
@@ -4,6 +4,7 @@ import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfig; import com.imyeyu.api.modules.gao.entity.GaoPackageConfig;
@@ -24,8 +25,8 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePurchaseItem;
import com.imyeyu.api.modules.gao.vo.GaoPackageOrderCreateData; import com.imyeyu.api.modules.gao.vo.GaoPackageOrderCreateData;
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.audit.OperationAudit;
import com.imyeyu.api.modules.user.service.PermissionChecker; import com.imyeyu.api.modules.user.service.PermissionChecker;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
@@ -44,6 +45,7 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -69,7 +71,6 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final PermissionChecker permissionChecker; private final PermissionChecker permissionChecker;
private final LoggerService loggerService;
@Override @Override
protected BaseMapper<GaoCustomerPackage, String> mapper() { protected BaseMapper<GaoCustomerPackage, String> mapper() {
@@ -128,10 +129,11 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
return super.get(id); return super.get(id);
} }
@OperationAudit(AuditOperation.GAO_CUSTOMER_PACKAGE_PURCHASE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoCustomerPackage purchase(GaoCustomerPackagePurchaseRequest request) { public GaoCustomerPackage purchase(GaoCustomerPackagePurchaseRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_CUSTOMER_PACKAGE_PURCHASE", request, () -> purchaseInternal(request)); return purchaseInternal(request);
} }
private GaoCustomerPackage purchaseInternal(GaoCustomerPackagePurchaseRequest request) { private GaoCustomerPackage purchaseInternal(GaoCustomerPackagePurchaseRequest request) {
@@ -151,10 +153,13 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
request.setItemList(defaultPurchaseItems(configItemList)); request.setItemList(defaultPurchaseItems(configItemList));
} }
List<GaoCustomerPackagePurchaseItem> requestItemList = new ArrayList<>(request.getItemList()); List<GaoCustomerPackagePurchaseItem> requestItemList = new ArrayList<>(request.getItemList());
normalizePurchaseItems(requestItemList);
verifyPurchaseItems(requestItemList); verifyPurchaseItems(requestItemList);
boolean canOverridePriceAndQuantity = permissionChecker.hasAny(ModuleCode.GAO, boolean canOverridePriceAndQuantity = permissionChecker.hasAny(ModuleCode.GAO,
com.imyeyu.api.modules.gao.bean.GaoPermissionCode.CUSTOMER_PACKAGE_PRICE_OVERRIDE.getValue()); GaoPermissionCode.CUSTOMER_PACKAGE_PRICE_OVERRIDE.getValue());
List<GaoCustomerPackagePurchaseItem> itemList = resolvePurchaseItems(config.getStoreId(), requestItemList, configItemList, canOverridePriceAndQuantity); boolean canCreateGift = permissionChecker.hasAny(ModuleCode.GAO, GaoPermissionCode.CUSTOMER_PACKAGE_GIFT_CREATE.getValue());
List<GaoCustomerPackagePurchaseItem> itemList = resolvePurchaseItems(config.getStoreId(), requestItemList, configItemList,
canOverridePriceAndQuantity, canCreateGift);
itemList.sort(Comparator.comparing(GaoCustomerPackagePurchaseItem::getSort).thenComparing(GaoCustomerPackagePurchaseItem::getSkuId) itemList.sort(Comparator.comparing(GaoCustomerPackagePurchaseItem::getSort).thenComparing(GaoCustomerPackagePurchaseItem::getSkuId)
.thenComparing(GaoCustomerPackagePurchaseItem::getSkuUnitId)); .thenComparing(GaoCustomerPackagePurchaseItem::getSkuUnitId));
request.setItemList(itemList); request.setItemList(itemList);
@@ -206,10 +211,11 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
return result; return result;
} }
@OperationAudit(AuditOperation.GAO_CUSTOMER_PACKAGE_CANCEL)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoCustomerPackage cancel(GaoCustomerPackageCancelRequest request) { public GaoCustomerPackage cancel(GaoCustomerPackageCancelRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_CUSTOMER_PACKAGE_CANCEL", request, () -> cancelInternal(request)); return cancelInternal(request);
} }
private GaoCustomerPackage cancelInternal(GaoCustomerPackageCancelRequest request) { private GaoCustomerPackage cancelInternal(GaoCustomerPackageCancelRequest request) {
@@ -245,6 +251,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem(); GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem();
item.setSkuId(configItem.getSkuId()); item.setSkuId(configItem.getSkuId());
item.setSkuUnitId(configItem.getSkuUnitId()); item.setSkuUnitId(configItem.getSkuUnitId());
item.setGifted(false);
item.setProductNameSnapshot(configItem.getProductNameSnapshot()); item.setProductNameSnapshot(configItem.getProductNameSnapshot());
item.setProductIdSnapshot(configItem.getProductIdSnapshot()); item.setProductIdSnapshot(configItem.getProductIdSnapshot());
item.setSkuCodeSnapshot(configItem.getSkuCodeSnapshot()); item.setSkuCodeSnapshot(configItem.getSkuCodeSnapshot());
@@ -264,6 +271,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
item.setItemType(GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT); item.setItemType(GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT);
item.setSkuId(source.getSkuId()); item.setSkuId(source.getSkuId());
item.setSkuUnitId(source.getSkuUnitId()); item.setSkuUnitId(source.getSkuUnitId());
item.setGifted(Boolean.TRUE.equals(source.getGifted()));
item.setProductNameSnapshot(source.getProductNameSnapshot()); item.setProductNameSnapshot(source.getProductNameSnapshot());
item.setProductIdSnapshot(source.getProductIdSnapshot()); item.setProductIdSnapshot(source.getProductIdSnapshot());
item.setSkuCodeSnapshot(source.getSkuCodeSnapshot()); item.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
@@ -283,6 +291,32 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
return item; return item;
} }
private void normalizePurchaseItems(List<GaoCustomerPackagePurchaseItem> itemList) {
Map<String, Integer> indexMap = new LinkedHashMap<>();
for (int i = 0; i < itemList.size(); i++) {
GaoCustomerPackagePurchaseItem item = itemList.get(i);
if (item == null) {
continue;
}
item.setGifted(Boolean.TRUE.equals(item.getGifted()));
if (item.getSkuId() == null || item.getSkuUnitId() == null || item.getTotalQuantity() == null) {
continue;
}
String key = item.getSkuId() + "|" + item.getSkuUnitId() + "|" + item.getGifted();
Integer existingIndex = indexMap.putIfAbsent(key, i);
if (existingIndex == null) {
continue;
}
GaoCustomerPackagePurchaseItem existingItem = itemList.get(existingIndex);
if (existingItem == null || existingItem.getTotalQuantity() == null) {
continue;
}
existingItem.setTotalQuantity(existingItem.getTotalQuantity().add(item.getTotalQuantity()));
itemList.set(i, null);
}
itemList.removeIf(item -> item == null);
}
private void verifyPurchaseItems(List<GaoCustomerPackagePurchaseItem> itemList) { private void verifyPurchaseItems(List<GaoCustomerPackagePurchaseItem> itemList) {
Set<String> keySet = new HashSet<>(); Set<String> keySet = new HashSet<>();
for (int i = 0; i < itemList.size(); i++) { for (int i = 0; i < itemList.size(); i++) {
@@ -293,14 +327,15 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
TimiException.required(item.getTotalQuantity(), "购买产品数量不能为空"); TimiException.required(item.getTotalQuantity(), "购买产品数量不能为空");
TimiException.requiredTrue(0 <= item.getTotalQuantity().signum(), "购买产品数量不能为负数"); TimiException.requiredTrue(0 <= item.getTotalQuantity().signum(), "购买产品数量不能为负数");
TimiException.requiredTrue(3 >= item.getTotalQuantity().stripTrailingZeros().scale(), "购买产品数量最多保留三位小数"); TimiException.requiredTrue(3 >= item.getTotalQuantity().stripTrailingZeros().scale(), "购买产品数量最多保留三位小数");
String key = item.getSkuId() + "|" + item.getSkuUnitId(); item.setGifted(Boolean.TRUE.equals(item.getGifted()));
String key = item.getSkuId() + "|" + item.getSkuUnitId() + "|" + item.getGifted();
TimiException.requiredTrue(keySet.add(key), "购买套餐内 SKU 和单位不能重复"); TimiException.requiredTrue(keySet.add(key), "购买套餐内 SKU 和单位不能重复");
item.setSort(TimiJava.defaultIfNull(item.getSort(), i)); item.setSort(TimiJava.defaultIfNull(item.getSort(), i));
} }
} }
private List<GaoCustomerPackagePurchaseItem> resolvePurchaseItems(String storeId, List<GaoCustomerPackagePurchaseItem> requestItemList, private List<GaoCustomerPackagePurchaseItem> resolvePurchaseItems(String storeId, List<GaoCustomerPackagePurchaseItem> requestItemList,
List<GaoPackageConfigItem> configItemList, boolean canOverridePriceAndQuantity) { List<GaoPackageConfigItem> configItemList, boolean canOverridePriceAndQuantity, boolean canCreateGift) {
Map<String, List<GaoPackageConfigItem>> configItemMap = configItemList.stream() Map<String, List<GaoPackageConfigItem>> configItemMap = configItemList.stream()
.collect(Collectors.groupingBy(GaoPackageConfigItem::getSkuId)); .collect(Collectors.groupingBy(GaoPackageConfigItem::getSkuId));
Map<String, BigDecimal> configBaseQuantityMap = new HashMap<>(); Map<String, BigDecimal> configBaseQuantityMap = new HashMap<>();
@@ -311,23 +346,32 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
Map<String, BigDecimal> requestBaseQuantityMap = new HashMap<>(); Map<String, BigDecimal> requestBaseQuantityMap = new HashMap<>();
List<GaoCustomerPackagePurchaseItem> result = new ArrayList<>(); List<GaoCustomerPackagePurchaseItem> result = new ArrayList<>();
for (GaoCustomerPackagePurchaseItem requestItem : requestItemList) { for (GaoCustomerPackagePurchaseItem requestItem : requestItemList) {
boolean gifted = Boolean.TRUE.equals(requestItem.getGifted());
List<GaoPackageConfigItem> skuConfigItemList = configItemMap.get(requestItem.getSkuId()); List<GaoPackageConfigItem> skuConfigItemList = configItemMap.get(requestItem.getSkuId());
if (gifted) {
TimiException.requiredTrue(canCreateGift, "没有客户套餐创建赠送权限");
} else {
TimiException.requiredTrue(skuConfigItemList != null && !skuConfigItemList.isEmpty(), "购买 SKU 不属于套餐配置"); TimiException.requiredTrue(skuConfigItemList != null && !skuConfigItemList.isEmpty(), "购买 SKU 不属于套餐配置");
}
GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(storeId, requestItem.getSkuId(), requestItem.getSkuUnitId()); GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(storeId, requestItem.getSkuId(), requestItem.getSkuUnitId());
if (!gifted) {
TimiException.requiredTrue(skuConfigItemList.stream().anyMatch(configItem -> 0 <= configItem.getBaseQuantityPerUnitSnapshot() TimiException.requiredTrue(skuConfigItemList.stream().anyMatch(configItem -> 0 <= configItem.getBaseQuantityPerUnitSnapshot()
.compareTo(selection.getUnit().getBaseQuantityPerUnit())), "套餐权益单位不能高于配置单位"); .compareTo(selection.getUnit().getBaseQuantityPerUnit())), "套餐权益单位不能高于配置单位");
}
BigDecimal requestBaseQuantity = toBaseQuantity(requestItem.getTotalQuantity(), selection.getUnit().getBaseQuantityPerUnit(), "套餐权益基础数量最多保留三位小数"); BigDecimal requestBaseQuantity = toBaseQuantity(requestItem.getTotalQuantity(), selection.getUnit().getBaseQuantityPerUnit(), "套餐权益基础数量最多保留三位小数");
if (!gifted) {
requestBaseQuantityMap.merge(requestItem.getSkuId(), requestBaseQuantity, BigDecimal::add); requestBaseQuantityMap.merge(requestItem.getSkuId(), requestBaseQuantity, BigDecimal::add);
}
if (requestItem.getTotalQuantity().signum() == 0) { if (requestItem.getTotalQuantity().signum() == 0) {
continue; continue;
} }
if (!Boolean.TRUE.equals(selection.getSku().getAllowDecimalQuantity())) { if (!Boolean.TRUE.equals(selection.getSku().getAllowDecimalQuantity())) {
TimiException.requiredTrue(requestItem.getTotalQuantity().stripTrailingZeros().scale() <= 0, "该 SKU 不允许小数数量"); TimiException.requiredTrue(requestItem.getTotalQuantity().stripTrailingZeros().scale() <= 0, "该 SKU 不允许小数数量");
} }
GaoPackageConfigItem template = resolveItemTemplate(skuConfigItemList, selection.getUnit().getId());
GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem(); GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem();
item.setSkuId(selection.getSku().getId()); item.setSkuId(selection.getSku().getId());
item.setSkuUnitId(selection.getUnit().getId()); item.setSkuUnitId(selection.getUnit().getId());
item.setGifted(gifted);
item.setProductNameSnapshot(selection.getProduct().getName()); item.setProductNameSnapshot(selection.getProduct().getName());
item.setProductIdSnapshot(selection.getProduct().getId()); item.setProductIdSnapshot(selection.getProduct().getId());
item.setSkuCodeSnapshot(selection.getSku().getCode()); item.setSkuCodeSnapshot(selection.getSku().getCode());
@@ -336,8 +380,14 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
item.setUnitSnapshot(selection.getUnit().getName()); item.setUnitSnapshot(selection.getUnit().getName());
item.setBaseQuantityPerUnitSnapshot(selection.getUnit().getBaseQuantityPerUnit()); item.setBaseQuantityPerUnitSnapshot(selection.getUnit().getBaseQuantityPerUnit());
item.setTotalQuantity(requestItem.getTotalQuantity()); item.setTotalQuantity(requestItem.getTotalQuantity());
if (gifted) {
item.setSort(requestItem.getSort());
item.setRemark(requestItem.getRemark());
} else {
GaoPackageConfigItem template = resolveItemTemplate(skuConfigItemList, selection.getUnit().getId());
item.setSort(template.getSort()); item.setSort(template.getSort());
item.setRemark(template.getRemark()); item.setRemark(template.getRemark());
}
result.add(item); result.add(item);
} }
TimiException.requiredTrue(!result.isEmpty(), "购买套餐至少需要一个产品"); TimiException.requiredTrue(!result.isEmpty(), "购买套餐至少需要一个产品");
@@ -377,9 +427,10 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
List<GaoCustomerPackagePurchaseItem> requestItemList = TimiJava.defaultIfNull(request.getItemList(), List.of()); List<GaoCustomerPackagePurchaseItem> requestItemList = TimiJava.defaultIfNull(request.getItemList(), List.of());
verifyPurchaseItems(requestItemList); verifyPurchaseItems(requestItemList);
TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应购买请求内容不一致"); TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应购买请求内容不一致");
Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), Function.identity())); Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId() + "|"
+ Boolean.TRUE.equals(item.getGifted()), Function.identity()));
for (GaoCustomerPackagePurchaseItem item : requestItemList) { for (GaoCustomerPackagePurchaseItem item : requestItemList) {
GaoSalesOrderItem existingItem = existingItemMap.get(item.getSkuId() + "|" + item.getSkuUnitId()); GaoSalesOrderItem existingItem = existingItemMap.get(item.getSkuId() + "|" + item.getSkuUnitId() + "|" + Boolean.TRUE.equals(item.getGifted()));
TimiException.required(existingItem, "requestNo 对应购买请求内容不一致"); TimiException.required(existingItem, "requestNo 对应购买请求内容不一致");
TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT
&& existingItem.getQuantity().compareTo(item.getTotalQuantity()) == 0 && existingItem.getQuantity().compareTo(item.getTotalQuantity()) == 0
@@ -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.GaoOrderFulfillmentRevokeRequest;
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem; import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.audit.OperationAudit;
import com.imyeyu.api.modules.user.service.RoleChecker; import com.imyeyu.api.modules.user.service.RoleChecker;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
@@ -79,7 +79,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
private final RoleChecker roleChecker; private final RoleChecker roleChecker;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final LoggerService loggerService;
@Override @Override
protected BaseMapper<GaoOrderFulfillment, String> mapper() { protected BaseMapper<GaoOrderFulfillment, String> mapper() {
@@ -147,10 +146,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
return mapper.countPendingBySalesOrderId(salesOrderId); return mapper.countPendingBySalesOrderId(salesOrderId);
} }
@OperationAudit(AuditOperation.GAO_ORDER_FULFILLMENT_CREATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoOrderFulfillment create(GaoOrderFulfillmentCreateRequest request) { public GaoOrderFulfillment create(GaoOrderFulfillmentCreateRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_CREATE", request, () -> createInternal(request)); return createInternal(request);
} }
private GaoOrderFulfillment createInternal(GaoOrderFulfillmentCreateRequest request) { private GaoOrderFulfillment createInternal(GaoOrderFulfillmentCreateRequest request) {
@@ -183,10 +183,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
return detail(fulfillment.getId()); return detail(fulfillment.getId());
} }
@OperationAudit(AuditOperation.GAO_ORDER_FULFILLMENT_PREPARE_STOCK)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoOrderFulfillment prepareStock(GaoOrderFulfillmentPrepareStockRequest request) { public GaoOrderFulfillment prepareStock(GaoOrderFulfillmentPrepareStockRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_PREPARE_STOCK", request, () -> prepareStockInternal(request)); return prepareStockInternal(request);
} }
private GaoOrderFulfillment prepareStockInternal(GaoOrderFulfillmentPrepareStockRequest request) { private GaoOrderFulfillment prepareStockInternal(GaoOrderFulfillmentPrepareStockRequest request) {
@@ -217,10 +218,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
return detail(fulfillment.getId()); return detail(fulfillment.getId());
} }
@OperationAudit(AuditOperation.GAO_ORDER_FULFILLMENT_VERIFY)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoOrderFulfillment verify(GaoOrderFulfillmentActionRequest request) { public GaoOrderFulfillment verify(GaoOrderFulfillmentActionRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_VERIFY", request, () -> verifyInternal(request)); return verifyInternal(request);
} }
private GaoOrderFulfillment verifyInternal(GaoOrderFulfillmentActionRequest request) { private GaoOrderFulfillment verifyInternal(GaoOrderFulfillmentActionRequest request) {
@@ -254,16 +256,18 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
return detail(fulfillment.getId()); return detail(fulfillment.getId());
} }
@OperationAudit(AuditOperation.GAO_ORDER_FULFILLMENT_REJECT)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoOrderFulfillment reject(GaoOrderFulfillmentActionRequest request) { public GaoOrderFulfillment reject(GaoOrderFulfillmentActionRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_REJECT", request, () -> releaseInternal(request, GaoOrderFulfillment.Status.REJECTED)); return releaseInternal(request, GaoOrderFulfillment.Status.REJECTED);
} }
@OperationAudit(AuditOperation.GAO_ORDER_FULFILLMENT_CANCEL)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoOrderFulfillment cancel(GaoOrderFulfillmentActionRequest request) { 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) { private GaoOrderFulfillment cancelInternal(GaoOrderFulfillmentActionRequest request) {
@@ -306,10 +310,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
return detail(fulfillment.getId()); return detail(fulfillment.getId());
} }
@OperationAudit(AuditOperation.GAO_ORDER_FULFILLMENT_REVOKE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoOrderFulfillment revoke(GaoOrderFulfillmentRevokeRequest request) { public GaoOrderFulfillment revoke(GaoOrderFulfillmentRevokeRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_ORDER_FULFILLMENT_REVOKE", request, () -> revokeInternal(request)); return revokeInternal(request);
} }
private GaoOrderFulfillment revokeInternal(GaoOrderFulfillmentRevokeRequest request) { private GaoOrderFulfillment revokeInternal(GaoOrderFulfillmentRevokeRequest request) {
@@ -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.GaoOrderPaymentReceiveRequest;
import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRefundRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRefundRequest;
import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.audit.OperationAudit;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.api.modules.user.service.PermissionChecker; import com.imyeyu.api.modules.user.service.PermissionChecker;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
@@ -44,12 +44,12 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final PermissionChecker permissionChecker; private final PermissionChecker permissionChecker;
private final LoggerService loggerService;
@OperationAudit(AuditOperation.GAO_SALES_ORDER_PAYMENT_RECEIVE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoFundRecord receive(GaoOrderPaymentReceiveRequest request) { 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) { private GaoFundRecord receiveInternal(GaoOrderPaymentReceiveRequest request) {
@@ -80,10 +80,10 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
return record; return record;
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_PAYMENT_VERIFY)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoFundRecord verify(String fundRecordId) { 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, "收款记录不是待核销状态"); TimiException.requiredTrue(record.getOperationType() == GaoFundRecord.OperationType.RECEIVE && record.getStatus() == GaoFundRecord.Status.PENDING, "收款记录不是待核销状态");
GaoSalesOrder order = orderService.getForUpdate(record.getBizId()); GaoSalesOrder order = orderService.getForUpdate(record.getBizId());
@@ -102,7 +102,6 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
pointTriggerService.onSalesOrderPaid(order); pointTriggerService.onSalesOrderPaid(order);
} }
return record; return record;
});
} }
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@@ -140,10 +139,11 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
return record; return record;
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_REFUND)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoFundRecord refund(GaoOrderPaymentRefundRequest request) { 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) { private GaoFundRecord refundInternal(GaoOrderPaymentRefundRequest request) {
@@ -177,10 +177,10 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
return record; return record;
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_CONFIRM_NO_REFUND)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoFundRecord confirmNoRefund(GaoOrderPaymentNoRefundRequest request) { 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()); GaoSalesOrder order = orderService.getForUpdate(request.getSalesOrderId());
String requestNo = requiredText(request.getRequestNo(), "not found requestNo"); String requestNo = requiredText(request.getRequestNo(), "not found requestNo");
@@ -199,13 +199,13 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
order.setRefundStatus(GaoSalesOrder.RefundStatus.REFUNDED); order.setRefundStatus(GaoSalesOrder.RefundStatus.REFUNDED);
orderService.updatePayment(order); orderService.updatePayment(order);
return record; return record;
});
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_FUND_REVOKE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoFundRecord revoke(GaoOrderPaymentRevokeRequest request) { 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) { private GaoFundRecord revokeInternal(GaoOrderPaymentRevokeRequest request) {
@@ -103,20 +103,17 @@ public class GaoOrderReminderServiceImplement implements GaoOrderReminderService
log.warn("GAO 订单提醒跳过不存在的店长用户: storeId[{}], userId[{}]", storeId, manager.getUserId()); log.warn("GAO 订单提醒跳过不存在的店长用户: storeId[{}], userId[{}]", storeId, manager.getUserId());
continue; continue;
} }
String remindedId = "%s:%s:%s:%s".formatted(LocalDate.now(ZoneId.of(ZONE_ID)), storeId, manager.getUserId(), reminderType); String remindedKey = "%s:%s:%s:%s".formatted(LocalDate.now(ZoneId.of(ZONE_ID)), storeId, manager.getUserId(), reminderType);
if (notifyService.existsByBizReminded(NOTIFY_BIZ_TYPE, reminderType, remindedId)) {
continue;
}
long sendAt = Time.now(); long sendAt = Time.now();
Notify notify = new Notify(); Notify notify = new Notify();
notify.setBizType(NOTIFY_BIZ_TYPE); notify.setBizType(NOTIFY_BIZ_TYPE);
notify.setBizRemindedType(reminderType); notify.setBizRemindedType(reminderType);
notify.setBizRemindedId(remindedId); notify.setBizRemindedKey(remindedKey);
notify.setDetailList(List.of( notify.setDetailList(List.of(
createInternalDetail(manager.getUserId(), content, sendAt), createInternalDetail(manager.getUserId(), content, sendAt),
createSmsDetail(user.getPhoneNo(), customerNames, action, content, sendAt) createSmsDetail(user.getPhoneNo(), customerNames, action, content, sendAt)
)); ));
notifyService.create(notify); notifyService.createIfAbsent(notify);
} catch (Exception e) { } catch (Exception e) {
log.error("GAO 订单提醒发送失败: storeId[{}], userId[{}], reminderType[{}]", storeId, manager.getUserId(), reminderType, e); log.error("GAO 订单提醒发送失败: storeId[{}], userId[{}], reminderType[{}]", storeId, manager.getUserId(), reminderType, e);
} }
@@ -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.GaoProduct;
import com.imyeyu.api.modules.gao.entity.GaoProductSku; import com.imyeyu.api.modules.gao.entity.GaoProductSku;
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit; import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.audit.OperationAudit;
import com.imyeyu.api.modules.gao.mapper.GaoPackageConfigItemMapper; import com.imyeyu.api.modules.gao.mapper.GaoPackageConfigItemMapper;
import com.imyeyu.api.modules.gao.mapper.GaoPackageConfigMapper; import com.imyeyu.api.modules.gao.mapper.GaoPackageConfigMapper;
import com.imyeyu.api.modules.gao.service.GaoPackageConfigService; import com.imyeyu.api.modules.gao.service.GaoPackageConfigService;
@@ -44,7 +44,6 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService<GaoP
private final GaoPackageConfigItemMapper itemMapper; private final GaoPackageConfigItemMapper itemMapper;
private final GaoProductSkuService skuService; private final GaoProductSkuService skuService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final LoggerService loggerService;
@Override @Override
protected BaseMapper<GaoPackageConfig, String> mapper() { protected BaseMapper<GaoPackageConfig, String> mapper() {
@@ -90,13 +89,11 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService<GaoP
.collect(java.util.stream.Collectors.groupingBy(GaoPackageConfigItem::getPackageConfigId)); .collect(java.util.stream.Collectors.groupingBy(GaoPackageConfigItem::getPackageConfigId));
} }
@OperationAudit(AuditOperation.GAO_PACKAGE_CONFIG_CREATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void create(GaoPackageConfig config) { public void create(GaoPackageConfig config) {
loggerService.execute(Logger.Module.GAO, "GAO_PACKAGE_CONFIG_CREATE", config, () -> {
createInternal(config); createInternal(config);
return config;
});
} }
private void createInternal(GaoPackageConfig config) { private void createInternal(GaoPackageConfig config) {
@@ -109,13 +106,11 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService<GaoP
saveItems(config.getId(), config.getItemList()); saveItems(config.getId(), config.getItemList());
} }
@OperationAudit(AuditOperation.GAO_PACKAGE_CONFIG_UPDATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void update(GaoPackageConfig request) { public void update(GaoPackageConfig request) {
loggerService.execute(Logger.Module.GAO, "GAO_PACKAGE_CONFIG_UPDATE", request, () -> {
updateInternal(request); updateInternal(request);
return request;
});
} }
private void updateInternal(GaoPackageConfig request) { private void updateInternal(GaoPackageConfig request) {
@@ -141,15 +136,13 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService<GaoP
saveItems(dbConfig.getId(), request.getItemList()); saveItems(dbConfig.getId(), request.getItemList());
} }
@OperationAudit(AuditOperation.GAO_PACKAGE_CONFIG_DELETE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void delete(String id) { public void delete(String id) {
loggerService.execute(Logger.Module.GAO, "GAO_PACKAGE_CONFIG_DELETE", id, () -> {
GaoPackageConfig config = get(id); GaoPackageConfig config = get(id);
TimiException.required(config, "not found package config"); TimiException.required(config, "not found package config");
super.delete(id); super.delete(id);
return id;
});
} }
/// 套餐配置只允许逻辑删除 /// 套餐配置只允许逻辑删除
@@ -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.GaoProductCategoryService;
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.vo.GaoProductCategoryPage; import com.imyeyu.api.modules.gao.vo.GaoProductCategoryPage;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.audit.OperationAudit;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
@@ -37,7 +37,6 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService<Ga
private final GaoProductCategoryMapper mapper; private final GaoProductCategoryMapper mapper;
private final GaoStoreScopeService storeScopeService; private final GaoStoreScopeService storeScopeService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final LoggerService loggerService;
@Override @Override
protected BaseMapper<GaoProductCategory, String> mapper() { protected BaseMapper<GaoProductCategory, String> mapper() {
@@ -59,6 +58,7 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService<Ga
return mapper.selectByStoreId(storeId); return mapper.selectByStoreId(storeId);
} }
@OperationAudit(AuditOperation.GAO_PRODUCT_CATEGORY_SORT)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void sort(List<String> idList) { public void sort(List<String> idList) {
@@ -66,7 +66,6 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService<Ga
return; return;
} }
List<String> distinctIdList = new ArrayList<>(new LinkedHashSet<>(idList)); List<String> distinctIdList = new ArrayList<>(new LinkedHashSet<>(idList));
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_SORT", distinctIdList, () -> {
Map<String, GaoProductCategory> categoryMap = mapper.selectByIdList(new LinkedHashSet<>(distinctIdList)).stream() Map<String, GaoProductCategory> categoryMap = mapper.selectByIdList(new LinkedHashSet<>(distinctIdList)).stream()
.collect(Collectors.toMap(GaoProductCategory::getId, Function.identity())); .collect(Collectors.toMap(GaoProductCategory::getId, Function.identity()));
TimiException.requiredTrue(categoryMap.size() == distinctIdList.size(), "产品分类列表包含不存在的分类"); TimiException.requiredTrue(categoryMap.size() == distinctIdList.size(), "产品分类列表包含不存在的分类");
@@ -74,23 +73,20 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService<Ga
storeScopeService.checkStoreId(category.getStoreId()); storeScopeService.checkStoreId(category.getStoreId());
} }
mapper.updateSort(distinctIdList, Time.now()); mapper.updateSort(distinctIdList, Time.now());
});
} }
@OperationAudit(AuditOperation.GAO_PRODUCT_CATEGORY_CREATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void create(GaoProductCategory category) { public void create(GaoProductCategory category) {
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_CREATE", category, () -> {
prepare(category, null); prepare(category, null);
super.create(category); super.create(category);
return category;
});
} }
@OperationAudit(AuditOperation.GAO_PRODUCT_CATEGORY_UPDATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void update(GaoProductCategory request) { public void update(GaoProductCategory request) {
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_UPDATE", request, () -> {
TimiException.required(request, "not found category"); TimiException.required(request, "not found category");
TimiException.required(request.getId(), "not found category.id"); TimiException.required(request.getId(), "not found category.id");
GaoProductCategory category = get(request.getId()); GaoProductCategory category = get(request.getId());
@@ -101,14 +97,12 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService<Ga
category.setStatus(request.getStatus()); category.setStatus(request.getStatus());
prepare(category, category.getId()); prepare(category, category.getId());
super.update(category); super.update(category);
return category;
});
} }
@OperationAudit(AuditOperation.GAO_PRODUCT_CATEGORY_DISABLE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void disable(String id) { public void disable(String id) {
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CATEGORY_DISABLE", id, () -> {
GaoProductCategory category = get(id); GaoProductCategory category = get(id);
TimiException.required(category, "not found category"); TimiException.required(category, "not found category");
storeScopeService.checkStoreId(category.getStoreId()); storeScopeService.checkStoreId(category.getStoreId());
@@ -116,8 +110,6 @@ public class GaoProductCategoryServiceImplement extends AbstractEntityService<Ga
category.setUpdatedAt(Time.now()); category.setUpdatedAt(Time.now());
category.setSort(TimiJava.defaultIfNull(category.getSort(), 0)); category.setSort(TimiJava.defaultIfNull(category.getSort(), 0));
super.update(category); super.update(category);
return category;
});
} }
@Override @Override
@@ -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.GaoProductSkuService;
import com.imyeyu.api.modules.gao.service.GaoProductSpecService; import com.imyeyu.api.modules.gao.service.GaoProductSpecService;
import com.imyeyu.api.modules.gao.vo.GaoProductPage; import com.imyeyu.api.modules.gao.vo.GaoProductPage;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.audit.OperationAudit;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
@@ -52,7 +52,6 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
private final ObjectProvider<GaoProductSkuService> skuServiceProvider; private final ObjectProvider<GaoProductSkuService> skuServiceProvider;
private final ObjectProvider<GaoProductSpecService> specServiceProvider; private final ObjectProvider<GaoProductSpecService> specServiceProvider;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final LoggerService loggerService;
@Override @Override
protected BaseMapper<GaoProduct, String> mapper() { protected BaseMapper<GaoProduct, String> mapper() {
@@ -100,24 +99,22 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
return list.stream().filter(product -> !product.getSkuList().isEmpty()).toList(); return list.stream().filter(product -> !product.getSkuList().isEmpty()).toList();
} }
@OperationAudit(AuditOperation.GAO_PRODUCT_CREATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void create(GaoProduct product) { public void create(GaoProduct product) {
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CREATE", product, () -> {
prepare(product, null); prepare(product, null);
super.create(product); super.create(product);
Map<String, String> specValueIdMap = saveSpecs(product, product.getSpecList()); Map<String, String> specValueIdMap = saveSpecs(product, product.getSpecList());
normalizeSkuSpecValueIds(product.getSkuList(), specValueIdMap); normalizeSkuSpecValueIds(product.getSkuList(), specValueIdMap);
saveSkus(product, product.getSkuList()); saveSkus(product, product.getSkuList());
attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), product.getAttachmentList()); attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), product.getAttachmentList());
return product;
});
} }
@OperationAudit(AuditOperation.GAO_PRODUCT_UPDATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void update(GaoProduct request) { public void update(GaoProduct request) {
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_UPDATE", request, () -> {
TimiException.required(request, "not found product"); TimiException.required(request, "not found product");
TimiException.required(request.getId(), "not found product.id"); TimiException.required(request.getId(), "not found product.id");
GaoProduct product = get(request.getId()); GaoProduct product = get(request.getId());
@@ -139,14 +136,12 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
if (request.getAttachmentList() != null) { if (request.getAttachmentList() != null) {
attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), request.getAttachmentList()); attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), request.getAttachmentList());
} }
return product;
});
} }
@OperationAudit(AuditOperation.GAO_PRODUCT_DISABLE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void disable(String id) { public void disable(String id) {
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_DISABLE", id, () -> {
GaoProduct product = get(id); GaoProduct product = get(id);
TimiException.required(product, "not found product"); 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"); TimiException.requiredTrue(skuServiceProvider.getObject().listByProductId(id).stream().noneMatch(sku -> sku.getStatus() == com.imyeyu.api.modules.gao.entity.GaoProductSku.Status.ACTIVE), "请先停用产品的全部 SKU");
@@ -154,8 +149,6 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
product.setUpdatedBy(userLoginService.getRequireLoginUserId()); product.setUpdatedBy(userLoginService.getRequireLoginUserId());
product.setUpdatedAt(Time.now()); product.setUpdatedAt(Time.now());
super.update(product); super.update(product);
return product;
});
} }
@Override @Override
@@ -76,6 +76,16 @@ public class GaoProductSkuServiceImplement extends AbstractEntityService<GaoProd
return sku; return sku;
} }
@Override
public List<GaoProductSku> listByIdList(Collection<String> idList) {
if (idList == null || idList.isEmpty()) {
return List.of();
}
List<GaoProductSku> result = mapper.selectByIdList(idList);
fillTransient(result);
return result;
}
@Override @Override
public List<GaoProductSku> listByProductId(String productId) { public List<GaoProductSku> listByProductId(String productId) {
TimiException.required(productId, "not found productId"); TimiException.required(productId, "not found productId");
@@ -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.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.entity.GaoProduct; 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.GaoProductSku;
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit; 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.entity.GaoProductStockRecord;
import com.imyeyu.api.modules.gao.mapper.GaoProductStockMapper; import com.imyeyu.api.modules.gao.mapper.GaoProductStockMapper;
import com.imyeyu.api.modules.gao.service.GaoProductService; 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.GaoProductSkuService;
import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService; 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.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.GaoProductStockChangeRequest;
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage; import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView; 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.gao.vo.GaoStockChangeItem;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.audit.OperationAudit;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
@@ -59,7 +59,6 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
private final GaoProductSkuService skuService; private final GaoProductSkuService skuService;
private final GaoStoreScopeService storeScopeService; private final GaoStoreScopeService storeScopeService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final LoggerService loggerService;
@Override @Override
public PageResult<GaoProductStock> pageByQuery(GaoProductStockPage page) { public PageResult<GaoProductStock> pageByQuery(GaoProductStockPage page) {
@@ -112,40 +111,46 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
return stock; return stock;
} }
@OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_INITIAL_IN)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoProductStock initialIn(GaoProductStockChangeRequest request) { 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) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoProductStock manualIn(GaoProductStockChangeRequest request) { 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) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoProductStock manualOut(GaoProductStockChangeRequest request) { 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) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoProductStock manualRevoke(GaoProductStockManualRevokeRequest request) { 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) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoProductStock stocktakeGain(GaoProductStockChangeRequest request) { 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) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoProductStock stocktakeLoss(GaoProductStockChangeRequest request) { 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 @Override
@@ -216,7 +221,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
GaoStockChangeItem item = GaoStockChangeItem.of(product, sku, unit, quantity, baseQuantity); GaoStockChangeItem item = GaoStockChangeItem.of(product, sku, unit, quantity, baseQuantity);
item.setSkuSpecSnapshot(skuService.getSkuSpecSnapshot(sku)); item.setSkuSpecSnapshot(skuService.getSkuSpecSnapshot(sku));
List<GaoProductStock> result = executeInternal(storeId, sku.getId(), List.of(item), requestNo, List<GaoProductStock> 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(); return result.getFirst();
} }
@@ -295,11 +300,11 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo, private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
String operatorUserId, GaoProductStockRecord.OperationType operationType, String remark) { 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<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo, private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
String operatorUserId, GaoProductStockRecord.OperationType operationType, String remark, List<Attachment> attachmentList) { String operatorUserId, GaoProductStockRecord.OperationType operationType, Long occurredAt, String remark, List<Attachment> attachmentList) {
TimiException.required(storeId, "not found storeId"); TimiException.required(storeId, "not found storeId");
TimiException.required(bizId, "not found stock bizId"); TimiException.required(bizId, "not found stock bizId");
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "库存变更至少需要一个产品"); TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "库存变更至少需要一个产品");
@@ -360,7 +365,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
record.setSourceRecordId(item.getSourceRecordId()); record.setSourceRecordId(item.getSourceRecordId());
record.setRequestNo(normalizedRequestNo); record.setRequestNo(normalizedRequestNo);
record.setOperatorUserId(operatorUserId); record.setOperatorUserId(operatorUserId);
record.setOccurredAt(Time.now()); record.setOccurredAt(TimiJava.defaultIfNull(occurredAt, Time.now()));
record.setRemark(remark); record.setRemark(remark);
recordService.createOperationRecord(record, attachmentList); recordService.createOperationRecord(record, attachmentList);
stock.setOnHandQuantity(onHandAfter); stock.setOnHandQuantity(onHandAfter);
@@ -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.GaoSalesOrderItemRequest;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.audit.AuditOperation;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.audit.OperationAudit;
import com.imyeyu.api.modules.user.service.PermissionChecker; import com.imyeyu.api.modules.user.service.PermissionChecker;
import com.imyeyu.api.modules.user.service.RoleChecker; import com.imyeyu.api.modules.user.service.RoleChecker;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
@@ -91,7 +91,6 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
private final PermissionChecker permissionChecker; private final PermissionChecker permissionChecker;
private final RoleChecker roleChecker; private final RoleChecker roleChecker;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final LoggerService loggerService;
@Override @Override
protected BaseMapper<GaoSalesOrder, String> mapper() { protected BaseMapper<GaoSalesOrder, String> mapper() {
@@ -116,6 +115,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
TimiException.required(order, "not found sales order"); TimiException.required(order, "not found sales order");
storeScopeService.checkStoreId(order.getStoreId()); storeScopeService.checkStoreId(order.getStoreId());
List<GaoSalesOrderItem> itemList = itemMapper.selectBySalesOrderId(order.getId()); List<GaoSalesOrderItem> itemList = itemMapper.selectBySalesOrderId(order.getId());
fillItemDisplayInfo(itemList);
for (GaoSalesOrderItem item : itemList) { for (GaoSalesOrderItem item : itemList) {
fillItemBalance(item); fillItemBalance(item);
} }
@@ -139,23 +139,23 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
return order; return order;
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_DIRECT_PRODUCT_CREATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoSalesOrder createDirectProductOrder(GaoDirectProductOrderCreateRequest request) { public GaoSalesOrder createDirectProductOrder(GaoDirectProductOrderCreateRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_DIRECT_PRODUCT_CREATE", request, () -> createDirectInternal(request)); return createDirectInternal(request);
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_UPDATE)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoSalesOrder updateRemarkAndAttachments(GaoSalesOrderUpdateRequest request) { 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, "not found sales order update request");
TimiException.required(request.getId(), "not found sales order.id"); TimiException.required(request.getId(), "not found sales order.id");
GaoSalesOrder order = getForUpdate(request.getId()); GaoSalesOrder order = getForUpdate(request.getId());
TimiException.requiredTrue(1 == mapper.updateRemark(order.getId(), request.getRemark()), "订单备注更新失败"); TimiException.requiredTrue(1 == mapper.updateRemark(order.getId(), request.getRemark()), "订单备注更新失败");
attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, order.getId(), request.getAttachmentList()); attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, order.getId(), request.getAttachmentList());
return detail(order.getId()); return detail(order.getId());
});
} }
private GaoSalesOrder createDirectInternal(GaoDirectProductOrderCreateRequest request) { private GaoSalesOrder createDirectInternal(GaoDirectProductOrderCreateRequest request) {
@@ -222,10 +222,11 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
data.getSaleAmount(), data.getItemList()); data.getSaleAmount(), data.getItemList());
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_CANCEL)
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoSalesOrder cancel(GaoSalesOrderCancelRequest request) { public GaoSalesOrder cancel(GaoSalesOrderCancelRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_CANCEL", request, () -> cancelInternal(request)); return cancelInternal(request);
} }
private GaoSalesOrder cancelInternal(GaoSalesOrderCancelRequest request) { private GaoSalesOrder cancelInternal(GaoSalesOrderCancelRequest request) {
@@ -452,9 +453,11 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
for (GaoSalesOrderItem item : itemList) { for (GaoSalesOrderItem item : itemList) {
TimiException.required(item, "销售订单明细不能为空"); TimiException.required(item, "销售订单明细不能为空");
TimiException.requiredTrue(item.getItemType() == expectedItemType, "订单项类型与订单类型不匹配"); TimiException.requiredTrue(item.getItemType() == expectedItemType, "订单项类型与订单类型不匹配");
item.setGifted(Boolean.TRUE.equals(item.getGifted()));
TimiException.required(item.getSkuId(), "订单项 SKU 不能为空"); TimiException.required(item.getSkuId(), "订单项 SKU 不能为空");
TimiException.required(item.getSkuUnitId(), "订单项 SKU 单位不能为空"); TimiException.required(item.getSkuUnitId(), "订单项 SKU 单位不能为空");
TimiException.requiredTrue(skuUnitKeySet.add(item.getSkuId() + "|" + item.getSkuUnitId()), String skuUnitKey = item.getSkuId() + "|" + item.getSkuUnitId() + "|" + item.getGifted();
TimiException.requiredTrue(skuUnitKeySet.add(skuUnitKey),
"同一订单不能重复 SKU 和单位"); "同一订单不能重复 SKU 和单位");
TimiException.required(item.getQuantity(), "订单项数量不能为空"); TimiException.required(item.getQuantity(), "订单项数量不能为空");
TimiException.requiredTrue(0 < item.getQuantity().signum(), "订单项数量必须大于 0"); TimiException.requiredTrue(0 < item.getQuantity().signum(), "订单项数量必须大于 0");
@@ -510,11 +513,14 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
List<GaoSalesOrderItem> existingItemList = itemMapper.selectBySalesOrderId(existing.getId()); List<GaoSalesOrderItem> existingItemList = itemMapper.selectBySalesOrderId(existing.getId());
TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应订单内容不一致"); TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应订单内容不一致");
Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream() Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream()
.collect(java.util.stream.Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), java.util.function.Function.identity())); .collect(java.util.stream.Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId() + "|" + Boolean.TRUE.equals(item.getGifted()),
java.util.function.Function.identity()));
for (GaoSalesOrderItem requestItem : requestItemList) { for (GaoSalesOrderItem requestItem : requestItemList) {
GaoSalesOrderItem existingItem = existingItemMap.get(requestItem.getSkuId() + "|" + requestItem.getSkuUnitId()); GaoSalesOrderItem existingItem = existingItemMap.get(requestItem.getSkuId() + "|" + requestItem.getSkuUnitId() + "|"
+ Boolean.TRUE.equals(requestItem.getGifted()));
TimiException.required(existingItem, "requestNo 对应订单内容不一致"); TimiException.required(existingItem, "requestNo 对应订单内容不一致");
TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT
&& Boolean.TRUE.equals(existingItem.getGifted()) == Boolean.TRUE.equals(requestItem.getGifted())
&& existingItem.getQuantity().compareTo(requestItem.getQuantity()) == 0 && existingItem.getQuantity().compareTo(requestItem.getQuantity()) == 0
&& existingItem.getSort().equals(requestItem.getSort()) && existingItem.getSort().equals(requestItem.getSort())
&& sameText(existingItem.getRemark(), requestItem.getRemark()), "requestNo 对应订单内容不一致"); && sameText(existingItem.getRemark(), requestItem.getRemark()), "requestNo 对应订单内容不一致");
@@ -562,6 +568,37 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
item.setRemainingBaseQuantity(baseQuantity.subtract(fulfilledBaseQuantity)); item.setRemainingBaseQuantity(baseQuantity.subtract(fulfilledBaseQuantity));
} }
/// 填充订单项数量展示所需的 SKU 信息
///
/// @param itemList 订单项列表
private void fillItemDisplayInfo(List<GaoSalesOrderItem> itemList) {
if (itemList == null || itemList.isEmpty()) {
return;
}
List<String> skuIdList = itemList.stream()
.map(GaoSalesOrderItem::getSkuId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
if (skuIdList.isEmpty()) {
return;
}
Map<String, GaoProductSku> 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<GaoSalesOrderItem> itemList) { private void fillItemAttachments(List<GaoSalesOrderItem> itemList) {
if (itemList == null || itemList.isEmpty()) { if (itemList == null || itemList.isEmpty()) {
return; return;
@@ -17,6 +17,9 @@ public class GaoCustomerPackagePurchaseItem {
/// SKU 单位 ID /// SKU 单位 ID
private String skuUnitId; private String skuUnitId;
/// true 表示请求将该明细作为赠送权益创建
private Boolean gifted;
/// 产品名称快照,由后端生成 /// 产品名称快照,由后端生成
private String productNameSnapshot; private String productNameSnapshot;
@@ -1,6 +1,7 @@
package com.imyeyu.api.modules.gao.vo; package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.java.bean.BasePage; import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.spring.bean.Page;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -12,7 +13,7 @@ import java.util.List;
/// @since 2026-08-13 /// @since 2026-08-13
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class GaoCustomerPage extends BasePage { public class GaoCustomerPage extends Page<GaoCustomer> {
/// 门店 ID /// 门店 ID
private String storeId; private String storeId;
@@ -28,6 +28,9 @@ public class GaoProductStockChangeRequest {
/// 业务数量 /// 业务数量
private BigDecimal quantity; private BigDecimal quantity;
/// 库存发生时间,毫秒时间戳
private Long occurredAt;
/// 备注 /// 备注
private String remark; private String remark;
@@ -0,0 +1,51 @@
package com.imyeyu.api.modules.system.audit;
import com.imyeyu.api.modules.system.entity.Logger;
import lombok.AllArgsConstructor;
import lombok.Getter;
/// 操作审计编码
///
/// @author Codex
/// @since 2026-09-09
@Getter
@AllArgsConstructor
public enum AuditOperation {
COMMON_NOTIFY_CREATE(Logger.Module.COMMON),
COMMON_NOTIFY_DELETE(Logger.Module.COMMON),
GAO_CUSTOMER_PACKAGE_PURCHASE(Logger.Module.GAO),
GAO_CUSTOMER_PACKAGE_CANCEL(Logger.Module.GAO),
GAO_SALES_ORDER_PAYMENT_RECEIVE(Logger.Module.GAO),
GAO_SALES_ORDER_PAYMENT_VERIFY(Logger.Module.GAO),
GAO_SALES_ORDER_REFUND(Logger.Module.GAO),
GAO_SALES_ORDER_CONFIRM_NO_REFUND(Logger.Module.GAO),
GAO_SALES_ORDER_FUND_REVOKE(Logger.Module.GAO),
GAO_ORDER_FULFILLMENT_CREATE(Logger.Module.GAO),
GAO_ORDER_FULFILLMENT_PREPARE_STOCK(Logger.Module.GAO),
GAO_ORDER_FULFILLMENT_VERIFY(Logger.Module.GAO),
GAO_ORDER_FULFILLMENT_REJECT(Logger.Module.GAO),
GAO_ORDER_FULFILLMENT_CANCEL(Logger.Module.GAO),
GAO_ORDER_FULFILLMENT_REVOKE(Logger.Module.GAO),
GAO_PACKAGE_CONFIG_CREATE(Logger.Module.GAO),
GAO_PACKAGE_CONFIG_UPDATE(Logger.Module.GAO),
GAO_PACKAGE_CONFIG_DELETE(Logger.Module.GAO),
GAO_PRODUCT_CATEGORY_SORT(Logger.Module.GAO),
GAO_PRODUCT_CATEGORY_CREATE(Logger.Module.GAO),
GAO_PRODUCT_CATEGORY_UPDATE(Logger.Module.GAO),
GAO_PRODUCT_CATEGORY_DISABLE(Logger.Module.GAO),
GAO_PRODUCT_CREATE(Logger.Module.GAO),
GAO_PRODUCT_UPDATE(Logger.Module.GAO),
GAO_PRODUCT_DISABLE(Logger.Module.GAO),
GAO_PRODUCT_STOCK_INITIAL_IN(Logger.Module.GAO),
GAO_PRODUCT_STOCK_MANUAL_IN(Logger.Module.GAO),
GAO_PRODUCT_STOCK_MANUAL_OUT(Logger.Module.GAO),
GAO_PRODUCT_STOCK_MANUAL_REVOKE(Logger.Module.GAO),
GAO_PRODUCT_STOCK_STOCKTAKE_GAIN(Logger.Module.GAO),
GAO_PRODUCT_STOCK_STOCKTAKE_LOSS(Logger.Module.GAO),
GAO_SALES_ORDER_DIRECT_PRODUCT_CREATE(Logger.Module.GAO),
GAO_SALES_ORDER_UPDATE(Logger.Module.GAO),
GAO_SALES_ORDER_CANCEL(Logger.Module.GAO);
final Logger.Module module;
}
@@ -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<String> 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) + "...";
}
}
@@ -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);
}
@@ -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();
}
@@ -0,0 +1,83 @@
package com.imyeyu.api.modules.system.audit;
import com.imyeyu.api.modules.system.entity.Logger;
import com.imyeyu.java.TimiJava;
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.name());
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(TimiJava.serializeThrowable(e));
logger.setDuration(duration(startNanos));
log.error("业务操作失败: {}", operation.name(), 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);
}
}
@@ -6,8 +6,6 @@ import com.imyeyu.spring.service.DestroyableService;
import com.imyeyu.spring.service.GettableService; import com.imyeyu.spring.service.GettableService;
import com.imyeyu.spring.service.PageableService; import com.imyeyu.spring.service.PageableService;
import java.util.function.Supplier;
/** /**
* 日志服务 * 日志服务
* *
@@ -18,51 +16,6 @@ public interface LoggerService extends GettableService<Logger, String>, Pageable
void createSync(Logger logger); void createSync(Logger logger);
/// 执行并记录有返回值的业务操作
///
/// @param logger 日志对象,调用方负责设置模块、操作和请求内容
/// @param action 业务操作
/// @param <T> 返回值类型
/// @return 业务操作返回值
<T> T execute(Logger logger, Supplier<T> 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 <T> 返回值类型
/// @return 业务操作返回值
default <T> T execute(Logger.Module module, String operation, Object content, Supplier<T> 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); void create(Logger.Module module, Logger.Level level, String operation, String content, String result);
default void info(Logger.Module module, String operation) { default void info(Logger.Module module, String operation) {
@@ -1,37 +1,41 @@
package com.imyeyu.api.modules.system.service.implement; package com.imyeyu.api.modules.system.service.implement;
import com.imyeyu.api.modules.system.entity.Logger; 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.mapper.LoggerMapper;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.service.LoggerService;
import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.entity.IDEntity;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier;
import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.function.Supplier;
/** /**
* 日志服务实现 * 日志服务实现
* *
* @author 夜雨 * @author 夜雨
* @since 2026-05-13 14:46 * @since 2026-05-13 14:46
*/ */
@Slf4j
@Service @Service
@RequiredArgsConstructor public class LoggerServiceImplement extends AbstractEntityService<Logger, String> implements LoggerService, AuditRecorder {
public class LoggerServiceImplement extends AbstractEntityService<Logger, String> implements LoggerService {
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final LoggerMapper mapper; 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 @Override
protected BaseMapper<Logger, String> mapper() { protected BaseMapper<Logger, String> mapper() {
return mapper; return mapper;
@@ -39,9 +43,13 @@ public class LoggerServiceImplement extends AbstractEntityService<Logger, String
@Override @Override
public void create(Logger logger) { public void create(Logger logger) {
// 默认异步
fillContext(logger); fillContext(logger);
Thread.startVirtualThread(() -> createSync(logger)); auditTaskExecutor.execute(() -> createSync(logger));
}
@Override
public void record(Logger logger) {
create(logger);
} }
@Override @Override
@@ -50,32 +58,6 @@ public class LoggerServiceImplement extends AbstractEntityService<Logger, String
super.create(logger); super.create(logger);
} }
@Override
public <T> T execute(Logger logger, Supplier<T> 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) { public void create(Logger.Module module, Logger.Level level, String operation, String content, String result) {
Logger log = new Logger(module, level, operation); Logger log = new Logger(module, level, operation);
log.setContent(content); log.setContent(content);
@@ -0,0 +1,5 @@
ALTER TABLE `gao_sales_order_item`
ADD COLUMN `gifted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'true 为赠送权益' AFTER `item_type`,
DROP KEY `uk_gao_sales_order_item_sku_unit`,
ADD UNIQUE KEY `uk_gao_sales_order_item_sku_unit_gifted` (`sales_order_id`, `sku_id`, `sku_unit_id`, `gifted`),
ADD KEY `idx_gao_sales_order_item_order_gifted` (`sales_order_id`, `gifted`, `sort`, `created_at`);
@@ -0,0 +1,25 @@
ALTER TABLE `notify`
CHANGE COLUMN `biz_reminded_id` `biz_reminded_key` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '业务提醒查重键',
ADD COLUMN `biz_reminded_active_key` TINYINT(1) GENERATED ALWAYS AS (IF(`deleted_at` IS NULL, 1, NULL)) STORED COMMENT '未删除查重键' INVISIBLE AFTER `deleted_at`,
DROP INDEX `idx_biz_reminded_created`,
ADD INDEX `idx_biz_reminded_key_created`(`biz_type` ASC, `biz_reminded_type` ASC, `biz_reminded_key` ASC, `created_at` ASC) USING BTREE;
UPDATE `notify` target
INNER JOIN (
SELECT
id,
ROW_NUMBER() OVER (
PARTITION BY biz_type, biz_reminded_type, biz_reminded_key
ORDER BY created_at DESC, id DESC
) AS rn
FROM `notify`
WHERE deleted_at IS NULL
AND biz_type IS NOT NULL
AND biz_reminded_type IS NOT NULL
AND biz_reminded_key IS NOT NULL
) duplicate ON duplicate.id = target.id
SET target.deleted_at = UNIX_TIMESTAMP() * 1000
WHERE duplicate.rn > 1;
ALTER TABLE `notify`
ADD UNIQUE INDEX `uk_biz_reminded_key_active`(`biz_type` ASC, `biz_reminded_type` ASC, `biz_reminded_key` ASC, `biz_reminded_active_key` ASC) USING BTREE;
@@ -1,6 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imyeyu.api.modules.common.mapper.NotifyMapper"> <mapper namespace="com.imyeyu.api.modules.common.mapper.NotifyMapper">
<insert id="insertIfAbsent">
INSERT INTO `notify` (
`id`,
`biz_type`,
`biz_reminded_type`,
`biz_reminded_key`,
`args`,
`created_by`,
`created_at`,
`updated_at`,
`deleted_at`
) VALUES (
#{id},
#{bizType},
#{bizRemindedType},
#{bizRemindedKey},
#{args},
#{createdBy},
#{createdAt},
#{updatedAt},
#{deletedAt}
)
ON DUPLICATE KEY UPDATE `id` = `id`
</insert>
<select id="selectManagementPage" resultType="com.imyeyu.api.modules.common.vo.NotifyManagementItem"> <select id="selectManagementPage" resultType="com.imyeyu.api.modules.common.vo.NotifyManagementItem">
SELECT SELECT
n.`id`, n.`id`,
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imyeyu.api.modules.common.mapper.SettingMapper">
<select id="selectPublicByQuery" resultType="com.imyeyu.api.modules.common.entity.Setting">
SELECT *
FROM `setting`
WHERE `biz_type` = #{bizType}
AND `is_private` = 0
<if test="bizId != null and bizId != ''">
AND `biz_id` = #{bizId}
</if>
<if test="codeList != null and !codeList.isEmpty()">
AND `code` IN
<foreach collection="codeList" item="code" separator="," open="(" close=")">
#{code}
</foreach>
</if>
AND (`deleted_at` IS NULL OR FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) &lt; `deleted_at`)
</select>
</mapper>
@@ -18,7 +18,6 @@
</if> </if>
AND `deleted_at` IS NULL AND `deleted_at` IS NULL
</select> </select>
<sql id="queryWhere"> <sql id="queryWhere">
`c`.`deleted_at` IS NULL `c`.`deleted_at` IS NULL
<if test="storeId != null and storeId != ''"> <if test="storeId != null and storeId != ''">
@@ -64,7 +63,18 @@
FROM `gao_customer` `c` FROM `gao_customer` `c`
WHERE WHERE
<include refid="queryWhere"/> <include refid="queryWhere"/>
<choose>
<when test="orderMap != null and !orderMap.isEmpty()">
ORDER BY
<foreach collection="orderMap" index="key" item="value" separator=",">
<bind name="underscoreKey" value="@com.imyeyu.utils.Text@camelCase2underscore(key)"/>
${underscoreKey} ${value}
</foreach>
</when>
<otherwise>
ORDER BY `c`.`created_at` DESC, `c`.`id` DESC ORDER BY `c`.`created_at` DESC, `c`.`id` DESC
</otherwise>
</choose>
LIMIT #{offset}, #{limit} LIMIT #{offset}, #{limit}
</select> </select>
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoProductSkuMapper"> <mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoProductSkuMapper">
<select id="selectByIdList" resultType="com.imyeyu.api.modules.gao.entity.GaoProductSku">
SELECT * FROM `gao_product_sku` WHERE `deleted_at` IS NULL
<choose><when test="idList != null and !idList.isEmpty()">AND `id` IN <foreach collection="idList" item="id" open="(" separator="," close=")">#{id}</foreach></when><otherwise>AND FALSE</otherwise></choose>
ORDER BY `id`
</select>
<select id="selectByProductId" resultType="com.imyeyu.api.modules.gao.entity.GaoProductSku"> <select id="selectByProductId" resultType="com.imyeyu.api.modules.gao.entity.GaoProductSku">
SELECT * FROM `gao_product_sku` WHERE `product_id` = #{productId} AND `deleted_at` IS NULL ORDER BY `sort`, `created_at` SELECT * FROM `gao_product_sku` WHERE `product_id` = #{productId} AND `deleted_at` IS NULL ORDER BY `sort`, `created_at`
</select> </select>