大数据项目(章节二)-----登陆风险评估&评估因子实现

评估因子实现

Java责任链模式
顾名思义,责任模式链为请求创建一系列接收者对象。 此模式基于请求的类型将请求的发送方和接收方分离。 这种模式是行为模式。

在这种模式中,通常每个接收器包含对另一个接收器的引用。如果一个对象不能处理请求,则它将相同的对象传递给下一个接收者等等。

servlet中的过滤器,struts2中的拦截器等都是采用了这种设计模式,把多个责任任务绑定在一条链chain上,由chain调用任务的执行,任务实现方法中又包含对chain的调用,这样就实现了任务与任务之间解耦,

  • 1.声明所有的风险因素
/**
 * 声明所有的风险因素
 */
public enum RiskFactor {
    Area("area"),//地域因素,登陆地所在城市
    DEVICE("device"),//登录设备
    TOTAL("total"),//登陆总次数
    TIMESLOT("timeslot"),//登陆时间段
    SIMILARITY("similarity"),//密码相似度
    INPUTFEATURE("inputfeature"),//输入特征
    SPEED("speed");//位移速度
    private String name;//风险因素的名称

    //构造
   RiskFactor(String name){
        this.name = name;
    }
}
  • 2.评估报告,每经过一次评估更新一次
package com.baizhi.entities;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * 评估报告,完成每次因子的评估都要更新一次评估报告
 * 应⽤信息
 * ⽤户唯⼀标识
 * 登录地区
 * 经纬度
 * 登录序列号
 * 评估时间
 * 输⼊特征
 * 地区
 * 速度
 * 设备
 * 习惯
 * 次数
 * 密码
 * QQ zhangsan Beijing 116.20,39.56 UUID
 * 2020-03-31
 * 10:10:00
 * True False True False True False True
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EvaluateReport implements Serializable {
    private String applicationName;//应用信息
    private String userIdentify;//y用户唯一标识
    private String loginSequence;//登陆序列号
    private long evaluateTime;//登陆时间
    private String cityName;//登陆地区
    private GeoPoint geoPoint;//经纬度
    //根据各个风险因子得到的评估因子map,为true则为有风险
    private Map metrics = new HashMap<>();

    //构造
    public EvaluateReport(String applicationName, String userIdentify, String loginSequence, long evaluateTime, String cityName, GeoPoint geoPoint) {
        this.applicationName = applicationName;
        this.userIdentify = userIdentify;
        this.loginSequence = loginSequence;
        this.evaluateTime = evaluateTime;
        this.cityName = cityName;
        this.geoPoint = geoPoint;

        //初始化所有风险因子都是false
        metrics.put(RiskFactor.AREA,false);
        metrics.put(RiskFactor.DEVICE,false);
        metrics.put(RiskFactor.SIMILARITY,false);
        metrics.put(RiskFactor.SPEED,false);
        metrics.put(RiskFactor.TIMESLOT,false);
        metrics.put(RiskFactor.INPUTFEATURE,false);
        metrics.put(RiskFactor.TOTAL,false);
    }
    
    @Override
    public String toString(){
        //风险报告
        String report = metrics.keySet()
                .stream()
                .sorted((RiskFactor r1, RiskFactor r2) -> (r1.name().compareTo(r2.name())))
                .map(r -> (metrics.get(r) + ""))
                .reduce((r1, r2) -> (r1 + " " + r2))
                .get();
        return applicationName+" "+userIdentify+" "+loginSequence+" "+evaluateTime+" "+cityName+ " "+geoPoint.getLongtitude()+","+geoPoint.getLatitude()+" "+report;

    }

    //写报告,根据风险因子的值
    public void signReport(RiskFactor riskFactor,boolean flag){
        metrics.put(riskFactor,flag);
    }
}

  • 3.评估责任抽象类
package com.baizhi.evaluate;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 对每个风险因子进行评估的责任抽象类
 * 每个风险因子进行评估都要实现该类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class Evaluate {
    //风险因子
    private RiskFactor riskFactor;

    /**
     *
     * @param evaluateData
     * @param historyData
     * @param evaluateReport
     * @param evaluateChain:驱动下一个Evaluate实例
     */
    public abstract  void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport,
                               EvaluateChain evaluateChain);
}

  • 4.责任链类
package com.baizhi.evaluate;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;

import java.util.List;

/**
 * 责任链类,根据position调用责任,
 */
public class EvaluateChain {
    //责任的下标
    private Integer position = 0;
    //所有需要执行的责任
    private List evaluates;
    public EvaluateChain(List evaluates) {
        this.evaluates = evaluates;
    }

    /**
     *
     * @param evaluateData 评估数据,本次登陆的数据
     * @param historyData 历史数据
     * @param evaluateReport 评估报告
     */
    public void doChain(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport){
        if(position < evaluates.size() - 1){
            //如果当前执行任务不是最后一个
            //获取责任
            Evaluate evaluate = evaluates.get(position);
            position++;
            //执行任务
            evaluate.eval(evaluateData,historyData,evaluateReport,this);
        }
    }
}

  • 历史数据类型
/**
 * 记录是用户的登录的历史状态
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HistoryData implements Serializable {
    Set historyCities;//历史登陆所有城市
    Set historyDevices;//历史登录设备
    Integer currentLoginCount;//当前登录次数
    Map> timeslot;//登陆时间段(一周的时间段,(一天的时间段,次数))
    Set historyPassword;//历史输入密码集合
    List historyFeature;//用户输入特性集合
    Long lastLoginTime;//上次登陆时间
    GeoPoint lastLoginGeoPoint;//上次登陆经纬度
}

接下来每次进行一次风险因子评估都要实现Evaluate

  • 1.异地登陆评估
    评估数据:当前登录城市
    历史数据: 留存⽤户曾经登录过的城市Set集合
package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.util.Set;

/**
 * 异地登陆评估
 * 本次登陆不是之前的登陆地
 */
public class AreaEvaluate extends Evaluate {
    public AreaEvaluate(){
        super(RiskFactor.AREA);
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        //填写评估报告
        evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getCityName(),historyData.getHistoryCities()));
        //调用下一个评估
        evaluateChain.doChain(evaluateData,historyData,evaluateReport);
    }

    /**
     * 
     * @param cityName 本次登陆城市
     * @param historyCities 历史登陆所有城市
     * @return
     */
    public Boolean doEval(String cityName, Set historyCities){
        if(historyCities == null || historyCities.size() == 0){
            //如果本次登陆是第一次
            return  false;
        }else{
            return !historyCities.contains(cityName);
        }
    }
}

  • 2.更换设备评估
    评估数据:当前登录设备信息
    历史数据: ⽤户最近登录过的N个设备-去重
package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.util.Set;

/**
 * 更换设备评估
 * 如果本次登录设备与前几次不一样则认为是存在风险
 */
public class DevideEvaluate extends Evaluate {
    public DevideEvaluate(){
        super(RiskFactor.DEVICE);
    }
    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        //填写报告
        evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getDeviceInformation(),historyData.getHistoryDevices()));
    }

    /**
     * 
     * @param device 本次登录设备
     * @param historyDevices 历史用过的登录设备
     */
    public Boolean doEval(String device, Set historyDevices){
        if (historyDevices == null || historyDevices.size() == 0){
            //如果是第一次登陆
            return false;
        }else{
            return !historyDevices.contains(device);
        }
    }
    
}

  • 3.当天累积登陆次数评估
    评估数据:-
    历史数据: 获取当天⽤户累计登录次数
package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

/**
 * 最大登陆次数,超过阈值认定有风险
 */
public class TotalEvaluate extends Evaluate {
    //登陆次数阈值
    private Integer threshold = 0;
    public TotalEvaluate(Integer threshold){
        super(RiskFactor.TOTAL);
        this.threshold = threshold;
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        //填写报告
        evaluateReport.signReport(getRiskFactor(),doEval(historyData.getCurrentLoginCount()));
        evaluateChain.doChain(evaluateData,historyData,evaluateReport);
    }

    /**
     * 
     * @param currentLoginCount 当前登录次数
     * @return
     */
    public Boolean doEval(Integer currentLoginCount){
        if(currentLoginCount == null){
            //第一次登陆
            return false;
        }
        return currentLoginCount - threshold > 0;
    }
}

  • 4.登陆时段习惯评估
    评估数据:获取当前评估时间 dayOfWeek 、 hourOfDay
    历史数据: 留存⽤户的所有的历史登录数据 dayOfWeek 、 hourOfDay 、 count
    需要从⽤户的历史登录时段中,计算出哪些时段是⽤户的登录习惯。
package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 登陆时间段习惯评估
 */
public class TimeSlotEvaluate extends Evaluate {
    private Integer threshold = 20;//设置登陆多少次之后再进行评估
    public TimeSlotEvaluate(Integer threshold){
        super(RiskFactor.TIMESLOT);
        this.threshold = threshold;
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        evaluateReport.signReport(RiskFactor.TIMESLOT,doEval(evaluateData.getEvaluateTime(),historyData.getTimeslot()));
        evaluateChain.doChain(evaluateData,historyData,evaluateReport);
    }


    public Boolean doEval(Long loginTime, Map> timeslot){
        String[] WEEKS={"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(loginTime);
        String dayOfWeek = WEEKS[c.get(Calendar.DAY_OF_WEEK) - 1];//星期几
        DecimalFormat decimalFormat = new DecimalFormat("00");
        String hourOfDay = decimalFormat.format(c.get(Calendar.HOUR_OF_DAY));//哪个小时
        //用户第一次登陆
        if(timeslot == null || timeslot.size() == 0){
            return false;
        }
        //如果历史时段不包含登陆时段
        if(!timeslot.containsKey(dayOfWeek)){
            //拿到总登陆次数,判断是否小于阈值
            Integer count = timeslot.entrySet()
                    .stream()
                    .map(t -> (t.getValue().entrySet()
                            .stream()
                            .map(k -> k.getValue())
                            .reduce((v1, v2) -> (v1 + v2))
                            .get()))
                    .reduce((v1, v2) -> v1 + v2)
                    .get();
            return count >= threshold;
        }else{
            //如果历史时段包含登陆时段
            //获取当天登陆的时段信息
            Map hours = timeslot.get(dayOfWeek);
            //查看当天登陆时段信息是否包含当前登录的小时时段
            if (!hours.containsKey(hourOfDay)){
                //如果不包含
                return true;
            }else{
                //包含----判断是否处于当天登陆时段的前几个活跃阶段
                Integer hourCount = hours.get(hourOfDay);//当前登陆时段次数
                List hourList = hours.entrySet()
                        .stream()
                        .map(t -> t.getValue())
                        .sorted()
                        .collect(Collectors.toList());//拿到当天登陆时段排序后的次数列表
                //获取阈值
                Integer thresholdCount = hourList.get(hourList.size() * 2 / 3);
                return hourCount < thresholdCount;
                
                //计算包含阈值的所有时段,看一下当前登录时段是否在该时段之内
                /*List hourList = hours.entrySet()
                        .stream()
                        .filter(t -> t.getValue() > hours.size() * 2 / 3)
                        .map(t->t.getKey())
                        .collect(Collectors.toList());
                return !hourList.contains(hourOfDay);*/
            }
        }
    }
}

  • 5.密码相似度评估

详情查看https://blog.csdn.net/qq_38040765/article/details/105253449

评估数据:获取乱序的密码
历史数据: 留存⽤户的所有的历史登录乱序密码Set

package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户登陆的密码相似度评估
 */
public class SimilarityEvaluate extends Evaluate{
    //相似度阈值,相似度大于该阈值,认定没有风险,反之亦然
    private Double thresholdSimilarity;

    //构造
    public SimilarityEvaluate(Double thresholdSimilarity){
        super(RiskFactor.SIMILARITY);
       this.thresholdSimilarity = thresholdSimilarity;
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getOrdernessPassword(),historyData.getHistoryPassword()));
        evaluateChain.doChain(evaluateData,historyData,evaluateReport);
    }


    /**
     * 逻辑执行,
     * @param loginPassword
     * @param historyPassword
     * @return 返回true,代表输入密码差异过大,登陆有风险
     */
    public Boolean doEval(String loginPassword, Set historyPassword){
        //如果用户是第一次登陆
        if(historyPassword == null || historyPassword.size() == 0){
            return false;
        }
        //获取字符袋
        Set wordBag = new HashSet<>();
        historyPassword.stream()
               .forEach(c->{
                   char[] chars = c.toCharArray();
                   for (char aChar : chars) {
                       wordBag.add(aChar);//加入到词袋中
                   }
               });
        //富华维度,可以把当前输入的字符页添加到字符袋中
        char[] chars = loginPassword.toCharArray();
        for (Character aChar : chars) {
            wordBag.add(aChar);
        }
        //把字符袋中的字符排序
        List wordList = wordBag.stream()
                .sorted()
                .collect(Collectors.toList());
        System.out.println("字符袋:" + wordList);
        //把历史密码转化为向量
        List historyVector = historyPassword.stream()
                .map(p -> transformStringToVector(wordList, p))
                .collect(Collectors.toList());
        //把当前密码转化为向量
        Integer[] loginVector = transformStringToVector(wordList, loginPassword);
        //获取没有风险的相似度集合
        List similarities = historyVector.stream()
                .map(h -> {
                    Double similarity = calculateSimilarity(h, loginVector);
                    System.out.println("相似度:" + similarity);
                    return similarity;
                })
                .filter(s -> s > thresholdSimilarity)//过滤出有风险的相似度
                .collect(Collectors.toList());
        return similarities.size() == 0;
    }

    /**
     * 把密码乱序转化为向量
     * @param wordBag 所有历史密码组成的排序好的字符袋
     * @param password 要转化的密码乱序
     * @return 转化后的向量
     */
    public Integer[] transformStringToVector(List wordBag,String password){
        //转化后的向量
        Integer[] vector = new Integer[wordBag.size()];
        //把密码乱序转化为map集合(字符,个数)
        Map passCharMap = new HashMap<>();
        char[] chars = password.toCharArray();
        //遍历字符数组
        for (Character c : chars) {
            //初始数量
            Integer count = 1;
            //如果map中有该字符
            if (passCharMap.containsKey(c)){
                count += passCharMap.get(c);
            }
            //放入到map中
            passCharMap.put(c,count);
        }
        //遍历字符袋,如果密码map中有该字符,向量该字符特征的属性就+1,
        for (int i = 0; i < wordBag.size(); i++) {
            Character c = wordBag.get(i);
            vector[i] = passCharMap.containsKey(c) ? passCharMap.get(c) : 0;
        }
        return vector;
    }

    /**
     * 计算两个向量的相似度
     * @param v1 v1向量
     * @param v2 v2向量
     * @return 返回相似度
     */
    public Double calculateSimilarity(Integer[] v1,Integer[] v2){
        //计算两个向量的夹角余弦,向量点乘/模的乘积
        //向量的点乘:v1 * v2
        Double dotMulti = 0.0;
        for (int i = 0; i < v1.length; i++) {
            dotMulti += v1[i] * v2[i];
        }
        //计算两个向量的模的平方
        Integer length1 = Arrays.stream(v1)
                .map(v -> v * v)
                .reduce((i1, i2) -> i1 + i2)
                .get();
        Integer length2 = Arrays.stream(v2)
                .map(v -> v * v)
                .reduce((i1, i2) -> i1 + i2)
                .get();
        //计算余弦
        return dotMulti / (Math.sqrt(length1) * Math.sqrt(length2));
    }

    public static void main(String[] args) {
        //乱序密码
        /*String pwd="123456abc";
        char[] chars = pwd.toCharArray();
        List characters=new ArrayList<>();
        for (char aChar : chars) {
            characters.add(aChar);
        }
        String ordernessPassword = characters.stream()
                .sorted((o1, o2)-> new Integer[]{-1, 0, 1}[new Random().nextInt(3)])
                .map(c -> c + "")
                .reduce((v1, v2) -> v1 + v2)
                .get();
        System.out.println(ordernessPassword);*/
        //测试密码相似度
        SimilarityEvaluate similarityEvaluate = new SimilarityEvaluate(0.98);
        HashSet pwds = new HashSet<>();
        pwds.add("456vcfg");
        pwds.add("ct12345");
        pwds.add("hjk8901");
        System.out.println(similarityEvaluate.doEval("vcfg451",pwds));

    }
    
}

  • 6.用户输入特性评估

详情https://blog.csdn.net/qq_38040765/article/details/105278983

package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户登陆输入特性
 */
public class FeatureEvaluate extends Evaluate{
    public FeatureEvaluate(){
        super(RiskFactor.INPUTFEATURE);
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getInputFeatures(),historyData.getHistoryFeature()));
        evaluateChain.doChain(evaluateData,historyData,evaluateReport);
    }

    /**
     * 1.计算圆⼼中点
     * 2.计算两两特征距离
     * 3.对距离进⾏排序(升序),取2/3处作为评估距离阈值 - threshold
     * 4.计算当前输⼊的特征距离中⼼点距离d
     * @param loginFeature 登陆特性
     * @param historyFeature 历史输入特性
     * @return 有风险,true
     */
    public Boolean doEval(Double[] loginFeature, List historyFeature){
        //保证历史数据至少有两条
        if(historyFeature == null || historyFeature.size() < 2)
            return false;
        //1.计算圆心,所有坐标均值
        List centerVector = Arrays.stream(historyFeature.stream()
                .reduce((v1, v2) -> {
                    Double[] sum = new Double[v1.length];//历史数据每个维度的总和数组
                    for (int i = 0; i < v1.length; i++) {
                        if (sum[i] == null)
                            sum[i] = 0.0;
                        sum[i] += v1[i] + v2[i];
                    }
                    return sum;
                }).get())//所有坐标值的和
                .map(s -> (s * 1.0) / historyFeature.size())
                .collect(Collectors.toList());
        //圆心字符串形式
        String strCenterVector = String.join(",", centerVector.stream()
                .map(s -> s + "")
                .collect(Collectors.toList()));
        System.out.println("圆心:"+strCenterVector);//展示圆心
        //2.计算两两特性距离
        List distanceList = new ArrayList<>();
        for (int i = 0; i < historyFeature.size(); i++) {
            for (int j = i + 1; j < historyFeature.size(); j++) {
                distanceList.add(distance(historyFeature.get(i),historyFeature.get(j)));
            }
        }
        System.out.println("两两距离:"+distanceList);//展示距离
        //3.对距离进⾏排序(升序),取2/3处作为评估距离阈值 - threshold
        List sortedDistanceList = distanceList.stream()
                .sorted()
                .collect(Collectors.toList());
        Double thresholdDistance = sortedDistanceList.get(sortedDistanceList.size() * 2 / 3);
        System.out.println("半径:" + thresholdDistance);
        //4.计算当前输入特性与圆心的距离
        Double distance = distance(loginFeature, centerVector.toArray(new Double[0]));
        System.out.println("当前输入与圆心的距离:" + distance);
        return distance > thresholdDistance;
    }

    /**
     * 计算两点距离
     * @param v1
     * @param v2
     * @return
     */
    public Double distance(Double[] v1,Double[] v2){
        Double sum = 0.0;
        for (int i = 0; i < v1.length; i++) {
            sum += (v1[i] - v2[i]) * (v1[i] - v2[i]);
        }
        return Math.sqrt(sum);
    }

    //测试
    public static void main(String[] args) {
        FeatureEvaluate inputFeatureEvaluate = new FeatureEvaluate();
        ArrayList latestInputFeatures = new ArrayList<>();
        latestInputFeatures.add(new Double[]{1000.0,1100.0,1800.0});
        latestInputFeatures.add(new Double[]{1100.0,1120.0,1750.0});
        latestInputFeatures.add(new Double[]{950.0,1250.0,2000.0});
        latestInputFeatures.add(new Double[]{1200.0,1050.0,1900.0});
        latestInputFeatures.add(new Double[]{1400.0,800.0,2500.0});

        inputFeatureEvaluate.doEval(new Double[]{1100.0,1000.0,1750.0},latestInputFeatures);
    }
}

  • 7.异地登陆判断

详情:https://blog.csdn.net/qq_38040765/article/details/105280508


import com.baizhi.entities.*;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import static java.lang.Math.*;

/**
 * 异地登录判断
 * 位移速度计算
 */
public class SpeedEvaluate extends Evaluate {
    private static final Double EARTH_RADIUS = 6371.393;//地球半径

    private Double thresholdSpeed;//阈值速度,超过该速度被认定为有盗号风险
    public SpeedEvaluate(Double thresholdSpeed){
        super(RiskFactor.SPEED);
        this.thresholdSpeed = thresholdSpeed;
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getEvaluateTime(),evaluateData.getGeoPoint(),
                historyData.getLastLoginTime(),historyData.getLastLoginGeoPoint()));
        evaluateChain.doChain(evaluateData,historyData,evaluateReport);
    }
    
    /**
     * 登陆位移速度计算
     * @param loginTime 本次登陆时间
     * @param loginGeoPoint 本次登陆经纬度
     * @param lastLoginTime 上次登陆时间
     * @param lastLoginGeoPoint 上次经纬度
     */
    public Boolean doEval(Long loginTime, GeoPoint loginGeoPoint,Long lastLoginTime,GeoPoint lastLoginGeoPoint){
        //用户第一次登陆
        if (lastLoginTime == null){
            return false;
        }
        //计算两地球面距离
        Double distance = calculateDistanceByGeoPoint(loginGeoPoint, lastLoginGeoPoint);
        System.out.println("两地距离:" + distance);
        //计算速度
        Double speed = distance / (((loginTime - lastLoginTime) * 1.0 )/(3600*1000));
        System.out.println("速度:" + speed);
        return speed > thresholdSpeed;
    }

    /**
     * 根据两地经纬度计算距离
     * @param g1
     * @param g2
     * @return
     */
    public Double calculateDistanceByGeoPoint(GeoPoint g1,GeoPoint g2){
        //把经纬度转换为弧度制
        double w1 = toRadians(g1.getLatitude());//纬度1
        double j1 = toRadians(g1.getLongtitude());//经度1

        double w2 = toRadians(g2.getLatitude());//纬度2
        double j2 = toRadians(g2.getLongtitude());//经度2

        //利用公式计算两地距离
        return EARTH_RADIUS * acos(cos(w1)*cos(w2)*cos(j1 - j2) + sin(w1)*sin(w2));
    }

    //测试
    public static void main(String[] args) throws ParseException {
        SpeedEvaluate speedEvaluate = new SpeedEvaluate(600.0);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long evaluateTime=sdf.parse("2020-04-01 10:10:00").getTime();
        GeoPoint p1=new GeoPoint();
        p1.setLongtitude(116.2317);//北京
        p1.setLatitude(39.5427);

        long lastLoginTime=sdf.parse("2020-04-01 08:00:00").getTime();
        GeoPoint p2=new GeoPoint();
        p2.setLongtitude(114.14);//郑州
        p2.setLatitude(34.16);

        speedEvaluate.doEval(evaluateTime,p1,lastLoginTime,p2);
    }
    
}
`

你可能感兴趣的:(大数据项目(章节二)-----登陆风险评估&评估因子实现)