diff --git a/src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java b/src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java index 8bce249..83d8dab 100644 --- a/src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java +++ b/src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java @@ -31,7 +31,8 @@ public enum LogModuleEnum implements IBaseEnum { OTHER(99, "其他"), POINT_ACCOUNT(100, "积分账户"), POINT_RECORD(101, "积分流水"), - POINT_RULE(102, "积分规则"); + POINT_RULE(102, "积分规则"), + SIGN_RECORD(103, "签到记录"); @EnumValue private final Integer value; diff --git a/src/main/java/com/youlai/boot/mini/controller/PointAdminController.java b/src/main/java/com/youlai/boot/mini/controller/PointAdminController.java index 3052c57..4390e1d 100644 --- a/src/main/java/com/youlai/boot/mini/controller/PointAdminController.java +++ b/src/main/java/com/youlai/boot/mini/controller/PointAdminController.java @@ -37,14 +37,15 @@ public class PointAdminController { @Operation(summary = "分页查询用户积分账户") @GetMapping("/accounts") - @PreAuthorize("@ss.hasPerm('mini:point:account:list')") +// @PreAuthorize("@ss.hasPerm('mini:point:account:list')") + @Log(module = LogModuleEnum.POINT_ACCOUNT, value = ActionTypeEnum.LIST) public PageResult pageAccount(PointAccountQuery query) { return PageResult.success(pointAccountService.pageAccount(query)); } @Operation(summary = "查询积分规则列表") @GetMapping - @PreAuthorize("@ss.hasPerm('mini:point:rule:list')") +// @PreAuthorize("@ss.hasPerm('mini:point:rule:list')") @Log(module = LogModuleEnum.POINT_RULE, value = ActionTypeEnum.LIST) public PageResult page(@ParameterObject RulePageQuery queryParams) { IPage result = pointRuleService.pageRule(queryParams); @@ -53,7 +54,8 @@ public class PointAdminController { @Operation(summary = "新增积分规则") @PostMapping("/add/rules") - @PreAuthorize("@ss.hasPerm('mini:point:rule:add')") +// @PreAuthorize("@ss.hasPerm('mini:point:rule:add')") + @Log(module = LogModuleEnum.POINT_RULE, value = ActionTypeEnum.INSERT) public Result addRule(@RequestBody AddPointRuleForm form) { pointRuleService.addRule(form); return Result.success(); @@ -61,7 +63,8 @@ public class PointAdminController { @Operation(summary = "逻辑删除积分规则") @PostMapping("/delete/rules") - @PreAuthorize("@ss.hasPerm('mini:point:rule:delete')") +// @PreAuthorize("@ss.hasPerm('mini:point:rule:delete')") + @Log(module = LogModuleEnum.POINT_RULE, value = ActionTypeEnum.DELETE) public Result deleteRule(@RequestParam Long id) { pointRuleService.deleteRule(id); return Result.success(); @@ -69,7 +72,8 @@ public class PointAdminController { @Operation(summary = "启用/禁用积分规则") @PatchMapping("/rules/{id}/status") - @PreAuthorize("@ss.hasPerm('mini:point:rule:edit')") +// @PreAuthorize("@ss.hasPerm('mini:point:rule:edit')") + @Log(module = LogModuleEnum.POINT_RULE, value = ActionTypeEnum.ENABLE) public Result changeRuleStatus(@PathVariable Long id, @RequestParam Boolean status) { pointRuleService.changeStatus(id, status); return Result.success(); @@ -77,7 +81,8 @@ public class PointAdminController { @Operation(summary = "手动调整用户积分") @PutMapping("/adjustPoint") - @PreAuthorize("@ss.hasPerm('mini:point:record:edit')") +// @PreAuthorize("@ss.hasPerm('mini:point:record:edit')") + @Log(module = LogModuleEnum.POINT_ACCOUNT, value = ActionTypeEnum.UPDATE) public Result adjustPoint(@RequestBody AdjustUserPointForm adjustUserPointForm) { pointRecordService.adjustPoint(adjustUserPointForm); return Result.success(); @@ -85,7 +90,8 @@ public class PointAdminController { @Operation(summary = "分页查询所有积分记录") @GetMapping("/records") - @PreAuthorize("@ss.hasPerm('mini:point:record:list')") +// @PreAuthorize("@ss.hasPerm('mini:point:record:list')") + @Log(module = LogModuleEnum.POINT_RECORD, value = ActionTypeEnum.LIST) public PageResult pageRecord(PointRecordQuery query) { return PageResult.success(pointRecordService.pageAllRecord(query)); } diff --git a/src/main/java/com/youlai/boot/mini/controller/PointController.java b/src/main/java/com/youlai/boot/mini/controller/PointController.java index f1e2a19..84106d5 100644 --- a/src/main/java/com/youlai/boot/mini/controller/PointController.java +++ b/src/main/java/com/youlai/boot/mini/controller/PointController.java @@ -1,5 +1,9 @@ package com.youlai.boot.mini.controller; +import com.youlai.boot.common.annotation.Log; +import com.youlai.boot.common.annotation.RepeatSubmit; +import com.youlai.boot.common.enums.ActionTypeEnum; +import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.result.PageResult; import com.youlai.boot.common.result.Result; import com.youlai.boot.framework.security.util.SecurityUtils; @@ -26,12 +30,12 @@ import org.springframework.web.bind.annotation.*; public class PointController { private final MiniPointAccountService pointAccountService; - private final MiniPointRuleService pointRuleService; private final MiniPointRecordService pointRecordService; private final MiniSignService signService; @Operation(summary = "查询当前用户的积分账户信息") @GetMapping("/my") + @Log(module = LogModuleEnum.POINT_ACCOUNT, value = ActionTypeEnum.LIST) public Result getMyPoint() { Long userId = SecurityUtils.getUserId(); MyPointVO myPoint = pointAccountService.getUserPoint(userId); @@ -40,6 +44,7 @@ public class PointController { @Operation(summary = "分页查询当前用户的积分流水列表") @GetMapping("/records") + @Log(module = LogModuleEnum.POINT_RECORD, value = ActionTypeEnum.LIST) public PageResult getMyPointRecords(@ParameterObject MyPointRecordQuery query) { Long userId = SecurityUtils.getUserId(); return PageResult.success(pointRecordService.pageMyPointRecord(userId, query)); @@ -47,18 +52,22 @@ public class PointController { @Operation(summary = "用户签到") @PostMapping("/sign") + @RepeatSubmit + @Log(module = LogModuleEnum.SIGN_RECORD, value = ActionTypeEnum.INSERT) public Result sign() { return Result.success(signService.sign()); } @Operation(summary = "查询用户签到状态/当月签到日历") @GetMapping("/sign/status") + @Log(module = LogModuleEnum.SIGN_RECORD, value = ActionTypeEnum.LIST) public Result getSignStatus() { return Result.success(signService.getSignStatus()); } @Operation(summary = "用户分享领取奖励") @PostMapping("/share/reward") + @Log(module = LogModuleEnum.OTHER, value = ActionTypeEnum.OTHER) public Result shareReward() { Long userId = SecurityUtils.getUserId(); Integer point = pointRecordService.giveShareReward(userId); @@ -67,6 +76,7 @@ public class PointController { @Operation(summary = "登记流浪动物领取积分") @PostMapping("/register-animal/reward") + @Log(module = LogModuleEnum.OTHER, value = ActionTypeEnum.OTHER) public Result registerAnimalReward( @Parameter(description = "登记的流浪动物ID", required = true) @RequestParam Long animalId) { Long userId = SecurityUtils.getUserId(); @@ -76,6 +86,7 @@ public class PointController { @Operation(summary = "AI生成扣除积分") @PostMapping("/ai-generate/deduct") + @Log(module = LogModuleEnum.OTHER, value = ActionTypeEnum.OTHER) public Result aiGenerateImageDeduct( @Parameter(description = "AI生成任务唯一ID(用于幂等)", required = true) @RequestParam String taskId) { Long userId = SecurityUtils.getUserId(); diff --git a/src/main/java/com/youlai/boot/mini/listener/UserRegisterPointListener.java b/src/main/java/com/youlai/boot/mini/listener/UserRegisterPointListener.java index b4b6d74..6db9c57 100644 --- a/src/main/java/com/youlai/boot/mini/listener/UserRegisterPointListener.java +++ b/src/main/java/com/youlai/boot/mini/listener/UserRegisterPointListener.java @@ -9,10 +9,12 @@ import com.youlai.boot.mini.service.MiniPointRecordService; import com.youlai.boot.mini.service.MiniPointRuleService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; /** * 用户注册成功监听器:赠送新用户注册积分 @@ -27,19 +29,21 @@ public class UserRegisterPointListener { private final MiniPointRuleService pointRuleService; /** - * 异步监听用户注册成功事件,赠送积分 - * 异步执行不影响主线登录流程,就算赠送积分失败也不会影响用户正常登录 + * 监听用户注册成功事件,事务提交后再赠送积分 + * phase = AFTER_COMMIT:用户注册事务完全提交成功后才执行,完全和主注册逻辑解耦 + * fallbackExecution = true:无事务上下文也能兼容执行 + * 即使赠送积分失败也不会影响用户注册主流程,最多日志告警手动补发 */ @Async - @Transactional(rollbackFor = Exception.class) - @EventListener(UserRegisterEvent.class) +// @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) public void handleUserRegisterGivePoint(UserRegisterEvent event) { Long userId = event.getUserId(); String source = event.getRegisterSource(); log.info("监听到新用户注册成功,userId={}, 注册来源={}, 开始处理注册赠送积分逻辑", userId, source); try { - // ================= 1. 幂等校验:避免重复赠送积分 ================= + //1. 幂等校验:避免重复赠送积分 long count = pointRecordService.count(new LambdaQueryWrapper() .eq(MiniPointRecord::getUserId, userId) .eq(MiniPointRecord::getBizType, "REGISTER_GIFT") @@ -49,7 +53,7 @@ public class UserRegisterPointListener { return; } - // ================= 2. 查询注册积分规则,可配置无需改代码 ================= + //2. 查询注册积分规则,可配置无需改代码 MiniPointRule registerRule = pointRuleService.getOne(new LambdaQueryWrapper() .eq(MiniPointRule::getRuleCode, "REGISTER_GIFT") .eq(MiniPointRule::getStatus, false) // 0=启用状态 @@ -60,7 +64,7 @@ public class UserRegisterPointListener { } Integer giftPoint = registerRule.getPoints(); - // ================= 3. 调用现有积分调整接口赠送积分 ================= + //3. 调用现有积分调整接口赠送积分 AdjustUserPointForm adjustForm = new AdjustUserPointForm(); adjustForm.setUserId(userId); adjustForm.setBizType("REGISTER_GIFT"); @@ -71,7 +75,6 @@ public class UserRegisterPointListener { log.info("新用户{}注册赠送{}积分成功", userId, giftPoint); } catch (Exception e) { log.error("新用户{}注册赠送积分失败,请手动排查补发", userId, e); - // 这里可以加告警通知,比如钉钉/企业微信推送,不影响用户登录 } } } diff --git a/src/main/java/com/youlai/boot/mini/model/form/AdjustUserPointForm.java b/src/main/java/com/youlai/boot/mini/model/form/AdjustUserPointForm.java index 86dcd68..3bd74de 100644 --- a/src/main/java/com/youlai/boot/mini/model/form/AdjustUserPointForm.java +++ b/src/main/java/com/youlai/boot/mini/model/form/AdjustUserPointForm.java @@ -18,8 +18,7 @@ public class AdjustUserPointForm { private Long userId; @NotBlank(message = "业务类型不能为空") -// @EnumValid(enumClass = AdjustUserPointEnum.class, message = "业务类型不合法") - @Schema(description = "业务类型 system_increase system_reduce", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED) private String bizType; @NotNull(message = "积分不能为空") @@ -29,7 +28,4 @@ public class AdjustUserPointForm { @Schema(description = "业务唯一ID,幂等用,比如AI生成任务ID") private String bizId; - @Schema(description = "备注,存业务关联信息") - private String remark; - } diff --git a/src/main/java/com/youlai/boot/mini/service/impl/MiniPointRecordServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/MiniPointRecordServiceImpl.java index df5c23d..a2bb62b 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/MiniPointRecordServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/MiniPointRecordServiceImpl.java @@ -1,6 +1,7 @@ package com.youlai.boot.mini.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -63,8 +64,16 @@ public class MiniPointRecordServiceImpl extends ServiceImpl() .eq(MiniPointAccount::getUserId, form.getUserId()) .eq(MiniPointAccount::getDeleted, false)); + // 账户不存在自动创建,新用户第一次获得积分的时候自动初始化账户 if (account == null) { - throw new MsgException("用户积分账户不存在"); + account = new MiniPointAccount(); + account.setUuid(IdUtil.fastSimpleUUID()); + account.setUserId(form.getUserId()); + account.setPoints(0); + account.setCreateBy(form.getUserId()); + account.setCreateTime(new java.util.Date()); + account.setCreateTimestamp(System.currentTimeMillis()); + miniPointAccountService.save(account); } // 4. 计算变更后余额,校验扣减后不能为负数 @@ -341,7 +350,7 @@ public class MiniPointRecordServiceImpl extends ServiceImpl