|
@@ -4,6 +4,7 @@ import cn.hutool.json.JSONUtil;
|
|
|
import com.fs.common.core.domain.R;
|
|
import com.fs.common.core.domain.R;
|
|
|
import com.fs.common.utils.DateUtils;
|
|
import com.fs.common.utils.DateUtils;
|
|
|
import com.fs.his.config.IntegralConfig;
|
|
import com.fs.his.config.IntegralConfig;
|
|
|
|
|
+import com.fs.his.config.PointsGrantRuleConfig;
|
|
|
import com.fs.his.domain.FsUser;
|
|
import com.fs.his.domain.FsUser;
|
|
|
import com.fs.his.domain.FsUserIntegralLogs;
|
|
import com.fs.his.domain.FsUserIntegralLogs;
|
|
|
import com.fs.his.domain.FsUserNewTask;
|
|
import com.fs.his.domain.FsUserNewTask;
|
|
@@ -13,6 +14,7 @@ import com.fs.his.mapper.FsUserMapper;
|
|
|
import com.fs.his.mapper.FsUserNewTaskMapper;
|
|
import com.fs.his.mapper.FsUserNewTaskMapper;
|
|
|
import com.fs.his.param.FsUserAddIntegralParam;
|
|
import com.fs.his.param.FsUserAddIntegralParam;
|
|
|
import com.fs.his.param.FsUserAddIntegralTemplateParam;
|
|
import com.fs.his.param.FsUserAddIntegralTemplateParam;
|
|
|
|
|
+import com.fs.his.param.FsUserGrantPointsParam;
|
|
|
import com.fs.his.param.FsUserIntegralLogsListUParam;
|
|
import com.fs.his.param.FsUserIntegralLogsListUParam;
|
|
|
import com.fs.his.param.FsUserIntegralLogsParam;
|
|
import com.fs.his.param.FsUserIntegralLogsParam;
|
|
|
import com.fs.his.service.IFsUserIntegralLogsService;
|
|
import com.fs.his.service.IFsUserIntegralLogsService;
|
|
@@ -20,14 +22,19 @@ import com.fs.his.vo.FsUserIntegralLogsListUVO;
|
|
|
import com.fs.his.vo.FsUserIntegralLogsListVO;
|
|
import com.fs.his.vo.FsUserIntegralLogsListVO;
|
|
|
import com.fs.his.vo.SubIntegralVO;
|
|
import com.fs.his.vo.SubIntegralVO;
|
|
|
import com.fs.system.service.ISysConfigService;
|
|
import com.fs.system.service.ISysConfigService;
|
|
|
|
|
+import org.redisson.api.RLock;
|
|
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDate;
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 积分记录Service业务层处理
|
|
* 积分记录Service业务层处理
|
|
@@ -47,6 +54,18 @@ public class FsUserIntegralLogsServiceImpl implements IFsUserIntegralLogsService
|
|
|
private ISysConfigService configService;
|
|
private ISysConfigService configService;
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private FsUserNewTaskMapper fsUserNewTaskMapper;
|
|
private FsUserNewTaskMapper fsUserNewTaskMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private RedissonClient redissonClient;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private StringRedisTemplate stringRedisTemplate;
|
|
|
|
|
+
|
|
|
|
|
+ /** Redis 分布式锁 Key 前缀:积分发放 */
|
|
|
|
|
+ private static final String INTEGRAL_GRANT_LOCK_KEY = "integral:grant:%d:%d";
|
|
|
|
|
+ /** Redis Key 前缀:用户每日积分累计 */
|
|
|
|
|
+ private static final String INTEGRAL_DAILY_KEY = "integral:grant:daily:%d:%s";
|
|
|
|
|
+ /** Redis Key 前缀:用户每月积分累计 */
|
|
|
|
|
+ private static final String INTEGRAL_MONTHLY_KEY = "integral:grant:monthly:%d:%s";
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 查询积分记录
|
|
* 查询积分记录
|
|
|
*
|
|
*
|
|
@@ -174,7 +193,7 @@ public class FsUserIntegralLogsServiceImpl implements IFsUserIntegralLogsService
|
|
|
if (integralLogs==null){
|
|
if (integralLogs==null){
|
|
|
FsUser userMap=new FsUser();
|
|
FsUser userMap=new FsUser();
|
|
|
userMap.setUserId(user.getUserId());
|
|
userMap.setUserId(user.getUserId());
|
|
|
- userMap.setIntegral(user.getIntegral()+1000L);
|
|
|
|
|
|
|
+// userMap.setIntegral(user.getIntegral()+1000L);
|
|
|
fsUserMapper.updateFsUser(userMap);
|
|
fsUserMapper.updateFsUser(userMap);
|
|
|
integralLogs = new FsUserIntegralLogs();
|
|
integralLogs = new FsUserIntegralLogs();
|
|
|
integralLogs.setIntegral(1000L);
|
|
integralLogs.setIntegral(1000L);
|
|
@@ -543,4 +562,264 @@ public class FsUserIntegralLogsServiceImpl implements IFsUserIntegralLogsService
|
|
|
map.put("isFinishFirstOrderPoint", isFinishFirstOrderPoint);
|
|
map.put("isFinishFirstOrderPoint", isFinishFirstOrderPoint);
|
|
|
return R.ok().put("data",map).put("isNewUser",isNewUser).put("createTime",createTime);
|
|
return R.ok().put("data",map).put("isNewUser",isNewUser).put("createTime",createTime);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ======================== 通用积分发放 ========================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 通用积分发放(基于 points.grantRule 规则)
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>处理流程:</p>
|
|
|
|
|
+ * <ol>
|
|
|
|
|
+ * <li>加载配置,禁用则跳过</li>
|
|
|
|
|
+ * <li>按场景分支处理:消费送积分 / 退款扣回 / 管理员添加</li>
|
|
|
|
|
+ * <li>范围校验:检查商品/分类是否在不送积分名单中</li>
|
|
|
|
|
+ * <li>Redisson 分布式锁(userId + logType 粒度)</li>
|
|
|
|
|
+ * <li>日/月上限校验(Redis 原子计数器 + DB 兜底)</li>
|
|
|
|
|
+ * <li>更新用户积分余额 + 写入流水</li>
|
|
|
|
|
+ * <li>最终释放锁</li>
|
|
|
|
|
+ * </ol>
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public R grantPoints(FsUserGrantPointsParam param) {
|
|
|
|
|
+ log.info("通用积分发放开始: userId={}, logType={}, amount={}", param.getUserId(), param.getLogType(), param.getAmount());
|
|
|
|
|
+
|
|
|
|
|
+ // ------- 1. 加载发放规则配置 -------
|
|
|
|
|
+ String json = configService.selectConfigByKey("points.grantRule");
|
|
|
|
|
+ PointsGrantRuleConfig rule;
|
|
|
|
|
+ try {
|
|
|
|
|
+ rule = JSONUtil.toBean(json, PointsGrantRuleConfig.class);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("积分发放规则配置解析失败: {}", json, e);
|
|
|
|
|
+ return R.error("积分发放规则配置异常,请联系管理员");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (rule == null || rule.getEnabled() == null || rule.getEnabled() != 1) {
|
|
|
|
|
+ log.info("积分发放规则未启用,跳过发放: userId={}", param.getUserId());
|
|
|
|
|
+ return R.ok("积分发放规则未启用");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Long userId = param.getUserId();
|
|
|
|
|
+ Integer logType = param.getLogType();
|
|
|
|
|
+ long integralCount;
|
|
|
|
|
+
|
|
|
|
|
+ // ------- 2. 按场景分支处理 -------
|
|
|
|
|
+ switch (logType) {
|
|
|
|
|
+ case 2: // 消费获得积分
|
|
|
|
|
+ if (param.getAmount() == null || param.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ return R.error("消费金额必须大于0");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 范围校验:是否在不送积分名单中
|
|
|
|
|
+ if (!isInGrantScope(rule, param.getProductId(), param.getCategoryId())) {
|
|
|
|
|
+ log.info("该商品/分类不在积分发放范围内,跳过: userId={}, productId={}, categoryId={}",
|
|
|
|
|
+ userId, param.getProductId(), param.getCategoryId());
|
|
|
|
|
+ return R.ok("该商品不参与积分活动");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 计算发放积分:amount × rate,进位取整
|
|
|
|
|
+ integralCount = param.getAmount()
|
|
|
|
|
+ .multiply(rule.getRate() != null ? rule.getRate() : BigDecimal.ZERO)
|
|
|
|
|
+ .setScale(0, BigDecimal.ROUND_CEILING)
|
|
|
|
|
+ .longValue();
|
|
|
|
|
+ if (integralCount <= 0) {
|
|
|
|
|
+ log.info("计算积分 <= 0,跳过发放: userId={}, amount={}, rate={}", userId, param.getAmount(), rule.getRate());
|
|
|
|
|
+ return R.ok("积分比例为0,无需发放");
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case 4: // 退款扣除积分
|
|
|
|
|
+ if (!Integer.valueOf(1).equals(rule.getRefundDeduct())) {
|
|
|
|
|
+ return R.ok("退款扣回积分未开启");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 通过原订单号倒查原始积分发放记录,直接扣除对应积分
|
|
|
|
|
+ if (param.getOriginalOrderNo() == null || param.getOriginalOrderNo().isEmpty()) {
|
|
|
|
|
+ return R.error("退款扣回需提供原订单号(originalOrderNo)");
|
|
|
|
|
+ }
|
|
|
|
|
+ FsUserIntegralLogs originalLog = fsUserIntegralLogsMapper.selectByUserIdAndBusinessId(
|
|
|
|
|
+ userId, param.getOriginalOrderNo(), 2);
|
|
|
|
|
+ if (originalLog == null || originalLog.getIntegral() == null || originalLog.getIntegral() <= 0) {
|
|
|
|
|
+ log.info("未找到原消费积分记录,无需扣除: userId={}, originalOrderNo={}", userId, param.getOriginalOrderNo());
|
|
|
|
|
+ return R.ok("未找到该订单的积分发放记录");
|
|
|
|
|
+ }
|
|
|
|
|
+ integralCount = -originalLog.getIntegral();
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case 23: // 管理员手动添加 —— 不校验范围、不校验上限
|
|
|
|
|
+ if (param.getAmount() == null || param.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ return R.error("发放金额(amount)必须大于0");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 管理员添加直接使用 amount 作为积分数
|
|
|
|
|
+ integralCount = param.getAmount().longValue();
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ return R.error("不支持的积分发放类型: " + logType);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------- 3. 分布式锁 -------
|
|
|
|
|
+ String lockKey = String.format(INTEGRAL_GRANT_LOCK_KEY, userId, logType);
|
|
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
|
|
+ boolean locked = false;
|
|
|
|
|
+ try {
|
|
|
|
|
+ locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
|
|
|
|
|
+ if (!locked) {
|
|
|
|
|
+ log.warn("获取积分发放锁失败: userId={}, logType={}", userId, logType);
|
|
|
|
|
+ return R.error("系统繁忙,请稍后重试");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------- 4. 日/月上限校验(如有需要在此扩展特定场景) -------
|
|
|
|
|
+ // 消费积分(logType=2)不限制日/月上限,管理员(logType=23)和退款(logType=4)也不校验
|
|
|
|
|
+
|
|
|
|
|
+ // ------- 5. 查询用户并发放 -------
|
|
|
|
|
+ FsUser user = fsUserMapper.selectFsUserByUserId(userId);
|
|
|
|
|
+ if (user == null) {
|
|
|
|
|
+ return R.error("用户不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ long oldIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
|
|
|
|
|
+ long newIntegral = oldIntegral + integralCount;
|
|
|
|
|
+ if (newIntegral < 0) {
|
|
|
|
|
+ newIntegral = 0L;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新用户积分余额
|
|
|
|
|
+ FsUser updateUser = new FsUser();
|
|
|
|
|
+ updateUser.setUserId(userId);
|
|
|
|
|
+ updateUser.setIntegral(newIntegral);
|
|
|
|
|
+ fsUserMapper.updateFsUser(updateUser);
|
|
|
|
|
+
|
|
|
|
|
+ // 写入积分流水
|
|
|
|
|
+ FsUserIntegralLogs logRecord = new FsUserIntegralLogs();
|
|
|
|
|
+ logRecord.setUserId(userId);
|
|
|
|
|
+ logRecord.setLogType(logType);
|
|
|
|
|
+ logRecord.setIntegral(integralCount);
|
|
|
|
|
+ logRecord.setBalance(newIntegral);
|
|
|
|
|
+ logRecord.setBusinessId(param.getBusinessId());
|
|
|
|
|
+ logRecord.setRemark(param.getRemark());
|
|
|
|
|
+ logRecord.setCreateTime(new Date());
|
|
|
|
|
+ fsUserIntegralLogsMapper.insertFsUserIntegralLogs(logRecord);
|
|
|
|
|
+
|
|
|
|
|
+ // 发放成功后无需累加 Redis 计数器(购买积分不设上限)
|
|
|
|
|
+
|
|
|
|
|
+ String typeDesc = FsUserIntegralLogTypeEnum.toType(logType) != null
|
|
|
|
|
+ ? FsUserIntegralLogTypeEnum.toType(logType).getDesc() : "积分变动";
|
|
|
|
|
+ log.info("{}成功: userId={}, logType={}, integralCount={}, balance={}",
|
|
|
|
|
+ typeDesc, userId, logType, integralCount, newIntegral);
|
|
|
|
|
+ return R.ok(typeDesc + "成功,积分变动: " + integralCount);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
|
|
+ log.error("获取积分发放锁被中断: userId={}, logType={}", userId, logType, e);
|
|
|
|
|
+ return R.error("系统异常,请稍后重试");
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("积分发放异常: userId={}, logType={}", userId, logType, e);
|
|
|
|
|
+ return R.error("积分发放失败: " + e.getMessage());
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (locked && lock.isHeldByCurrentThread()) {
|
|
|
|
|
+ lock.unlock();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验商品/分类是否在积分发放范围内
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return true=在范围内可发放, false=在排除名单中不发放
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean isInGrantScope(PointsGrantRuleConfig rule, Long productId, Long categoryId) {
|
|
|
|
|
+ if (rule.getScopeType() == null || rule.getScopeType() == 1) {
|
|
|
|
|
+ return true; // 全场商品参与
|
|
|
|
|
+ }
|
|
|
|
|
+ if (rule.getScopeType() == 2 && categoryId != null && rule.getExcludeCategoryIds() != null) {
|
|
|
|
|
+ // 指定分类不参与 —— 当前分类命中则排除
|
|
|
|
|
+ return !rule.getExcludeCategoryIds().contains(categoryId);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (rule.getScopeType() == 3 && productId != null && rule.getExcludeProductIds() != null) {
|
|
|
|
|
+ // 指定商品不参与 —— 当前商品命中则排除
|
|
|
|
|
+ return !rule.getExcludeProductIds().contains(productId);
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验日/月发放上限
|
|
|
|
|
+ * Redis 原子计数器为主,DB 查询为兜底
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param rule 发放规则
|
|
|
|
|
+ * @param userId 用户ID
|
|
|
|
|
+ * @param logType 积分类型
|
|
|
|
|
+ * @param integralCount 本次发放积分
|
|
|
|
|
+ * @return null=通过校验, 非null=校验失败(含错误提示)
|
|
|
|
|
+ */
|
|
|
|
|
+ private R checkGrantLimit(PointsGrantRuleConfig rule, Long userId, Integer logType, long integralCount) {
|
|
|
|
|
+ String today = java.time.LocalDate.now().toString();
|
|
|
|
|
+ String yearMonth = java.time.YearMonth.now().toString();
|
|
|
|
|
+
|
|
|
|
|
+ //日上限校验
|
|
|
|
|
+ if (rule.getDailyLimit() != null && rule.getDailyLimit() > 0) {
|
|
|
|
|
+ String dailyKey = String.format(INTEGRAL_DAILY_KEY, userId, today);
|
|
|
|
|
+ Long dailyTotal = getOrInitRedisCounter(dailyKey, userId, logType, true);
|
|
|
|
|
+ if (dailyTotal != null && dailyTotal + integralCount > rule.getDailyLimit()) {
|
|
|
|
|
+ log.info("日积分已达上限: userId={}, dailyTotal={}, limit={}", userId, dailyTotal, rule.getDailyLimit());
|
|
|
|
|
+ return R.error("今日积分获取已达上限(" + rule.getDailyLimit() + ")");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //月上限校验
|
|
|
|
|
+ if (rule.getMonthlyLimit() != null && rule.getMonthlyLimit() > 0) {
|
|
|
|
|
+ String monthlyKey = String.format(INTEGRAL_MONTHLY_KEY, userId, yearMonth);
|
|
|
|
|
+ Long monthlyTotal = getOrInitRedisCounter(monthlyKey, userId, logType, false);
|
|
|
|
|
+ if (monthlyTotal != null && monthlyTotal + integralCount > rule.getMonthlyLimit()) {
|
|
|
|
|
+ log.info("月积分已达上限: userId={}, monthlyTotal={}, limit={}", userId, monthlyTotal, rule.getMonthlyLimit());
|
|
|
|
|
+ return R.error("本月积分获取已达上限(" + rule.getMonthlyLimit() + ")");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取或初始化 Redis 计数器(日/月)
|
|
|
|
|
+ * Redis 无数据时兜底查询 DB 并回写
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param key Redis Key
|
|
|
|
|
+ * @param userId 用户ID
|
|
|
|
|
+ * @param logType 积分类型
|
|
|
|
|
+ * @param isDaily true=日查询, false=月查询
|
|
|
|
|
+ * @return 当前累计积分
|
|
|
|
|
+ */
|
|
|
|
|
+ private Long getOrInitRedisCounter(String key, Long userId, Integer logType, boolean isDaily) {
|
|
|
|
|
+ String value = stringRedisTemplate.opsForValue().get(key);
|
|
|
|
|
+ if (value != null) {
|
|
|
|
|
+ return Long.parseLong(value);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Redis 无缓存 → DB 兜底
|
|
|
|
|
+ Long dbTotal;
|
|
|
|
|
+ if (isDaily) {
|
|
|
|
|
+ Integer result = fsUserIntegralLogsMapper.selectTodayTotalIntegral(
|
|
|
|
|
+ new Integer[]{logType}, userId);
|
|
|
|
|
+ dbTotal = result != null ? result.longValue() : 0L;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ dbTotal = fsUserIntegralLogsMapper.selectMonthlyTotalIntegral(userId, logType);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 回写 Redis(根据是日 or 月设置过期时间)
|
|
|
|
|
+ stringRedisTemplate.opsForValue().set(key, String.valueOf(dbTotal),
|
|
|
|
|
+ isDaily ? 24 : 31 * 24, TimeUnit.HOURS);
|
|
|
|
|
+ return dbTotal;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 发放成功后累加 Redis 日/月计数器
|
|
|
|
|
+ */
|
|
|
|
|
+ private void incrementRedisCounter(Long userId, Integer logType, long integralCount) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String today = java.time.LocalDate.now().toString();
|
|
|
|
|
+ String yearMonth = java.time.YearMonth.now().toString();
|
|
|
|
|
+ String dailyKey = String.format(INTEGRAL_DAILY_KEY, userId, today);
|
|
|
|
|
+ String monthlyKey = String.format(INTEGRAL_MONTHLY_KEY, userId, yearMonth);
|
|
|
|
|
+ stringRedisTemplate.opsForValue().increment(dailyKey, integralCount);
|
|
|
|
|
+ stringRedisTemplate.opsForValue().increment(monthlyKey, integralCount);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("Redis计数器累加失败(不影响主流程): userId={}, logType={}", userId, logType, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|