Java,用户刷屏检测\相似字符串检测
背景
近期有几个业务方提出一需求,期望判断一个用户在短期内是否存在刷屏现象,出现后能对其做出限制,并上报。
刷屏定义:取出用户近期20条评论,如果有50%的评论是"相似"的,则认为该用户是在刷屏
相似定义:两条评论的字符串最小编辑距离 / 长串的长度 < 0.2,即两串的80%是相同的,则认为两串相似。
关于最小编辑距离
@Slf4j
public class SimpleBrushDetectionFilter implements ReviewFilter {
// Todo 参数可实时调
private int USER_RECENT_REVIEW_LIST_SIZE = 20;
private int SIMILARITY_THRESHOLD = 80;
private double BRUSH_THRESHOLD = 0.5;// 该值不允许低于0.5,否则会出现用户循环被ban
private int BAN_SECOND = 3600 * 24;//一天
private int LIST_EXPIRE_SECOND = 3600 * 24 * 3;//三天
@Override
public ReviewFilterModel filter(ReviewFilterModel reviewFilterModel) {
if (reviewFilterModel.isEnd()) {
return reviewFilterModel;
}
long userId = reviewFilterModel.getReviewInfo().getUserId();
if (userId <= 0) {
log.info("错误的userId {}", userId);
return reviewFilterModel;
}
BrowserRedisService banRedisInstance = BrowserRedisService
.getRedisService(RedisPrefix.REVIEW_SIMPLE_BRUSH_DETECTION_BAN);
String str = banRedisInstance.get("" + userId);
if (StrUtil.isNotBlank(str)
// BAN_SECOND的expire set非原子性。出错时需要额外判断一下
&& (System.currentTimeMillis() - Long.parseLong(str)) < BAN_SECOND * 1000) {
banReview(reviewFilterModel, userId);
return reviewFilterModel;
}
if (StrUtil.isNotBlank(str) && (System.currentTimeMillis() - Long.parseLong(str)) > BAN_SECOND * 1000) {
banRedisInstance.del("" + userId);
}
return simpleBrushDetect(reviewFilterModel);
}
private void banReview(ReviewFilterModel reviewFilterModel, long userId) {
log.info("user {} 疑似刷屏,限制发表评论", userId);
reviewFilterModel.setEnd(true);
reviewFilterModel.setPass(false);
reviewFilterModel.setReason("该用户疑似近期出现恶意刷屏,限制发表评论");
}
private ReviewFilterModel simpleBrushDetect(ReviewFilterModel reviewFilterModel) {
BrowserRedisService listRedisInstance = BrowserRedisService
.getRedisService(RedisPrefix.REVIEW_SIMPLE_BRUSH_DETECTION_LIST);
long userId = reviewFilterModel.getReviewInfo().getUserId();
List<String> userRecentReview = listRedisInstance
.lrange("" + userId, 0, USER_RECENT_REVIEW_LIST_SIZE);
if (null == userRecentReview) {
// 将当前评论塞入队列中
listRedisInstance.rpush("" + userId, reviewFilterModel.getReviewInfo().getDocuments());
return reviewFilterModel;
}
userRecentReview.add(reviewFilterModel.getReviewInfo().getDocuments());
// 正确的暴力做法是,将20个串依次互相两两对比,但是这样复杂度太高了
// 这里采用一个取巧的方法,将20个串按字典序排序,然后依次左右对比,效果应该也可以接受
Collections.sort(userRecentReview);
int cnt = 0;
for (int i = 0; i < userRecentReview.size() - 1; i++) {
int similarity = towStringSimilarity(userRecentReview.get(i),
userRecentReview.get(i + 1));
if (similarity > SIMILARITY_THRESHOLD) {
cnt++;
}
}
if (cnt > BRUSH_THRESHOLD * USER_RECENT_REVIEW_LIST_SIZE) {
log.info("user {} 疑似刷屏,禁止发言{}秒", userId, BAN_SECOND);
BrowserRedisService banRedisInstance = BrowserRedisService
.getRedisService(RedisPrefix.REVIEW_SIMPLE_BRUSH_DETECTION_BAN);
banRedisInstance.set("" + userId, "" + System.currentTimeMillis());
banRedisInstance.expire("" + userId, BAN_SECOND);
// 为了避免用户禁言到期后再次触发逻辑,list中删除2/3的评论
listRedisInstance.ltrim("" + userId, -1, -USER_RECENT_REVIEW_LIST_SIZE / 3);
banReview(reviewFilterModel, userId);
}
// 将当前评论塞入队列中
listRedisInstance.rpush("" + userId, reviewFilterModel.getReviewInfo().getDocuments());
listRedisInstance.ltrim("" + userId, -1, -USER_RECENT_REVIEW_LIST_SIZE);
// 刷新整条list的过期时间
listRedisInstance.expire("" + userId, LIST_EXPIRE_SECOND);
return reviewFilterModel;
}
/**
* 返回两个字符串的相似度。 当某个串长度小于5的时候,认为其不构成可比性
*
* @return int [0,100]
*/
private static int towStringSimilarity(String word1, String word2) {
if (word1.length() < 5 || word2.length() < 5) {
return 0;
}
int distance = towStringMinDistance(word1, word2);
return 100
- distance / (word1.length() > word2.length() ? word1.length() : word2.length()) * 100;
}
/**
* 返回两条字符串的最短编辑距离,
*
* 即将word2转变成word1的最小操作次数。
*
* 采用二维动态规划实现,时间复杂度O(N^2)
*/
private static int towStringMinDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
if (m == 0) {
return n;
}
if (n == 0) {
return m;
}
int[][] f = new int[m + 1][n + 1];
for (int i = 0; i <= m; i++) {
f[i][0] = i;
}
for (int j = 0; j <= n; j++) {
f[0][j] = j;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
f[i][j] = f[i - 1][j - 1];
} else {
f[i][j] = min(f[i - 1][j - 1], f[i - 1][j], f[i][j - 1]) + 1;
}
}
}
return f[m][n];
}
private static int min(int a, int b, int c) {
return (a > b ? (b > c ? c : b) : (a > c ? c : a));
}
}
Java,用户刷屏检测\相似字符串检测的更多相关文章
- String类之endsWith方法--->检测该字符串以xx为结尾
endsWith(XX)方法是java内置类String类的一个内置方法,我们直接拿来用即可了,下边是api说明:检测该字符串以xx为结尾,结果返回布尔值 public class Demo { pu ...
- 检测传入字符串是否存在重复字符,返回boolean
检测传入字符串是否存在重复字符,返回boolean,比如"abc"返回true:"aac"返回false 这里提供两种思路: 第一种: import java. ...
- C#如何检测一个字符串是不是合法的URL
C#如何检测一个字符串是不是合法的URL using System.Text.RegularExpressions; /// <summary> /// 检测串值是否 ...
- JAVA基础——重新认识String字符串
深入剖析Java之String字符串 在程序开发中字符串无处不在,如用户登陆时输入的用户名.密码等使用的就是字符串. 在 Java 中,字符串被作为 String 类型的对象处理. String 类位 ...
- JavaScript浏览器检测之客户端检测
客户端检测一共分为三种,分别为:能力检测.怪癖检测和用户代理检测,通过这三种检测方案,我们可以充分的了解当前浏览器所处系统.所支持的语法.所具有的特殊性能. 一.能力检测: 能力检测又称作为特性检测, ...
- Java实现微信菜单json字符串拼接
Java实现微信菜单json字符串拼接 微信菜单拼接json字符串方法 >>>>>>>>>>>>>>>> ...
- C#、Java实现按字节截取字符串包含中文汉字和英文字符数字标点符号等
C#.Java实现按字节截取字符串,字符串中包含中文汉字和英文字符数字标点符号等. 在实际项目应用过程中,尤其是在web开发时可能遇到的比较多,就以我的(JiYF笨小孩管理系统)为例,再发布文章时候, ...
- Java 用户输入
章节 Java 基础 Java 简介 Java 环境搭建 Java 基本语法 Java 注释 Java 变量 Java 数据类型 Java 字符串 Java 类型转换 Java 运算符 Java 字符 ...
- Java中XML格式的字符串4读取方式的简单比较
Java中XML格式的字符串4读取方式的简单比较 1.java自带的DOM解析. import java.io.StringReader; import javax.xml.parsers.Docum ...
随机推荐
- Jenkins 自动触发执行的配置
1. 两种触发方式 2. jenkins 和 github 同步配置 ngrok 安装 webhook 配置 1. 两种触发条件 Jenkins 中建立的任务是可以设置自动触发,更进一步的实现自动化. ...
- MVC之实现基于token的认证
安装Nuget包 项目中添加包:dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer 添加认证配置 Startup类中添加如 ...
- IOS Widget(3):SwiftUI开发小组件布局入门
引言 经过上一篇文章,我们已经可以在桌面上展示出一个小组件出来了,你肯定想小试牛刀,动手改一改,那我们就从改小组件的布局做起吧.本文不会讲解Swift语法,如果是熟悉Flutter,Kotlin这 ...
- POJ2570 二进制,位运算,Floyd
题意: 给你一个有向图,两点之间有多种连接方式,然后每次询问都问你点A,B之间有哪些方式可以到达,每个小字母是一个方式. 思路: 很巧妙的位运算和Floyd应用,借助Floyd ...
- Python脚本破解Linux口令(crypt模块)
环境 Kali Linux ,python版本2.7.13 . 我们利用Linux系统中的 crypt 模块模拟了Linux系统中用户密码的加密,在Windows中是不存在这个库的. 在Linux系统 ...
- Docker用Commit给容器做快照
关于 commit 镜像是容器的基础,每次执行 docker run 的时候都会指定哪个镜像作为容器运行的基础. 镜像是多层存储,每一层是在前一层的基础上进行修改:而容器同样也是多层存储,是在以镜像为 ...
- 安装mysql警告: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY
CentOS安装rpm安装MySQL时爆出警告: warning: mysql-community-server-5.7.18-1.el6.x86_64.rpm: Header V3 DSA/SHA1 ...
- 【哲学角度看软件测试】要想软件“一想之美”,UI 测试少不了
摘要:软件测试的最高层次需求是:UI测试,也就是这个软件"长得好不好看". 为了让读者更好地理解测试,我们从最基础的概念开始介绍.以一个软件的"轮回"为例,下图 ...
- c语言编程学习之二维数组
二维数组 c语言按照行主序存储二维数组.也就是说,二维数组元素在内存中的位置是连续的,每行末尾元素(若不是最后一行)的下一个元素就是下一行的首元素. 如下图所示 接下来我们来分析一下如何将二维数组所有 ...
- SimpleDateFormat线程不安全的5种解决方案!
1.什么是线程不安全? 线程不安全也叫非线程安全,是指多线程执行中,程序的执行结果和预期的结果不符的情况就叫做线程不安全. 线程不安全的代码 SimpleDateFormat 就是一个典型的线程不 ...