敏感词过滤功能开发总结

发布时间:2025-12-09 11:50:43 浏览次数:2

公司最近有这样的一个需求:针对用户发布的动态、评论,如果触发了后台设置的舆情词功能,则触发下沉操作(状态标识)。查询时候如果是下沉的动态或者评论,那么自己能看到但是其他人看不到。

这个需求里面有几个功能点:后台需要一个舆情词功能管理,当用户发布动态和评论后 ,需要判断出来是否包含有舆情词。这就涉及到一个算法以及速度的问题。

一般这种功能是有三方的,比如腾讯百度之类的,都有这种三方接口。但是考虑到接入三方rpc调用的速度问题,决定看看有没有类似的组件。 于是,github上搜了下,确实还有。

github地址:https://github.com/toolgood/ToolGood.Words

我主要是用java语言的,把java相关的代码重新建一个maven项目拷贝到自己的项目里面。作为过滤使用

后台的crud功能,写完后有个问题是这样的:每次我新增或者修改了舆情词,在过滤时候每次都要把这些内容重新加载到内存中,但是如果每次都加载,随着内存越来越多,会出现oom的问题。

于是我在项目启动时候加了预热功能,代码如下:

package com.gwm.lafeng.consts;import com.gwm.lafeng.WordsSearchEx2;import com.gwm.lafeng.dao.community.BbsSentimentDao;import org.apache.commons.collections.CollectionUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;import javax.annotation.Resource;import java.util.List;/** * @Author fanht * @Description * @Date 2022/1/21 2:44 下午 * @Version 1.0 */@Servicepublic class WarmUpConstants {    private Logger logger = LoggerFactory.getLogger(this.getClass());    public static WordsSearchEx2 sensitiveWords;    @Resource    private BbsSentimentDao bbsSentimentDao;        @PostConstruct    public void WarmUpConstants() {        initSentiment();    }    private void initSentiment() {        logger.info("=====================初始化舆情词=========================");        try {            List<String> sentimentList = bbsSentimentDao.queryAllSentimentWords();            if (CollectionUtils.isNotEmpty(sentimentList)) {                sensitiveWords = new WordsSearchEx2();                sensitiveWords.SetKeywords(sentimentList);            }        } catch (Exception e) {            logger.error("初始化舆情词异常",e);        }        logger.info("===============初始化舆情词end============");    }}

  然后每次新增修改删除时候,把数据库的数据重新查询出来放到内存中。考虑到这个如果同步比较慢,因此加入了异步操作。

 application 添加允许异步  @EnableAsync   /**     * 每次新增、修改、导入成功后刷新内存中的舆情词库     */    private void resetWarmUp(){        try {            asyncWarmUpUtils.resetWarmUp();        } catch (Exception e) {            logger.error("重置舆情词库异常",e);        }    }/** * @Author fanht * @Description * @Date 2022/1/24 5:58 下午 * @Version 1.0 */@Componentpublic class AsyncWarmUpUtils {    private Logger logger = LoggerFactory.getLogger(this.getClass());    @Autowired    private BbsSentimentDao bbsSentimentDao;    /**     * 每次新增、修改、导入成功后刷新内存中的舆情词库 考虑比较慢 加上异步     */    @Async    public void resetWarmUp(){        try {            List<String> sentimentList = bbsSentimentDao.queryAllSentimentWords();            if (org.apache.commons.collections.CollectionUtils.isNotEmpty(sentimentList)) {                WarmUpConstants.sensitiveWords.SetKeywords(sentimentList);            }        } catch (Exception e) {            logger.error("重置舆情词库异常",e);        }    }}

  

它这个慢主要是用toolGoods 自带的过滤词时候,如果每次都调用,是特别慢的。

这样放进去之后,每次检测时候直接判断就行了:

/**     * 判断发布评论是否含有舆情词     * @param commentDTO     * @return true or false     */    private  PublishCommentDTO hasSentimentWords(PublishCommentDTO commentDTO){        try {            //如果已经触发了下沉操作 则不再判断 此处主要影响mq计算评论的个数            if(BbsConstant.IS_SINK_1.equals(commentDTO.getIsSink()) || StringUtils.isEmpty(commentDTO.getCommentContent())){                return commentDTO;            }            if(WarmUpConstants.sensitiveWords == null){                return  commentDTO;            }            //todo 这一步很耗时 此处为优化后的            WordsSearchEx2 sensitiveWords = WarmUpConstants.sensitiveWords;            if(sensitiveWords.ContainsAny(commentDTO.getCommentContent())){                LOG.info("评论含有舆情词");                List<WordsSearchResult> senWords = sensitiveWords.FindAll(commentDTO.getCommentContent());                StringBuffer stringBuffer = new StringBuffer();                senWords.forEach(f ->{                    stringBuffer.append(f.Keyword + ",");                });                commentDTO.setIsSink(BbsConstant.IS_SINK_1);                commentDTO.setHasSentimentWords(BbsConstant.HAS_SENTIMENT);                commentDTO.setSentimentWords(stringBuffer.toString().length() > 200 ? stringBuffer.toString().substring(0,200):stringBuffer.toString());            }else {                commentDTO.setIsSink(BbsConstant.IS_SINK_0);                commentDTO.setHasSentimentWords(BbsConstant.NO_SENTIMENT);                commentDTO.setSentimentWords(null);            }            return commentDTO;        } catch (Exception e) {            LOG.error("判断评论是否含有舆情词异常",e);        }        return commentDTO;    }

  

如果是定时检测的,代码如下

package com.gwm.lafeng.timingtask;import com.alibaba.excel.util.CollectionUtils;import com.alibaba.fastjson.JSONObject;import com.gwm.lafeng.WordsSearchEx2;import com.gwm.lafeng.WordsSearchResult;import com.gwm.lafeng.common.config.BbsConstant;import com.gwm.lafeng.common.util.DateUtils;import com.gwm.lafeng.consts.WarmUpConstants;import com.gwm.lafeng.dao.community.BbsPostsDao;import com.gwm.lafeng.dao.community.BbsSentimentDao;import com.gwm.lafeng.document.CommunityStreamDocument;import com.gwm.lafeng.entity.community.BbsPosts;import com.gwm.lafeng.service.community.EsCommunityStreamService;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import javax.annotation.Resource;import java.util.Date;import java.util.List;/** * @Author fanht * @Description 触发舆情词下沉功能 * @Date 2022/1/15 9:53 上午 * @Version 1.0 */@Component@EnableSchedulingpublic class TimingBbsSentimentManager {    private Logger logger = LoggerFactory.getLogger(this.getClass());    @Resource    private EsCommunityStreamService esCommunityStreamService;    @Resource    private BbsPostsDao bbsPostsDao;    /**     *每10S执行一次     */    @Scheduled(cron = "10 * * * * ?")    public void job() {        logger.info("=======触发舆情词则自动下沉start============");        //查询10s内 审核通过的动态        List<BbsPosts> postsList = bbsPostsDao.queryCheckPass(DateUtils.jumpMinute(new Date(), -24*60));        if (!CollectionUtils.isEmpty(postsList)) {            WordsSearchEx2 sensitiveWords = WarmUpConstants.sensitiveWords;            postsList.forEach(t -> {                BbsPosts bbsPosts = new BbsPosts();                bbsPosts.setId(t.getId());                StringBuffer stringBuffer = new StringBuffer();                boolean tas = StringUtils.isEmpty(t.getTitle())? false : sensitiveWords.ContainsAny(t.getTitle());                boolean cas =StringUtils.isEmpty(t.getContent())?  false :  sensitiveWords.ContainsAny(t.getContent());                if(tas ){                    List<WordsSearchResult> senWords = sensitiveWords.FindAll(t.getTitle());                    senWords.forEach(s -> {                        stringBuffer.append(s.Keyword + ",");                    });                }                if(cas){                    List<WordsSearchResult> senWords = sensitiveWords.FindAll(t.getContent());                    senWords.forEach(s -> {                        stringBuffer.append(s.Keyword + ",");                    });                }                if(tas || cas){                    bbsPosts.setHasSentimentWords(BbsConstant.HAS_SENTIMENT);                    bbsPosts.setSentimentWords(stringBuffer.toString().length() > 200 ? stringBuffer.toString().substring(0,200):stringBuffer.toString());                    logger.info("动态标题or内容还有舆情词,触发下沉操作"+ JSONObject.toJSON(bbsPosts));                    bbsPostsDao.updateSentimentSink(bbsPosts);                    CommunityStreamDocument communityStreamDocument = new CommunityStreamDocument();                    communityStreamDocument.setId(bbsPosts.getId().toString());                    communityStreamDocument.setIsSink(BbsConstant.IS_SINK_1);                    try {                        esCommunityStreamService.saveOrUpdateDocument(communityStreamDocument);                    } catch (Exception e) {                       logger.error("定时更新话题舆情词下沉异常",e);                    }                }            });        }    }}

  

遇到的问题记录:

1.首先就是oom的问题

刚开始开发完的时候,写完定时后发现服务器在dev环境老是报oom 的问题。是因为自己写的job ,每隔一分钟执行一次。每次都要把信息加载到toolGoodls的内存中。但是不清楚为何没有回收,导致内存越来越大,最终oom。解决方案就是 自己写了一个static的 静态类,在预热中加载到内存中。我比较了下之前的的写法:

//todo 这一步很耗时
WordsSearchEx2 sensitiveWords = new WordsSearchEx2();
sensitiveWords.SetKeywords(sentimentList);


这样写是特别耗时和耗费内存的。

2.crud后及时生效
为了实时生效,我是在项目初始化后先查下库之后放入到了内存中,之后每次修改删除添加,就同步做修改。这样做到了实时生效,速度也基本在100ms以内。也没有出现oom的问题

3.异步处理
同步的话,在crud之后把最新的敏感词放入内存中比较慢,这里是用了注解@Async的异步注解,用异步线程池也是可以的。如果是异步线程池 可以这样处理
    ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("async-sentiment-words-%d").build();    public final ExecutorService executorService = new ThreadPoolExecutor(5,20,10L, TimeUnit.MILLISECONDS,            new LinkedBlockingQueue<>(20),factory,new ThreadPoolExecutor.AbortPolicy());        executorService.submit(new Callable<Object>() {            @Override            public Object call() throws Exception {                //异步方法                              return null;            }        });



词库异常
需要做网站?需要网络推广?欢迎咨询客户经理 13272073477