我看拉勾一拍之系统架构

今年年中的时候由公司平台部转组到Alpha项目中心负责公司一拍项目组的技术研发工作,到现在已经快有将近半年的时间了,随着对系统的越来越熟悉,对原有系统的架构也越来越感到有些不合理的地方,随着自己水平的提升感觉对架构也有了一点自己的理解,所以今天就借这个机会说说自己的不成熟的建议。 一. 原有的架构 俗话说,一图胜千言,直接上图: 解释一下这几个系统分别的作用: 后台管理系统不用说了,管理C和B可见的内容; C端用户系统,是对C可见的一个系统,一拍是一个招聘系统,所以就是对候选人操作的后台; B端用户系统,是对B可见的一个系统,通俗点讲就是HR操作的后台; Dubbo系统,是对兄弟部门和B提供服务的一个系统; Recommend系统,其实也是一个Dubbo系统,区别在于Dubbo系统是对外提供各种服务的,而Recommend系统是发现全站系统用户的行为,然后对用户的行为进行分析,甄选出一部分C端用户作为一拍的现在用户; msgpush系统,是用netty做的一个实时消息推送的IM服务,目前主要是给后台管理用户和C端候选人聊天的一个系统; 其中:1. 在我们接手之前B和C是同一个系统,也就是说B是我们这半年新加的一个系统;2. Recommend和msgpush系统也同样是我们这半年新增的一个系统 二. 系统架构存在的问题 目前后台管理系统、C端用户系统、Dubbo系统各自独立,这样存在的问题: 最主要的是各自分别操作数据库,这样只要底层数据库发送变动,那么三个系统操作数据库的地方都要同步修改三次; 操作数据库的地方代码冗余,很多地方都一样,这样一个地方出bug,三个地方要同步修改,然后都要上线; 后台管理系统采用分层的模式分模块而不是根据业务分模块,这样每次上线service和dao都要先deploy jar到maven私服; 当时为了快速迭代,Recommend系统也是单独操作操作数据库,不过还好用了后台管理系统的dao这个jar包,但是首先根本不需要这么重的一个jar包,其次jar出bug了,有时候他也需要重新上线啊,不然这个jar包就会一直很旧,当然只要不涉及到他操作数据库的地方出bug,你不改也是可以的; 目前C和B虽然已经分开,但如果用户激增,横向扩展依然不合理,只能整体加机器,而不能针对性对某些模块单独加机器; 代码中存在的问题:不知道什么原因大量的逻辑被写在了controller层,导致代码可复用性差; 由于之前后台管理系统和其他系统不是同一个团队开发的,命名各有各的风格,代码不仅冗余还同样一个类名字不一样; 很多系统日志配置的也有问题,错误日志和最基本的业务日志没有区分开,目前在将就用; 系统中的jar依赖不仅存在循环依赖,而且加入了大量的自己不需要的依赖,导致各种jar冲突出问题; 综合以上问题,我个人认为这是一个:可维护性、可扩展性不高的系统。 三. 我的个人思考 同样先来一张图,来总体说明一下我的想法: 整体来说只有相对独立的实时消息推送系统不懂,然后把其余的各个业务层抽象成微服务,采用公司目前使用比较成熟的dubbo作为rpc框架,controller层只负责业务转发不负责逻辑的一个简单系统,这样带来的好处: Dubbo系统作为核心的业务系统,分别对兄弟部门、后台管理系统、C端用户系统、B端用户系统、Recommend系统提供服务,如果用户量增加,不仅可以整体增加Dubbo系统的机器,也可以把调用量大的接口单独拆出来,部署到另外的机器上,实现隔离,不会因某个接口调用量大,导致整个系统不可用,而且把数据库的底层操作也放到了这个Dubbo系统中,这样就可以避免数据库修改,要修改多处的问题; 抽象出来的这个dubbo系统,不仅可以解决后台系统和其他系统命名不一样的问题,而且和可以解决不同团队造成的coding style不一的问题,一举多得; 由于后台管理系统、C端用户系统、B端用户系统、Recommend系统都调用Dubbo系统,所以他们的controller层讲极其简单,很多业务逻辑类似的东西全部放到了Dubbo系统里面,代码的可复用性提高了不少; 抽象出来的逻辑都统一放到了dubbo中,这样系统如果有bug,这样就做到了一个地方修改,这样多个地方就可以同时生效; 由于业务都在Dubbo系统里面这样同时也避免了曾经出现了,后台管理系统和BC系统使用的缓存不一致,导致缓存出问题的这种低级bug; 由于controller层简单没有逻辑,这样就可以避免目前由于后台管理系统单机,修改一个业务逻辑bug重启系统,导致后台不可用的问题,因为只需要重启dubbo就行了; 这样controller层变得很轻,只需要一个简单的servlet容器,对机器的要求会降低不少; 四. 备注 由于我工作时间不长,见过的系统更有限,所以对系统架构几乎没有什么经验,这些只是我个人的一点很粗浅的理解,例如把dubbo做的那么重,虽然可扩展性提高了不少,但其实也不知道算不算合理,因为调用rpc服务,肯定会增加网络IO延时,所以这些算是我个人的抛砖引玉吧,一方面希望对同样和我一样没有经验的小伙伴能有所帮助,另一方面希望有经验的小伙伴能留言交流 五. 总结 以上便是我个人对拉勾一拍所有的核心系统进行了审视后的一番分析,如果这些核心系统架构的重构真的达到自己的理想状况这将是一番浩大的工程,对于高速发展的互联网公司来说,这就是一边驾驶者一辆高速前进的汽车,一边对这辆汽车进行换轮胎换发动机,先不说工作量的问题,难度程度也可见一斑。

October 23, 2016 · 1 分钟 · Bridge Li

Redis 3.0入门二之集群搭建和使用

上一篇文章讲了redis的主从搭建,主从一般只能解决我们读写分离的问题,可以增加我们的系统的负载能力,但是并不能解决单点问题,大家应该知道在互联网公司各个服务肯定不能出现单点问题,所以这一节就记录一下如果让我们的系统更加高可用。 一、集群搭建 需要先说明的是,集群搭建需要至少6个节点:3主3从(因为没有那么多机器,所以就在一台上搞了) 创建文件夹redis-cluster,然后在其下面分别创建6个文件夹,存放6个实例 mkdir -p /usr/local/redis-cluster mkdir 7001;mkdir 7002;mkdir 7003;mkdir 7004;mkdir 7005;mkdir 7006 把之前redis.conf配置文件分别copy到700*下,修改各个实例的配置文件的内容,如下: ①. daemonize yes ②. port 700*(一台机器端口号肯定不能相同,就和文件夹一样吧) ③. bind ip(和当前机器的ip地址绑定) ④. dir /usr/local/redis-cluster/700*/(文件存储位置应该不一样吧,原因都知道) ⑤. cluster-enabled yes ⑥. cluster-config-flie nodes-700*.conf(700*就和端口一样吧) ⑦. cluster-node-timeout 5000 ⑧. appendonly yes 安装ruby yum install ruby yum install rubygems gem install redis 启动各个实例 /usr/local/redis/bin/redis-server /usr/local/redis-cluster/700*/redis.conf 创建集群 cd /usr/local/redis-3.0.0-rc2/src ./redis-trib.rb create -replicas 1 ip:7001 ip:7002 ip:7003 ip:7004 ip:7005 ip:7006 然后看输出日志,会有一步需要输入yes,然后集群就创建完成了 验证,连接任意一个客户端即可 /usr/local/redis/bin/redis-cli -c -h -p (-c表示集群模式,指定ip和端口) 然后可以输入一些命令 ...

September 16, 2016 · 2 分钟 · Bridge Li

Redis 3.0入门一之主从搭建

周末没事看北京尚学堂之前的公开课视频,发现了白贺翔老师有一节课讲redis 3.0的视频教程,还不错,以下是学习笔记。 一、单机版搭建 首先是下载地址:http://redis.io/download,假设我们下载是redis-3.0.0-rc2.tar.gz 安装步骤: 把我们下载好的redis-3.0.0-rc2.tar.gz放到Linux的/usr/local文件夹下 解压tar -xzvf redis-3.0.0-rc2.tar.gz -C /usr/local/ 进入到redis-3.0.0-rc2目录下,进项make 进入到src下进行安装make install,验证(ll查看发现src下的目录,有redis-server、redis-cli即可) 建立两个文件夹存放redis命令和配置文件 mkdir -p /usr/local/redis/etc mkdir -p /usr/local/redis/bin 把redis-3.0.0-rc2下的redis.conf移动到/usr/local/redis/etc下 mv redis.conf /usr/local/redis/etc 把redis-3.0.0-rc2/src里的mkreleasehdr.sh、redis-benchmark、redis-check-aof、redis-check-dump、redis-cli、redis-server文件移动到bin下,命令 mv mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server /usr/local/redis/bin 启动并指定配置文件 /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf 退出改为后台启动 退出就不说了,改为后台启动,编辑 /usr/local/redis/etc/redis.conf找到 daemonize no 改为 daemonize yes 修改持久化文件存放的位置,修改 dir ./ 为 dir /usr/local/redis/data/ redis客户端的使用 /usr/local/redis/binredis-cli -h host -p port 设置密码 通过刚才的操作应该可以发现redis默认是没有密码的,这样很不安全,设置密码的方法是编辑/usr/local/redis/etc/redis.conf找到requirepass 这一行,设置 requirepass bridgeli 这样通过客户端进入的时候加一个参数 -a 跟上你的密码就好了 二、 主从搭建 当然是首先搭建两台单机版,然后如果想要某一台成为slave,就编辑这一台机器的配置文集,找到 #slaveof <masterip> <masterport> 这一行(对,默认是被注释掉的,这不废话吗),然后按照这个模式,写上master的信息就好了,之后重启主从机器,就可以通过命令:info来查看主从角色 三、redis服务器监控(哨兵) 实现步骤(在任一台服务器上配置sentinel.conf): ...

August 28, 2016 · 1 分钟 · Bridge Li

Dubbo远程debug方法

公司项目的rpc服务基于阿里巴巴的dubbo架构,开发dubbo项目的时候测试只能跑junit test,但实际工作中由于很多时候junit test写的不全,出了问题只能再加日志分析原因(典型的没事找事型),这次和公司移动端的推送联调IM服务,发现他们已经把老夫之前听说的远程debug用在了实际工作中,刚好趁此机会实验了一把,以下是笔记,以待自己和需要的朋友参考。 dubbo服务的设置 我们自己观察dubbo的start.sh和start.bat这两个脚本会发现有如下两端代码 ①. start.sh JAVA_DEBUG_OPTS="" if [ "$1" = "debug" ]; then JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n " fi ②. start.bat if ""%1"" == ""debug"" goto debug if ""%1"" == ""jmx"" goto jmx java -Xms64m -Xmx1024m -XX:MaxPermSize=64M -classpath ..\conf;%LIB_JARS% com.alibaba.dubbo.container.Main goto end :debug java -Xms64m -Xmx1024m -XX:MaxPermSize=64M -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n -classpath ..\conf;%LIB_JARS% com.alibaba.dubbo.container.Main goto end :end 也就是说,脚本已经支持远程debug,只需要的在启动的时候传入一个参数 debug 即可,其余的几乎不用做任何修改 eclipse的设置 当我们把远程的服务以支持debug的模式启动之后,就需要把本地的项目也起来了,否则怎么debug呢,本地的设置其实非常简单,一张图搞定 看了这张图,我相信不用我多说了,远程远程debug如此简单

August 14, 2016 · 1 分钟 · Bridge Li

多线程应用之批量数据处理

我们都知道多线程是为了加快数据处理的,但至于怎么用,因为在工作中,我一直很少用,所以对多线程不是很了解。之前处理一个功能时,由于没有经验,导致速度很慢,前一段时间经老大提示,可以用多线程解决,突然发现原来多线程可以这么用可以来处理这一类问题,今天记录一下,作为笔记也作为一个给读者的参考,好了先说一下问题:公司的业务的业务不仅分模块而且是分库分表的,这样就导致一个问题,当我们要查询一个数据时,不能连表查询,不能只通过一个接口获得数据,最容易想到的常规做法就是: public List<Data> queryDatas() { List<Data> datas = queryDataFromDB(); if(null != datas && datas.size() > 0) { for(Data data : datas) { Object object = getObjectFromDb(data.getId()); data.setAttr1(object.getAttr); } } return datas; } 这么做,虽然可以满足业务需求,但效率实在是太低了,尤其是列表数据越大时,如果不只一个属性要这么做时,速度是会慢到要死人的。所以经老大提示参考同事的实现就采用了如下方法: private static final ExecutorService executor = Executors.newFixedThreadPool(20); public List<Data> queryDatas() { List<Data> datas = queryDataFromDB(); if(null != datas && datas.size() > 0) { batchSetAttr(datas); } return datas; } private boolean batchSetAttr(final List<Data> datas) { final CompletionService<Data> completionService = new ExecutorCompletionService<>(executor); for (final Data data : datas) { completionService.submit(new Callable<Data>() { @Override public Data call() throws Exception { Object object = getObjectFromDb(data.getId()); data.setAttr1(object.getAttr); return data; } }); } try { for (int i = 0, size = datas.size(); i < size; i++) { Future<Data> future = completionService.take(); Data d = future.get(); } } catch (InterruptedException e) { logger.error("InterruptedException", e); return false; } catch (ExecutionException e) { logger.error("ExecutionException", e); return false; } return true; } 利用多线程批量查询,返回时一一设置值,最终达到提高速度的目的。 最后需要说明一点:线程池的大小,大家可以根据自己的实际情况来设置,并不是越大越好;

July 24, 2016 · 1 分钟 · Bridge Li

全文索引服务solr入门四之solr集群搭建与应用

经过前面几篇文章的努力,我们的solr服务终于跑起来了,可以对外提供服务了,虽然有一点小瑕疵(对中文支持不是很好,但可以通过IK分词来解决,示例可以看老夫之前的这篇文章),但还是存在一个致命的问题:单点!对于现代互联网公司,这个一个不可忍受的缺点,所以本节我们就来解决这个问题,在搭建集群之前,我们先看看要搭建的集群的目标: 从这张图上,我们可以清晰看到我们需要三台zookeeper服务器和四台solr服务器,一共七台服务器(实际上老夫去哪找那么多集群,所以使用伪分布式的方式搭建集群,即在同一台服务器上创建多个实例模拟集群),确定了目标之后,我们就开始搭建我们的集群。 四. zookeeper集群搭建 Zookeeper有一个选举机制,选举谁是leader谁是follower。成为主节点,需要得到半数以上的投票。尽可能为奇数节点。 创建三个zookeeper实例 在zookeeper1、2、3文件夹下分别创建一个data目录。在data目录下创建一个myid的文件。文件的内容zookeeper1下就是1,zookeeper2下就是2,zookeeper3下就是3。 需要修改zookeeper的配置文件。把Zookeeper1confzoo_sample.cfg改名为zoo.cfg(示例有误,配置文件中的dataDir路径需要使用“/”而不是“”) 启动zookeeper。 启动成功之后: 启动zookeeper的客户端,测试zookeeper是否好用。 五. Solr实例搭建 需要4个solr实例来完成。 第一步:创建4个tomcat实例 第二步:搭建4个solr的单机版环境(该系列的前几篇就写这个了)。 从复制过来的单机版: 修改tomcat的端口号。 修改solrhome的位置。修改web.xml 六. 集群搭建 搭建步骤 ①. 让zookeeper集群集中管理配置文件。把配置文件上传到zookeeper。 把solrhome/collection1/conf文件夹的内容上传到zookeeper集群。 把:D:tempSolrCloudsolrhome1collection1conf上传。 命令: java -classpath D:tempSolrCloudtomcat1webappssolrWEB-INFlib/* org.apache.solr.cloud.ZkCLI -zkhost 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183 -cmd upconfig -confdir D:tempSolrCloudsolrhome1collection1conf -confname myconf 需要保证执行命令之前,zookeeper集群是启动状态。 查看zookeeper下的配置文件。 修改每个solrhome下的solr.xml文件。 告诉每个solr实例zookeeper集群的位置。 修改每一台solr的tomcat 的 bin目录下catalina.bat文件中加入DzkHost指定zookeeper服务器地址: set JAVA_OPTS="-DzkHost=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183" 启动所有tomcat 访问solr服务 创建有两片的collection。在浏览器的url中输入如下内容: http://127.0.0.1:8080/solr/admin/collections?action=CREATE&name=collection2&numShards=2&replicationFactor=2 删除collection1(之前单机版的时候创建的) http://127.0.0.1:8080/solr/admin/collections?action=DELETE&name=collection1 删除之后,就是这个样子: 七. 集群的使用 后台的管理功能和单机版完全一致,如下图,所以略过。 使用solrJ管理solr集群 ①. 添加集群 ...

July 10, 2016 · 1 分钟 · Bridge Li

全文索引服务solr入门三之solrJ的应用

三. 使用solrJ和spring集成 再上一篇和上上一篇文章中我们先搭建了一个solr服务器和学习了solr服务器后台的使用,这一次我们将直接进入实战:和spring集成,在继承之前我们先看看所需要的solr的jar文件都是那些(spring的那些大家就自己玩吧,我相信都知道的) 所需的jar文件 直接上图片,就是图上的这些图片,当然大家可以自己找maven依赖(jar文件这个最简单了,没有的话一定会报classnotfoundException,加上就好了) spring的配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd "> <!-- 配置扫描包 --> <context:component-scan base-package="cn.bridgeli"/> <!-- 配置注解驱动 --> <mvc:annotation-driven/> <!-- 配置注解驱动 --> <tx:annotation-driven/> <!-- jsp视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" > <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/"></property> <!-- 后缀 --> <property name="suffix" value=".jsp"></property> </bean> <!-- 单机版solr --> <bean class="org.apache.solr.client.solrj.impl.HttpSolrServer"> <constructor-arg name="baseURL" value="http://localhost:8080/solr/"></constructor-arg> </bean> <!-- 集群版SolrCloud --> <!-- <bean class="org.apache.solr.client.solrj.impl.CloudSolrServer"> <constructor-arg name="zkHost" value="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"></constructor-arg> <property name="defaultCollection" value="collection2"></property> </bean> --> </beans> 简单吧,大家只要注意到单机版就行了,因为我们这次只用到了单机版,下面就要看源码实现了 ...

June 26, 2016 · 3 分钟 · Bridge Li

全文索引服务solr入门二之认识管理后台

二. 认识solr管理后台 上一篇文章中我们搭建起来的其实就是一个solr管理后台,这节课我们就是来简单认识他,具体的操作当然由大家自己试验了。下面我们先直接选择左侧的默认collection1你会看到下面出来很多东西,其中有几个比较重要:Analyzer、Documents、Query,下面我们认识第一个:Analyzer。 认识Analyzer 查看当前索引库中的字段列表,及分词效果。 ①. 域、字段列表:所有的域都是定义在schema.xml配置文件中。在solr中域必须是先定义后使用。 如果想修改域的定义及自定义域需要修改schema.xml。 ②. 查看域的分词效果。 可以直接在Field Value中贴一段文字,然后点击按钮Analyzer Values,查看效果。 需要说明的是:默认对英文支持尚可,中文支持很差。实际生产用zk中文分词 Documents 索引库的维护功能。可以对索引库进行增删改操作。 ①. 添加文档 I. 在solr中一条记录就是一个文档。 II. 文档可以使用json数据格式描述:key就是域名(字段名),value:值。 III. 在solr中每个文档必须有一个id域。类似于关系型数据库中表的主键。必须有且不能重复。 IV. 域必须先定义后使用。必须在schema.xml中定义。 ②. 更新文档 添加一个新的文档如果id存在会想把id对应的文档删除,然后再添加以新的。先删除后添加。 ③. 删除文档 I. 根据id删除 <delete> <id>a001</id> </delete> <commit/> II. 根据查询删除 删除所有文档: <delete> <query>*:*</query> </delete> <commit/> 删除指定id的文档: <delete> <query>id:change.me</query> </delete> <commit/> Query 查询索引库 q:查询条件 fq:过滤条件,查询语句和查询的语法完全相同。可以设置多个过滤条件 sort:排序条件 start rows:分页条件。start起始记录,rows每页显示的记录数。 fl:返回结果中域的列表 df:默认搜索域 hl:高亮显示 hl.fl:高亮显示的域 hl.simple.pre:高亮前缀 hl.simple.post:高亮后缀 说到这里,对这个solr管理后台的简单认识就完了,只要读者自己多动手实践几次,对这个后台将会熟练掌握,但是仅仅有这个后台在实际生产中肯定是不够的,所以在下一篇文章中老夫将会介绍solr的客户端solrj的简单应用,那可是可以直接早生产中用了哦

June 11, 2016 · 1 分钟 · Bridge Li

全文索引服务solr入门一之单机版服务器搭建

一直对全文索引很感兴趣,但由于工作上一直没用到,所以就没有上心去看,有一次闲着无聊,偶然发现传智播客的公开课上有一节讲这个的,听了一下还不错,于是整理成笔记,供老夫和其他朋友需要的时候参考。 老夫相信看这篇文章的人对solr一定有所了解,不了解的请用Google百度一下,所以虽然是一个系列,但这里老夫只会讲怎么用,争取让看了这个系列文章的人能里面先跑起来,好了,下面直接切入主题。 一. solr的安装与配置 solr的运行环境 ① . 需要安装jdk,要求jdk的版本为1.7.0以上版本。 ② . Tomcat要求7以上版本。 ③ . 操作系统:linux、windows都可以。 Solr和tomcat的集成 ① . 安装jdk、安装tomcat ② . 把solr-4.10.4.zip包解压。 ③ . 把dist目录下的solr-4.10.3.war文件放到tomcat下webapp目录下。改名为solr.war。改名不是必须的,为了方便访问。 ④ . 解压war包。启动tomcat自动解压。 ⑤ . 把solr.war删除。删除war包时要关闭tomcat。 ⑥ . 把D:tempsolr-4.10.3examplelibext目录下的所有的jar包添加到solr工程中。D:tempapache-tomcat-7.0.53webappssolrWEB-INFlib ⑦ . 创建一个solrhome(solr所有配置文件存放的目录)。 把sorl文件夹复制到其他路径下,改名为solrhome,改名的目的是为了便于理解。 ⑧. 告诉sorl服务器solrhome的位置。 修改solr工程的web.xml文件。 D:tempsolrhomecollection1:索引库存放的位置。 ⑨ . 启动tomcat。 访问:http://localhost:8080/solr,如果看到下面这个界面,那么就是成功了 至此一个单机版的solr服务器就搭建起来了,比较简单,接下来的第二篇会老夫会介绍这个服务器搭建起来以后怎么玩

May 29, 2016 · 1 分钟 · Bridge Li

DFA算法应用之敏感词过滤

公司在做一个社区应用,由于我朝特色,众所周知社区应用有一个很重要的就是要进行敏感词的过滤,这块由一个同事负责,听他说,有一个算法叫DFA,可以做这个,个人比较感兴趣,就到网上查了一些资料,有一篇文章写的特别好,老夫的这篇文章就是把其核心的部分(就是怎么应用,老夫一直有一个观点,理论弱于实践,理论懂得再多不会用一点用没有,所以老夫认为应用是核心)摘出来,留作笔记,如果有想了解其原理的,请点击下方的参考资料,好了,既然是应用那么就直接上代码了: package cn.bridgeli.dfa; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; public class SensitivewordFilter { @SuppressWarnings("rawtypes") private Map sensitiveWordMap = null; public static int minMatchTYpe = 1; // 最小匹配规则 public static int maxMatchType = 2; // 最大匹配规则 /** * 初始化敏感词库 */ public SensitivewordFilter() { sensitiveWordMap = new SensitiveWordInit().initKeyWord(); } /** * 判断文字是否包含敏感字符 * * @param txt * 文字 * @param matchType * 匹配规则 1:最小匹配规则,2:最大匹配规则 * @return 若包含返回true,否则返回false */ public boolean isContaintSensitiveWord(String txt, int matchType) { boolean flag = false; for (int i = 0; i < txt.length(); i++) { int matchFlag = this.CheckSensitiveWord(txt, i, matchType); // 判断是否包含敏感字符 if (matchFlag > 0) { flag = true; } } return flag; } /** * 获取文字中的敏感词 * * @param txt * 文字 * @param matchType * 匹配规则 1:最小匹配规则,2:最大匹配规则 * @return */ public Set<String> getSensitiveWord(String txt, int matchType) { Set<String> sensitiveWordList = new HashSet<String>(); for (int i = 0; i < txt.length(); i++) { int length = CheckSensitiveWord(txt, i, matchType); if (length > 0) { sensitiveWordList.add(txt.substring(i, i + length)); i = i + length - 1; // 减1的原因,是因为for会自增 } } return sensitiveWordList; } /** * 替换敏感字字符 * * @param txt * 文字 * @param matchType * 匹配规则 1:最小匹配规则,2:最大匹配规则 * @param replaceChar * 替换字符,默认* */ public String replaceSensitiveWord(String txt, int matchType, String replaceChar) { String resultTxt = txt; Set<String> set = getSensitiveWord(txt, matchType); // 获取所有的敏感词 Iterator<String> iterator = set.iterator(); String word = null; String replaceString = null; while (iterator.hasNext()) { word = iterator.next(); replaceString = getReplaceChars(replaceChar, word.length()); resultTxt = resultTxt.replaceAll(word, replaceString); } return resultTxt; } /** * 获取替换字符串 * * @param replaceChar * 替换字符,默认* * @param length * 文字长度 * @return */ private String getReplaceChars(String replaceChar, int length) { String resultReplace = replaceChar; for (int i = 1; i < length; i++) { resultReplace += replaceChar; } return resultReplace; } /** * 检查文字中是否包含敏感字符,检查规则如下:<br> * * @param txt * @param beginIndex * @param matchType * @return,如果存在,则返回敏感词字符的长度,不存在返回0 */ @SuppressWarnings({ "rawtypes" }) public int CheckSensitiveWord(String txt, int beginIndex, int matchType) { boolean flag = false; // 敏感词结束标识位:用于敏感词只有1位的情况 int matchFlag = 0; // 匹配标识数默认为0 char word = 0; Map nowMap = sensitiveWordMap; for (int i = beginIndex; i < txt.length(); i++) { word = txt.charAt(i); nowMap = (Map) nowMap.get(word); // 获取指定key if (nowMap != null) { // 存在,则判断是否为最后一个 matchFlag++; // 找到相应key,匹配标识+1 if ("1".equals(nowMap.get("isEnd"))) { // 如果为最后一个匹配规则,结束循环,返回匹配标识数 flag = true; // 结束标志位为true if (SensitivewordFilter.minMatchTYpe == matchType) { // 最小规则,直接返回,最大规则还需继续查找 break; } } } else { // 不存在,直接返回 break; } } if (matchFlag < 2 || !flag) { // 长度必须大于等于1,为词 matchFlag = 0; } return matchFlag; } public static void main(String[] args) { SensitivewordFilter filter = new SensitivewordFilter(); System.out.println("敏感词的数量:" + filter.sensitiveWordMap.size()); String string = "太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。" + "然后法轮功 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪," + "难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。"; + "难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。"; Set<String> set = filter.getSensitiveWord(string, 1); System.out.println("语句中包含敏感词的个数为:" + set.size() + "。包含:" + set); } } 这个主要是应用,DFA的核心是下面: ...

May 2, 2016 · 4 分钟 · Bridge Li