Browse Source

feat(second): 新增风控数据记录功能- 在 alien-gateway 模块中启用 Feign 客户端支持
- 新增 RiskControlController 控制器用于接收风控数据请求
- 新增 RiskControlService 接口及其实现类 RiskControlServiceImpl
- 实现风控数据的插入逻辑,并增加异常处理
- 修改 SecondGoodsServiceImpl 类,集成风控检查逻辑
- 调整 checkUserPublishSameCategoryLimit 方法以使用动态时间窗口
- 新增 SecondServiceFeign 接口以便通过 Feign 进行远程调用
- 更新相关查询条件以适配新的风控规则判断逻辑

wxd 1 month ago
parent
commit
2ba8574d79

+ 2 - 0
alien-gateway/src/main/java/shop/alien/gateway/AlienGatewayApplication.java

@@ -3,6 +3,7 @@ package shop.alien.gateway;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
 import org.springframework.context.annotation.ComponentScan;
 
 /**
@@ -14,6 +15,7 @@ import org.springframework.context.annotation.ComponentScan;
  */
 @ComponentScan({"shop.alien.gateway.*"})
 @MapperScan({"shop.alien.gateway.mapper"})
+@EnableFeignClients(basePackages = "shop.alien.gateway.feign")
 @SpringBootApplication
 public class AlienGatewayApplication {
 

+ 25 - 0
alien-gateway/src/main/java/shop/alien/gateway/feign/SecondServiceFeign.java

@@ -0,0 +1,25 @@
+package shop.alien.gateway.feign;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@FeignClient(name = "alien-second", url = "${feign.second.url:}")
+public interface SecondServiceFeign {
+    
+    /**
+     * 记录风控数据
+     *
+     * @param userId     用户ID
+     * @param ruleType   规则类型 1:洗钱嫌疑 2:账号异常 3:交易欺诈 4:异常发布
+     * @param ruleName   规则名称
+     * @param businessId 业务ID
+     * @param detailInfo 详细信息(JSON格式)
+     */
+    @PostMapping("/riskControl/record")
+    void recordRiskControlData(@RequestParam("userId") Integer userId,
+                               @RequestParam("ruleType") Integer ruleType,
+                               @RequestParam("ruleName") String ruleName,
+                               @RequestParam("businessId") Integer businessId,
+                               @RequestParam("detailInfo") String detailInfo);
+}

+ 34 - 0
alien-second/src/main/java/shop/alien/second/controller/RiskControlController.java

@@ -0,0 +1,34 @@
+package shop.alien.second.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import shop.alien.second.service.RiskControlService;
+
+@RestController
+@RequestMapping("/riskControl")
+@RequiredArgsConstructor
+public class RiskControlController {
+    
+    private final RiskControlService riskControlService;
+    
+    /**
+     * 记录风控数据
+     *
+     * @param userId     用户ID
+     * @param ruleType   规则类型 1:洗钱嫌疑 2:账号异常 3:交易欺诈 4:异常发布
+     * @param ruleName   规则名称
+     * @param businessId 业务ID
+     * @param detailInfo 详细信息(JSON格式)
+     */
+    @PostMapping("/record")
+    public void recordRiskControlData(@RequestParam("userId") Integer userId,
+                                      @RequestParam("ruleType") Integer ruleType,
+                                      @RequestParam("ruleName") String ruleName,
+                                      @RequestParam("businessId") Integer businessId,
+                                      @RequestParam("detailInfo") String detailInfo) {
+        riskControlService.recordRiskControlData(userId, ruleType, ruleName, businessId, detailInfo);
+    }
+}

+ 25 - 0
alien-second/src/main/java/shop/alien/second/service/RiskControlService.java

@@ -0,0 +1,25 @@
+package shop.alien.second.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.second.SecondRiskControlRecord;
+
+/**
+ * 风控服务接口
+ */
+public interface RiskControlService extends IService<SecondRiskControlRecord> {
+    
+    /**
+     * 记录风控数据
+     *
+     * @param userId     用户ID
+     * @param ruleType   规则类型 1:洗钱嫌疑 2:账号异常 3:交易欺诈 4:异常发布
+     * @param ruleName   规则名称
+     * @param businessId 业务ID
+     * @param detailInfo 详细信息(JSON格式)
+     */
+    void recordRiskControlData(Integer userId,
+                               Integer ruleType,
+                               String ruleName,
+                               Integer businessId,
+                               String detailInfo);
+}

+ 56 - 0
alien-second/src/main/java/shop/alien/second/service/impl/RiskControlServiceImpl.java

@@ -0,0 +1,56 @@
+package shop.alien.second.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.second.SecondRiskControlRecord;
+import shop.alien.mapper.second.SecondRiskControlRecordMapper;
+import shop.alien.second.service.RiskControlService;
+
+import java.util.Date;
+
+/**
+ * 风控服务实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class RiskControlServiceImpl extends ServiceImpl<SecondRiskControlRecordMapper, SecondRiskControlRecord> implements RiskControlService {
+
+    private final SecondRiskControlRecordMapper secondRiskControlRecordMapper;
+
+    /**
+     * 记录风控数据
+     *
+     * @param userId     用户ID
+     * @param ruleType   规则类型 1:洗钱嫌疑 2:账号异常 3:交易欺诈 4:异常发布
+     * @param ruleName   规则名称
+     * @param businessId 业务ID
+     * @param detailInfo 详细信息(JSON格式)
+     */
+    @Override
+    public void recordRiskControlData(Integer userId,
+                                       Integer ruleType,
+                                       String ruleName,
+                                       Integer businessId,
+                                       String detailInfo) {
+        try {
+            SecondRiskControlRecord record = new SecondRiskControlRecord();
+            record.setUserId(userId);
+            record.setRuleType(ruleType);
+            record.setRuleName(ruleName);
+            record.setBusinessId(businessId);
+            record.setDetailInfo(detailInfo);
+            record.setCreatedTime(new Date());
+            record.setUpdatedTime(new Date());
+            record.setDeleteFlag(0);
+            record.setCreatedUserId(userId);
+            record.setUpdatedUserId(userId);
+
+            secondRiskControlRecordMapper.insert(record);
+        } catch (Exception e) {
+            log.error("记录风控数据时发生异常: 用户ID={}, 规则类型={},业务id={}", userId, ruleType, businessId, e);
+        }
+    }
+}

+ 43 - 35
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java

@@ -16,41 +16,29 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 import shop.alien.config.properties.RiskControlProperties;
 import shop.alien.entity.SecondVideoTask;
 import shop.alien.entity.second.*;
-import shop.alien.entity.second.SecondTradeRecord;
+import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
 import shop.alien.entity.second.vo.*;
-import shop.alien.entity.second.vo.SecondGoodsDetailVo;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.LifeUserVo;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
-import shop.alien.mapper.second.SecondGoodsAuditMapper;
-import shop.alien.mapper.second.SecondGoodsMapper;
-import shop.alien.mapper.second.SecondGoodsRecordMapper;
-import shop.alien.mapper.second.SecondTradeRecordMapper;
+import shop.alien.mapper.second.*;
 import shop.alien.second.feign.AlienStoreFeign;
 import shop.alien.second.service.PlatformSecondTradeService;
+import shop.alien.second.service.RiskControlService;
 import shop.alien.second.service.SecondGoodsService;
 import shop.alien.second.service.VideoModerationService;
 import shop.alien.util.common.Constants;
-import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
 import shop.alien.util.common.VideoUtils;
 import shop.alien.util.common.safe.*;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -154,6 +142,11 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
 
     @Autowired
     private RiskControlProperties riskControlProperties;
+    
+    /**
+     * 风控服务
+     */
+    private final RiskControlService riskControlService;
 
     @Override
     public SecondGoodsRecordDetailVo getAdminGoodsRecordDetail(Integer recordId) {
@@ -560,7 +553,7 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
                 // TODO 插入风控记录
 
             }
-            
+
             // 检查用户是否在24小时内发布同类商品超过阈值
             if (!checkUserPublishSameCategoryLimit(goods)) {
                 log.warn("用户 {} 在24小时内发布同类商品次数超过限制", goodsDTO.getUserId());
@@ -613,15 +606,17 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
     private boolean checkUserPublishLimit(SecondGoods goods) {
         // 获取配置的阈值
         int publishLimit = riskControlProperties.getTradeFraud().getPublishCount24h();
+        // 时间窗口(小时)
+        int timeWindowHours = riskControlProperties.getTradeFraud().getTimeWindowHours();
 
-        // 计算24小时前的时间
-        Date twentyFourHoursAgo = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000L);
+        // 计算时间窗口前的时间
+        Date timeWindowStart = new Date(System.currentTimeMillis() - timeWindowHours * 60 * 60 * 1000L);
 
         // 查询用户在24小时内发布的商品数量
         LambdaQueryWrapper<SecondGoodsRecord> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(SecondGoodsRecord::getUserId, goods.getUserId())
                 .eq(SecondGoodsRecord::getId, goods.getId())
-                .ge(SecondGoodsRecord::getReleaseTime, twentyFourHoursAgo)
+                .ge(SecondGoodsRecord::getReleaseTime, timeWindowStart)
                 .in(SecondGoodsRecord::getGoodsStatus,
                         SecondGoodsStatusEnum.LISTED.getCode(),
                         SecondGoodsStatusEnum.SOLD.getCode());
@@ -633,33 +628,46 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
     }
 
     /**
-     * 检查用户在24小时内发布同类商品的数量是否超过限制
+     * 检查用户在指定时间窗口内发布同类商品的数量是否超过限制
      * @param goods 商品信息
      * @return 是否未超过限制
      */
     private boolean checkUserPublishSameCategoryLimit(SecondGoods goods) {
         // 获取配置的阈值
         int sameCategoryLimit = riskControlProperties.getAbnormalPublish().getSameCategoryCount24h();
+        int timeWindowHours = riskControlProperties.getAbnormalPublish().getTimeWindowHours();
         
-        // 计算24小时前的时间
-        Date twentyFourHoursAgo = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000L);
+        // 计算时间窗口前的时间
+        Date timeWindowStart = new Date(System.currentTimeMillis() - timeWindowHours * 60 * 60 * 1000L);
         
-        // 查询用户在24小时内发布同类商品的数量
-        LambdaQueryWrapper<SecondGoodsRecord> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(SecondGoodsRecord::getUserId, goods.getUserId())
-                .eq(SecondGoodsRecord::getCategoryOneId, goods.getCategoryOneId())
-                .eq(SecondGoodsRecord::getCategoryTwoId, goods.getCategoryTwoId())
-                .ge(SecondGoodsRecord::getReleaseTime, twentyFourHoursAgo)
-                .in(SecondGoodsRecord::getGoodsStatus,
+        // 查询用户在时间窗口内发布同类商品的数量
+        LambdaQueryWrapper<SecondGoods> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(SecondGoods::getUserId, goods.getUserId())
+                .eq(SecondGoods::getCategoryOneId, goods.getCategoryOneId())
+                .eq(SecondGoods::getCategoryTwoId, goods.getCategoryTwoId())
+                .ge(SecondGoods::getReleaseTime, timeWindowStart)
+                .in(SecondGoods::getGoodsStatus,
                         SecondGoodsStatusEnum.LISTED.getCode(),
                         SecondGoodsStatusEnum.SOLD.getCode());
-        
-        int sameCategoryCount = secondGoodsRecordMapper.selectCount(queryWrapper);
-        
-        // 如果发布数量超过限制,返回false
-        return sameCategoryCount < sameCategoryLimit;
+
+        // 获取发布
+        List<SecondGoods> secondGoodsList = list(queryWrapper);
+        // 提取商品id集合
+        List<Integer> secondGoodsIds = secondGoodsList.stream().map(SecondGoods::getId).collect(Collectors.toList());
+        // 转成json
+        String json = JSON.toJSONString(secondGoodsIds);
+        // 获取实际发布数量
+        int sameCategoryCount = secondGoodsList.size();
+        // 如果发布数量超过限制,记录风控数据
+        if (sameCategoryCount >= sameCategoryLimit) {
+            riskControlService.recordRiskControlData(goods.getUserId(), 4, "异常发布-同类商品发布频率", goods.getId(),json);
+            return false;
+        }
+        return true;
     }
 
+
+
     /**
      * 执行内容审核
      * @param goodsDTO 商品信息