接风控责任链之测试
因为我们在之前的责任里引需求有用到一个记录用户登录的历史状态的一个实体类
/**
* 记录是用户的登录的历史状态
*/
public class HistoryData implements Serializable {
private Set<String> historyCities; //登录过的历史 城市集合
private Set<String> historyDeviceInformations; // 设备
private Integer currentDayLoginCount;// 一天登录的次数 不用实现 未来直接在状态上取
// 星期几 几点 几次
private Map<String,Map<String,Integer>> historyLoginTimeSlot;//历史上登录的时间
//历史密码合集
private Set<String> historyOrdernessPasswords;
//客户输入特征集合
private List<Double[]> latestInputFeatures;
//客户上次登录的经纬度
private GeoPoint geoPoints;
//客户上次登录的时间
private Long historyLoginTime;
因为这里的数据并不能像 登录数据 和 登录成功数据 一样直接从数据中获取进行封装,它涉及着一些代码运算,所以我们需要再用责任链来将数据计算并封装!
和之前的步骤一样 同样是创建一个接口或实现类来作为责任链的链条
public interface Updater {
//创建一个链 通过这个链来更新各个维度的历史数据
public void update(LoginSuccessData loginSuccessData, HistoryData historyData
,UpdaterChain udaterChain);
}
还有往下调用并计算数目的链头
public class UpdaterChain {
private int position=0;
// 持有所有的Updater 的实例
private List<Updater> updaters;
public UpdaterChain(List<Updater> updaters) {
this.updaters = updaters;
}
// 触发链的执行
public void doChain(LoginSuccessData loginSuccessData, HistoryData historyData){
//判断是否已经调完Updater
if(position<updaters.size()){
//获取一个责任
Updater updater = updaters.get(position);
position+=1; //调的位置每调一次加一
//往下传
updater.update(loginSuccessData,historyData,this);
}
}
}
下面就是对责任链责任实例的封装了(也就是将数据整理运算成我们需要的数据的代码)
public class HistoryCities implements Updater {
//从登陆数据中获取登录城市
@Override
public void update(LoginSuccessData loginSuccessData, HistoryData historyData, UpdaterChain udaterChain) {
//获取登录成功的城市
String cityName = loginSuccessData.getCityName();
//拿到之前登录的历史城市状态 判断是否为空
Set<String> historyCities = historyData.getHistoryCities();
if(historyCities==null){
historyCities=new HashSet<>();
}
//将数据存到set 里 然后更改历史城市状态
historyCities.add(cityName);
historyData.setHistoryCities(historyCities);
//链式启动我们的链往下传
udaterChain.doChain(loginSuccessData,historyData);
}
}
public class GeoPointsUpdate implements Updater {
//登录经纬度更新
@Override
public void update(LoginSuccessData loginSuccessData, HistoryData historyData, UpdaterChain udaterChain) {
GeoPoint geoPoint = loginSuccessData.getGeoPoint();
historyData.setGeoPoints(geoPoint);
//链式启动我们的链往下传
udaterChain.doChain(loginSuccessData,historyData);
}
}
public class HistoryDeviceInformations implements Updater {
//历史上客户登录成功的设备信息
@Override
public void update(LoginSuccessData loginSuccessData, HistoryData historyData, UpdaterChain udaterChain) {
String deviceInformation = loginSuccessData.getDeviceInformation();
Set<String> historyDeviceInformations = historyData.getHistoryDeviceInformations();
if (historyDeviceInformations==null){
historyDeviceInformations=new HashSet<>();
}
historyDeviceInformations.add(deviceInformation);
historyData.setHistoryDeviceInformations(historyDeviceInformations);
udaterChain.doChain(loginSuccessData,historyData);
}
}
public class HistoryLoginTime implements Updater {
//更新客户的登录成功时间
@Override
public void update(LoginSuccessData loginSuccessData, HistoryData historyData, UpdaterChain udaterChain) {
long evaluateTime = loginSuccessData.getEvaluateTime();
historyData.setHistoryLoginTime(evaluateTime);
udaterChain.doChain(loginSuccessData,historyData);
}
}
public class HistoryLoginTimeSlot implements Updater {
//保存客户的登录成功时间段
@Override
public void update(LoginSuccessData loginSuccessData, HistoryData historyData, UpdaterChain udaterChain) {
long evaluateTime = loginSuccessData.getEvaluateTime();
String[] WEEKS={"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
Calendar calendar = Calendar.getInstance(); //获取一个日历的实例
calendar.setTimeInMillis(evaluateTime); //将登录的时间存进去
//Calendar.DAY_OF_WEEK 会获取到一个1~7 的数字 代表星期几 从WEEKS 数组中取相应的字符串
String dayOfWeek = WEEKS[calendar.get(Calendar.DAY_OF_WEEK) - 1];
//时间格式 小时 以两位数字表示
DecimalFormat decimalFormat=new DecimalFormat("00");
//登录的时间 几点
String hourOfDay= decimalFormat.format(calendar.get(Calendar.HOUR_OF_DAY));//01 02 ... 24
Map<String, Map<String, Integer>> historyLoginTimeSlot = historyData.getHistoryLoginTimeSlot();
if (historyLoginTimeSlot==null){
historyLoginTimeSlot=new HashMap<>();
}
//判断这个星期这个时间有没有登录过
if(historyLoginTimeSlot.containsKey(dayOfWeek)){
if (historyLoginTimeSlot.get(dayOfWeek).containsKey(hourOfDay)){
Integer integer = historyLoginTimeSlot.get(dayOfWeek).get(hourOfDay);
historyLoginTimeSlot.get(dayOfWeek).put(hourOfDay,integer+1);
}else {
historyLoginTimeSlot.get(dayOfWeek).put(hourOfDay,1);
}
}else {
Map<String, Integer> map=new HashMap<>();
map.put(hourOfDay,1);
historyLoginTimeSlot.put(dayOfWeek,map);
}
historyData.setHistoryLoginTimeSlot(historyLoginTimeSlot);
udaterChain.doChain(loginSuccessData,historyData);
}
}
public class HistoryOrdernessPasswords implements Updater {
//所有的历史密码
@Override
public void update(LoginSuccessData loginSuccessData, HistoryData historyData, UpdaterChain udaterChain) {
String ordernessPassword = loginSuccessData.getOrdernessPassword();
Set<String> historyOrdernessPasswords = historyData.getHistoryOrdernessPasswords();
if (historyOrdernessPasswords==null){
historyOrdernessPasswords=new HashSet<>();
}
historyOrdernessPasswords.add(ordernessPassword);
historyData.setHistoryOrdernessPasswords(historyOrdernessPasswords);
udaterChain.doChain(loginSuccessData,historyData);
}
}
public class LatestInputFeatures implements Updater {
private Integer count=10;//默认保存最近的前十个
//输入特征集合
@Override
public void update(LoginSuccessData loginSuccessData, HistoryData historyData, UpdaterChain udaterChain) {
Double[] inputFeatures = loginSuccessData.getInputFeatures();
List<Double[]> latestInputFeatures = historyData.getLatestInputFeatures();
if(latestInputFeatures==null){
latestInputFeatures=new ArrayList<>();
}
if (latestInputFeatures.size()==count){
latestInputFeatures.remove(0);
}
latestInputFeatures.add(inputFeatures);
historyData.setLatestInputFeatures(latestInputFeatures);
udaterChain.doChain(loginSuccessData,historyData);
}
}
我们的测试需要将一串文本用正则表达式来提取需要的内容封装带实体类里
所以我们定义了一个工具类
在工具类中我们需要进行数据的抽取
无论是抽取何种数据,都需要首先可以正确的从日志中,获取我们需要的数据信息。
使用正则表达式对日志进行格式匹配与提取。
在线正则生成网站:https://regex101.com/
package com.baizhi.until;
import com.baizhi.enties.EvaluateData;
import com.baizhi.enties.GeoPoint;
import com.baizhi.enties.LoginSuccessData;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EvaluateUtil {
public static final String LEGAL_REGEX="^INFO\\s(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2})\\s([a-z0-9\\u4e00-\\u9fa5]*)\\s(EVALUATE|SUCCESS)\\s\\[([a-z0-9\\u4e00-\\u9fa5]*)\\]\\s([a-z0-9]{32})\\s\\\"([a-z0-9\\.\\-\\,]{6,12})\\\"\\s([a-z\\u4e00-\\u9fa5]*)\\s\\\"([0-9\\.\\,]*)\\\"\\s\\[([0-9\\,\\.]*)\\]\\s\\\"(.*)\\\"";
public static final String EVALUATE_REGEX="^INFO\\s(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2})\\s([a-z0-9\\u4e00-\\u9fa5]*)\\s(EVALUATE)\\s\\[([a-z0-9\\u4e00-\\u9fa5]*)\\]\\s([a-z0-9]{32})\\s\\\"([a-z0-9\\.\\-\\,]{6,12})\\\"\\s([a-z\\u4e00-\\u9fa5]*)\\s\\\"([0-9\\.\\,]*)\\\"\\s\\[([0-9\\,\\.]*)\\]\\s\\\"(.*)\\\"";
public static final String SUCCESS_REGEX="^INFO\\s(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2})\\s([a-z0-9\\u4e00-\\u9fa5]*)\\s(SUCCESS)\\s\\[([a-z0-9\\u4e00-\\u9fa5]*)\\]\\s([a-z0-9]{32})\\s\\\"([a-z0-9\\.\\-\\,]{6,12})\\\"\\s([a-z\\u4e00-\\u9fa5]*)\\s\\\"([0-9\\.\\,]*)\\\"\\s\\[([0-9\\,\\.]*)\\]\\s\\\"(.*)\\\"";
public static final Pattern LEGAL_PATTERN = Pattern.compile(LEGAL_REGEX, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
public static final Pattern EVALUATE_PATTERN = Pattern.compile(EVALUATE_REGEX, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
public static final Pattern SUCCESS_PATTERN = Pattern.compile(SUCCESS_REGEX, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
//判断格式是否合法
public static Boolean isLegal(String input){
Matcher matcher = LEGAL_PATTERN.matcher(input);
return matcher.matches();
}
//判断是否为登录数据
public static Boolean isEvaluate(String input){
Matcher matcher = EVALUATE_PATTERN.matcher(input);
return matcher.matches();
}
//判断是否为登录成功数据
public static Boolean isLoginSuccess(String input){
Matcher matcher = SUCCESS_PATTERN.matcher(input);
return matcher.matches();
}
//将数据封装到登录数据实体类
public static EvaluateData parseEvaluateData(String input)throws ParseException {
//指定一个验证数据对象
EvaluateData evaluateData = new EvaluateData();
//获取匹配体
Matcher matcher = EvaluateUtil.EVALUATE_PATTERN.matcher(input);
//如果配配到了
if(matcher.find()){
//遍历
for (int i = 0; i <= matcher.groupCount(); i++) {
switch (i){
case 1:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(matcher.group(i));
//传递时间参数
evaluateData.setEvaluateTime(date.getTime());
case 2:
//设置应用名
evaluateData.setApplicationName(matcher.group(i));
break;
case 4:
//设置用户标识
evaluateData.setUserIdentify(matcher.group(i));
break;
case 5:
//设置应用序列
evaluateData.setLoginSequence(matcher.group(i));
break;
case 6:
//设置密码
evaluateData.setOrdernessPassword(matcher.group(i));
break;
case 7:
//设置城市
evaluateData.setCityName(matcher.group(i));
break;
case 8:
//设置经纬度
String geoparams = matcher.group(i);
String[] geos = geoparams.split(",");
//指定一个经纬度对象
GeoPoint geoPoint = new GeoPoint(Double.parseDouble(geos[0]),Double.parseDouble(geos[1]));
//设置位置对象
evaluateData.setGeoPoint(geoPoint);
break;
case 9:
//设置输入特性
String featrues = matcher.group(i);
String[] featureGroup = featrues.split(",");
Double[] doubleFertrue = {Double.parseDouble(featureGroup[0]),Double.parseDouble(featureGroup[1]),Double.parseDouble(featureGroup[2])};
evaluateData.setInputFeatures(doubleFertrue);
break;
case 10:
//设置设备信息
evaluateData.setDeviceInformation(matcher.group(i));
break;
}
}
}
return evaluateData;
}
//将数据封装到登录成功数据实体类
public static LoginSuccessData parseLoginSuccessData(String input)throws ParseException{
//指定一个验证数据对象
LoginSuccessData loginSuccessData = new LoginSuccessData();
//获取匹配体
Matcher matcher = EvaluateUtil.SUCCESS_PATTERN.matcher(input);
//如果配配到了
if(matcher.find()){
//遍历
for (int i = 0; i <= matcher.groupCount(); i++) {
switch (i){
case 1:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(matcher.group(i));
//传递时间参数
loginSuccessData.setEvaluateTime(date.getTime());
case 2:
//设置应用名
loginSuccessData.setApplicationName(matcher.group(i));
break;
case 4:
//设置用户标识
loginSuccessData.setUserIdentify(matcher.group(i));
break;
case 5:
//设置应用序列
loginSuccessData.setLoginSequence(matcher.group(i));
break;
case 6:
//设置密码
loginSuccessData.setOrdernessPassword(matcher.group(i));
break;
case 7:
//设置城市
loginSuccessData.setCityName(matcher.group(i));
break;
case 8:
//设置经纬度
String geoparams = matcher.group(i);
String[] geos = geoparams.split(",");
//指定一个经纬度对象
GeoPoint geoPoint = new GeoPoint(Double.parseDouble(geos[0]),Double.parseDouble(geos[1]));
//设置位置对象
loginSuccessData.setGeoPoint(geoPoint);
break;
case 9:
//设置输入特性
String featrues = matcher.group(i);
String[] featureGroup = featrues.split(",");
Double[] doubleFertrue = {Double.parseDouble(featureGroup[0]),Double.parseDouble(featureGroup[1]),Double.parseDouble(featureGroup[2])};
loginSuccessData.setInputFeatures(doubleFertrue);
break;
case 10:
//设置设备信息
loginSuccessData.setDeviceInformation(matcher.group(i));
break;
}
}
}
return loginSuccessData;
}
//对工具类的小测试
public static void main(String[] args)throws ParseException {
String input="INFO 2020-03-31 10:12:00 Q1Q应用1 evaluate [张三] 6ebaf4ac780f40f486359f3ea6934620 \"123456\" Beijing \"116.4,39.5\" [1200,15000,2100] \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\"";
System.out.println(EvaluateUtil.parseEvaluateData(input));
}
}
下面就是我们真正的测试了
package text;
import com.baizhi.enties.EvaluateData;
import com.baizhi.enties.EvaluateReport;
import com.baizhi.enties.HistoryData;
import com.baizhi.enties.LoginSuccessData;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;
import com.baizhi.evaluate.impl.*;
import com.baizhi.until.EvaluateUtil;
import com.baizhi.update.Updater;
import com.baizhi.update.UpdaterChain;
import com.baizhi.update.impl.*;
import org.junit.Before;
import org.junit.Test;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
public class UpdateTest {
/**
* @testData :登录成功的历史数据
* @evaluateData :登录的数据
* */
String[] testData={
"INFO 2020-03-31 10:12:00 QQ SUCCESS [张三] 6ebaf4ac780f40f486359f3ea6934620 \"123456\" Beijing \"116.4,39.5\" [1250,14000,2000] \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\"",
"INFO 2020-04-02 10:12:00 QQ SUCCESS [张三] 6ebaf4ac780f40f486359f3ea6934621 \"123456\" Beijing \"116.4,39.5\" [1200,15800,2100] \"Mozilla/6.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\"",
"INFO 2020-04-02 10:50:00 QQ SUCCESS [张三] 6ebaf4ac780f40f486359f3ea6934622 \"123456\" Beijing \"116.4,39.5\" [1300,17000,2200] \"Mozilla/7.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\"",
"INFO 2020-04-03 10:12:00 QQ SUCCESS [张三] 6ebaf4ac780f40f486359f3ea6934623 \"456123\" Zhengzhou \"114.4,34.5\" [1400,16000,2100] \"Mozilla/8.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\""
};
String evaluateData= "INFO 2020-04-03 10:12:01 QQ EVALUATE [张三] 6ebaf4ac780f40f486359f3ea6934620 \"103158\" jinan \"116.4,39.5\" [1250,14000,2000] \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\"";
//历史状态责任链集合
private List<Updater> updaters;
//状态评估责任链集合
private List<Evaluate> evaluate;
@Before//这个注解的意思是,执行测试方面前会先执行这个方法
public void before(){
//往历史状态责任链集合里存入责任实例
updaters=new ArrayList<>();
updaters.add(new GeoPointsUpdate());
updaters.add(new HistoryCities());
updaters.add(new HistoryDeviceInformations());
updaters.add(new HistoryLoginTime());
updaters.add(new HistoryLoginTimeSlot());
updaters.add(new HistoryOrdernessPasswords());
updaters.add(new LatestInputFeatures());
//状态评估责任链集合里存入责任实例
evaluate=new ArrayList<>();
evaluate.add(new AreaEvaluate());
evaluate.add(new DeviceEvaluate());
evaluate.add(new InputFeatureEvaluate());
evaluate.add(new SimilarityEvaluate(0.87));
evaluate.add(new SpeedEvaluate(600.0));
evaluate.add(new TimeSlotEvaluate(2));
evaluate.add(new TotalEvaluate(3));
}
@Test
public void testUpdate() throws ParseException {
//创建用户的登录的历史状态实体类对象
HistoryData historyData = new HistoryData();
//循环将不同的历史登录成功数据通过责任链变成状态存入历史状态实体类内
for (int i=0;i<testData.length;i++) {
//开启责任链
UpdaterChain updaterChain = new UpdaterChain(this.updaters);
LoginSuccessData loginSuccessData = EvaluateUtil.parseLoginSuccessData(testData[i]);
updaterChain.doChain(loginSuccessData, historyData);
}
historyData.setCurrentDayLoginCount(100);//一天已登录的次数
//开启登录风险评估责任链
EvaluateData evaluateData = EvaluateUtil.parseEvaluateData(this.evaluateData);
EvaluateReport evaluateReport = new EvaluateReport(evaluateData.getApplicationName(),
evaluateData.getUserIdentify(),
evaluateData.getLoginSequence(),
evaluateData.getEvaluateTime(),
evaluateData.getCityName(),
evaluateData.getGeoPoint());
EvaluateChain evaluateChain = new EvaluateChain(evaluate);
evaluateChain.daChain(evaluateData,historyData,evaluateReport);
System.out.println(" AREA DEVICE INPUTFEATURE SIMILARITY SPEED TIMESLOT TOTAL");
System.out.println(evaluateReport);
}
}