|
|
@@ -2,6 +2,7 @@ package com.ytpm.service.impl;
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
import cn.hutool.core.util.IdUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import com.github.pagehelper.PageHelper;
|
|
|
@@ -13,7 +14,9 @@ import com.ytpm.agent.model.YtPlatformDeblocking;
|
|
|
import com.ytpm.agent.view.AgentAppClassView;
|
|
|
import com.ytpm.agent.view.AgentEnableAppView;
|
|
|
import com.ytpm.agent.view.AgentUserInfo;
|
|
|
+import com.ytpm.app.model.DefaultRiskConfig;
|
|
|
import com.ytpm.app.model.YtAppUserLoginRecord;
|
|
|
+import com.ytpm.app.model.YtDyzAdRecord;
|
|
|
import com.ytpm.app.model.YtDyzLoginRecord;
|
|
|
import com.ytpm.app.model.YtDyzUser;
|
|
|
import com.ytpm.app.view.YtAppUserListView;
|
|
|
@@ -53,14 +56,21 @@ import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
import java.lang.reflect.Field;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.text.ParseException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
+import java.util.Calendar;
|
|
|
import java.util.Collections;
|
|
|
import java.util.Comparator;
|
|
|
import java.util.Date;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
+import java.util.concurrent.Executors;
|
|
|
+import java.util.concurrent.ScheduledExecutorService;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@Slf4j(topic = "风控服务")
|
|
|
@@ -80,8 +90,6 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
|
|
|
@Resource
|
|
|
private RiskManageMapper riskManageMapper;
|
|
|
@Resource
|
|
|
- private AppLoginRecordMapper loginRecordMapper;
|
|
|
- @Resource
|
|
|
private AppFeign appFeign;
|
|
|
/**
|
|
|
* 查询配置字段选项
|
|
|
@@ -391,20 +399,49 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 校验用户是否处于风控中
|
|
|
+ * 登录检查默认风控配置
|
|
|
*/
|
|
|
@Override
|
|
|
- public Result<?> checkRisk(String openid) {
|
|
|
- YtAppUser appUser = riskUserMapper.selectByUserId(openid);
|
|
|
- if(Objects.isNull(appUser)){
|
|
|
- throw new CustomerException(RepMessage.OBJECT_NOT_EXIST);
|
|
|
- }
|
|
|
- if(!UserStatusEnum.NORMAL.getCode().equals(appUser.getUserStatus())){
|
|
|
- throw new CustomerException(RepMessage.CURRENT_USER_RISKING);
|
|
|
- }
|
|
|
+ public Result<?> checkRisk(YtDyzUser dyzUser) {
|
|
|
+ RiskTemplateView view = configMapper.getByCode(dyzUser.getRiskCode());
|
|
|
+ checkDefaultRiskConfig(dyzUser, view.getConfigList());
|
|
|
return Result.resultOk(RepMessage.QUERY_SUCCESS);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 校验默认风控配置, 固定配置值不改
|
|
|
+ * days ?天内为1
|
|
|
+ * ipPrefix IP前?段为4
|
|
|
+ * ipSameCount 同IP登录数量为>2
|
|
|
+ */
|
|
|
+ private void checkDefaultRiskConfig(YtDyzUser dyzUser, List<RiskConfigView> configList) {
|
|
|
+ //封装配置值map
|
|
|
+ Map<String, String> configMap = configList.stream().collect(
|
|
|
+ Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
|
|
|
+ //封装用户值map
|
|
|
+ List<YtDyzLoginRecord> recordList = dyzUser.getLoginRecordList();
|
|
|
+ Calendar instance = Calendar.getInstance();
|
|
|
+ instance.add(Calendar.DATE, -Integer.parseInt(configMap.get("days")));
|
|
|
+ Date yesterday = instance.getTime();
|
|
|
+ //拿到同IP登录次数
|
|
|
+ long count = recordList.stream()
|
|
|
+ .peek(s->s.setLoginIp(s.getLoginIp().substring(0,Integer.parseInt(configMap.get("ipPrefix")))))
|
|
|
+ .filter(
|
|
|
+ s -> s.getLoginTime().after(yesterday) && s.getLoginTime().before(new Date())
|
|
|
+ ).count();
|
|
|
+ int intCount = Math.toIntExact(count);
|
|
|
+ int ipSameCount = Integer.parseInt(configMap.get("ipSameCount"));
|
|
|
+ if(intCount > ipSameCount){//校验同IP登录次数大于预设值,锁定用户
|
|
|
+ YtDyzUser newDyzUser = new YtDyzUser();
|
|
|
+ newDyzUser.setUserId(dyzUser.getUserId());
|
|
|
+ newDyzUser.setUserStatus(UserStatusEnum.LOCK.getCode());
|
|
|
+ newDyzUser.setRiskCode("313");
|
|
|
+ newDyzUser.setRiskReason("风控编码313");
|
|
|
+ appFeign.updateUserInfo(newDyzUser);
|
|
|
+ throw new CustomerException("当前用户已被风控!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 根据appId查询应用配置模版
|
|
|
*/
|
|
|
@@ -413,6 +450,102 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
|
|
|
return Result.resultObjOk(configMapper.getAppConfig(appId));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 根据风控编码获取风控配置
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Result<RiskTemplateView> getRiskByCode(String code) {
|
|
|
+ return Result.resultObjOk(configMapper.getByCode(code));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验广告的默认风控配置
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void checkAdRisk(String userId,BigDecimal userRevenue) {
|
|
|
+ ResultTable<YtDyzAdRecord> table = appFeign.adRecords(userId);
|
|
|
+ List<YtDyzAdRecord> records = table.getData();
|
|
|
+ //校验风控742规则
|
|
|
+ RiskTemplateView ecpmLimit= configMapper.getByCode("742");
|
|
|
+ checkRisk742(ecpmLimit,userId,records);
|
|
|
+ //校验风控746规则
|
|
|
+ checkRisk746(userId,records,userRevenue);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验默认风控规则746
|
|
|
+ */
|
|
|
+ private void checkRisk746(String userId, List<YtDyzAdRecord> records, BigDecimal userRevenue) {
|
|
|
+ RiskTemplateView revenue = configMapper.getByCode("746");
|
|
|
+ Map<String, String> revenueMap = revenue.getConfigList().stream().collect(
|
|
|
+ Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
|
|
|
+ List<YtDyzAdRecord> revenues = records.stream().filter(
|
|
|
+ s -> s.getRevenue().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
|
|
|
+ int incomeCount = 0;
|
|
|
+
|
|
|
+ incomeCount = revenues.isEmpty()?incomeCount:records.size();
|
|
|
+
|
|
|
+ if(userRevenue.compareTo(BigDecimal.ZERO) > 0){
|
|
|
+ ++incomeCount;
|
|
|
+ }
|
|
|
+ //判断当日获得收益的广告达到预设数值,触发风控规则
|
|
|
+ int rewardCount = Integer.parseInt(revenueMap.get("rewardCount"));
|
|
|
+ if(rewardCount>=incomeCount){
|
|
|
+ BigDecimal income = new BigDecimal(revenueMap.get("income"));
|
|
|
+ //获取最先的两条
|
|
|
+ BigDecimal reduce = revenues.stream().map(YtDyzAdRecord::getRevenue).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ if(income.compareTo(reduce)<0){
|
|
|
+ riskLockUser(userId, "746","最低收益限制","用户已被风控,风控编码:746");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验默认风控规则742
|
|
|
+ */
|
|
|
+ private void checkRisk742(RiskTemplateView ecpmLimit,String userId,List<YtDyzAdRecord> records) {
|
|
|
+ Map<String, String> limitMap = ecpmLimit.getConfigList().stream().collect(
|
|
|
+ Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
|
|
|
+ int adCount = 0;
|
|
|
+ adCount = records.isEmpty()?adCount:records.size();
|
|
|
+ ++adCount;
|
|
|
+ //判断当日观看视频数已经达到风控条件预设的视频数
|
|
|
+ int firstAdCount = Integer.parseInt(limitMap.get("firstAdCount"));
|
|
|
+ if(firstAdCount>=adCount){
|
|
|
+ //判断存在ecpm小于预设值 达到预设条数时触发风控
|
|
|
+ int haveCount = Integer.parseInt(limitMap.get("haveCount"));
|
|
|
+ long count = records.stream().filter(
|
|
|
+ s -> (Integer.parseInt(s.getEcpm()) * 10) > haveCount).count();
|
|
|
+ int exact = Math.toIntExact(count);
|
|
|
+ if(count>exact){
|
|
|
+ riskLockUser(userId, "742","激励视频ecpm值控制","用户已被风控,风控编码:742");
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 风控锁定用户
|
|
|
+ */
|
|
|
+ private void riskLockUser(String userId, String riskCode, String tempName, String errMsg) {
|
|
|
+ YtDyzUser next = new YtDyzUser();
|
|
|
+ next.setUserId(userId);
|
|
|
+ next.setUserStatus(UserStatusEnum.LOCK.getCode());
|
|
|
+ next.setRiskCode(riskCode);
|
|
|
+ next.setRiskReason(tempName);
|
|
|
+ appFeign.updateUserInfo(next);
|
|
|
+ //TODO 创建一次性定时任务用于24小时候解锁用户
|
|
|
+ ScheduledExecutorService scheduled = Executors.newSingleThreadScheduledExecutor();
|
|
|
+ scheduled.schedule(()->{
|
|
|
+ YtDyzUser user = new YtDyzUser();
|
|
|
+ user.setUserId(userId);
|
|
|
+ user.setUserStatus(UserStatusEnum.NORMAL.getCode());
|
|
|
+ appFeign.updateUserInfo(user);
|
|
|
+ scheduled.shutdown();
|
|
|
+ },24, TimeUnit.HOURS);
|
|
|
+ throw new CustomerException(errMsg);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 修改用户信息
|
|
|
*/
|
|
|
@@ -478,4 +611,88 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
|
|
|
riskUserMapper.addBannedRecord(banned);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验风控规则
|
|
|
+ * TODO 第一版本仅处理登录相关 后续优化成 按照粒度(不同APP不同粒度:如答题粒度分为 用户、登录、答题记录、提现记录等) 划分
|
|
|
+ */
|
|
|
+ private void checkCondition(Object obj,RiskTemplateView data) {
|
|
|
+ List<RiskConfigView> configList = data.getConfigList();
|
|
|
+ for (RiskConfigView configView : configList) {
|
|
|
+ //校验配置类型对应字段 这里传入的Obj 就是风控粒度对象
|
|
|
+ checkConfig(obj,configView);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 逐个校验配置 当前仅有好运答题王的用户
|
|
|
+ */
|
|
|
+ private void checkConfig(Object obj, RiskConfigView vo) {
|
|
|
+ Field[] fields = obj.getClass().getDeclaredFields();
|
|
|
+ List<Field> cFields = Arrays.stream(fields).filter(
|
|
|
+ s -> s.getName().equals(vo.getFieldName())).collect(Collectors.toList());
|
|
|
+ if(CollUtil.isEmpty(cFields)){
|
|
|
+ throw new CustomerException("未匹配到准入条件字段请联系管理员!");
|
|
|
+ }
|
|
|
+ for (Field conditionField : cFields) {
|
|
|
+ conditionField.setAccessible(true);
|
|
|
+ try{
|
|
|
+ //多选校验值包含 单选校验值相等
|
|
|
+ Object o = conditionField.get(obj);
|
|
|
+ if(2==vo.getMulty()){
|
|
|
+ checkSameVal(o, vo.getConfigVal(), vo.getFieldDesc());
|
|
|
+ }else{
|
|
|
+ checkContainsVal(o,vo.getConfigVal(), vo.getFieldDesc());
|
|
|
+ }
|
|
|
+ }catch (Exception e){
|
|
|
+ throw new CustomerException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验值包含
|
|
|
+ */
|
|
|
+ private void checkContainsVal(Object o, String configVal, String fieldDesc) {
|
|
|
+ try{
|
|
|
+ String[] split = configVal.split(",");
|
|
|
+ if(o instanceof Date){
|
|
|
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
+ Date begin = format.parse(split[0]);
|
|
|
+ Date end = format.parse(split[1]);
|
|
|
+ Date date = (Date) o;
|
|
|
+ if(!DateUtil.isIn(date, begin, end)){
|
|
|
+ throw new CustomerException(fieldDesc+"不符合准入条件!");
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ if(!Arrays.asList(split).contains(o.toString())){
|
|
|
+ throw new CustomerException(fieldDesc+"不符合准入条件!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }catch (Exception e){
|
|
|
+ throw new CustomerException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验值相同
|
|
|
+ */
|
|
|
+ private void checkSameVal(Object o, String configVal,String desc) {
|
|
|
+ if(o instanceof Integer && Integer.parseInt(configVal) != (Integer) o){
|
|
|
+ throw new CustomerException(desc+"不符合准入条件!");
|
|
|
+ }
|
|
|
+ if(o instanceof String && !configVal.equals(String.valueOf(o))){
|
|
|
+ throw new CustomerException(desc+"不符合准入条件!");
|
|
|
+ }
|
|
|
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
+ try {
|
|
|
+ if(o instanceof Date && !format.parse(configVal).equals((Date)o)){
|
|
|
+ throw new CustomerException(desc+"不符合准入条件!");
|
|
|
+ }
|
|
|
+ } catch (ParseException e) {
|
|
|
+ throw new CustomerException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
}
|