背景

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

刷屏定义:取出用户近期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. Day08_40_集合_List

    List集合 List接口是继承Collection接口,所以Collection集合中有的方法,List集合也会继承过来,可以直接使用. All Superinterfaces: Collectio ...

  2. 1083 List Grades

    Given a list of N student records with name, ID and grade. You are supposed to sort the records with ...

  3. Libraries

    Math.ceil() The Math.ceil() function returns the smallest integer greater than or equal to a given n ...

  4. 限制pyqt5应用程序 只允许打开一次

    起因 pyqt5程序创建桌面快捷方式后,多次单击图标 会打开多个UI界面,这种情况肯定是不允许的! 解决 if __name__ == '__main__': try: app = QtWidgets ...

  5. 【JVM】JVM中的垃圾回收算法

    1.标记 -清除算法 "标记-清除"(Mark-Sweep)算法,如它的名字一样,算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收 ...

  6. jumpserver2

    测试环境 CPU: 64位双核处理器 内存: 4G DDR3 数据库:mysql 版本大于等于 5.6 mariadb 版本大于等于 5.5.6 环境 系统: CentOS 7 IP: 192.168 ...

  7. hdu4067 费用流(混合欧拉的宽展和延伸)

    题意:        给以一个图,每个有向边都有两个权值,a,b其中a是保留这条边的花费,b是删除这条边的花费,让你删去一些边使图满足一下要求: (1)只有一个起点和一个终点 (2)所有的边都是又向的 ...

  8. 使用Github+Picgo搭建图床

    虽然我的大部分博客使用的腾讯云的对象存储(COS)作为图床,但是腾讯云的免费对象存储空间结束了,购买资源西南地区大致存储资源包50元/12月+下行流量9元/3月,价格较为高昂,而使用GitHub或者G ...

  9. 【python】Leetcode每日一题-不同的子序列

    [python]Leetcode每日一题-不同的子序列 [题目描述] 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数. 字符串的一个 子序列 是指,通过删除一些(也可以 ...

  10. linux当前运行进程

    一:linux查询服务器服务进程 inux中的ps命令是Process Status的缩写.ps命令用来列出系统中当前运行的那些进程.ps命令列出的是当前那些进程的快照, 就是执行ps命令的那个时刻的 ...