1.定义一个风险评估接口 从不同维度实现风险评估 包括输入向量风险评估,登录IP地址风险评估,密码输入匹配度风险评估,登录设备风险评估。本章实现输入向量风险评估,什么输入向量?可以理解为用户的输入账号密码验证码等的一组时间集合,在评估模型中会转为向量进行分析计算。怎么获取用户的输入账号密码验证码的时间?这个可以用自定义的Js插件实现,具体操作参考我的博客。
2.RiskEvaluate.scala 一个接口 其他风险评估 遵循该接口
package com.dyf.evaluate
/*
*created by 丁郁非
*date 2019/7/18
* 风险平评估接口 通过当前值与历史值的评估 得出是否有风险
*/ trait RiskEvaluate {
def evaluate(currentValue: Any, hisstoryValue: Any): (String, Boolean)
}
3.InputVectorsEvaluate.scala 是RiskEvaluate的一个具体实现,具体功能 注释描述的很清楚了
package com.dyf.evaluate.impl
import com.dyf.evaluate.RiskEvaluate
import scala.collection.mutable.ListBuffer
/*
*created by 丁郁非
*date 2019/7/18
* 用户键盘输入每个input记录的时间转为向量与历史的输入向量评估 进行风险分析
* 格式如(1.555,2.3,2.1)看做当前用户输入账号密码验证码的时间
* 他的历史输入可能为[(2,2,3),(1.1,2.2,2.2),(2.1,2.2,2),(1,2,3)]
*/
class InputVectorsEvaluate extends RiskEvaluate {
def doEvaluate(currentInputVector: Array[Double], historyVector: ListBuffer[Array[Double]]): (String, Boolean) = {
val n = historyVector.length
if (n < 2) {
/*对于历史向量数据量不足2的情况下 无法做风险评估 可认为新用户首次或第二次登陆 可认为无风险*/
return ("result", false)
}
//计算平均向量 平均向量是用户所有历史向量的一个平均值比如得到平均向量(1.55,1.32,1.2)
val avgVector = new Array[Double](currentInputVector.length)
for (index <- 0 until currentInputVector.length) {
avgVector(index) = historyVector.map(arr => arr(index)).reduce(_ + _) / historyVector.size //对历史向量的每个向量同位置上的数据累加求平均值
}
//根据坐标之间距离计算公式计算历史向量中中每个向量与其他向量的距离 得到存有任意两个向量点之间距离的数组distance 在对该数组自然排序
//这个数组可以看做用户输入习惯的一个集合 在该集合中有输入时间慢的情况 也有输入时间快的情况 设置合理的阈值 对每个新来的向量 判断新向量与
//平均向量的距离 在与该数组比对 就可以发现该距离在数组中的大体位置 如果耗时过长 则会在数组的靠后部分 可认为此次输入有一定的风险 不太符合用户的
//平常输入习惯 随着以后每次历史向量的变化 如果用户习惯慢慢改变 则该距离数组distance的平均距离也会改变 但是阈值设定多少与数组长度无关
//这里设置在数组的三分之一处的值为阈值 超过该阈值 可认为有风险 但该风险只是评估中的一项的 输入时长是用户决定的 所以还要结合其他的风险评估
var distances = new ListBuffer[Double]
for (vectory1 <- historyVector; vectory2 <- historyVector; if (vectory1 != vectory2)) { //历史向量中任意两个点坐标的距离
//必须保证vectory1与vectory2不是同一个向量 计算他们的距离
var total: Double = 0
for (index <- 0 until currentInputVector.length) {
total += Math.pow((vectory1(index) - vectory2(index)), 2)
}
//添加距离到distance数组中
distances += Math.sqrt(total)
//将向量从historyVector中移除
historyVector -= vectory1
}
//对distances结果进行排序
distances = distances.sortBy(distance => distance)
//计算当前输入向量和平均向量的距离
var curentDistance: Double = 0
for (index <- 0 until avgVector.length) {
curentDistance += Math.pow((avgVector(index) - currentInputVector(index)), 2)
}
curentDistance = Math.sqrt(curentDistance)
//从distance数组中获取阈值 这里的三分之一 就是数组的三分之一处
var threshold: Double = distances(((n * (n - 1)) / 2)*(1/3) )
println("距离:" + distances.map(t => t + "").reduce(_ + "," + _))
println("avgVector:" + avgVector.map(t => t + "").reduce(_ + "," + _))
println("currentInputVector:" + currentInputVector.map(t => t + "").reduce(_ + "," + _))
println("阈值threshold: " + threshold + " currentDistance: " + curentDistance)
return ("FORM_INPUT", curentDistance > threshold)
}
override def evaluate(currentValue: Any, historyValue: Any): (String, Boolean) = {
doEvaluate(currentValue.asInstanceOf[Array[Double]], historyValue.asInstanceOf[ListBuffer[Array[Double]]]);
}
}