Selaa lähdekoodia

Merge remote-tracking branch 'origin/master'

小杜 4 kuukautta sitten
vanhempi
commit
b96cda231d

+ 15 - 8
yt-advertise/advertise-service/src/main/java/com/ytpm/util/TakuRequestUtil.java

@@ -34,15 +34,22 @@ public class TakuRequestUtil {
     public final static String PUBLISHER_KEY = "d4b80bef5f946f618c9e781070aedbf2b1a073d4";
 
     public static void main(String[] args){
-        ComprehensiveReportParam param = new ComprehensiveReportParam();
-        param.setStartdate(20250615);
-        param.setEnddate(20250620);
-        param.setTime_zone("UTC-8");
-//        param.setAdsource_id_list(Collections.singletonList(9272062));
+//        ComprehensiveReportParam param = new ComprehensiveReportParam();
+//        param.setStartdate(20250623);
+//        param.setEnddate(20250624);
+//        param.setTime_zone("UTC-8");
+//        param.setAdsource_id_list(Collections.singletonList(9308489));
 //        param.setGroup_by(Collections.singletonList("adsource"));
-        param.setApp_id_list(Arrays.asList("a685138d49cd2d","a684009039113d"));
-        param.setGroup_by(Collections.singletonList("app"));
-        String result = TakuRequestUtil.doRequest(HttpPost.METHOD_NAME, "/v2/fullreport", JSON.toJSONString(param));
+        JSONObject object = new JSONObject();
+        object.put("start_date",20250623);
+        object.put("end_date",20250624);
+        object.put("currency","CNY");
+        object.put("time_zone","UTC-8");
+
+        object.put("adsource_id_list",Collections.singletonList(9308489));
+//        param.setApp_id_list(Arrays.asList("a685138d49cd2d","a684009039113d"));
+//        param.setGroup_by(Collections.singletonList("app"));
+        String result = TakuRequestUtil.doRequest(HttpPost.METHOD_NAME, "/v3/report/tk_hour", JSON.toJSONString(object));
         ComprehensiveAppReport report = JSONObject.parseObject(result, ComprehensiveAppReport.class);
         System.err.println(result);
     }

+ 0 - 45
yt-agent/agent-service/src/main/java/com/ytpm/service/impl/YtAppUserServiceImpl.java

@@ -50,8 +50,6 @@ import java.util.stream.Collectors;
 @Service
 public class YtAppUserServiceImpl implements YtAppUserService {
 
-    @Resource
-    private AppUserMapper appUserMapper;
     @Resource
     private AppMapper appMapper;
     @Resource
@@ -64,8 +62,6 @@ public class YtAppUserServiceImpl implements YtAppUserService {
     private AgentStaticsMapper staticsMapper;
     @Resource
     private AppFeign appFeign;
-    @Resource
-    private AdvertiseFeign advertiseFeign;
 
 
     /**
@@ -138,50 +134,9 @@ public class YtAppUserServiceImpl implements YtAppUserService {
     public ResultTable<YtUserEcpmListView> ecpmList(String userId) {
         ResultTable<YtDyzAdRecord> table = appFeign.adRecords(userId);
         List<YtDyzAdRecord> data = table.getData();
-        if(CollUtil.isEmpty(data)){
-            return ResultTable.resultTableOk(new PageInfo<>(data));
-        }
-        //远程调用获取广告报告
-        List<ComprehensiveAppReport> resultData =  getComprehensiveReport(data);
-        //组装数据返回前端
-        combineEcpmData(resultData,data);
         return ResultTable.resultTableOk(new PageInfo<>(data));
     }
 
-    /**
-     * 组装ECPM数据返回
-     */
-    private void combineEcpmData(List<ComprehensiveAppReport> records, List<YtDyzAdRecord> data) {
-        Map<Integer,ComprehensiveAppReport> adSourceReportMap = records.stream().collect(
-                Collectors.toMap(s->s.getAdSource().getAdsource_id(), O->O));
-        for (YtDyzAdRecord datum : data) {
-            if(!adSourceReportMap.containsKey(datum.getAdSourceId()))continue;
-            ComprehensiveAppReport report = adSourceReportMap.get(datum.getAdSourceId());
-            datum.setRequest(report.getRequest());
-            datum.setFillrate(report.getFillrate());
-            datum.setImpression(report.getImpression());
-            datum.setClick(report.getClick());
-            datum.setEcpm(report.getEcpm());
-            datum.setRevenue(report.getRevenue());
-            datum.setImpression_api(report.getImpression_api());
-            datum.setClick_api(report.getClick_api());
-            datum.setEcpm_api(report.getEcpm_api());
-        }
-    }
-
-    /**
-     * 远程调用广告API获取报告
-     */
-    private List<ComprehensiveAppReport> getComprehensiveReport(List<YtDyzAdRecord> data) {
-        List<Integer> collect = data.stream().map(YtDyzAdRecord::getAdSourceId).collect(Collectors.toList());
-        ComprehensiveReportParam param = new ComprehensiveReportParam();
-        param.setStartdate(DateUtil.getDateNum(LocalDate.now().minusDays(7)));
-        param.setEnddate(DateUtil.getDateNum(LocalDate.now()));
-        param.setTime_zone("UTC-8");
-        param.setAdsource_id_list(collect);
-        param.setGroup_by(Collections.singletonList("adsource"));
-        return advertiseFeign.getAppReport(param);
-    }
 
     /**
      * 查询用户当日播放视频总数

+ 24 - 89
yt-app/app-service/src/main/java/com/ytpm/controller/WxController.java

@@ -1,8 +1,8 @@
 package com.ytpm.controller;
 
 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 cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.ytpm.agent.enums.UserStatusEnum;
@@ -25,6 +25,8 @@ import com.ytpm.app.model.YtDyzPowerRecord;
 import com.ytpm.app.model.YtDyzUser;
 import com.ytpm.risk.view.RiskConfigView;
 import com.ytpm.risk.view.RiskTemplateView;
+import com.ytpm.util.DateUtil;
+import com.ytpm.util.RedisService;
 import com.ytpm.util.ShiroSubjectUtil;
 import com.ytpm.util.WebUtils;
 import io.swagger.annotations.Api;
@@ -43,7 +45,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.annotation.Resource;
 import java.lang.reflect.Field;
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -67,6 +71,8 @@ public class WxController {
     @Value("${yt.platform.appId}")
     private String PLATFORM_APP_ID;
     private final static String GRANT_TYPE = "authorization_code";
+    @Resource
+    private RedisService redisService;
 
     @Autowired
     private AppUserMapper appUserMapper;
@@ -82,6 +88,9 @@ public class WxController {
     @Transactional(rollbackFor = Exception.class)
     public Result<YtDyzUser> wxLogin(@RequestBody WxLoginParam param) {
         WxLoginResult loginResult = getWechatLoginInfo(param.getWxCode());
+        if(Objects.isNull(loginResult)|| StrUtil.isBlank(loginResult.getOpenid())){
+            throw new CustomerException("微信登录失败,请刷新授权码!");
+        }
         String openid = loginResult.getOpenid();
         WxUserInfo wxUserInfo = getWechatUserInfo(loginResult.getAccess_token(),loginResult.getOpenid());
         if(Objects.isNull(wxUserInfo)) {
@@ -95,13 +104,13 @@ public class WxController {
         }else{
             deadWithUserCrud(old,wxUserInfo,param);
         }
-        // 添加用户登录记录
-        addLoginRecord(param,openid);
-        //TODO 返回数据前校验当前应用的风控规则
-//        Result<RiskTemplateView> riskCondition = riskFeign.getRiskCondition(PLATFORM_APP_ID, PLATFORM_ACCESS_SECRET);
-//        checkCondition(old,riskCondition.getData());
         //设置最后一次答题问题ID、今日答题数、历史答题数
         setExtInfo(old,wxUserInfo.getHeadimgurl());
+        //调用风控服务校验默认风控配置
+        old.setRiskCode("313");
+        riskFeign.checkRisk(old);
+        // 添加用户登录记录
+        addLoginRecord(param,openid);
         return Result.resultOk(RepMessage.LOGIN_SUCCESS, old);
     }
 
@@ -147,88 +156,6 @@ public class WxController {
         return loginResult;
     }
 
-    /**
-     * 校验风控规则
-     * 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("未匹配到准入条件字段请联系管理员!");
-        }
-        Field conditionField = cFields.get(0);
-        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());
-        }
-    }
-
-
     /**
      * Shiro 认证登录
      */
@@ -275,13 +202,21 @@ public class WxController {
      * 注册用户
      */
     private void registryUser(WxLoginParam param,WxUserInfo wxUserInfo,WxLoginResult loginResult, YtDyzUser old) {
-        old.setUserId(IdUtil.fastSimpleUUID());
+        old.setUserId(redisService.getAppUserId());
         old.setNickName(wxUserInfo.getNickname());
         old.setLastLoginTime(new Date());
         old.setRegistryTime(new Date());
         old.setLastLoginIp(param.getLoginIp());
         old.setLoginDays(1);
         old.setPower(0);
+        old.setTotalVideo(0);
+        old.setTotalIncome(BigDecimal.ZERO);
+        old.setRedPacketAmount(BigDecimal.ZERO);
+        old.setRedPacketBalance(BigDecimal.ZERO);
+        old.setPointsBalance(BigDecimal.ZERO);
+        old.setPointsTotal(BigDecimal.ZERO);
+        old.setWithdrawTotal(BigDecimal.ZERO);
+        old.setSignDays(0);
         old.setUserStatus(UserStatusEnum.NORMAL.getCode());
         old.setWxOpenId(loginResult.getOpenid());
         old.setHeadImg(wxUserInfo.getHeadimgurl());

+ 11 - 3
yt-app/app-service/src/main/java/com/ytpm/service/impl/AdServiceImpl.java

@@ -1,21 +1,23 @@
 package com.ytpm.service.impl;
 
 import cn.hutool.core.util.IdUtil;
+import com.ytpm.advertise.enums.AdPlatformTypeEnum;
+import com.ytpm.agent.enums.UserStatusEnum;
 import com.ytpm.app.model.YtDyzAdRecord;
 import com.ytpm.app.model.YtDyzUser;
 import com.ytpm.app.param.DyzAdRecordParam;
 import com.ytpm.dao.AdRecordMapper;
 import com.ytpm.dao.AppUserMapper;
+import com.ytpm.feign.RiskFeign;
 import com.ytpm.general.RepMessage;
 import com.ytpm.general.Result;
 import com.ytpm.service.AdService;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.math.BigDecimal;
-import java.util.Date;
 import java.util.Objects;
 
 @Service
@@ -25,6 +27,10 @@ public class AdServiceImpl implements AdService {
     private AdRecordMapper adRecordMapper;
     @Autowired
     private AppUserMapper appUserMapper;
+    @Qualifier("com.ytpm.feign.RiskFeign")
+    @Autowired
+    private RiskFeign riskFeign;
+
     /**
      * 保存广告记录
      */
@@ -39,7 +45,7 @@ public class AdServiceImpl implements AdService {
         YtDyzAdRecord adRecord = new YtDyzAdRecord();
         BeanUtils.copyProperties(param, adRecord);
         adRecord.setRecordId(IdUtil.fastSimpleUUID());
-        adRecord.setFinishTime(new Date());
+        adRecord.setNetworkName(AdPlatformTypeEnum.getDesc(Integer.parseInt(param.getNetworkFormId())));
         adRecordMapper.addOne(adRecord);
         //修改用户信息, 广告次数+1  总收益 + revenue
         YtDyzUser dyzUser = new YtDyzUser();
@@ -47,6 +53,8 @@ public class AdServiceImpl implements AdService {
         dyzUser.setTotalVideo(Objects.isNull(user.getTotalVideo())?1:(user.getTotalVideo()+1));
         dyzUser.setTotalIncome(user.getTotalIncome().add(param.getRevenue()));
         appUserMapper.updateUser(dyzUser);
+        //调用风控广告校验
+        riskFeign.checkAdRisk(param.getUserId(),param.getRevenue());
         return Result.resultOk(RepMessage.SAVE_SUCCESS);
     }
 }

+ 12 - 3
yt-app/app-service/src/main/resources/mapper/AdRecordMapper.xml

@@ -15,7 +15,11 @@
          network_name,
          network_placement_id,
          begin_time,
-         finish_time
+         finish_time,
+         result_json,
+         ad_source_index,
+         ad_source_type,
+         ecpm
         )
         values
         (
@@ -29,13 +33,18 @@
          #{networkName},
          #{networkPlacementId},
          #{beginTime},
-         #{finishTime}
+         #{finishTime},
+         #{resultJson},
+         #{adSourceIndex},
+         #{adSourceType},
+         #{ecpm}
         )
     </insert>
     <select id="getByUserId" resultType="com.ytpm.app.model.YtDyzAdRecord">
         select
-            record_id, user_id, nick_name, placement_id, ad_source_id, revenue, network_form_id, network_name, network_placement_id, finish_time, begin_time
+            record_id, user_id, nick_name, placement_id, ad_source_id, revenue, network_form_id, network_name, network_placement_id, finish_time, begin_time,result_json,ad_source_type,ad_source_index,ecpm
         from yt_dyz_ad_record
         where user_id = #{userId}
+        order by finish_time
     </select>
 </mapper>

+ 47 - 0
yt-common/src/main/java/com/ytpm/app/model/DefaultRiskConfig.java

@@ -0,0 +1,47 @@
+package com.ytpm.app.model;
+
+import com.ytpm.custom.CustomField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 默认的风控配置
+ */
+@Data
+@ApiModel("默认风控配置")
+@AllArgsConstructor
+@NoArgsConstructor
+public class DefaultRiskConfig {
+    @CustomField
+    @ApiModelProperty("?天内")
+    private int days;
+    @CustomField
+    @ApiModelProperty("IP前?段")
+    private int ipPrefix;
+    @CustomField
+    @ApiModelProperty("同IP登录数量")
+    private int ipSameCount;
+    @CustomField
+    @ApiModelProperty("当日前?条激励视频")
+    private int firstAdCount;
+    @CustomField
+    @ApiModelProperty("ecpm值")
+    private int ecpm;
+    @CustomField
+    @ApiModelProperty("总收益")
+    private BigDecimal income;
+
+    @CustomField(desc = "当日前?条获得奖励的激励视频")
+    @ApiModelProperty("当日前?条获得奖励的激励视频")
+    private int rewardCount;
+
+    @CustomField(desc = "有?条")
+    @ApiModelProperty("有?条")
+    private int haveCount;
+
+}

+ 10 - 4
yt-common/src/main/java/com/ytpm/app/model/YtDyzAdRecord.java

@@ -36,10 +36,16 @@ public class YtDyzAdRecord extends ComprehensiveCommonView {
     private String networkName;
     @ApiModelProperty("广告平台广告位ID")
     private String networkPlacementId;
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @ApiModelProperty("开始时间")
-    private Date beginTime;
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private String beginTime;
     @ApiModelProperty("完成时间")
-    private Date finishTime;
+    private String finishTime;
+    @ApiModelProperty("SDK返回json")
+    private String resultJson;
+    @ApiModelProperty("Taku预估eCPM 对应adsource_price")
+    private String ecpm;
+    @ApiModelProperty("广告源竞价中排序优先级")
+    private int adSourceIndex;
+    @ApiModelProperty("广告源类型")
+    private int adSourceType;
 }

+ 2 - 0
yt-common/src/main/java/com/ytpm/app/model/YtDyzUser.java

@@ -104,4 +104,6 @@ public class YtDyzUser {
     /** 登录历史记录 */
     @ApiModelProperty("登录历史记录")
     private List<YtDyzLoginRecord> loginRecordList;
+    /** 风控编码 */
+    private String riskCode;
 }

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

@@ -1,12 +1,10 @@
 package com.ytpm.app.param;
 
-import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.math.BigDecimal;
-import java.util.Date;
 
 @Data
 @ApiModel("用户观看广告记录")
@@ -33,4 +31,14 @@ public class DyzAdRecordParam {
     private String beginTime;
     @ApiModelProperty("完成时间")
     private String finishTime;
+    @ApiModelProperty("当天观看广告次数")
+    private int adCount;
+    @ApiModelProperty("Taku预估eCPM 对应adsource_price")
+    private String ecpm;
+    @ApiModelProperty("广告源竞价中排序优先级")
+    private int adSourceIndex;
+    @ApiModelProperty("广告源类型")
+    private int adSourceType;
+    @ApiModelProperty("SDK返回json数据")
+    private String resultJson;
 }

+ 20 - 0
yt-common/src/main/java/com/ytpm/util/RedisService.java

@@ -9,6 +9,8 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.Calendar;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -199,6 +201,24 @@ public class RedisService {
         return valOpsStr.increment(key,delta);
     }
 
+    /**
+     * 获取应用用户ID
+     */
+    public String getAppUserId() {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMdd");
+        LocalDate currentDate = LocalDate.now();
+        LocalDate yesterday = currentDate.minusDays(1);
+        String todayKey = currentDate.format(formatter);
+        String yesterdayKey = yesterday.format(formatter);
+        if(this.hasKey(todayKey)){
+            this.incr(todayKey);
+            return this.getStr(todayKey);
+        }
+        this.del(yesterdayKey);
+        this.setStr(todayKey, todayKey+"01");
+        return this.getStr(todayKey);
+    }
+
     /**
      *
      * 生成业务单编号

+ 13 - 2
yt-risk/risk-feign/src/main/java/com/ytpm/feign/RiskFeign.java

@@ -1,5 +1,7 @@
 package com.ytpm.feign;
 
+import com.ytpm.app.model.YtDyzAdRecord;
+import com.ytpm.app.model.YtDyzUser;
 import com.ytpm.feign.fallback.RiskFeignFallBack;
 import com.ytpm.general.Result;
 import com.ytpm.general.ResultTable;
@@ -20,6 +22,9 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
 
+import java.math.BigDecimal;
+import java.util.List;
+
 @FeignClient(name = "risk-manage", fallback = RiskFeignFallBack.class)
 public interface RiskFeign {
 
@@ -59,9 +64,15 @@ public interface RiskFeign {
     @GetMapping("/riskConfig/templateView")
     Result<RiskTemplateView> templateView(@RequestParam("templateId")String templateId);
 
-    @GetMapping("/riskManage/checkRisk")
-    Result<?> checkRisk(@RequestParam("openid")String openid);
+    @PostMapping("/public/checkRisk")
+    Result<?> checkRisk(@RequestBody YtDyzUser ytDyzUser);
 
     @GetMapping("/public/getRiskCondition")
     Result<RiskTemplateView> getRiskCondition(@RequestParam("app_id")String appId, @RequestParam("access_secret")String secret);
+
+    @GetMapping("/public/getRiskByCode")
+    Result<RiskTemplateView> getRiskByCode(@RequestParam("code")String code);
+
+    @GetMapping("/public/checkAdRisk")
+    void checkAdRisk(@RequestParam("userId")String userId,@RequestParam("revenue")BigDecimal revenue);
 }

+ 31 - 0
yt-risk/risk-manage/src/main/java/com/ytpm/controller/PublicApiController.java

@@ -1,5 +1,6 @@
 package com.ytpm.controller;
 
+import com.ytpm.app.model.YtDyzUser;
 import com.ytpm.general.Result;
 import com.ytpm.risk.view.RiskTemplateView;
 import com.ytpm.service.RiskService;
@@ -7,10 +8,13 @@ import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.math.BigDecimal;
+
 @RestController
 @RequestMapping("/public")
 public class PublicApiController {
@@ -27,4 +31,31 @@ public class PublicApiController {
         return riskService.getAppConfig(appId);
     }
 
+    /**
+     * 登录检查默认风控配置
+     */
+    @ApiOperation("登录检查默认风控配置")
+    @PostMapping("/checkRisk")
+    public Result<?> checkRisk(@RequestBody YtDyzUser dyzUser){
+        return riskService.checkRisk(dyzUser);
+    }
+
+    /**
+     * 根据风控编码获取风控配置
+     */
+    @ApiOperation("根据风控编码获取风控配置")
+    @GetMapping("/getRiskByCode")
+    public Result<RiskTemplateView> getRiskByCode(@RequestParam("code")String code){
+        return riskService.getRiskByCode(code);
+    }
+
+    /**
+     * 校验广告的默认风控配置
+     */
+    @ApiOperation("校验广告的默认风控配置")
+    @GetMapping("/checkAdRisk")
+    public void checkAdRisk(@RequestParam("userId")String userId,@RequestParam("revenue") BigDecimal revenue){
+        riskService.checkAdRisk(userId,revenue);
+    }
+
 }

+ 1 - 10
yt-risk/risk-manage/src/main/java/com/ytpm/controller/RiskManageController.java

@@ -1,6 +1,7 @@
 package com.ytpm.controller;
 
 import com.ytpm.agent.view.AgentUserInfo;
+import com.ytpm.app.model.YtDyzUser;
 import com.ytpm.general.Result;
 import com.ytpm.general.ResultTable;
 import com.ytpm.risk.param.RiskBannedListParam;
@@ -96,14 +97,4 @@ public class RiskManageController {
         param.setOperatorName(userInfo.getNickName());
         return riskService.relativeApp(param);
     }
-
-    /**
-     * 校验用户是否属于风控中
-     */
-    @ApiOperation("校验用户是否属于风控中")
-    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, paramType = "path")
-    @GetMapping("/checkRisk")
-    public Result<?> checkRisk(@RequestParam("openid")String openid){
-        return riskService.checkRisk(openid);
-    }
 }

+ 5 - 0
yt-risk/risk-manage/src/main/java/com/ytpm/dao/RiskConfigMapper.java

@@ -64,4 +64,9 @@ public interface RiskConfigMapper {
      * 根据应用获取配置类
      */
     List<AgentAppClassView> getAppClazz(@Param("appId") String appId);
+
+    /**
+     * 根据风控编码获取风控配置
+     */
+    RiskTemplateView getByCode(@Param("riskCode") String riskCode);
 }

+ 15 - 2
yt-risk/risk-manage/src/main/java/com/ytpm/service/RiskService.java

@@ -1,6 +1,7 @@
 package com.ytpm.service;
 
 import com.ytpm.agent.view.AgentUserInfo;
+import com.ytpm.app.model.YtDyzUser;
 import com.ytpm.general.Result;
 import com.ytpm.general.ResultTable;
 import com.ytpm.risk.param.RiskBannedListParam;
@@ -15,6 +16,8 @@ import com.ytpm.risk.view.RiskConfigView;
 import com.ytpm.risk.view.RiskDeblockingListView;
 import com.ytpm.risk.view.RiskTemplateView;
 
+import java.math.BigDecimal;
+
 public interface RiskService {
     /**
      * 查询配置字段选项
@@ -70,12 +73,22 @@ public interface RiskService {
     Result<?> updateConfig(RiskConfigParam param);
 
     /**
-     * 校验用户是否处于风控中
+     * 登录检查默认风控配置
      */
-    Result<?> checkRisk(String openid);
+    Result<?> checkRisk(YtDyzUser dyzUser);
 
     /**
      * 根据appId查询应用配置模版
      */
     Result<RiskTemplateView> getAppConfig(String appId);
+
+    /**
+     * 根据风控编码获取风控配置
+     */
+    Result<RiskTemplateView> getRiskByCode(String code);
+
+    /**
+     * 校验广告的默认风控配置
+     */
+    void checkAdRisk(String userId, BigDecimal revenue);
 }

+ 228 - 11
yt-risk/risk-manage/src/main/java/com/ytpm/service/impl/RiskServiceImpl.java

@@ -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());
+        }
+    }
+
 }

+ 20 - 0
yt-risk/risk-manage/src/main/resources/mapper/RiskConfigMapper.xml

@@ -195,4 +195,24 @@
         from yt_app_class
         where enabled = 1 and app_id = #{appId}
     </select>
+    <select id="getByCode" resultType="com.ytpm.risk.view.RiskTemplateView">
+        SELECT
+            rt.template_id,
+            rt.template_code,
+            rt.template_name,
+            rt.template_content,
+            rt.enabled,
+            rc.config_id,
+            rc.field_name,
+            rc.field_desc,
+            rc.config_type,
+            rc.config_val,
+            rc.multy
+        FROM
+            yt_risk_template rt
+                LEFT JOIN yt_risk_template_config rtc ON rt.template_id = rtc.template_id
+                LEFT JOIN yt_risk_config rc ON rtc.config_id = rc.config_id
+        WHERE
+            rt.template_code = #{templateCode}
+    </select>
 </mapper>