背景

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

刷屏定义:取出用户近期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. C#通过rabbitmq实现定时任务(延时队列)

    本文主要讲解如何通过RabbitMQ实现定时任务(延时队列) 环境准备 需要在MQ中进行安装插件 地址链接 插件介绍地址:https://www.rabbitmq.com/blog/2015/04/1 ...

  2. logstash收集springboot日志

    logstash收集springboot日志 maven依赖 <dependency> <groupId>net.logstash.logback</groupId> ...

  3. 【新手指南】Android Studio中应用App的相关配置

    前言: 注意这是一个对于Android开发入门学习者而言的一个教程,因为自己平时很少使用Android进行原生应用的开发,对于使用Android Studio配置Android App应用的一些参数( ...

  4. 1035 Password

    To prepare for PAT, the judge sometimes has to generate random passwords for the users. The problem ...

  5. Methods

    string.prototype.trim() The trim() method removes whitespace from both ends of a string. Whitespace ...

  6. 【Scrapy(一)】 Scrapy爬虫的基础执行流程

    安装scrapy模块 : pip install scrapy  创建scrapy项目 1.scrapy startprojecty 项目名称  注意:如果创建失败,可以先卸载原有的scrapy模块, ...

  7. hdu2235 机器人的容器

    题意: 机器人的容器 Time Limit: 3000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  8. POJ1328贪心放雷达

    题意:        有一个二维坐标,y>0是海,y<=0是陆地,然后只能在y=0的岸边上放雷达,有n个城市需要被监控,问最少放多少个雷达. 思路:       贪心去做就行了,其实题目不 ...

  9. Linux获取权限

    目录 Bash反弹shell Python反弹shell 写入命令到定时任务文件 写入SSH公钥 写入/etc/profile文件 当我们可以在远程Linux主机上执行任意命令或写入任意数据到任意文件 ...

  10. 神经网络与机器学习 笔记—多层感知器(MLP)

    多层感知器(MLP) Rosenblatt感知器和LMS算法,都是单层的并且是单个神经元构造的神经网络,他们的局限性是只能解决线性可分问题,例如Rosenblatt感知器一直没办法处理简单异或问题.然 ...