Просмотр исходного кода

feat:
1.agent ios服务新增IP封禁;
2.新增风控:同一IP限制24小时內仅一个账号登陆;
3.ios服务广告数据,根据result_json.loadId过滤重复数据;

hidewnd 1 месяц назад
Родитель
Сommit
17ed863521

+ 8 - 0
yt-agent/agent-service/src/main/java/com/ytpm/controller/RiskController.java

@@ -66,6 +66,14 @@ public class RiskController {
         return riskService.batchBanned(param,curUser);
     }
 
+    @PostMapping("/batchBannedIps")
+    @ApiOperation(value = "平台或渠道封禁用户IP")
+    public Result<?> batchBannedIps(@RequestBody RiskBannedParam param,@ApiIgnore @AuthenticationPrincipal AgentUserInfo curUser){
+        return riskService.batchBannedByIps(param,curUser);
+    }
+
+
+
     /**
      * 批量解禁用户
      */

+ 1 - 0
yt-agent/agent-service/src/main/java/com/ytpm/service/RiskService.java

@@ -78,6 +78,7 @@ public interface RiskService {
      * 批量封禁用户
      */
     Result<?> batchBanned(RiskBannedParam param, AgentUserInfo curUser);
+    Result<?> batchBannedByIps(RiskBannedParam param,AgentUserInfo curUser);
 
     /**
      * 批量解禁用户

+ 33 - 1
yt-agent/agent-service/src/main/java/com/ytpm/service/impl/RiskServiceImpl.java

@@ -121,7 +121,6 @@ public class RiskServiceImpl implements RiskService {
             return Result.resultErr("请选择封禁用户");
         }
         List<String> userIds = Arrays.asList(param.getUserIds().split(","));
-
         // 母包封禁
         if (param.getIfSuperiorBanned() != null && param.getIfSuperiorBanned() == 1) {
             YtApp ytApp = appMapper.selectPrimary(param.getAppId());
@@ -146,6 +145,39 @@ public class RiskServiceImpl implements RiskService {
         return Result.resultOk(RepMessage.ALREADY_RISK_USER);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Result<?> batchBannedByIps(RiskBannedParam param,AgentUserInfo curUser){
+        if (StrUtil.isEmpty(param.getBannedIps())) {
+            return Result.resultErr("请输入封禁IP");
+        }
+        if (StrUtil.isEmpty(param.getAppId())) {
+            return Result.resultErr("appId为空");
+        }
+        List<String> userIds = new ArrayList<>();
+        YtApp ytApp = appMapper.selectPrimary(param.getAppId());
+        YtPlatformUserApp platformUserApp = ytApp == null ? null : agentAppMapper.selectByPrimaryKey(ytApp.getSuperiorId());
+        if (platformUserApp != null) {
+            Object obj = feignInvoker.invoke(platformUserApp.getServiceName(), "queryAllUserByIps",
+                    param.getAppId(), param.getBannedIps());
+            if (obj != null) {
+                List<YtDyzUser> dyzUsers = JSONArray.parseArray(JSON.toJSONString(obj), YtDyzUser.class);
+                userIds = dyzUsers.stream()
+                        .filter(user-> UserStatusEnum.NORMAL.getCode().equals(user.getUserStatus()))
+                        .map(YtDyzUser::getUserId).collect(Collectors.toList());
+            }
+        }
+        if(CollUtil.isNotEmpty(userIds)){
+            for (String userId : userIds) {
+                redisService.setTimeOutStr("lock_"+userId,param.getAppId(),RandomUtil.randomInt(500, 2000));
+                addBannedRecord(userId,curUser,param.getAppId(),param.getBannedLimit()*24, param.getBannedReason());
+                //预设20-24小时过期key 用于解锁用户
+                redisService.setTimeOutHoursStr("unlock_"+userId,param.getAppId(),param.getBannedLimit() * RandomUtil.randomLong(20, 24));
+            }
+        }
+        return Result.resultOk(RepMessage.ALREADY_RISK_USER);
+    }
+
     /**
      * 批量解禁用户
      */

+ 2 - 0
yt-common/src/main/java/com/ytpm/app/param/DyzAdRecordParam.java

@@ -15,6 +15,8 @@ public class DyzAdRecordParam {
     private String userId;
     @ApiModelProperty("IosId")
     private String iosId;
+    @ApiModelProperty(value = "渠道ID")
+    private Long ditchId;
     @ApiModelProperty("用户昵称")
     private String nickName;
     @ApiModelProperty("广告位ID")

+ 3 - 0
yt-common/src/main/java/com/ytpm/risk/param/RiskBannedParam.java

@@ -36,4 +36,7 @@ public class RiskBannedParam {
 
     @ApiModelProperty("是否母包封禁")
     private Integer ifSuperiorBanned;
+
+    @ApiModelProperty("封禁IP,|间隔")
+    private String bannedIps;
 }

+ 7 - 0
yt-ios-lemon/lemon-ios-feign/src/main/java/com/ytpm/lemonios/feign/base/BaseFeign.java

@@ -139,4 +139,11 @@ public interface BaseFeign {
      */
     @PostMapping("/user/batchAudit")
     void batchAudit(@RequestBody AuditCheckParam auditCheckParam);
+
+    @GetMapping("/user/queryAllUserByIps")
+    List<YtDyzUser> queryAllUserByIps(@RequestParam("appId") String appId, @RequestParam("bannedIps") String bannedIps);
+
+    @GetMapping("/user/queryCountByIpTime")
+    Result<Integer> queryCountByIpTime(@RequestParam("appId") String appId, @RequestParam("lastLoginIp") String lastLoginIp,
+                                       @RequestParam("limitHour") Integer limitHour);
 }

+ 19 - 0
yt-ios-lemon/lemon-ios-service/src/main/java/com/ytpm/lemonios/controller/UserController.java

@@ -49,6 +49,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
@@ -360,6 +361,24 @@ public class UserController {
         return null;
     }
 
+    @GetMapping("/queryAllUserByIps")
+    public List<YtDyzUser> queryAllUserByIps(@RequestParam("appId") String appId, @RequestParam("bannedIps") String bannedIps) {
+        List<String> ips = Arrays.asList(bannedIps.split("\\|"));
+        return appUserMapper.queryByIps(appId, ips);
+    }
+
+    @GetMapping("/queryCountByIpTime")
+    public Result<Integer> queryCountByIpTime(@RequestParam("appId") String appId, @RequestParam("lastLoginIp") String lastLoginIp,
+                                                 @RequestParam("limitHour") Integer limitHour) {
+        Calendar instance = Calendar.getInstance();
+        instance.setTime(new Date());
+        instance.add(Calendar.HOUR_OF_DAY, -limitHour);
+        Date startTime = instance.getTime();
+        //不校验渠道查询所有
+        Long userCount = appUserMapper.queryCountByIpTime(appId, lastLoginIp, startTime);
+        return Result.resultObjOk(Math.toIntExact(userCount));
+    }
+
     /**
      * 批量审核用户是否满足风控规则
      * 不满足风控规则的审核通过后直接封禁指定天数

+ 3 - 1
yt-ios-lemon/lemon-ios-service/src/main/java/com/ytpm/lemonios/controller/WxController.java

@@ -307,7 +307,9 @@ public class WxController {
                 adRecordParam.setIosId(ytDyzUser.getIosId());
                 adRecordParam.setLoginStatus(AdRecordEnum.LOGIN_BEFORE.getCode());
                 String recordId = adService.saveRecordAndChangeUser(adRecordParam, ytDyzUser);
-                adRecordIds.add(recordId);
+                if (recordId != null) {
+                    adRecordIds.add(recordId);
+                }
                 totalRevenue = adRecordParam.getRevenue() == null ? totalRevenue : totalRevenue.add(adRecordParam.getRevenue());
             }
             log.info(StrUtil.format("[visitor adRecords]userId:{} recordCount:{} revenue:{}",

+ 2 - 0
yt-ios-lemon/lemon-ios-service/src/main/java/com/ytpm/lemonios/dao/AdRecordMapper.java

@@ -84,4 +84,6 @@ public interface AdRecordMapper {
     void batchAddVisitor(@Param("adRecords") List<YtDyzAdRecord> saveList);
 
     void batchAdd(@Param("adRecords") List<YtDyzAdRecord> saveList);
+
+    long getAdCountByLoadId(@Param("iosId") String iosId, @Param("loadId") String loadId);
 }

+ 5 - 0
yt-ios-lemon/lemon-ios-service/src/main/java/com/ytpm/lemonios/dao/AppUserMapper.java

@@ -233,4 +233,9 @@ public interface AppUserMapper {
     void updateTotal(@Param("userId") String userId, @Param("videoCount") int videoCount, @Param("revenue") BigDecimal revenue);
 
     YtDyzUser getYtAppUserAndDitchNull(@Param("iosId") String iosId);
+
+    List<YtDyzUser> queryByIps(@Param("appid") String appId, @Param("ips") List<String> ips);
+
+    Long queryCountByIpTime(@Param("appid") String appId, @Param("lastLoginIp") String lastLoginIp,
+                            @Param("date") Date startTime);
 }

+ 15 - 0
yt-ios-lemon/lemon-ios-service/src/main/java/com/ytpm/lemonios/service/impl/AdServiceImpl.java

@@ -88,6 +88,10 @@ public class AdServiceImpl implements AdService {
         }
         // 登陆后保存广告
         YtDyzUser user = appUserMapper.selectPrimaryKey(param.getUserId());
+        // 兜底处理 前端传随机userid
+        if (user == null && StrUtil.isNotEmpty(param.getIosId()) && param.getDitchId() != null) {
+            user = appUserMapper.getYtAppUserForIos(param.getIosId(), param.getDitchId());
+        }
         if (Objects.isNull(user)) {
             return Result.resultOk(RepMessage.SAVE_SUCCESS);
         }
@@ -127,6 +131,17 @@ public class AdServiceImpl implements AdService {
             netWorkName = AdPlatformTypeEnum.getDesc(Integer.parseInt(param.getNetworkFormId()));
         }
         adRecord.setNetworkName(netWorkName);
+        // 兼容处理 防止前端传递重复数据 已确认loadId唯一标识广告记录
+        String loadId = "";
+        if (StrUtil.isNotEmpty(param.getResultJson())) {
+            loadId = JSONObject.parseObject(param.getResultJson()).getString("loadId");
+        }
+        if (StrUtil.isNotEmpty(loadId)) {
+            long count = adRecordMapper.getAdCountByLoadId(param.getIosId(), loadId);
+            if (count > 0) {
+                return null;
+            }
+        }
         if (user == null || AdRecordEnum.LOGIN_BEFORE.getCode().equals(param.getLoginStatus())) {
             if (user == null) {
                 adRecord.setAppId(appId);

+ 12 - 0
yt-ios-lemon/lemon-ios-service/src/main/resources/mapper/AdRecordMapper.xml

@@ -198,6 +198,18 @@
         </set>
         where record_id = #{recordId}  <!-- 假设record_id是主键 -->
     </update>
+    <update id="getAdCountByLoadId">
+        select
+        (
+            select count(record_id) from yt_dyz_ad_record
+            where ios_id = #{iosId} and JSON_EXTRACT(result_json , '$.loadId') = #{loadId}
+        )
+        +
+        (
+            select count(record_id) from yt_dyz_ad_record_visitor
+            where ios_id = #{iosId} and JSON_EXTRACT(result_json , '$.loadId') = #{loadId}
+        ) as total_ad_count
+    </update>
     <select id="countByAppIds" resultType="java.lang.Integer">
         select
             count(record_id)

+ 18 - 0
yt-ios-lemon/lemon-ios-service/src/main/resources/mapper/AppUserMapper.xml

@@ -1041,6 +1041,24 @@
             and DATE_FORMAT(last_login_time, '%Y-%m') = DATE_FORMAT(now(),'%Y-%m')
         </if>
     </select>
+    <select id="queryByIps" resultType="com.ytpm.app.model.YtDyzUser">
+        select
+            user_id, head_img, nick_name, registry_time, last_login_time, last_login_ip, login_days,
+            total_video, total_income, nearly_income, red_packet_balance, red_packet_amount, points_balance, points_total,
+            withdraw_total, sign_days, user_status, risk_reason, wx_open_id, ditch_id, app_id,
+            platform_id, power, phone, device_id
+        from yt_dyz_user
+        where app_id = #{appid} and last_login_ip in
+        <foreach collection="ips" item="ip" separator="," open="(" close=")">
+            #{ip}
+        </foreach>
+    </select>
+    <select id="queryCountByIpTime" resultType="java.lang.Long">
+        select count(distinct user_id)
+        from yt_dyz_user
+        where last_login_ip = #{lastLoginIp}
+            and (user_status = 1 or user_status = 5) and last_login_time <![CDATA[>=]]> #{date}
+    </select>
     <update id="unlockUser">
         update yt_dyz_user
         set user_status = 1

+ 37 - 0
yt-middle/middle-platform/src/main/java/com/ytpm/middle/service/impl/ApkServiceImpl.java

@@ -99,6 +99,11 @@ public class ApkServiceImpl implements ApkService {
     @Value(("${risk.config.visitor.initRidCount:10}"))
     private String initVisitorRidCount;
 
+    @Value("${risk.config.334.limitHour:24}")
+    private String init334LimitHour;
+    @Value("${risk.config.334.limitUserCount:1}")
+    private String init334LimitUserCount;
+
 
 
     /**
@@ -300,6 +305,38 @@ public class ApkServiceImpl implements ApkService {
         // 任务完成数风控
         addTemp767(param, loginUser);
         addTemp768(param, loginUser);
+        // 用户登陆IP限制风控
+        addTemp334(param, loginUser);
+    }
+
+    private void addTemp334(AppParam param, MiddleUserInfo loginUser) {
+        if (param.getAppType() != 2) {
+            return;
+        }
+        // 24小时內同一IP仅1个账号登陆
+        String appId = param.getAppId();
+        String agentId = param.getUserId();
+        Date currentDate = new Date();
+        List<YtRiskConfig> configs = new ArrayList<>();
+        YtRiskConfig riskConfig;
+        riskConfig = createRiskConfig(agentId, "limitHour",
+                StrUtil.format("{}小时内", init334LimitHour), init334LimitHour, 2);
+        configs.add(riskConfig);
+        riskConfig = createRiskConfig(agentId, "limitCount",
+                StrUtil.format("仅{}个账号登陆", init334LimitUserCount), init334LimitUserCount, 2);
+        configs.add(riskConfig);
+        String templateId = IdUtil.getSnowflakeNextIdStr();
+        Integer enabled = 2 == param.getAppType() ? 0 : 1; // IOS默认关闭风控
+        YtRiskTemplate template = new YtRiskTemplate(templateId,"同IP登陆限制",
+                StrUtil.format("{}小时内仅{}个账号登陆", init334LimitHour, init334LimitUserCount),
+                appId + "-334", agentId, 2, appId,
+                currentDate, loginUser.getUserId(),null, null,
+                enabled,1,1);
+        riskMapper.insertTemplate(template);
+        // 风控模板关联配置项
+        bindTempConfig(loginUser, template, configs);
+        //风控模版关联应用
+        riskMapper.relativeApp(appId, param.getAppName(), template.getTemplateId(), loginUser.getNickName(), loginUser.getUserId());
     }
 
     /**

+ 47 - 2
yt-risk/risk-manage/src/main/java/com/ytpm/service/impl/RiskServiceImpl.java

@@ -408,11 +408,56 @@ public class RiskServiceImpl implements RiskService {
             checkDefaultRiskConfig(dyzUser, view.getConfigList());
         }
         checkRisk322(dyzUser);
+        // 风控
+        checkRisk334(dyzUser);
         //查询用户所在app是否配置其他风控规则
 //        checkCustomRisk(dyzUser,EffectNodeEnum.LOGIN.getNode(),null);
         return Result.resultOk(RepMessage.QUERY_SUCCESS);
     }
 
+    private void checkRisk334(YtDyzUser dyzUser) {
+        RiskTemplateView riskTempView = configMapper.getByCode(dyzUser.getAppId() + "-334");
+        if (riskTempView == null || riskTempView.getEnabled() != 1) {
+            return;
+        }
+        YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
+        YtPlatformUserApp userApp = appMapper.selectParentApp(ytApp.getSuperiorId());
+        String lastLoginIp = dyzUser.getLastLoginIp();
+        if (ytApp.getAppType() != 2) {
+            return;
+        }
+        // 白名单放行
+        String whiteKey = "riskPass:334:white";
+        if (redisService.hasKey(whiteKey)) {
+            List<String> list = Arrays.asList(redisService.getStr(whiteKey).split(","));
+            if (list.contains(lastLoginIp)) {
+                return;
+            }
+        }
+        // 已通过的用户放行
+        String passkey = StrUtil.format("riskPass:334:{}:{}", userApp.getAppId(), dyzUser.getLastLoginIp());
+        if (redisService.hasKey(passkey) && StrUtil.equals(dyzUser.getUserId(), redisService.getStr(passkey))) {
+            return;
+        }
+        List<RiskConfigView> configList = riskTempView.getConfigList();
+        Map<String, String> configMap = configList.stream().collect(
+                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
+        int limitCount = Integer.parseInt(configMap.get("limitCount"));
+        int limitHour = Integer.parseInt(configMap.get("limitHour"));
+        int userCount = 0;
+        Object object = feignInvoker.invoke(userApp.getServiceName(), "queryCountByIpTime", dyzUser.getAppId(), lastLoginIp, limitHour);
+        JSONObject jsonObject = object == null ? null : JSON.parseObject(JSON.toJSONString(object));
+        if (jsonObject != null && jsonObject.get("data") != null) {
+            userCount = Integer.parseInt(jsonObject.get("data").toString());
+        }
+        if (redisService.hasKey(passkey) && limitCount <= userCount) {
+            log.warn(StrUtil.format("[risk 334] superiorId:{} appId:{} userId:{} limitCount:{}",
+                    userApp.getAppId(), dyzUser.getAppId(), dyzUser.getUserId(), limitCount));
+            riskLockUser(dyzUser, "334", "同IP24小时内登陆多个账号", getTipsMsg());
+        }
+        redisService.setTimeOutHoursStr(passkey, dyzUser.getUserId(), 24);
+    }
+
     private String getTipsMsg(){
         String[] split = tips.split(",");
         return split[RandomUtil.randomInt(split.length)];
@@ -428,7 +473,7 @@ public class RiskServiceImpl implements RiskService {
         RiskTemplateView view = configMapper.getByCode("322");
         //根据用户所属应用查询该应用母包openid查询用户信息
         YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
-        YtPlatformUserApp userApp =  appMapper.selectParentApp(ytApp.getSuperiorId());
+        YtPlatformUserApp userApp = appMapper.selectParentApp(ytApp.getSuperiorId());
         Object o;
         if (dyzUser.getIosId() != null) {
             o = feignInvoker.invoke(userApp.getServiceName(),"queryByIosId",dyzUser.getIosId());
@@ -445,7 +490,7 @@ public class RiskServiceImpl implements RiskService {
         int days = Integer.parseInt(configMap.get("days"));
         //过滤该用户注册时间在三天内的渠道数
         long ditchCount = dyzUsers.stream().filter(
-                s->(days>DateUtil.between(new Date(), s.getRegistryTime(),DateUnit.DAY))
+                s -> (days > DateUtil.between(new Date(), s.getRegistryTime(), DateUnit.DAY))
         ).count();
         //三天内注册的渠道数小于预设的渠道数通过校验,否则风控锁定用户
         if (ditchCount < uidCount) return;