背景

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

刷屏定义:取出用户近期20条评论,如果有50%的评论是"相似"的,则认为该用户是在刷屏

相似定义:两条评论的字符串最小编辑距离 / 长串的长度 < 0.2,即两串的80%是相同的,则认为两串相似。

关于最小编辑距离

  1. @Slf4j
  2. public class SimpleBrushDetectionFilter implements ReviewFilter {
  3. // Todo 参数可实时调
  4. private int USER_RECENT_REVIEW_LIST_SIZE = 20;
  5. private int SIMILARITY_THRESHOLD = 80;
  6. private double BRUSH_THRESHOLD = 0.5;// 该值不允许低于0.5,否则会出现用户循环被ban
  7. private int BAN_SECOND = 3600 * 24;//一天
  8. private int LIST_EXPIRE_SECOND = 3600 * 24 * 3;//三天
  9. @Override
  10. public ReviewFilterModel filter(ReviewFilterModel reviewFilterModel) {
  11. if (reviewFilterModel.isEnd()) {
  12. return reviewFilterModel;
  13. }
  14. long userId = reviewFilterModel.getReviewInfo().getUserId();
  15. if (userId <= 0) {
  16. log.info("错误的userId {}", userId);
  17. return reviewFilterModel;
  18. }
  19. BrowserRedisService banRedisInstance = BrowserRedisService
  20. .getRedisService(RedisPrefix.REVIEW_SIMPLE_BRUSH_DETECTION_BAN);
  21. String str = banRedisInstance.get("" + userId);
  22. if (StrUtil.isNotBlank(str)
  23. // BAN_SECOND的expire set非原子性。出错时需要额外判断一下
  24. && (System.currentTimeMillis() - Long.parseLong(str)) < BAN_SECOND * 1000) {
  25. banReview(reviewFilterModel, userId);
  26. return reviewFilterModel;
  27. }
  28. if (StrUtil.isNotBlank(str) && (System.currentTimeMillis() - Long.parseLong(str)) > BAN_SECOND * 1000) {
  29. banRedisInstance.del("" + userId);
  30. }
  31. return simpleBrushDetect(reviewFilterModel);
  32. }
  33. private void banReview(ReviewFilterModel reviewFilterModel, long userId) {
  34. log.info("user {} 疑似刷屏,限制发表评论", userId);
  35. reviewFilterModel.setEnd(true);
  36. reviewFilterModel.setPass(false);
  37. reviewFilterModel.setReason("该用户疑似近期出现恶意刷屏,限制发表评论");
  38. }
  39. private ReviewFilterModel simpleBrushDetect(ReviewFilterModel reviewFilterModel) {
  40. BrowserRedisService listRedisInstance = BrowserRedisService
  41. .getRedisService(RedisPrefix.REVIEW_SIMPLE_BRUSH_DETECTION_LIST);
  42. long userId = reviewFilterModel.getReviewInfo().getUserId();
  43. List<String> userRecentReview = listRedisInstance
  44. .lrange("" + userId, 0, USER_RECENT_REVIEW_LIST_SIZE);
  45. if (null == userRecentReview) {
  46. // 将当前评论塞入队列中
  47. listRedisInstance.rpush("" + userId, reviewFilterModel.getReviewInfo().getDocuments());
  48. return reviewFilterModel;
  49. }
  50. userRecentReview.add(reviewFilterModel.getReviewInfo().getDocuments());
  51. // 正确的暴力做法是,将20个串依次互相两两对比,但是这样复杂度太高了
  52. // 这里采用一个取巧的方法,将20个串按字典序排序,然后依次左右对比,效果应该也可以接受
  53. Collections.sort(userRecentReview);
  54. int cnt = 0;
  55. for (int i = 0; i < userRecentReview.size() - 1; i++) {
  56. int similarity = towStringSimilarity(userRecentReview.get(i),
  57. userRecentReview.get(i + 1));
  58. if (similarity > SIMILARITY_THRESHOLD) {
  59. cnt++;
  60. }
  61. }
  62. if (cnt > BRUSH_THRESHOLD * USER_RECENT_REVIEW_LIST_SIZE) {
  63. log.info("user {} 疑似刷屏,禁止发言{}秒", userId, BAN_SECOND);
  64. BrowserRedisService banRedisInstance = BrowserRedisService
  65. .getRedisService(RedisPrefix.REVIEW_SIMPLE_BRUSH_DETECTION_BAN);
  66. banRedisInstance.set("" + userId, "" + System.currentTimeMillis());
  67. banRedisInstance.expire("" + userId, BAN_SECOND);
  68. // 为了避免用户禁言到期后再次触发逻辑,list中删除2/3的评论
  69. listRedisInstance.ltrim("" + userId, -1, -USER_RECENT_REVIEW_LIST_SIZE / 3);
  70. banReview(reviewFilterModel, userId);
  71. }
  72. // 将当前评论塞入队列中
  73. listRedisInstance.rpush("" + userId, reviewFilterModel.getReviewInfo().getDocuments());
  74. listRedisInstance.ltrim("" + userId, -1, -USER_RECENT_REVIEW_LIST_SIZE);
  75. // 刷新整条list的过期时间
  76. listRedisInstance.expire("" + userId, LIST_EXPIRE_SECOND);
  77. return reviewFilterModel;
  78. }
  79. /**
  80. * 返回两个字符串的相似度。 当某个串长度小于5的时候,认为其不构成可比性
  81. *
  82. * @return int [0,100]
  83. */
  84. private static int towStringSimilarity(String word1, String word2) {
  85. if (word1.length() < 5 || word2.length() < 5) {
  86. return 0;
  87. }
  88. int distance = towStringMinDistance(word1, word2);
  89. return 100
  90. - distance / (word1.length() > word2.length() ? word1.length() : word2.length()) * 100;
  91. }
  92. /**
  93. * 返回两条字符串的最短编辑距离,
  94. *
  95. * 即将word2转变成word1的最小操作次数。
  96. *
  97. * 采用二维动态规划实现,时间复杂度O(N^2)
  98. */
  99. private static int towStringMinDistance(String word1, String word2) {
  100. int m = word1.length();
  101. int n = word2.length();
  102. if (m == 0) {
  103. return n;
  104. }
  105. if (n == 0) {
  106. return m;
  107. }
  108. int[][] f = new int[m + 1][n + 1];
  109. for (int i = 0; i <= m; i++) {
  110. f[i][0] = i;
  111. }
  112. for (int j = 0; j <= n; j++) {
  113. f[0][j] = j;
  114. }
  115. for (int i = 1; i <= m; i++) {
  116. for (int j = 1; j <= n; j++) {
  117. if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
  118. f[i][j] = f[i - 1][j - 1];
  119. } else {
  120. f[i][j] = min(f[i - 1][j - 1], f[i - 1][j], f[i][j - 1]) + 1;
  121. }
  122. }
  123. }
  124. return f[m][n];
  125. }
  126. private static int min(int a, int b, int c) {
  127. return (a > b ? (b > c ? c : b) : (a > c ? c : a));
  128. }
  129. }

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. HTTP/1.1、HTTP/2、HTTP/3 演变

    HTTP/1.1 相比 HTTP/1.0 提高了什么性能? 针对 HTTP/1.1 的性能瓶颈,HTTP/2 做了什么优化? HTTP/2 有哪些缺陷?HTTP/3 做了哪些优化? HTTP/1.1 ...

  2. 关于width的继承和获取

    absolute元素(如果没有设置width值),其宽度自适应于内部元素, <!DOCTYPE html> <html lang="en"> <hea ...

  3. .NET 5学习笔记(12)——WinUI 3 Project Reunion 0.5

    2021年3月的时候,Win UI 3终于来到了第一个稳定的支持版本,可用于创建发布到Micosoft Store的应用.据某软的说法,这个叫WinUI 3 Project Reunion 0.5的版 ...

  4. hdu5251最小矩形覆盖

    题意(中问题直接粘吧)矩形面积 Problem Description 小度熊有一个桌面,小度熊剪了很多矩形放在桌面上,小度熊想知道能把这些矩形包围起来的面积最小的矩形的面积是多少.   Input ...

  5. UVA11462年龄排序

    题意:       给你200w个人的年龄,年龄的范围是1-100,然后让你从小到大排序输出所有人的年龄,题目还特意强调输入文件限制25MB,题目内存限制2MB. 思路:      比较经典又简单的一 ...

  6. [LeetCode每日一题]153.寻找旋转排序数组中的最小值

    [LeetCode每日一题]153.寻找旋转排序数组中的最小值 问题 已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组.例如,原数组 nums = [0,1, ...

  7. InnoDB解决幻读的方案——LBCC&MVCC

    最近要在公司内做一次技术分享,思来想去不知道该分享些什么,最后在朋友的提示下,准备分享一下MySQL的InnoDB引擎下的事务幻读问题与解决方案--LBCC&MVCC.经过好几天的熬夜通宵,终 ...

  8. [物联网] 电气 & 工控

    原理 一次回路和二次回路 一次回路:强电部分(380伏---22万伏),连接发电机.电动机.变压器.电网线路.电网开关.电网避雷器等等 二次回路:弱电部分,指的是控制线路.保护线路.测量线路.计量线路 ...

  9. 山东浪潮超越3B4000申泰RM5120-L

    龙芯解决方案 首页 > 龙芯业务 > 龙芯解决方案和产品生态 > 整机产品 > 服务器 > 详情 超越申泰RM5120-L 服务器 超越申泰RM5120-L 服务器 20 ...

  10. 如何用WINPE备份电脑系统;电脑备份 听语音

    如何用WINPE备份电脑系统:电脑备份 听语音 原创 | 浏览:1046 | 更新:2017-09-30 15:09 1 2 3 4 5 6 7 分步阅读 备份系统已经成为一种常态,我们在安装完成系统 ...