背景

近期有几个业务方提出一需求,期望判断一个用户在短期内是否存在刷屏现象,出现后能对其做出限制,并上报。

刷屏定义:取出用户近期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,用户刷屏检测\相似字符串检测的更多相关文章

  1. String类之endsWith方法--->检测该字符串以xx为结尾

    endsWith(XX)方法是java内置类String类的一个内置方法,我们直接拿来用即可了,下边是api说明:检测该字符串以xx为结尾,结果返回布尔值 public class Demo { pu ...

  2. 检测传入字符串是否存在重复字符,返回boolean

    检测传入字符串是否存在重复字符,返回boolean,比如"abc"返回true:"aac"返回false 这里提供两种思路: 第一种: import java. ...

  3. C#如何检测一个字符串是不是合法的URL

    C#如何检测一个字符串是不是合法的URL using System.Text.RegularExpressions;    /// <summary>         /// 检测串值是否 ...

  4. JAVA基础——重新认识String字符串

    深入剖析Java之String字符串 在程序开发中字符串无处不在,如用户登陆时输入的用户名.密码等使用的就是字符串. 在 Java 中,字符串被作为 String 类型的对象处理. String 类位 ...

  5. JavaScript浏览器检测之客户端检测

    客户端检测一共分为三种,分别为:能力检测.怪癖检测和用户代理检测,通过这三种检测方案,我们可以充分的了解当前浏览器所处系统.所支持的语法.所具有的特殊性能. 一.能力检测: 能力检测又称作为特性检测, ...

  6. Java实现微信菜单json字符串拼接

    Java实现微信菜单json字符串拼接 微信菜单拼接json字符串方法 >>>>>>>>>>>>>>>> ...

  7. C#、Java实现按字节截取字符串包含中文汉字和英文字符数字标点符号等

    C#.Java实现按字节截取字符串,字符串中包含中文汉字和英文字符数字标点符号等. 在实际项目应用过程中,尤其是在web开发时可能遇到的比较多,就以我的(JiYF笨小孩管理系统)为例,再发布文章时候, ...

  8. Java 用户输入

    章节 Java 基础 Java 简介 Java 环境搭建 Java 基本语法 Java 注释 Java 变量 Java 数据类型 Java 字符串 Java 类型转换 Java 运算符 Java 字符 ...

  9. Java中XML格式的字符串4读取方式的简单比较

    Java中XML格式的字符串4读取方式的简单比较 1.java自带的DOM解析. import java.io.StringReader; import javax.xml.parsers.Docum ...

随机推荐

  1. Docker笔记(一) 基础知识

    官方文档地址:https://www.docker.com/get-started 中文参考手册:https://docker_practice.gitee.io/zh-cn 笔记原作者:陈艳男 B站 ...

  2. 去css 冗余方法

    1.float在某种意义上而言与display:inline-block属性的作用是一模一样的, 所以类似于display:block; float:left; 就是说.float:left 可以让行 ...

  3. Think5之ajax批量删除数据功能

    //批量删除学员信息 public function deleteMany() { $id = input('post.'); //判断id是数组还是一个数值 if(is_array($id)){ f ...

  4. hdu5062 简单题

    题意:       求区间逆序数的个数,逆序数增加了个要求就是必须要是先升序在降序例如12321或者123321这样的. 思路:        水题直接写就行了,数据范围不大,估计直接求也不会超时,我 ...

  5. Windows核心编程 第四章 进程(下)

    4.3 终止进程的运行 若要终止进程的运行,可以使用下面四种方法: • 主线程的进入点函数返回(最好使用这个方法) . • 进程中的一个线程调用E x i t P r o c e s s函数(应该避免 ...

  6. node-OS&Domain&Net&Path

    OS--------------------------------------------- Node.js os 模块提供了一些基本的系统操作函数. var os = require(" ...

  7. 从零开始搞监控系统(1)——SDK

    目前市面上有许多成熟的前端监控系统,但我们没有选择成品,而是自己动手研发.这里面包括多个原因: 填补H5日志的空白 节约公司费用支出 可灵活地根据业务自定义监控 回溯时间能更长久 反哺运营和产品,从而 ...

  8. 一文读懂 SuperEdge 云边隧道

    作者 李腾飞,腾讯容器技术研发工程师,腾讯云TKE后台研发,SuperEdge核心开发成员. 杜杨浩,腾讯云高级工程师,热衷于开源.容器和Kubernetes.目前主要从事镜像仓库,Kubernete ...

  9. Docker系列——Grafana+Prometheus+Node-exporter服务器监控平台(一)

    在最近的博文中,都是介绍监控平台的搭建,其实并不难,主要是需要自己动手操作,实践一番就会了. 有天在想,云上的服务器,是不是也可以搭建一个监控平台,所以就捣鼓了一下,不过遗憾的是,使用阿里云开源的插件 ...

  10. golang:面向对象总结

    所谓的面向对象其实就是找一个专门做这个事的人来做,不用关心具体怎么实现的.所以说,面向过程强调的是过程,步骤.而面向对象强调的是对象,也就是干事的人. Go语言:面向对象语言特性 方法 嵌入 接口 没 ...