liuchengsen 1 месяц назад
Родитель
Сommit
330e5051fe

+ 54 - 7
zhijiayun-activity/src/main/java/com/xuekairui/activity/service/ActivityInviteService.java

@@ -16,9 +16,11 @@ import com.xuekairui.user.entity.BusinessLicense;
 import com.xuekairui.user.entity.User;
 import com.xuekairui.user.enums.LicenseStatus;
 import com.xuekairui.user.enums.MembershipLevel;
+import com.xuekairui.user.enums.OperationType;
 import com.xuekairui.user.mapper.BusinessLicenseMapper;
 import com.xuekairui.user.mapper.UserMapper;
 import com.xuekairui.user.service.MembershipService;
+import com.xuekairui.user.service.OperationAuditLogService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.dao.DuplicateKeyException;
@@ -64,6 +66,7 @@ public class ActivityInviteService implements ActivityInviteGate {
     private final UserMapper userMapper;
     private final BusinessLicenseMapper businessLicenseMapper;
     private final MembershipService membershipService;
+    private final OperationAuditLogService auditLogService;
 
     // ==========================================
     // ActivityInviteGate 门面实现(供 InviteService 调用)
@@ -92,7 +95,7 @@ public class ActivityInviteService implements ActivityInviteGate {
         }
 
         // REGISTRATION 模式 → 立即发放
-        grantWithLimit(inviterId, inviteeId, config);
+        grantWithLimit(inviterId, inviteeId, config, "REGISTRATION");
     }
 
     /**
@@ -108,7 +111,7 @@ public class ActivityInviteService implements ActivityInviteGate {
             log.info("邀请有礼活动未开启,跳过已入驻补填发奖: inviter={}, invitee={}", inviterId, inviteeId);
             return;
         }
-        grantWithLimit(inviterId, inviteeId, config);
+        grantWithLimit(inviterId, inviteeId, config, "BIND_APPROVED");
         log.info("已入驻用户补填邀请码,发放邀请奖励: inviter={}, invitee={}", inviterId, inviteeId);
     }
 
@@ -174,7 +177,7 @@ public class ActivityInviteService implements ActivityInviteGate {
             }
 
             // 补发奖励
-            grantWithLimit(inviterId, inviteeId, config);
+            grantWithLimit(inviterId, inviteeId, config, "RETROACTIVE");
             granted++;
         }
 
@@ -250,7 +253,7 @@ public class ActivityInviteService implements ActivityInviteGate {
         }
 
         Long inviterId = invitee.getInviterId();
-        grantWithLimit(inviterId, userId, config);
+        grantWithLimit(inviterId, userId, config, "LICENSE_APPROVED");
     }
 
     // ==========================================
@@ -321,8 +324,10 @@ public class ActivityInviteService implements ActivityInviteGate {
     /**
      * 带活动限制的奖励发放(活动开启时使用)。
      * 校验 30 次上限 + 幂等插入记录 + 发放会员。
+     *
+     * @param triggerSource 触发来源,用于审计日志(如 REGISTRATION / LICENSE_APPROVED / BIND_APPROVED / RETROACTIVE)
      */
-    private void grantWithLimit(Long inviterId, Long inviteeId, ActivityInviteConfig config) {
+    private void grantWithLimit(Long inviterId, Long inviteeId, ActivityInviteConfig config, String triggerSource) {
         // 1. 校验邀请人奖励次数上限
         long rewarded = rewardRecordMapper.countByInviter(inviterId);
         int maxReward = config.getMaxInviterReward() != null ? config.getMaxInviterReward() : 30;
@@ -345,9 +350,24 @@ public class ActivityInviteService implements ActivityInviteGate {
             return;
         }
 
-        // 3. 发放奖励
+        // 3. 发放会员奖励
         doGrantReward(inviterId, inviteeId, days);
-        log.info("邀请有礼活动奖励已发放: inviter={}, invitee={}, days={}", inviterId, inviteeId, days);
+
+        // 4. 审计日志:记录邀请奖励发放
+        String inviterName = getUserNickname(inviterId);
+        String inviteeName = getUserNickname(inviteeId);
+        auditLogService.logSuccess(
+                inviterId,
+                inviterName,
+                "USER",
+                OperationType.INVITE_REWARD_GRANT,
+                "INVITE_REWARD:" + inviteeId,
+                inviterId,
+                null,
+                Map.of("triggerSource", triggerSource, "rewardDays", days, "rewardLevel", "PRO"),
+                String.format("%s → 邀请人%s获赠%d天PRO会员,触发来源:%s", inviteeName, inviterName, days, triggerSource));
+
+        log.info("邀请有礼活动奖励已发放: inviter={}, invitee={}, days={}, trigger={}", inviterId, inviteeId, days, triggerSource);
     }
 
     /**
@@ -396,10 +416,37 @@ public class ActivityInviteService implements ActivityInviteGate {
             record.setRevokeTime(LocalDateTime.now());
             rewardRecordMapper.updateById(record);
             count++;
+
+            // 审计日志:记录奖励撤销
+            String inviterName = getUserNickname(record.getInviterId());
+            String inviteeName = getUserNickname(inviteeId);
+            auditLogService.logSuccess(
+                    revokerId,
+                    "运营(反作弊)",
+                    "ADMIN",
+                    OperationType.INVITE_REWARD_GRANT,
+                    "INVITE_REVOKE:" + inviteeId,
+                    record.getInviterId(),
+                    Map.of("rewardDays", record.getRewardDays(), "rewardLevel", "PRO"),
+                    null,
+                    String.format("撤销邀请奖励:邀请人%s(因被邀请人%s作弊,原因:%s)", inviterName, inviteeName, reason));
+
             log.warn("反作弊:撤销邀请奖励记录 id={}, inviter={}, invitee={}, reason={}",
                     record.getId(), record.getInviterId(), inviteeId, reason);
         }
 
         return count;
     }
+
+    // ==========================================
+    // 辅助方法
+    // ==========================================
+
+    /**
+     * 获取用户昵称(用于审计日志等场景)
+     */
+    private String getUserNickname(Long userId) {
+        User user = userMapper.selectById(userId);
+        return user != null && user.getNickname() != null ? user.getNickname() : String.valueOf(userId);
+    }
 }

+ 18 - 3
zhijiayun-payment/src/main/java/com/xuekairui/payment/controller/AdminPaymentController.java

@@ -4,6 +4,8 @@ import com.xuekairui.common.Result;
 import com.xuekairui.payment.dto.AdminPlanUpsertRequest;
 import com.xuekairui.payment.dto.PaymentPlanResponse;
 import com.xuekairui.payment.service.PaymentPlanService;
+import com.xuekairui.user.enums.OperationType;
+import com.xuekairui.user.service.OperationAuditLogService;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -21,6 +23,7 @@ import java.util.Map;
 public class AdminPaymentController {
 
     private final PaymentPlanService paymentPlanService;
+    private final OperationAuditLogService auditLogService;
 
     /**
      * 查询所有支付方案(含已下架)
@@ -35,8 +38,12 @@ public class AdminPaymentController {
      */
     @PostMapping("/plans")
     public Result<PaymentPlanResponse> createPlan(
+            @RequestParam Long operatorId,
             @Valid @RequestBody AdminPlanUpsertRequest request) {
-        return Result.success(paymentPlanService.createPlan(request));
+        PaymentPlanResponse plan = paymentPlanService.createPlan(request);
+        auditLogService.logAdmin(operatorId, OperationType.PAYMENT_PLAN_CREATE,
+                "PAYMENT_PLAN:" + plan.getId(), plan.getId());
+        return Result.success(plan);
     }
 
     /**
@@ -44,9 +51,13 @@ public class AdminPaymentController {
      */
     @PutMapping("/plans/{id}")
     public Result<PaymentPlanResponse> updatePlan(
+            @RequestParam Long operatorId,
             @PathVariable Long id,
             @Valid @RequestBody AdminPlanUpsertRequest request) {
-        return Result.success(paymentPlanService.updatePlan(id, request));
+        PaymentPlanResponse plan = paymentPlanService.updatePlan(id, request);
+        auditLogService.logAdmin(operatorId, OperationType.PAYMENT_PLAN_UPDATE,
+                "PAYMENT_PLAN:" + id, id);
+        return Result.success(plan);
     }
 
     /**
@@ -54,9 +65,13 @@ public class AdminPaymentController {
      */
     @PutMapping("/plans/{id}/status")
     public Result<Void> toggleStatus(
+            @RequestParam Long operatorId,
             @PathVariable Long id,
             @RequestBody Map<String, Integer> body) {
-        paymentPlanService.toggleStatus(id, body.get("status"));
+        Integer status = body.get("status");
+        paymentPlanService.toggleStatus(id, status);
+        auditLogService.logAdmin(operatorId, OperationType.PAYMENT_PLAN_TOGGLE,
+                "PAYMENT_PLAN:" + id, id);
         return Result.success();
     }
 }

+ 3 - 0
zhijiayun-payment/src/main/java/com/xuekairui/payment/dto/CreateOrderRequest.java

@@ -16,4 +16,7 @@ public class CreateOrderRequest {
 
     @NotBlank(message = "支付渠道不能为空")
     private String channel;
+
+    /** 微信小程序用户 openid(渠道为 WECHAT_MINIAPP 时必填) */
+    private String openid;
 }

+ 3 - 0
zhijiayun-payment/src/main/java/com/xuekairui/payment/dto/OrderResponse.java

@@ -37,6 +37,9 @@ public class OrderResponse {
     /** 支付链接/码串(微信=code_url,支付宝precreate=qr_code码串) */
     private String payUrl;
 
+    /** 微信小程序支付参数(WECHAT_MINIAPP 渠道返回,用于 wx.requestPayment) */
+    private java.util.Map<String, String> miniAppPayParams;
+
     /** 订单过期时间 */
     private LocalDateTime expireTime;
 

+ 3 - 0
zhijiayun-payment/src/main/java/com/xuekairui/payment/dto/SwitchChannelRequest.java

@@ -12,4 +12,7 @@ public class SwitchChannelRequest {
 
     @NotBlank(message = "支付渠道不能为空")
     private String channel;
+
+    /** 微信小程序用户 openid(切换到 WECHAT_MINIAPP 时必填) */
+    private String openid;
 }

+ 2 - 1
zhijiayun-payment/src/main/java/com/xuekairui/payment/enums/PaymentChannel.java

@@ -10,7 +10,8 @@ import java.util.Arrays;
 @Getter
 public enum PaymentChannel {
     WECHAT("微信支付"),
-    ALIPAY("支付宝");
+    ALIPAY("支付宝"),
+    WECHAT_MINIAPP("微信小程序支付");
 
     private final String displayName;
 

+ 8 - 0
zhijiayun-user/src/main/java/com/xuekairui/user/enums/OperationType.java

@@ -80,6 +80,14 @@ public enum OperationType {
     /** 查询爬虫配额 */
     CRAWLER_QUOTA_QUERY("CRAWLER", "QUOTA_QUERY", "查询爬虫配额", false),
 
+    // ==================== 支付模块 ====================
+    /** 创建支付方案 */
+    PAYMENT_PLAN_CREATE("PAYMENT", "PLAN_CREATE", "创建支付方案", true),
+    /** 更新支付方案 */
+    PAYMENT_PLAN_UPDATE("PAYMENT", "PLAN_UPDATE", "更新支付方案", true),
+    /** 支付方案上架/下架 */
+    PAYMENT_PLAN_TOGGLE("PAYMENT", "PLAN_TOGGLE", "支付方案上下架", true),
+
     // ==================== 系统管理模块 ====================
     /** 修改系统配置 */
     SYSTEM_CONFIG_UPDATE("SYSTEM", "CONFIG_UPDATE", "修改系统配置", true),

+ 40 - 7
zhijiayun-user/src/main/java/com/xuekairui/user/service/BusinessLicenseService.java

@@ -6,10 +6,12 @@ import com.xuekairui.common.ErrorCode;
 import com.xuekairui.common.event.ActivityEventRedisService;
 import com.xuekairui.user.dto.BusinessLicenseReviewResponse;
 import com.xuekairui.user.entity.BusinessLicense;
+import com.xuekairui.user.entity.User;
 import com.xuekairui.user.enums.LicenseStatus;
 import com.xuekairui.user.enums.MembershipLevel;
 import com.xuekairui.user.enums.OperationType;
 import com.xuekairui.user.mapper.BusinessLicenseMapper;
+import com.xuekairui.user.mapper.UserMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -40,6 +42,7 @@ public class BusinessLicenseService {
     private final MembershipService membershipService;
     private final ActivityEventRedisService activityEventRedisService;
     private final OperationAuditLogService auditLogService;
+    private final UserMapper userMapper;
 
     // ======================== 查询方法 ========================
 
@@ -214,9 +217,23 @@ public class BusinessLicenseService {
             });
         }
 
-        auditLogService.logAdmin(reviewerId, OperationType.LICENSE_APPROVE,
-                "LICENSE:" + license.getId(), license.getId());
-        log.info("入驻信息审核通过: licenseId={}, reviewerId={}", licenseId, reviewerId);
+        // 审计日志:审核通过,记录客户信息
+        User submitter = userMapper.selectById(userId);
+        String userName = submitter != null && submitter.getNickname() != null
+                ? submitter.getNickname() : String.valueOf(userId);
+        String storeName = license.getStoreName() != null ? license.getStoreName() : "未知药店";
+        auditLogService.logSuccess(
+                reviewerId != null ? reviewerId : 0L,
+                "管理员",
+                "ADMIN",
+                OperationType.LICENSE_APPROVE,
+                "LICENSE:" + license.getId(),
+                license.getId(),
+                null,
+                Map.of("userId", userId, "userName", userName, "storeName", storeName),
+                String.format("审核通过客户「%s」(药店:%s)的入驻申请", userName, storeName));
+        log.info("入驻审核通过: licenseId={}, userId={}, userName={}, reviewerId={}",
+                licenseId, userId, userName, reviewerId);
     }
 
     /**
@@ -237,10 +254,26 @@ public class BusinessLicenseService {
         license.setRejectReason(rejectReason);
         businessLicenseMapper.updateById(license);
 
-        auditLogService.logAdmin(reviewerId, OperationType.LICENSE_REJECT,
-                "LICENSE:" + license.getId(), license.getId());
-        log.info("入驻信息审核驳回: licenseId={}, reviewerId={}, reason={}",
-                licenseId, reviewerId, rejectReason);
+        // 审计日志:审核驳回,记录客户信息
+        Long userId = license.getUserId();
+        User submitter = userMapper.selectById(userId);
+        String userName = submitter != null && submitter.getNickname() != null
+                ? submitter.getNickname() : String.valueOf(userId);
+        String storeName = license.getStoreName() != null ? license.getStoreName() : "未知药店";
+        auditLogService.logSuccess(
+                reviewerId != null ? reviewerId : 0L,
+                "管理员",
+                "ADMIN",
+                OperationType.LICENSE_REJECT,
+                "LICENSE:" + license.getId(),
+                license.getId(),
+                null,
+                Map.of("userId", userId, "userName", userName, "storeName", storeName, "rejectReason",
+                        rejectReason != null ? rejectReason : ""),
+                String.format("驳回客户「%s」(药店:%s)的入驻申请,原因:%s",
+                        userName, storeName, rejectReason != null ? rejectReason : "未填写"));
+        log.info("入驻审核驳回: licenseId={}, userId={}, userName={}, reviewerId={}, reason={}",
+                licenseId, userId, userName, reviewerId, rejectReason);
     }
 
     /**

+ 13 - 3
zhijiayun-user/src/main/java/com/xuekairui/user/service/CrawlerService.java

@@ -14,6 +14,7 @@ import com.xuekairui.user.enums.OperationType;
 import com.xuekairui.user.mapper.CouponMapper;
 import com.xuekairui.user.mapper.CrawlerUsageLogMapper;
 import com.xuekairui.user.mapper.UserCouponMapper;
+import com.xuekairui.user.mapper.UserMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -42,6 +43,7 @@ public class CrawlerService {
 
     private final CrawlerUsageLogMapper crawlerUsageLogMapper;
     private final UserCouponMapper userCouponMapper;
+    private final UserMapper userMapper;
     private final CouponMapper couponMapper;
     private final MembershipService membershipService;
     private final CouponService couponService;
@@ -170,7 +172,7 @@ public class CrawlerService {
                 consumeFromQuota(userId, quotaCount);
             }
             recordUsage(userId, count, "MIXED", "优惠券+" + shortage + ",配额+" + quotaCount, platform);
-            auditLogService.logSuccess(userId, null, "USER",
+            auditLogService.logSuccess(userId, getUserNickname(userId), "USER",
                     OperationType.CRAWLER_QUOTA_DEDUCT, "CRAWLER_CONSUME:" + count, userId);
             return true;
         }
@@ -178,7 +180,7 @@ public class CrawlerService {
         // 日配额充足
         if (useCouponFirst && consumeWithCoupon(userId, count)) {
             recordUsage(userId, count, "COUPON", "使用优惠券", platform);
-            auditLogService.logSuccess(userId, null, "USER",
+            auditLogService.logSuccess(userId, getUserNickname(userId), "USER",
                     OperationType.CRAWLER_QUOTA_DEDUCT, "CRAWLER_CONSUME:" + count, userId);
             return true;
         }
@@ -186,7 +188,7 @@ public class CrawlerService {
         // 从配额中扣减(额外配额优先,快过期优先)
         consumeFromQuota(userId, count);
         recordUsage(userId, count, "QUOTA", null, platform);
-        auditLogService.logSuccess(userId, null, "USER",
+        auditLogService.logSuccess(userId, getUserNickname(userId), "USER",
                 OperationType.CRAWLER_QUOTA_DEDUCT, "CRAWLER_CONSUME:" + count, userId);
         return true;
     }
@@ -291,6 +293,14 @@ public class CrawlerService {
         });
     }
 
+    /**
+     * 获取用户昵称(用于审计日志等场景)
+     */
+    private String getUserNickname(Long userId) {
+        com.xuekairui.user.entity.User user = userMapper.selectById(userId);
+        return user != null ? user.getNickname() : String.valueOf(userId);
+    }
+
     /**
      * 获取用户爬虫使用记录
      */

+ 1 - 0
zhijiayun-user/src/main/java/com/xuekairui/user/service/OperationAuditLogService.java

@@ -229,6 +229,7 @@ public class OperationAuditLogService {
             case "COUPON" -> "COUPON";
             case "TRIAL" -> "TRIAL_CONFIG";
             case "CRAWLER" -> "CRAWLER_QUOTA";
+            case "PAYMENT" -> "PAYMENT";
             default -> "SYSTEM";
         };
     }