From 391396898e72181ffd4d23f60200196e41a5404a Mon Sep 17 00:00:00 2001
From: zhongrj <646384940@qq.com>
Date: Tue, 23 Jan 2024 10:57:44 +0800
Subject: [PATCH] 新增敏感词校验,物业公司评价校验

---
 src/main/java/org/springblade/modules/words/sensitiveword/service/ISensitivewordService.java         |   48 
 src/main/java/org/springblade/modules/words/WordsSearchEx2.java                                      |  170 +
 src/main/java/org/springblade/modules/words/internals/BaseMatch.java                                 |  457 +++
 src/main/java/org/springblade/modules/words/internals/TrieNodeEx.java                                |  140 +
 src/main/java/org/springblade/modules/words/internals/TrieNode3.java                                 |   52 
 src/main/java/org/springblade/modules/property/service/IPropertyCompanyCommentService.java           |    2 
 src/main/java/org/springblade/modules/words/WordsSearch.java                                         |  158 +
 src/main/java/org/springblade/modules/words/sensitiveword/service/impl/SensitivewordServiceImpl.java |   51 
 src/main/java/org/springblade/modules/words/internals/Translate.java                                 |  212 +
 src/main/java/org/springblade/modules/words/sensitiveword/entity/Sensitiveword.java                  |   45 
 src/main/java/org/springblade/modules/words/NumHelper.java                                           |  188 +
 src/main/java/org/springblade/modules/words/StringMatch.java                                         |  291 ++
 src/main/java/org/springblade/modules/words/internals/BaseSearch.java                                |  119 
 src/main/java/org/springblade/modules/property/controller/PropertyCompanyCommentController.java      |    8 
 src/main/java/org/springblade/modules/words/StringSearchEx.java                                      |  174 +
 src/main/java/org/springblade/modules/words/WordsHelper.java                                         |  284 ++
 src/main/java/org/springblade/modules/words/internals/TrieNode2.java                                 |   50 
 src/main/java/org/springblade/modules/words/PinyinMatch.java                                         |  332 ++
 src/main/java/org/springblade/modules/words/IllegalWordsSearchResult.java                            |   31 
 src/main/java/org/springblade/modules/words/WordsSearchResult.java                                   |   33 
 src/main/java/org/springblade/modules/words/DemoApplication.java                                     | 1082 +++++++
 src/main/java/org/springblade/modules/words/sensitiveword/vo/SensitivewordVO.java                    |   36 
 src/main/java/org/springblade/modules/words/StringMatchEx.java                                       |  346 ++
 src/main/java/org/springblade/modules/words/internals/PinyinDict.java                                |  277 ++
 src/main/java/org/springblade/modules/words/sensitiveword/dto/SensitivewordDTO.java                  |   34 
 src/main/java/org/springblade/modules/property/entity/PropertyCompanyCommentEntity.java              |    4 
 src/main/java/org/springblade/modules/words/WorksService.java                                        |   61 
 src/main/java/org/springblade/modules/property/service/impl/PropertyCompanyCommentServiceImpl.java   |   39 
 src/main/java/org/springblade/modules/words/StringSearchEx2.java                                     |  164 +
 src/main/java/org/springblade/modules/words/IllegalWordsSearch.java                                  |  636 ++++
 src/main/java/org/springblade/modules/words/WordsSearchEx.java                                       |  178 +
 src/main/java/org/springblade/modules/words/internals/TwoTuple.java                                  |   11 
 src/main/java/org/springblade/modules/words/internals/BasePinyinMatch.java                           |  269 +
 src/main/java/org/springblade/modules/words/sensitiveword/controller/SensitivewordController.java    |  124 
 src/main/java/org/springblade/modules/words/sensitiveword/mapper/SensitivewordMapper.xml             |   21 
 src/main/java/org/springblade/modules/words/WordsMatch.java                                          |  304 ++
 src/main/java/org/springblade/modules/words/PinyinMatch2.java                                        |  188 +
 src/main/java/org/springblade/modules/words/internals/BaseMatchEx.java                               |  192 +
 src/main/java/org/springblade/modules/words/sensitiveword/mapper/SensitivewordMapper.java            |   48 
 src/main/java/org/springblade/modules/words/internals/BaseSearchEx2.java                             |  305 ++
 src/main/java/org/springblade/modules/words/internals/BaseSearchEx.java                              |  347 ++
 src/main/java/org/springblade/modules/words/internals/IntDictionary.java                             |   78 
 src/main/java/org/springblade/modules/words/StringSearch.java                                        |  151 +
 src/main/java/org/springblade/modules/words/WordsMatchEx.java                                        |  358 ++
 src/main/java/org/springblade/modules/words/internals/TrieNode.java                                  |   51 
 src/main/java/org/springblade/modules/words/internals/TrieNode3Ex.java                               |   53 
 src/main/java/org/springblade/modules/words/internals/TrieNode2Ex.java                               |   51 
 47 files changed, 8,243 insertions(+), 10 deletions(-)

diff --git a/src/main/java/org/springblade/modules/property/controller/PropertyCompanyCommentController.java b/src/main/java/org/springblade/modules/property/controller/PropertyCompanyCommentController.java
index be85a7a..0570bb7 100644
--- a/src/main/java/org/springblade/modules/property/controller/PropertyCompanyCommentController.java
+++ b/src/main/java/org/springblade/modules/property/controller/PropertyCompanyCommentController.java
@@ -7,6 +7,8 @@
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import lombok.AllArgsConstructor;
 import javax.validation.Valid;
+
+import org.apache.logging.log4j.util.Strings;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.secure.utils.AuthUtil;
@@ -94,7 +96,11 @@
 	@ApiOperation(value = "新增", notes = "传入propertyCompanyComment")
 	public R saveOrUpdate(@Valid @RequestBody PropertyCompanyCommentVO propertyCompanyComment) {
 		propertyCompanyComment.setCreateUser(AuthUtil.getUserId());
-		return R.status(propertyCompanyCommentService.saveOrUpdatePropertyCompanyComment(propertyCompanyComment));
+		String msg = propertyCompanyCommentService.saveOrUpdatePropertyCompanyComment(propertyCompanyComment);
+		if (!Strings.isBlank(msg)){
+			return R.data(201,"",msg);
+		}
+		return R.data(200,"",msg);
 	}
 
 	/**
diff --git a/src/main/java/org/springblade/modules/property/entity/PropertyCompanyCommentEntity.java b/src/main/java/org/springblade/modules/property/entity/PropertyCompanyCommentEntity.java
index f891f9a..92f1605 100644
--- a/src/main/java/org/springblade/modules/property/entity/PropertyCompanyCommentEntity.java
+++ b/src/main/java/org/springblade/modules/property/entity/PropertyCompanyCommentEntity.java
@@ -65,9 +65,9 @@
 	@ApiModelProperty(value = "审核时间")
 	private Date checkTime;
 	/**
-	 * 审核状态
+	 * 审核状态 1:待审核 2:审核通过 3:审核不通过
 	 */
-	@ApiModelProperty(value = "审核状态")
+	@ApiModelProperty(value = "审核状态 1:待审核 2:审核通过 3:审核不通过")
 	private Integer checkStatus;
 	/**
 	 * 审核备注
diff --git a/src/main/java/org/springblade/modules/property/service/IPropertyCompanyCommentService.java b/src/main/java/org/springblade/modules/property/service/IPropertyCompanyCommentService.java
index 60e745c..fd38e90 100644
--- a/src/main/java/org/springblade/modules/property/service/IPropertyCompanyCommentService.java
+++ b/src/main/java/org/springblade/modules/property/service/IPropertyCompanyCommentService.java
@@ -43,5 +43,5 @@
 	/**
 	 * 物业公司评论表 自定义新增或修改
 	 */
-    boolean saveOrUpdatePropertyCompanyComment(PropertyCompanyCommentVO propertyCompanyComment);
+	String saveOrUpdatePropertyCompanyComment(PropertyCompanyCommentVO propertyCompanyComment);
 }
diff --git a/src/main/java/org/springblade/modules/property/service/impl/PropertyCompanyCommentServiceImpl.java b/src/main/java/org/springblade/modules/property/service/impl/PropertyCompanyCommentServiceImpl.java
index 43e140b..51c22f9 100644
--- a/src/main/java/org/springblade/modules/property/service/impl/PropertyCompanyCommentServiceImpl.java
+++ b/src/main/java/org/springblade/modules/property/service/impl/PropertyCompanyCommentServiceImpl.java
@@ -19,6 +19,7 @@
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.apache.logging.log4j.util.Strings;
+import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.modules.property.entity.PropertyCompanyCommentEntity;
 import org.springblade.modules.property.entity.PropertyCompanyScoreEntity;
@@ -27,10 +28,13 @@
 import org.springblade.modules.property.mapper.PropertyCompanyCommentMapper;
 import org.springblade.modules.property.service.IPropertyCompanyCommentService;
 import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.modules.words.WorksService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Map;
 
 /**
  * 物业公司评论表 服务实现类
@@ -43,6 +47,9 @@
 
 	@Autowired
 	private IPropertyCompanyScoreService propertyCompanyScoreService;
+
+	@Autowired
+	private WorksService worksService;
 
 	@Override
 	public IPage<PropertyCompanyCommentVO> selectPropertyCompanyCommentPage(IPage<PropertyCompanyCommentVO> page, PropertyCompanyCommentVO propertyCompanyComment) {
@@ -58,12 +65,32 @@
 	 */
 	@Override
 	@Transactional(rollbackFor = Exception.class)
-	public boolean saveOrUpdatePropertyCompanyComment(PropertyCompanyCommentVO propertyCompanyComment) {
-		boolean save = save(propertyCompanyComment);
-		// 更新评分
-		updatePropertyCompanyScore(propertyCompanyComment);
-		// 返回
-		return save;
+	public String saveOrUpdatePropertyCompanyComment(PropertyCompanyCommentVO propertyCompanyComment) {
+		boolean flag = false;
+		// 先进行敏感词过滤
+		Map<String, Object> map = worksService.interceptWords(propertyCompanyComment.getContent());
+		// 获取敏感词校验结果
+		String iswords = map.get("iswords").toString();
+		if (iswords.equals("false")){
+			// 审核通过
+			propertyCompanyComment.setCheckStatus(2);
+			flag = save(propertyCompanyComment);
+			// 更新评分
+			if(flag) {
+				updatePropertyCompanyScore(propertyCompanyComment);
+			}
+			// 返回
+			return "操作成功";
+		}else {
+			// 审核不通过
+			propertyCompanyComment.setCheckStatus(3);
+			// 设置审核不通过的说明
+			propertyCompanyComment.setCheckRemark(map.get("words").toString());
+			// 保存
+			save(propertyCompanyComment);
+			// 返回
+			return "当前评论内容中带有敏感词,当前评论无法生效!";
+		}
 	}
 
 	/**
diff --git a/src/main/java/org/springblade/modules/words/DemoApplication.java b/src/main/java/org/springblade/modules/words/DemoApplication.java
new file mode 100644
index 0000000..3fb5f91
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/DemoApplication.java
@@ -0,0 +1,1082 @@
+package org.springblade.modules.words;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.util.StopWatch;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+public class DemoApplication {
+
+	public static void main(String[] args) throws Exception {
+
+//		test_StringSearch();
+//		test_WordsSearch();
+
+		Map content = new HashMap();
+		content = interceptWords("nihoahsodahioda日本人,大萨达");
+		System.out.println(content);
+//		test_StringSearchEx();
+//		test_WordsSearchEx();
+//
+//		test_StringSearchEx2();
+//		test_WordsSearchEx2();
+//		test_IllegalWordsSearch();
+//
+//		test_StringMatch();
+//		test_WordsMatch();
+//
+//		test_StringMatchEx();
+//		test_WordsMatchEx();
+//
+//		test_PinyinMatch();
+//		test_PinyinMatch2();
+//
+//		test_Pinyin();
+//		test_words();
+
+		// try {
+		// 	test_save_load();
+		// 	test_IllegalWordsSearch_loadWordsFormBinaryFile();
+		// } catch (Exception e) {
+		// 	e.printStackTrace();
+		// }
+		// test_times();
+
+//		test_issues_54();
+//		test_issues_57();
+//		test_issues_57_2();
+//		test_issues_57_3();
+//		test_issues_65();
+//		test_issues_74();
+	}
+
+	public static Map interceptWords(String content) {
+		List<String> list = new ArrayList<String>();
+		list.add("美国");
+		list.add("日本");
+		StringSearch iwords = new StringSearch();
+		iwords.SetKeywords(list);
+
+		Map res = new HashMap();
+
+		boolean b = iwords.ContainsAny(content);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		res.put("iswords",String.valueOf(b));
+
+		String str = iwords.Replace(content, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+		res.put("content",str);
+
+		String text = "";
+		List<String> all = iwords.FindAll(content);
+		for (int i = 0; i < all.size(); i++) {
+			text += all.get(i) + ",";
+		}
+		String words = "";
+		if(!text.equals("")){
+			words = text.substring(0,text.length()-1);
+		}
+
+
+		res.put("words",words);
+
+		return res;
+	}
+
+	public static void test_StringSearch() {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("中国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("StringSearch run Test.");
+
+		StringSearch iwords = new StringSearch();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		String f = iwords.FindFirst(test);
+		if (f != "中国") {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<String> all = iwords.FindAll(test);
+		if (all.get(0) != "中国") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1) != "国人") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_StringSearchEx() {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("中国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("StringSearchEx run Test.");
+
+		StringSearchEx iwords = new StringSearchEx();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		String f = iwords.FindFirst(test);
+		if (f != "中国") {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<String> all = iwords.FindAll(test);
+		if (all.get(0) != "中国") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1) != "国人") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_StringSearchEx2() {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("中国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("StringSearchEx2 run Test.");
+
+		StringSearchEx2 iwords = new StringSearchEx2();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		String f = iwords.FindFirst(test);
+		if (f != "中国") {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<String> all = iwords.FindAll(test);
+		if (all.get(0) != "中国") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1) != "国人") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_WordsSearch() {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("中国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("WordsSearch run Test.");
+
+		WordsSearch iwords = new WordsSearch();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		WordsSearchResult f = iwords.FindFirst(test);
+		if (f.Keyword != "中国") {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<WordsSearchResult> all = iwords.FindAll(test);
+		if (all.get(0).Keyword != "中国") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1).Keyword != "国人") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_WordsSearchEx() throws IOException {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("中国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("WordsSearchEx run Test.");
+
+		WordsSearchEx iwords2 = new WordsSearchEx();
+		iwords2.SetKeywords(list);
+		iwords2.Save("WordsSearchEx.dat");
+
+		WordsSearchEx iwords = new WordsSearchEx();
+		iwords.Load("WordsSearchEx.dat");
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		WordsSearchResult f = iwords.FindFirst(test);
+		if (f.Keyword.equals("中国") == false) {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<WordsSearchResult> all = iwords.FindAll(test);
+		if (all.get(0).Keyword.equals("中国") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1).Keyword.equals("国人") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_WordsSearchEx2() {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("中国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("WordsSearchEx2 run Test.");
+
+		WordsSearchEx2 iwords = new WordsSearchEx2();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		WordsSearchResult f = iwords.FindFirst(test);
+		if (f.Keyword != "中国") {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<WordsSearchResult> all = iwords.FindAll(test);
+		if (all.get(0).Keyword != "中国") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1).Keyword != "国人") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_IllegalWordsSearch() {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("中国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("IllegalWordsSearch run Test.");
+
+		IllegalWordsSearch iwords = new IllegalWordsSearch();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		IllegalWordsSearchResult f = iwords.FindFirst(test);
+		if (f.Keyword.equals("中国") == false) {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<IllegalWordsSearchResult> all = iwords.FindAll(test);
+		if (all.get(0).Keyword.equals("中国") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1).Keyword.equals("国人") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_StringMatch() throws Exception {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("[中美]国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("StringMatch run Test.");
+
+		StringMatch iwords = new StringMatch();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		String f = iwords.FindFirst(test);
+		if (!f.equals("中国")) {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<String> all = iwords.FindAll(test);
+		if (!all.get(0).equals("中国")) {
+			System.out.println("FindAll is Error.");
+		}
+		if (!all.get(1).equals("国人")) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_StringMatchEx() throws Exception {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("[中美]国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("StringMatchEx run Test.");
+
+		StringMatchEx iwords = new StringMatchEx();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		String f = iwords.FindFirst(test);
+		if (!f.equals("中国")) {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<String> all = iwords.FindAll(test);
+		if (!all.get(0).equals("中国")) {
+			System.out.println("FindAll is Error.");
+		}
+		if (!all.get(1).equals("国人")) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_WordsMatch() throws Exception {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("[中美]国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("WordsMatch run Test.");
+
+		WordsMatch iwords = new WordsMatch();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		WordsSearchResult f = iwords.FindFirst(test);
+		if (f.Keyword.equals("中国") == false) {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<WordsSearchResult> all = iwords.FindAll(test);
+		if (all.get(0).Keyword.equals("中国") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1).Keyword.equals("国人") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_WordsMatchEx() throws Exception {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("[中美]国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("WordsMatchEx run Test.");
+
+		WordsMatchEx iwords = new WordsMatchEx();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		WordsSearchResult f = iwords.FindFirst(test);
+		if (f.Keyword.equals("中国") == false) {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<WordsSearchResult> all = iwords.FindAll(test);
+		if (all.get(0).Keyword.equals("中国") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1).Keyword.equals("国人") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_PinyinMatch() throws NumberFormatException, IOException {
+		String s = "北京|天津|河北|辽宁|吉林|黑龙江|山东|江苏|上海|浙江|安徽|福建|江西|广东|广西|海南|河南|湖南|湖北|山西|内蒙古|宁夏|青海|陕西|甘肃|新疆|四川|贵州|云南|重庆|西藏|香港|澳门|台湾";
+		List<String> list = new ArrayList<String>();
+		String[] ss = s.split("\\|");
+		for (String st : ss) {
+			list.add(st);
+		}
+		PinyinMatch match = new PinyinMatch();
+		match.SetKeywords(list);
+		System.out.println("PinyinMatch run Test.");
+
+		List<String> all = match.Find("BJ");
+		if (all.get(0).equals("北京") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+
+		all = match.Find("北J");
+		if (all.get(0).equals("北京") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+
+		all = match.Find("北Ji");
+		if (all.get(0).equals("北京") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+		all = match.Find("Su");
+		if (all.get(0).equals("江苏") == false) {
+			System.out.println("Find is Error.");
+		}
+
+		all = match.Find("Sdon");
+		if (all.get(0).equals("山东") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+		all = match.Find("S东");
+		if (all.get(0).equals("山东") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+
+		List<Integer> all2 = match.FindIndex("BJ");
+		if (all2.get(0) != 0) {
+			System.out.println("FindIndex is Error.");
+		}
+		if (all2.size() != 1) {
+			System.out.println("FindIndex is Error.");
+		}
+
+		all = match.FindWithSpace("S 东");
+		if (all.get(0).equals("山东") == false) {
+			System.out.println("FindWithSpace is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("FindWithSpace is Error.");
+		}
+
+		all = match.FindWithSpace("h 江");
+		if (all.get(0).equals("黑龙江") == false) {
+			System.out.println("FindWithSpace is Error.");
+		}
+
+		all2 = match.FindIndexWithSpace("B J");
+		if (all2.get(0) != 0) {
+			System.out.println("FindIndexWithSpace is Error.");
+		}
+		if (all2.size() != 1) {
+			System.out.println("FindIndexWithSpace is Error.");
+		}
+
+		all = match.FindWithSpace("京 北");
+		if (all.size() != 0) {
+			System.out.println("FindWithSpace is Error.");
+		}
+
+		all = match.FindWithSpace("黑龙 龙江");
+		if (all.size() != 0) {
+			System.out.println("FindWithSpace is Error.");
+		}
+
+		all = match.FindWithSpace("黑龙 江");
+		if (all.get(0).equals("黑龙江") == false) {
+			System.out.println("FindWithSpace is Error.");
+		}
+		all = match.FindWithSpace("黑 龙 江");
+		if (all.get(0).equals("黑龙江") == false) {
+			System.out.println("FindWithSpace is Error.");
+		}
+	}
+
+	private static void test_PinyinMatch2() throws Exception {
+		String s = "北京|天津|河北|辽宁|吉林|黑龙江|山东|江苏|上海|浙江|安徽|福建|江西|广东|广西|海南|河南|湖南|湖北|山西|内蒙古|宁夏|青海|陕西|甘肃|新疆|四川|贵州|云南|重庆|西藏|香港|澳门|台湾";
+		List<String> list = new ArrayList<String>();
+		String[] ss = s.split("\\|");
+		for (String st : ss) {
+			list.add(st);
+		}
+		PinyinMatch2<String> match = new PinyinMatch2<String>(list);
+		match.SetKeywordsFunc(new Function<String, String>() {
+			@Override
+			public String apply(String t) {
+				return t;
+			}
+		});
+
+		System.out.println("PinyinMatch2 run Test.");
+
+		List<String> all = match.Find("BJ");
+		if (all.get(0).equals("北京") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+
+		all = match.Find("北J");
+		if (all.get(0).equals("北京") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+
+		all = match.Find("北Ji");
+		if (all.get(0).equals("北京") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+		all = match.Find("Su");
+		if (all.get(0).equals("江苏") == false) {
+			System.out.println("Find is Error.");
+		}
+
+		all = match.Find("Sdon");
+		if (all.get(0).equals("山东") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+		all = match.Find("S东");
+		if (all.get(0).equals("山东") == false) {
+			System.out.println("Find is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("Find is Error.");
+		}
+
+		all = match.FindWithSpace("S 东");
+		if (all.get(0).equals("山东") == false) {
+			System.out.println("FindWithSpace is Error.");
+		}
+		if (all.size() != 1) {
+			System.out.println("FindWithSpace is Error.");
+		}
+
+		all = match.FindWithSpace("h 江");
+		if (all.get(0).equals("黑龙江") == false) {
+			System.out.println("FindWithSpace is Error.");
+		}
+
+		all = match.FindWithSpace("京 北");
+		if (all.size() != 0) {
+			System.out.println("FindWithSpace is Error.");
+		}
+
+		all = match.FindWithSpace("黑龙 龙江");
+		if (all.size() != 0) {
+			System.out.println("FindWithSpace is Error.");
+		}
+
+		all = match.FindWithSpace("黑龙 江");
+		if (all.get(0).equals("黑龙江") == false) {
+			System.out.println("FindWithSpace is Error.");
+		}
+		all = match.FindWithSpace("黑 龙 江");
+		if (all.get(0).equals("黑龙江") == false) {
+			System.out.println("FindWithSpace is Error.");
+		}
+	}
+
+	private static void test_save_load() throws IOException {
+		String test = "我是中国人";
+		List<String> list = new ArrayList<String>();
+		list.add("中国");
+		list.add("国人");
+		list.add("zg人");
+		System.out.println("test_save_load run Test.");
+
+		StringSearchEx2 search = new StringSearchEx2();
+		search.SetKeywords(list);
+		search.Save("1.dat");
+
+		StringSearchEx2 iwords = new StringSearchEx2();
+		iwords.Load("1.dat");
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		String f = iwords.FindFirst(test);
+		if (f != "中国") {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<String> all = iwords.FindAll(test);
+		if (all.get(0) != "中国") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1) != "国人") {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.size() != 2) {
+			System.out.println("FindAll is Error.");
+		}
+
+		String str = iwords.Replace(test, '*');
+		if (str.equals("我是***") == false) {
+			System.out.println("Replace is Error.");
+		}
+	}
+
+	private static void test_times() {
+		String ts = readLineByLineJava8("BadWord.txt");
+		String[] sp = ts.split("[\r\n]");
+		List<String> list = new ArrayList<String>();
+		for (String item : sp) {
+			list.add(item);
+		}
+		String words = readLineByLineJava8("Talk.txt");
+
+		StringSearchEx2 iwords = new StringSearchEx2();
+		iwords.SetKeywords(list);
+
+		StopWatch sw = new StopWatch();
+		sw.start("校验耗时");
+		for (int i = 0; i < 100000; i++) {
+			// iwords.ContainsAny(words);
+			iwords.FindAll(words);
+			// System.out.println(list2.size());
+		}
+		sw.stop();
+		System.out.println(sw.getTotalTimeMillis() + "ms");
+
+	}
+
+	private static String readLineByLineJava8(String filePath) {
+		StringBuilder contentBuilder = new StringBuilder();
+		try (Stream<String> stream = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8)) {
+			stream.forEach(s -> contentBuilder.append(s).append("\n"));
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return contentBuilder.toString();
+	}
+
+	private static void test_IllegalWordsSearch_loadWordsFormBinaryFile() throws IOException {
+
+		long l1 = System.currentTimeMillis();
+
+		IllegalWordsSearch search = new IllegalWordsSearch();
+		long l2 = System.currentTimeMillis();
+		System.out.println("IllegalWordsSearch init time:" + (l2 - l1));
+
+		search.Load(new ClassPathResource("IllegalWordsSearch.dat").getFile().getAbsolutePath());
+		long l3 = System.currentTimeMillis();
+		System.out.println("load Load time:" + (l3 - l2));
+
+		String test = "卖毒品哈哈哈哈毛澤東porn哈哈哈哈胡锦涛pornasds哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛胡锦涛撒旦撒旦pornporn哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛porn"
+				+ "哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛porn哈哈哈哈胡錦濤porn哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛porn"
+				+ "哈哈哈哈胡锦涛porn哈哈哈哈胡锦涛porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn"
+				+ "哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn"
+				+ "哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn"
+				+ "哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn"
+				+ "哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn哈哈哈哈或porn";
+
+		boolean b = search.ContainsAny(test);
+		if (!b) {
+			System.out.println("ContainsAny is Error.");
+		}
+		long l4 = System.currentTimeMillis();
+		System.out.println("ContainsAny time:" + (l4 - l3));
+
+		String str = search.Replace(test, '*');
+		long l5 = System.currentTimeMillis();
+		System.out.println("Replace Result:" + str);
+		System.out.println("Replace time:" + (l5 - l4));
+	}
+
+	private static void test_IllegalWordsSearch_saveToBinaryFile() throws IOException {
+		List<String> list = new ArrayList<>();
+		try (BufferedReader bufferedReader = new BufferedReader(
+				new InputStreamReader(new ClassPathResource("sensi_words.txt").getInputStream()))) {
+			for (String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) {
+				list.add(line);
+			}
+		}
+		IllegalWordsSearch search = new IllegalWordsSearch();
+		search.SetKeywords(list);
+		search.Save("IllegalWordsSearch.dat");
+	}
+
+	private static void test_Pinyin() throws NumberFormatException, IOException {
+		System.out.println("text_Pinyin run Test.");
+		List<String> t = WordsHelper.GetAllPinyin('芃');
+		if (t.get(0).equals("Peng") == false) {
+			System.out.println("GetAllPinyin is Error.");
+		}
+
+		String a = WordsHelper.GetPinyinFast("阿");
+		if (a.equals("A") == false) {
+			System.out.println("GetPinyinFast is Error.");
+		}
+
+		String b = WordsHelper.GetPinyin("摩擦棒");
+		if (b.equals("MoCaBang") == false) {
+			System.out.println("GetPinyin is Error.");
+		}
+		b = WordsHelper.GetPinyin("秘鲁");
+		if (b.equals("BiLu") == false) {
+			System.out.println("GetPinyin is Error.");
+		}
+
+		String py = WordsHelper.GetPinyinFast("我爱中国");
+		if (py.equals("WoAiZhongGuo") == false) {
+			System.out.println("GetPinyinFast is Error.");
+		}
+
+		py = WordsHelper.GetPinyin("快乐,乐清");
+		if (py.equals("KuaiLe,YueQing") == false) {
+			System.out.println("GetPinyin is Error.");
+		}
+
+		py = WordsHelper.GetPinyin("快乐清理");
+		if (py.equals("KuaiLeQingLi") == false) {
+			System.out.println("GetPinyin is Error.");
+		}
+
+		py = WordsHelper.GetPinyin("我爱中国", true);
+		if (py.equals("WǒÀiZhōngGuó") == false) {
+			System.out.println("GetPinyin is Error.");
+		}
+
+		py = WordsHelper.GetFirstPinyin("我爱中国");
+		if (py.equals("WAZG") == false) {
+			System.out.println("GetPinyin is Error.");
+		}
+
+		List<String> pys = WordsHelper.GetAllPinyin('传');
+		if (pys.get(0).equals("Chuan") == false) {
+			System.out.println("GetAllPinyin is Error.");
+		}
+		if (pys.get(1).equals("Zhuan") == false) {
+			System.out.println("GetAllPinyin is Error.");
+		}
+
+		py = WordsHelper.GetPinyinForName("单一一");
+		if (py.equals("ShanYiYi") == false) {
+			System.out.println("GetPinyinForName is Error.");
+		}
+
+		py = WordsHelper.GetPinyinForName("单一一", true);
+		if (py.equals("ShànYīYī") == false) {
+			System.out.println("GetPinyinForName is Error.");
+		}
+
+		List<String> all = WordsHelper.GetAllPinyin('石');
+		if (all.size() == 0) {
+			System.out.println("GetAllPinyin is Error.");
+		}
+
+	}
+
+	private static void test_words() throws Exception {
+		System.out.println("test_words run Test.");
+		String s = WordsHelper.ToSimplifiedChinese("壹佰贰拾叁億肆仟伍佰陆拾柒萬捌仟玖佰零壹元壹角贰分");
+		if (s.equals("壹佰贰拾叁亿肆仟伍佰陆拾柒万捌仟玖佰零壹元壹角贰分") == false) {
+			System.out.println("ToSimplifiedChinese is Error.");
+		}
+
+		String tw = WordsHelper.ToTraditionalChinese("壹佰贰拾叁亿肆仟伍佰陆拾柒万捌仟玖佰零壹元壹角贰分");
+		if (tw.equals("壹佰貳拾叄億肆仟伍佰陸拾柒萬捌仟玖佰零壹元壹角貳分") == false) {
+			System.out.println("ToTraditionalChinese is Error.");
+		}
+
+		String tw2 = WordsHelper.ToTraditionalChinese("原代码11", 2);
+		if (tw2.equals("原始碼11") == false) {
+			System.out.println("ToTraditionalChinese is Error.");
+		}
+
+		String tw3 = WordsHelper.ToTraditionalChinese("反反复复", 2);
+		if (tw3.equals("反反覆覆") == false) {
+			System.out.println("ToTraditionalChinese is Error.");
+		}
+
+		String tw4 = WordsHelper.ToTraditionalChinese("这人考虑事情总是反反复复的", 2);
+		if (tw4.equals("這人考慮事情總是反反覆覆的") == false) {
+			System.out.println("ToTraditionalChinese is Error.");
+		}
+
+	}
+
+	public static void test_issues_54() {
+		IllegalWordsSearch search = new IllegalWordsSearch();
+		search.SetKeywords(Arrays.asList("test", "world", "this", "hello", "monster"));
+		String result = search.Replace("test, hahaha, this is a hello world", '*');
+		if (result.equals("****, hahaha, **** is a ***** *****") == false) {
+			System.out.println("IllegalWordsSearch Replace is Error.");
+		}
+	}
+	public static void test_issues_57(){
+		String test = "一,二二,三三三,四四四四,五五五五五,六六六六六六";
+		List<String> list = new ArrayList<String>();
+		list.add("一");
+		list.add("二二");
+		list.add("三三三");
+		list.add("四四四四");
+		list.add("五五五五五");
+		list.add("六六六六六六");
+		System.out.println("test_issues_57 run Test.");
+
+		IllegalWordsSearch iwords = new IllegalWordsSearch();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		IllegalWordsSearchResult f = iwords.FindFirst(test);
+		if (f.Keyword.equals("一") == false) {
+			System.out.println("FindFirst is Error.");
+		}
+
+		List<IllegalWordsSearchResult> all = iwords.FindAll(test);
+		if (all.get(0).Keyword.equals("一") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(1).Keyword.equals("二二") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(2).Keyword.equals("三三三") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(3).Keyword.equals("四四四四") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(4).Keyword.equals("五五五五五") == false) {
+			System.out.println("FindAll is Error.");
+		}
+		if (all.get(5).Keyword.equals("六六六六六六") == false) {
+			System.out.println("FindAll is Error.");
+		}
+	}
+
+	public static void test_issues_57_2(){
+        String test = "jameson吃饭";
+		List<String> list = new ArrayList<String>();
+		list.add("jameson吃饭");
+		list.add("吃饭jameson");
+		System.out.println("test_issues_57_2 run Test.");
+
+		IllegalWordsSearch iwords = new IllegalWordsSearch();
+		iwords.SetKeywords(list);
+
+		boolean b = iwords.ContainsAny(test);
+		if (b == false) {
+			System.out.println("ContainsAny is Error.");
+		}
+
+		IllegalWordsSearchResult f = iwords.FindFirst(test);
+		if (f.Keyword.equals("jameson吃饭") == false) {
+			System.out.println("FindFirst is Error.");
+		}
+	}
+	public static void test_issues_57_3(){
+		String test = "his is sha ash";
+        List<String> list = new ArrayList<String>();
+        list.add("ash");
+        list.add("sha");
+        list.add("bcd");
+        System.out.println("test_issues_57_3 run Test.");
+
+        IllegalWordsSearch iwords = new IllegalWordsSearch();
+        iwords.SetKeywords(list);
+
+        boolean b = iwords.ContainsAny(test);
+        if (b == false) {
+            System.out.println("ContainsAny is Error.");
+        }
+
+        IllegalWordsSearchResult f = iwords.FindFirst(test);
+        if (f == null || f.Keyword.equals("sha") == false) {
+            System.out.println("FindFirst is Error.");
+        }
+	}
+	public static void test_issues_65(){
+		String test = "fFuck";
+        List<String> list = new ArrayList<String>();
+        list.add("fuck");
+        list.add("ffx");
+        list.add("bcd");
+        System.out.println("test_issues_65 run Test.");
+
+        IllegalWordsSearch iwords = new IllegalWordsSearch();
+        iwords.SetKeywords(list);
+
+        boolean b = iwords.ContainsAny(test);
+        if (b == false) {
+            System.out.println("ContainsAny is Error.");
+        }
+
+        String f = iwords.Replace(test);
+        if (f == null || f.equals("*****") == false) {
+            System.out.println("Replace is Error.");
+        }
+	}
+
+	public static void test_issues_74(){
+        List<String> list =loadKeywords(new File("sensi_words.txt"));
+        System.out.println("test_issues_74 run Test.");
+
+		IllegalWordsSearch iwords = new IllegalWordsSearch();
+        iwords.SetKeywords(list);
+		String test = "机机歪歪";
+
+		boolean b = iwords.ContainsAny(test);
+		if (b==false) {
+            System.out.println("ContainsAny is Error.");
+		}
+	}
+
+	public static List<String> loadKeywords(File file){
+		List<String> keyArray=new ArrayList<String>();
+		try{
+			BufferedReader br = new BufferedReader(new FileReader(file));//构造一个BufferedReader类来读取文件
+			String s = null;
+			while((s = br.readLine())!=null){//使用readLine方法,一次读一行
+				keyArray.add(s);
+			}
+			br.close();
+		}catch(Exception e){
+			e.printStackTrace();
+		}
+		return keyArray;
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/IllegalWordsSearch.java b/src/main/java/org/springblade/modules/words/IllegalWordsSearch.java
new file mode 100644
index 0000000..1b0f8e9
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/IllegalWordsSearch.java
@@ -0,0 +1,636 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseSearchEx;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * 最新版本的IllegalWordsSearch, 与2020.05.24以前的版本不兼容, IllegalWordsSearch类太费精力了,头发稀疏了。
+ * 我未来可能以敏感词过滤做为创业项目,所以这是最后的开源版本,不再免费补bug了。
+ * IllegalWordsSearch修复了2020-10-8日前所有bug。
+ */
+@Deprecated
+public class IllegalWordsSearch extends BaseSearchEx {
+    public class SkipWordFilterHandler {
+        public char c;
+        public String text;
+        public int index;
+
+        public SkipWordFilterHandler(final char c, final String text, final int index) {
+            this.c = c;
+            this.text = text;
+            this.index = index;
+        }
+    }
+
+    public class CharTranslateHandler {
+        public char c;
+        public String text;
+        public int index;
+
+        public CharTranslateHandler(final char c, final String text, final int index) {
+            this.c = c;
+            this.text = text;
+            this.index = index;
+        }
+    }
+
+    public class StringMatchHandler {
+        public String text;
+        public int start;
+        public int end;
+        public String keyword;
+        public int keywordIndex;
+        public String matchKeyword;
+        public int blacklistIndex;
+
+        public StringMatchHandler(final String text, final int start, final int end, final String keyword,
+                final int keywordIndex, final String matchKeyword, final int blacklistIndex) {
+            this.text = text;
+            this.start = start;
+            this.end = end;
+            this.keyword = keyword;
+            this.keywordIndex = keywordIndex;
+            this.matchKeyword = matchKeyword;
+            this.blacklistIndex = blacklistIndex;
+        }
+    }
+
+    /**
+     * 使用跳词过滤器,默认使用
+     */
+    public boolean UseSkipWordFilter = true;
+    private final String _skipList = " \t\r\n~!@#$%^&*()_+-=【】、[]{}|;"
+            + "':\",。、《》?αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩①②③④⑤⑥⑦⑧⑨⑩⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇≈≡≠=≤≥<>≮≯∷±+-×÷/∫∮∝∞∧∨∑∏∪∩∈∵∴⊥∥∠⌒⊙≌∽√§№☆★○●◎◇◆□℃‰€■△▲※→←↑↓〓¤°#&@\︿_ ̄―♂♀┌┍┎┐┑┒┓─┄┈├┝┞┟┠┡┢┣│┆┊┬┭┮┯┰┱┲┳┼┽┾┿╀╁╂╃└┕┖┗┘┙┚┛━┅┉┤┥┦┧┨┩┪┫┃┇┋┴┵┶┷┸┹┺┻╋╊╉╈╇╆╅╄";
+    private boolean[] _skipBitArray;
+
+    /** 过滤跳词 */
+    public Function<SkipWordFilterHandler, Boolean> SkipWordFilter;
+    /**
+     * 字符转化,可以设置繁简转化、忽略大小写,启用后UseIgnoreCase开启无效
+     * 若想使用CharTranslateHandler,请先添加事件CharTranslateHandler, 再用SetKeywords设置关键字
+     */
+    public Function<CharTranslateHandler, Character> CharTranslate;
+
+    /**
+     * 自定义字符串匹配
+     */
+    public Function<StringMatchHandler, Boolean> StringMatch;
+
+    /**
+     * 使用重复词过滤器
+     */
+    public boolean UseDuplicateWordFilter = true;
+    /**
+     * 使用黑名单过滤器
+     */
+    private int[] _blacklist = new int[0];
+    /**
+     * 使用半角转化器
+     */
+    public boolean UseDBCcaseConverter = true;
+    /**
+     * 使用忽略大小写
+     */
+    public boolean UseIgnoreCase = true;
+
+    /**
+     * 最新版本的IllegalWordsSearch, 与2020.05.24以前的版本不兼容, IllegalWordsSearch类太费精力了,头发稀疏了。
+     * 我未来可能以敏感词过滤做为创业项目,所以这是最后的开源版本,不再免费补bug了。
+     * IllegalWordsSearch修复了2020-10-8日前所有bug。
+     */
+    public IllegalWordsSearch() {
+        _skipBitArray = new boolean[Character.MAX_VALUE + 1];
+        for (int i = 0; i < _skipList.length(); i++) {
+            _skipBitArray[_skipList.charAt(i)] = true;
+        }
+        SkipWordFilter = null;
+        CharTranslate = null;
+        StringMatch = null;
+    }
+
+    /**
+     * 设置跳词
+     *
+     * @param skipList
+     */
+    public void SetSkipWords(final String skipList) {
+
+        _skipBitArray = new boolean[Character.MAX_VALUE + 1];
+        if (skipList != null) {
+            for (int i = 0; i < skipList.length(); i++) {
+                _skipBitArray[skipList.charAt(i)] = true;
+            }
+        }
+    }
+
+    /**
+     * 设置关键字 如果想使用CharTranslateHandler,请先添加事件CharTranslateHandler,
+     * 再用SetKeywords设置关键字 使用CharTranslateHandler后,UseIgnoreCase配置无效
+     * 如果不使用忽略大小写,请先UseIgnoreCase设置为false,再用SetKeywords设置关键字
+     *
+     * @param keywords
+     */
+    public void SetKeywords(final List<String> keywords) {
+        if (CharTranslate != null) {
+            final Set<String> kws = new HashSet<String>(keywords);
+            final List<String> list = new ArrayList<String>();
+            for (final String item : kws) {
+                final StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < item.length(); i++) {
+                    final char c = CharTranslate.apply(new CharTranslateHandler(item.charAt(i), item, i));
+                    sb.append(c);
+                }
+                list.add(sb.toString());
+            }
+            super.SetKeywords(list);
+        } else if (UseDBCcaseConverter || UseIgnoreCase) {
+            final Set<String> kws = new HashSet<String>(keywords);
+            final List<String> list = new ArrayList<String>();
+            for (final String item : kws) {
+                list.add(ToSenseWord(item));
+            }
+            super.SetKeywords(list);
+        } else {
+            super.SetKeywords(keywords);
+        }
+    }
+
+    protected void Save(final FileOutputStream bw) throws IOException {
+        super.Save(bw);
+
+        bw.write(UseSkipWordFilter ? 1 : 0);
+        bw.write(NumHelper.serialize(_skipBitArray.length));
+        for (final boolean item : _skipBitArray) {
+            bw.write(item ? 1 : 0);
+        }
+
+        bw.write(UseDuplicateWordFilter ? 1 : 0);
+        bw.write(NumHelper.serialize(_blacklist.length));
+        for (final int item : _blacklist) {
+            bw.write(NumHelper.serialize(item));
+        }
+
+        bw.write(UseDBCcaseConverter ? 1 : 0);
+        bw.write(UseIgnoreCase ? 1 : 0);
+    }
+
+    public void Load(final InputStream br) throws IOException {
+        super.Load(br);
+
+        UseSkipWordFilter = br.read() > 0;
+        int length = NumHelper.read(br);
+        _skipBitArray = new boolean[length];
+        for (int i = 0; i < length; i++) {
+            _skipBitArray[i] = br.read() > 0;
+        }
+
+        UseDuplicateWordFilter = br.read() > 0;
+        length = NumHelper.read(br);
+        _blacklist = new int[length];
+        for (int i = 0; i < length; i++) {
+            _blacklist[i] = NumHelper.read(br);
+        }
+
+        UseDBCcaseConverter = br.read() > 0;
+        UseIgnoreCase = br.read() > 0;
+    }
+
+    /**
+     * 在文本中查找所有的关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public List<IllegalWordsSearchResult> FindAll(final String text) {
+        final List<IllegalWordsSearchResult> results = new ArrayList<IllegalWordsSearchResult>();
+        int p = 0;
+        char pChar = (char) 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            char t1 = text.charAt(i);
+            if (UseSkipWordFilter) {
+                if (SkipWordFilter != null) {// 跳词跳过
+                    if (SkipWordFilter.apply(new SkipWordFilterHandler(t1, text, i))) {
+                        continue;
+                    }
+                } else if (_skipBitArray[t1]) {
+                    continue;
+                }
+            }
+
+            if (CharTranslate != null) { // 字符串转换
+                t1 = CharTranslate.apply(new CharTranslateHandler(t1, text, i));
+            } else if (UseDBCcaseConverter || UseIgnoreCase) {
+                t1 = ToSenseWord(t1);
+            }
+            final int t = _dict[t1];
+            if (t == 0) {
+                pChar = t1;
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index > -1) {
+                    next = _nextIndex[p].GetValue(index);
+                } else if (UseDuplicateWordFilter && pChar == t1) {
+                    next = p;
+                } else {
+                    next = _first[t];
+                }
+            }
+
+            if (next != 0) {
+                if (_end[next] < _end[next + 1] && CheckNextChar(text, t1, i)) {
+                    for (int j = _end[next]; j < _end[next + 1]; j++) {
+                        final int index = _resultIndex[j];
+                        final IllegalWordsSearchResult r = GetGetIllegalResult(text, i, index);
+                        if (r != null) {
+                            results.add(r);
+                        }
+                    }
+                }
+            }
+            p = next;
+            pChar = t1;
+        }
+        return results;
+    }
+
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public IllegalWordsSearchResult FindFirst(final String text) {
+        int p = 0;
+        char pChar = (char) 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            char t1 = text.charAt(i);
+            if (UseSkipWordFilter) {
+                if (SkipWordFilter != null) {// 跳词跳过
+                    if (SkipWordFilter.apply(new SkipWordFilterHandler(t1, text, i))) {
+                        continue;
+                    }
+                } else if (_skipBitArray[t1]) {
+                    continue;
+                }
+            }
+
+            if (CharTranslate != null) { // 字符串转换
+                t1 = CharTranslate.apply(new CharTranslateHandler(t1, text, i));
+            } else if (UseDBCcaseConverter || UseIgnoreCase) {
+                t1 = ToSenseWord(t1);
+            }
+            final int t = _dict[t1];
+            if (t == 0) {
+                pChar = t1;
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index > -1) {
+                    next = _nextIndex[p].GetValue(index);
+                } else if (UseDuplicateWordFilter && pChar == t1) {
+                    next = p;
+                } else {
+                    next = _first[t];
+                }
+            }
+
+            if (next != 0) {
+                if (_end[next] < _end[next + 1] && CheckNextChar(text, t1, i)) {
+                    for (int j = _end[next]; j < _end[next + 1]; j++) {
+                        final int index = _resultIndex[j];
+                        final IllegalWordsSearchResult r = GetGetIllegalResult(text, i, index);
+                        if (r != null) {
+                            return r;
+                        }
+                    }
+                }
+            }
+            p = next;
+            pChar = t1;
+        }
+        return null;
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public boolean ContainsAny(final String text) {
+        int p = 0;
+        char pChar = (char) 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            char t1 = text.charAt(i);
+            if (UseSkipWordFilter) {
+                if (SkipWordFilter != null) {// 跳词跳过
+                    if (SkipWordFilter.apply(new SkipWordFilterHandler(t1, text, i))) {
+                        continue;
+                    }
+                } else if (_skipBitArray[t1]) {
+                    continue;
+                }
+            }
+
+            if (CharTranslate != null) { // 字符串转换
+                t1 = CharTranslate.apply(new CharTranslateHandler(t1, text, i));
+            } else if (UseDBCcaseConverter || UseIgnoreCase) {
+                t1 = ToSenseWord(t1);
+            }
+            final int t = _dict[t1];
+            if (t == 0) {
+                pChar = t1;
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index > -1) {
+                    next = _nextIndex[p].GetValue(index);
+                } else if (UseDuplicateWordFilter && pChar == t1) {
+                    next = p;
+                } else {
+                    next = _first[t];
+                }
+            }
+
+            if (next != 0) {
+                if (_end[next] < _end[next + 1] && CheckNextChar(text, t1, i)) {
+                    for (int j = _end[next]; j < _end[next + 1]; j++) {
+                        final int index = _resultIndex[j];
+                        final IllegalWordsSearchResult r = GetGetIllegalResult(text, i, index);
+                        if (r != null) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            p = next;
+            pChar = t1;
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 文本
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        int p = 0;
+        char pChar = (char) 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            char t1 = text.charAt(i);
+            if (UseSkipWordFilter) {
+                if (SkipWordFilter != null) {// 跳词跳过
+                    if (SkipWordFilter.apply(new SkipWordFilterHandler(t1, text, i))) {
+                        continue;
+                    }
+                } else if (_skipBitArray[t1]) {
+                    continue;
+                }
+            }
+
+            if (CharTranslate != null) { // 字符串转换
+                t1 = CharTranslate.apply(new CharTranslateHandler(t1, text, i));
+            } else if (UseDBCcaseConverter || UseIgnoreCase) {
+                t1 = ToSenseWord(t1);
+            }
+            final int t = _dict[t1];
+            if (t == 0) {
+                pChar = t1;
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index > -1) {
+                    next = _nextIndex[p].GetValue(index);
+                } else if (UseDuplicateWordFilter && pChar == t1) {
+                    next = p;
+                } else {
+                    next = _first[t];
+                }
+            }
+
+            if (next != 0) {
+                if (_end[next] < _end[next + 1] && CheckNextChar(text, t1, i)) {
+                    for (int j = _end[next]; j < _end[next + 1]; j++) {
+                        final int index = _resultIndex[j];
+                        final IllegalWordsSearchResult r = GetGetIllegalResult(text, i, index);
+                        if (r != null) {
+                            for (int k = r.Start; k <= r.End; k++) {
+                                result.setCharAt(k, replaceChar);
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+            p = next;
+            pChar = t1;
+        }
+        return result.toString();
+    }
+
+    private boolean CheckNextChar(final String text, final char c, final int end) {
+        if (IsEnglishOrNumber(c) == false) {
+            return true;
+        }
+        if (end + 1 < text.length()) {
+            char e1 = text.charAt(end + 1);
+            if (UseSkipWordFilter) {
+                if (SkipWordFilter != null) {// 跳词跳过
+                    if (SkipWordFilter.apply(new SkipWordFilterHandler(e1, text, end + 1))) {
+                        return true;
+                    }
+                } else if (_skipBitArray[e1]) {
+                    return true;
+                }
+            }
+            if (CharTranslate != null) { // 字符串转换
+                e1 = CharTranslate.apply(new CharTranslateHandler(e1, text, end + 1));
+            } else if (UseDBCcaseConverter || UseIgnoreCase) {
+                e1 = ToSenseWord(e1);
+            }
+            if (IsEnglishOrNumber(e1)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private IllegalWordsSearchResult GetGetIllegalResult(String text, int end, int index) {
+        String key = _keywords[index];
+
+        int keyIndex = key.length() - 1;
+        int start = end;
+        for (int i = end; i >= 0; i--) {
+            char s2 = text.charAt(i);
+            if (UseSkipWordFilter) {
+                if (SkipWordFilter != null) {
+                    if (SkipWordFilter.apply(new SkipWordFilterHandler(s2, text, i))) {
+                        continue;
+                    }
+                } else if (_skipBitArray[s2]) {
+                    continue;
+                }
+            }
+
+            if (CharTranslate != null) { // 字符串转换
+                s2 = CharTranslate.apply(new CharTranslateHandler(s2, text, i));
+            } else if (UseDBCcaseConverter || UseIgnoreCase) {
+                s2 = ToSenseWord(s2);
+            }
+            if (s2 == key.charAt(keyIndex)) {
+                keyIndex--;
+                if (keyIndex == -1) {
+                    start = i;
+                    break;
+                }
+            }
+        }
+        for (int i = start; i >= 0; i--) {
+            char s2 = text.charAt(i);
+            if (CharTranslate != null) { // 字符串转换
+                s2 = CharTranslate.apply(new CharTranslateHandler(s2, text, i));
+            } else if (UseDBCcaseConverter || UseIgnoreCase) {
+                s2 = ToSenseWord(s2);
+            }
+            if (s2 != key.charAt(0)) {
+                break;
+            }
+            start = i;
+        }
+        return GetGetIllegalResult(text, key, start, end, index);
+    }
+
+    private IllegalWordsSearchResult GetGetIllegalResult(String text, String key, int start, int end, int index) {
+        if (start > 0) {
+            char s1 = text.charAt(start);
+            if (CharTranslate != null) { // 字符串转换
+                s1 = CharTranslate.apply(new CharTranslateHandler(s1, text, start));
+            }
+            if (IsEnglishOrNumber(s1)) {
+                char s2 = text.charAt(start - 1);
+                if (CharTranslate != null) { // 字符串转换
+                    s2 = CharTranslate.apply(new CharTranslateHandler(s2, text, start - 1));
+                } else if (UseDBCcaseConverter || UseIgnoreCase) {
+                    s2 = ToSenseWord(s2);
+                }
+                if (IsEnglishOrNumber(s2)) {
+                    return null;
+                }
+            }
+        }
+
+        final String keyword = text.substring(start, end + 1);
+        final int bl = _blacklist.length > index ? _blacklist[index] : 0;
+        if (StringMatch != null) {
+            if (StringMatch.apply(new StringMatchHandler(text, start, end, keyword, index, key, _blacklist[index]))) {
+                return new IllegalWordsSearchResult(keyword, start, end, index, key, bl);
+            }
+            return null;
+        }
+        return new IllegalWordsSearchResult(keyword, start, end, index, key, bl);
+    }
+
+    /**
+     * 设置黑名单
+     *
+     * @param blacklist
+     * @throws IllegalArgumentException
+     */
+    public void SetBlacklist(final int[] blacklist) throws IllegalArgumentException {
+        if (_keywords == null) {
+            throw new IllegalArgumentException("请先使用SetKeywords方法设置关键字!");
+        }
+        if (blacklist.length != _keywords.length) {
+            throw new IllegalArgumentException("请关键字与黑名单列表的长度要一样长!");
+        }
+        _blacklist = blacklist;
+    }
+
+    private Boolean IsEnglishOrNumber(final char c) {
+        if (c < 128) {
+            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String ToSenseWord(final String text) {
+        final StringBuilder stringBuilder = new StringBuilder(text.length());
+        for (int i = 0; i < text.length(); i++) {
+            stringBuilder.append(ToSenseWord(text.charAt(i)));
+        }
+        return stringBuilder.toString();
+    }
+
+    private Character ToSenseWord(final Character c) {
+
+        if (UseIgnoreCase) {
+            if (c >= 'A' && c <= 'Z')
+                return (char) (c | 0x20);
+        }
+        if (UseDBCcaseConverter) {
+            if (c == 12288)
+                return ' ';
+            if (c >= 65280 && c < 65375) {
+                Character k = (char) (c - 65248);
+                if (UseIgnoreCase) {
+                    if ('A' <= k && k <= 'Z') {
+                        k = (char) (k | 0x20);
+                    }
+                }
+                return (char) k;
+            }
+        }
+        return c;
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/IllegalWordsSearchResult.java b/src/main/java/org/springblade/modules/words/IllegalWordsSearchResult.java
new file mode 100644
index 0000000..5d19181
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/IllegalWordsSearchResult.java
@@ -0,0 +1,31 @@
+package org.springblade.modules.words;
+
+
+public class IllegalWordsSearchResult
+{
+    public IllegalWordsSearchResult(final String keyword, final int start, final int end, final int index,
+            final String matchKeyword, final int type)
+    {
+        MatchKeyword = matchKeyword;
+        End = end;
+        Start = start;
+        Index = index;
+        Keyword = keyword;
+        BlacklistType = type;
+    }
+
+    /**开始位置 */
+    public int Start;
+    /**结束位置 */
+    public int End ;
+    /**原始文本 */
+    public String Keyword ;
+    /**关键字 */
+    public String MatchKeyword;
+    /**黑名单类型 */
+    public int BlacklistType ;
+    /**索引 */
+    public int Index;
+
+
+}
diff --git a/src/main/java/org/springblade/modules/words/NumHelper.java b/src/main/java/org/springblade/modules/words/NumHelper.java
new file mode 100644
index 0000000..e1323ac
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/NumHelper.java
@@ -0,0 +1,188 @@
+package org.springblade.modules.words;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author yongxuan.he
+ * @date 2020/3/17
+ */
+public class NumHelper {
+
+    public enum SerializableType {
+        /** 大小在-128~127之间的整数,占用空间为1字节 */
+        TINY_INT(1),
+        /** 大小在-32768~32767之间的整数,占用空间为2字节 */
+        SMALL_INT(2),
+        /** 大小在-8388608~8388607之间的整数,占用空间为3字节 */
+        MEDIUM_INT(3),
+        /** int类型 */
+        INT(4),
+        ;
+
+        private final int flag;
+
+        private static Map<Integer, SerializableType> typeMap;
+        static {
+            Map<Integer, SerializableType> tmpMap = new HashMap<>();
+            for (SerializableType type : SerializableType.values()) {
+                tmpMap.put(type.getFlag(), type);
+            }
+            typeMap = tmpMap;
+        }
+        public static SerializableType getType(int flag) {
+            return typeMap.get(flag);
+        }
+
+        SerializableType(int flag) {
+            this.flag = flag;
+        }
+
+        public int getFlag() {
+            return flag;
+        }
+    }
+
+    private interface Serializer {
+        byte[] serialize(int a);
+    }
+
+    private static Serializer tinyIntWriter = v -> {
+        if (v < -128 || v > 127) {
+            throw new RuntimeException("not tinyInt: " + v);
+        }
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write(v);
+        return out.toByteArray();
+    };
+    private static Serializer smallIntWriter = v -> {
+        if (v < -32768 || v > 32767) {
+            throw new RuntimeException("not smallInt: " + v);
+        }
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write((v >>> 8) & 0xFF);
+        out.write(v & 0xFF);
+        return out.toByteArray();
+    };
+    private static Serializer mediumIntWriter = v -> {
+        if (v < -8388608 || v > 8388607) {
+            throw new RuntimeException("not mediumInt: " + v);
+        }
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write((v >>> 16) & 0xFF);
+        out.write((v >>> 8) & 0xFF);
+        out.write(v & 0xFF);
+        return out.toByteArray();
+    };
+
+    private static Serializer intWriter = v -> {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write((v >>> 24) & 0xFF);
+        out.write((v >>> 16) & 0xFF);
+        out.write((v >>> 8) & 0xFF);
+        out.write(v & 0xFF);
+        return out.toByteArray();
+    };
+
+    private static final Map<SerializableType, Serializer> simpleWriterMap = new ConcurrentHashMap<>();
+    static {
+        simpleWriterMap.put(SerializableType.TINY_INT, tinyIntWriter);
+        simpleWriterMap.put(SerializableType.SMALL_INT, smallIntWriter);
+        simpleWriterMap.put(SerializableType.MEDIUM_INT, mediumIntWriter);
+        simpleWriterMap.put(SerializableType.INT, intWriter);
+    }
+
+    public static byte[] serialize(int v) {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        Serializer serializer;
+        int typeFlag;
+
+        if(v >= -128 && v<= 127) {
+            serializer = simpleWriterMap.get(SerializableType.TINY_INT);
+            typeFlag = SerializableType.TINY_INT.getFlag();
+        } else if(v >= -32768 && v <= 32767) {
+            serializer = simpleWriterMap.get(SerializableType.SMALL_INT);
+            typeFlag = SerializableType.SMALL_INT.getFlag();
+        } else if(v >= -8388608 && v <= 8388607){
+            serializer = simpleWriterMap.get(SerializableType.MEDIUM_INT);
+            typeFlag = SerializableType.MEDIUM_INT.getFlag();
+        } else {
+            serializer = simpleWriterMap.get(SerializableType.INT);
+            typeFlag = SerializableType.INT.getFlag();
+        }
+        out.write(typeFlag);
+        byte[] bytes = serializer.serialize(v);
+        out.write(bytes, 0, bytes.length);
+        return out.toByteArray();
+    }
+
+    public interface Deserializer {
+        int deserialize(InputStream in) throws IOException;
+    }
+
+    private static Deserializer tinyIntReader = in -> {
+        int ch = in.read();
+        if (ch < 0)
+            throw new RuntimeException("deserializing");
+        if ((0x80 & ch) == 0x80) {
+            ch = 0xffffff00 | ch;
+        }
+        return ch;
+    };
+    private static Deserializer smallIntReader = in -> {
+        int ch1 = in.read();
+        int ch2 = in.read();
+        if ((ch1 | ch2) < 0)
+            throw new RuntimeException("deserializing");
+        int ch = (ch1 << 8) + ch2;
+        if ((0x8000 & ch) == 0x8000) {
+            ch = 0xffff0000 | ch;
+        }
+        return ch;
+    };
+    private static Deserializer mediumIntReader = in -> {
+        int ch1 = in.read();
+        int ch2 = in.read();
+        int ch3 = in.read();
+        if ((ch1 | ch2 | ch3) < 0)
+            throw new RuntimeException("deserializing");
+        int ch = (ch1 << 16) + (ch2 << 8) + (ch3);
+        if ((0x800000 & ch) == 0x800000) {
+            ch = 0xff000000 | ch;
+        }
+        return ch;
+    };
+
+    private static Deserializer intReader = in -> {
+        int ch1 = in.read();
+        int ch2 = in.read();
+        int ch3 = in.read();
+        int ch4 = in.read();
+        if ((ch1 | ch2 | ch3 | ch4) < 0)
+            throw new RuntimeException("deserializing");
+        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
+    };
+
+    private static final Map<SerializableType, Deserializer> simpleReaderMap = new ConcurrentHashMap<>();
+    static {
+        simpleReaderMap.put(SerializableType.TINY_INT, tinyIntReader);
+        simpleReaderMap.put(SerializableType.SMALL_INT, smallIntReader);
+        simpleReaderMap.put(SerializableType.MEDIUM_INT, mediumIntReader);
+        simpleReaderMap.put(SerializableType.INT, intReader);
+    }
+
+    public static int read(InputStream in) throws IOException {
+        int flag = in.read();
+        SerializableType type = SerializableType.getType(flag);
+        Deserializer deserializer = simpleReaderMap.get(type);
+
+        if(deserializer == null) {
+            throw new RuntimeException("wrong flag: " + flag);
+        }
+        return deserializer.deserialize(in);
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/PinyinMatch.java b/src/main/java/org/springblade/modules/words/PinyinMatch.java
new file mode 100644
index 0000000..db0a08f
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/PinyinMatch.java
@@ -0,0 +1,332 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BasePinyinMatch;
+import org.springblade.modules.words.internals.PinyinDict;
+import org.springblade.modules.words.internals.TwoTuple;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PinyinMatch extends BasePinyinMatch {
+    private String[] _keywords;
+    private String[] _keywordsFirstPinyin;
+    private String[][] _keywordsPinyin;
+    private int[] _indexs;
+
+    /**
+     * 设置关键字,注:索引会被清空
+     *
+     * @param keywords
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public void SetKeywords(final List<String> keywords) throws NumberFormatException, IOException {
+        _keywords = keywords.toArray(new String[0]);
+        _keywordsFirstPinyin = new String[_keywords.length];
+        _keywordsPinyin = new String[_keywords.length][];
+        for (int i = 0; i < _keywords.length; i++) {
+            final String text = _keywords[i];
+            final String[] pys = PinyinDict.GetPinyinList(text, 0);
+            String fpy = "";
+            for (int j = 0; j < pys.length; j++) {
+                pys[j] = pys[j].toUpperCase();
+                fpy += pys[j].charAt(0);
+            }
+            _keywordsPinyin[i] = pys;
+            _keywordsFirstPinyin[i] = fpy;
+        }
+        _indexs = null;
+    }
+
+    /**
+     * 设置关键字,注:索引会被清空
+     *
+     * @param keywords
+     * @param pinyin
+     */
+    public void SetKeywords(final List<String> keywords, final List<String> pinyin) {
+        SetKeywords(keywords, pinyin, ',');
+    }
+
+    /**
+     * 设置关键字,注:索引会被清空
+     *
+     * @param keywords
+     * @param pinyin
+     * @param splitChar
+     */
+    public void SetKeywords(final List<String> keywords, final List<String> pinyin, final char splitChar) {
+        _keywords = keywords.toArray(new String[0]);
+        _keywordsFirstPinyin = new String[_keywords.length];
+        _keywordsPinyin = new String[_keywords.length][];
+        for (int i = 0; i < _keywords.length; i++) {
+            final String text = pinyin.get(i);
+            final String[] pys = text.split(((Character) splitChar).toString());
+            String fpy = "";
+            for (int j = 0; j < pys.length; j++) {
+                pys[j] = pys[j].toUpperCase();
+                fpy += pys[j].charAt(0);
+            }
+            _keywordsPinyin[i] = pys;
+            _keywordsFirstPinyin[i] = fpy;
+        }
+        _indexs = null;
+    }
+
+    /**
+     * 设置索引
+     *
+     * @param indexs
+     * @throws Exception
+     */
+    public void SetIndexs(final List<Integer> indexs) throws Exception {
+        if (_keywords == null) {
+            throw new Exception("请先使用 SetKeywords 方法");
+        }
+        if (indexs.size() < _keywords.length) {
+            throw new Exception("indexs 数组长度大于 keywords");
+        }
+        _indexs = new int[indexs.size()];
+        for (int i = 0; i < indexs.size(); i++) {
+            _indexs[i] = indexs.get(i);
+        }
+    }
+
+    /**
+     * 查询
+     *
+     * @param key
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public List<String> Find(String key) throws NumberFormatException, IOException {
+        key = key.toUpperCase().trim();
+        if (key == null || key.equals("")) {
+            return null;
+        }
+
+        final boolean hasPinyin = key.matches("^.*?[A-Z]+.*$");// Pattern.matches("[a-zA-Z]",key);
+        if (hasPinyin == false) {
+            final List<String> rs = new ArrayList<String>();
+            for (int i = 0; i < _keywords.length; i++) {
+                final String keyword = _keywords[i];
+                if (keyword.contains(key)) {
+                    rs.add(keyword);
+                }
+            }
+            return rs;
+        }
+
+        final List<String> pykeys = SplitKeywords(key);
+        int minLength = Integer.MAX_VALUE;
+        final List<TwoTuple<String, String[]>> list = new ArrayList<TwoTuple<String, String[]>>();
+        for (final String pykey : pykeys) {
+            final String[] keys = pykey.split(((Character) (char) 0).toString());
+            if (minLength > keys.length) {
+                minLength = keys.length;
+            }
+            MergeKeywords(keys, 0, "", list);
+        }
+
+        final PinyinSearch search = new PinyinSearch();
+        search.SetKeywords2(list);
+        final List<String> result = new ArrayList<String>();
+        for (int i = 0; i < _keywords.length; i++) {
+            final String keywords = _keywords[i];
+            if (keywords.length() < minLength) {
+                continue;
+            }
+            final String fpy = _keywordsFirstPinyin[i];
+            final String[] pylist = _keywordsPinyin[i];
+
+            if (search.Find(fpy, keywords, pylist)) {
+                result.add(keywords);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 查询索引
+     *
+     * @param key
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public List<Integer> FindIndex(String key) throws NumberFormatException, IOException {
+        key = key.toUpperCase().trim();
+        if (key == null || key.equals("")) {
+            return null;
+        }
+        final boolean hasPinyin = key.matches("^.*?[A-Z]+.*$");// Pattern.matches("[a-zA-Z]",key);
+        if (hasPinyin == false) {
+            final List<Integer> rs = new ArrayList<Integer>();
+            for (int i = 0; i < _keywords.length; i++) {
+                final String keyword = _keywords[i];
+                if (keyword.contains(key)) {
+                    if (_indexs == null) {
+                        rs.add(i);
+                    } else {
+                        rs.add(_indexs[i]);
+                    }
+                }
+            }
+            return rs;
+        }
+
+        final List<String> pykeys = SplitKeywords(key);
+        int minLength = Integer.MAX_VALUE;
+        final List<TwoTuple<String, String[]>> list = new ArrayList<TwoTuple<String, String[]>>();
+        for (final String pykey : pykeys) {
+            final String[] keys = pykey.split(((Character) (char) 0).toString());
+            if (minLength > keys.length) {
+                minLength = keys.length;
+            }
+            MergeKeywords(keys, 0, "", list);
+        }
+
+        final PinyinSearch search = new PinyinSearch();
+        search.SetKeywords2(list);
+        final List<Integer> result = new ArrayList<Integer>();
+        for (int i = 0; i < _keywords.length; i++) {
+            final String keywords = _keywords[i];
+            if (keywords.length() < minLength) {
+                continue;
+            }
+            final String fpy = _keywordsFirstPinyin[i];
+            final String[] pylist = _keywordsPinyin[i];
+            if (search.Find(fpy, keywords, pylist)) {
+                if (_indexs == null) {
+                    result.add(i);
+                } else {
+                    result.add(_indexs[i]);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 查询,空格为通配符
+     *
+     * @param keywords
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public List<String> FindWithSpace(String keywords) throws NumberFormatException, IOException {
+        keywords = keywords.toUpperCase().trim();
+        if (keywords == null || keywords.equals("")) {
+            return null;
+        }
+        if (keywords.contains(" ") == false) {
+            return Find(keywords);
+        }
+
+        final List<TwoTuple<String, String[]>> list = new ArrayList<TwoTuple<String, String[]>>();
+        final List<Integer> indexs = new ArrayList<Integer>();
+        int minLength = 0;
+        int keysCount;
+        {
+            final String[] keys = keywords.split(" ");
+            keysCount = keys.length;
+            for (int i = 0; i < keys.length; i++) {
+                final String key = keys[i];
+                final List<String> pykeys = SplitKeywords(key);
+                int min = Integer.MAX_VALUE;
+                for (final String pykey : pykeys) {
+                    final String[] keys2 = pykey.split(((Character) (char) 0).toString());
+                    if (min > keys2.length) {
+                        min = keys2.length;
+                    }
+                    MergeKeywords(keys2, 0, "", list, i, indexs);
+                }
+                minLength += min;
+            }
+        }
+
+        final PinyinSearch search = new PinyinSearch();
+        search.SetKeywords2(list);
+        search.SetIndexs(indexs);
+
+        final List<String> result = new ArrayList<String>();
+        for (int i = 0; i < _keywords.length; i++) {
+            final String keywords2 = _keywords[i];
+            if (keywords2.length() < minLength) {
+                continue;
+            }
+            final String fpy = _keywordsFirstPinyin[i];
+            final String[] pylist = _keywordsPinyin[i];
+
+            if (search.Find2(fpy, keywords2, pylist, keysCount)) {
+                result.add(keywords2);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 查询索引号,空格为通配符
+     *
+     * @param keywords
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public List<Integer> FindIndexWithSpace(String keywords) throws NumberFormatException, IOException {
+        keywords = keywords.toUpperCase().trim();
+        if (keywords == null || keywords.equals("")) {
+            return null;
+        }
+        if (keywords.contains(" ") == false) {
+            return FindIndex(keywords);
+        }
+
+        final List<TwoTuple<String, String[]>> list = new ArrayList<TwoTuple<String, String[]>>();
+        final List<Integer> indexs = new ArrayList<Integer>();
+        int minLength = 0;
+        int keysCount;
+        {
+            final String[] keys = keywords.split(" ");
+            keysCount = keys.length;
+            for (int i = 0; i < keys.length; i++) {
+                final String key = keys[i];
+                final List<String> pykeys = SplitKeywords(key);
+                int min = Integer.MAX_VALUE;
+                for (final String pykey : pykeys) {
+                    final String[] keys2 = pykey.split(((Character) (char) 0).toString());
+                    if (min > keys2.length) {
+                        min = keys2.length;
+                    }
+                    MergeKeywords(keys2, 0, "", list, i, indexs);
+                }
+                minLength += min;
+            }
+        }
+
+        final PinyinSearch search = new PinyinSearch();
+        search.SetKeywords2(list);
+        search.SetIndexs(indexs);
+
+        final List<Integer> result = new ArrayList<Integer>();
+        for (int i = 0; i < _keywords.length; i++) {
+            final String keywords2 = _keywords[i];
+            if (keywords2.length() < minLength) {
+                continue;
+            }
+            final String fpy = _keywordsFirstPinyin[i];
+            final String[] pylist = _keywordsPinyin[i];
+            if (search.Find2(fpy, keywords2, pylist, keysCount)) {
+                if (_indexs == null) {
+                    result.add(i);
+                } else {
+                    result.add(_indexs[i]);
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/PinyinMatch2.java b/src/main/java/org/springblade/modules/words/PinyinMatch2.java
new file mode 100644
index 0000000..556b63f
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/PinyinMatch2.java
@@ -0,0 +1,188 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BasePinyinMatch;
+import org.springblade.modules.words.internals.PinyinDict;
+import org.springblade.modules.words.internals.TwoTuple;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+public class PinyinMatch2<T> extends BasePinyinMatch {
+    private final List<T> _list;
+    private Function<T, String> _keywordsFunc;
+    private Function<T, String> _pinyinFunc;
+    private char _splitChar = ',';
+
+    /**
+     * 拼音匹配, 不支持[0x20000-0x2B81D]
+     *
+     * @param list
+     */
+    public PinyinMatch2(final List<T> list) {
+        _list = list;
+        _keywordsFunc = null;
+        _pinyinFunc = null;
+    }
+
+    /**
+     * 设置获取关键字的方法
+     *
+     * @param keywordsFunc
+     */
+    public void SetKeywordsFunc(final Function<T, String> keywordsFunc) {
+        _keywordsFunc = keywordsFunc;
+    }
+
+    /**
+     * 设置获取拼音的方法
+     *
+     * @param pinyinFunc
+     */
+    public void SetPinyinFunc(final Function<T, String> pinyinFunc) {
+        _pinyinFunc = pinyinFunc;
+    }
+
+    /**
+     * 设置拼音分隔符
+     *
+     * @param splitChar
+     */
+    public void SetPinyinSplitChar(final char splitChar) {
+        _splitChar = splitChar;
+    }
+
+    /**
+     * 查询
+     *
+     * @param keywords
+     * @return
+     * @throws Exception
+     */
+    public List<T> Find(String keywords) throws Exception {
+        if (_keywordsFunc == null) {
+            throw new Exception("请先使用SetKeywordsFunc方法。");
+        }
+        keywords = keywords.toUpperCase().trim();
+        if (keywords == null || keywords.equals("")) {
+            return null;
+        }
+        final List<T> result = new ArrayList<T>();
+        final boolean hasPinyin = keywords.matches("^.*?[A-Z]+.*$");// Pattern.matches("[a-zA-Z]",key);
+        if (hasPinyin == false) {
+            for (final T item : _list) {
+                final String keyword = _keywordsFunc.apply(item);
+                if (keyword.contains(keywords)) {
+                    result.add(item);
+                }
+            }
+            return result;
+        }
+
+        final List<String> pykeys = SplitKeywords(keywords);
+        int minLength = Integer.MAX_VALUE;
+        final List<TwoTuple<String, String[]>> list = new ArrayList<TwoTuple<String, String[]>>();
+        for (final String pykey : pykeys) {
+            final String[] keys = pykey.split(((Character) (char) 0).toString());
+            if (minLength > keys.length) {
+                minLength = keys.length;
+            }
+            MergeKeywords(keys, 0, "", list);
+        }
+
+        final PinyinSearch search = new PinyinSearch();
+        search.SetKeywords2(list);
+        for (final T item : _list) {
+            final String keyword = _keywordsFunc.apply(item);
+            if (keyword.length() < minLength) {
+                continue;
+            }
+            String fpy = "";
+            String[] pylist;
+            if (_pinyinFunc == null) {
+                pylist = PinyinDict.GetPinyinList(keyword, 0);
+            } else {
+                pylist = _pinyinFunc.apply(item).split(((Character) _splitChar).toString());
+            }
+            for (int j = 0; j < pylist.length; j++) {
+                pylist[j] = pylist[j].toUpperCase();
+                fpy += pylist[j].charAt(0);
+            }
+            if (search.Find(fpy, keyword, pylist)) {
+                result.add(item);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 查询,空格为通配符
+     *
+     * @param keywords
+     * @return
+     * @throws Exception
+     */
+    public List<T> FindWithSpace(String keywords) throws Exception {
+        if (_keywordsFunc == null) {
+            throw new Exception("请先使用SetKeywordsFunc方法。");
+        }
+        keywords = keywords.toUpperCase().trim();
+        if (keywords == null || keywords.equals("")) {
+            return null;
+        }
+        if (keywords.contains(" ") == false) {
+            return Find(keywords);
+        }
+
+        final List<TwoTuple<String, String[]>> list = new ArrayList<TwoTuple<String, String[]>>();
+        final List<Integer> indexs = new ArrayList<Integer>();
+        int minLength = 0;
+        int keysCount;
+        {
+
+            final String[] keys = keywords.split(" ");
+            keysCount = keys.length;
+            for (int i = 0; i < keys.length; i++) {
+                final String key = keys[i];
+                final List<String> pykeys = SplitKeywords(key);
+                int min = Integer.MAX_VALUE;
+                for (final String pykey : pykeys) {
+                    final String[] keys2 = pykey.split(((Character) (char) 0).toString());
+                    if (min > keys2.length) {
+                        min = keys2.length;
+                    }
+                    MergeKeywords(keys2, 0, "", list, i, indexs);
+                }
+                minLength += min;
+            }
+        }
+
+        final PinyinSearch search = new PinyinSearch();
+        search.SetKeywords2(list);
+        search.SetIndexs(indexs);
+
+        final List<T> result = new ArrayList<T>();
+        for (final T item : _list) {
+            final String keyword = _keywordsFunc.apply(item);
+            if (keyword.length() < minLength) {
+                continue;
+            }
+            String fpy = "";
+            String[] pylist;
+            if (_pinyinFunc == null) {
+                pylist = PinyinDict.GetPinyinList(keyword, 0);
+            } else {
+                pylist = _pinyinFunc.apply(item).split(((Character)_splitChar).toString());
+            }
+            for (int j = 0; j < pylist.length; j++) {
+                pylist[j] = pylist[j].toUpperCase();
+                fpy += pylist[j].charAt(0);
+            }
+            if (search.Find2(fpy, keyword, pylist, keysCount)) {
+                result.add(item);
+            }
+        }
+        return result;
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/StringMatch.java b/src/main/java/org/springblade/modules/words/StringMatch.java
new file mode 100644
index 0000000..8700c97
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/StringMatch.java
@@ -0,0 +1,291 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseMatch;
+import org.springblade.modules.words.internals.TrieNode3;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 文本搜索匹配, ,支持 部分 正则 如 . ? [ ] \ ( | ) ,不支持( )内再嵌套( )
+ */
+public class StringMatch extends BaseMatch {
+
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public String FindFirst(final String text) {
+        TrieNode3 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+
+            TrieNode3 tn;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    if (ptr.HasWildcard) {
+                        final String result = FindFirst(text, i + 1, ptr.WildcardNode);
+                        if (result != null) {
+                            return result;
+                        }
+                    }
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    final int length = _keywordLength[tn.Results.get(0)];
+                    final int s = i - length + 1;
+                    if (s >= 0) {
+                        return text.substring(s, i + 1);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return null;
+    }
+
+    private String FindFirst(final String text, final int index, TrieNode3 ptr) {
+        for (int i = index; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr.HasKey(t) == false) {
+                if (ptr.HasWildcard) {
+                    final String result = FindFirst(text, i + 1, ptr.WildcardNode);
+                    if (result != null) {
+                        return result;
+                    }
+                }
+                return null;
+            }
+            tn = ptr.GetValue(t);
+
+            if (tn.End) {
+                final int length = _keywordLength[tn.Results.get(0)];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    return text.substring(s, i + 1);
+                }
+            }
+            ptr = tn;
+        }
+        return null;
+    }
+
+    /**
+     * 在文本中查找所有的关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public List<String> FindAll(final String text) {
+        TrieNode3 ptr = null;
+        final List<String> result = new ArrayList<String>();
+
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    if (ptr.HasWildcard) {
+                        FindAll(text, i + 1, ptr.WildcardNode, result);
+                    }
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    for (final Integer item : tn.Results) {
+                        final int length = _keywordLength[item];
+                        final int s = i - length + 1;
+                        if (s >= 0) {
+                            final String key = text.substring(s, i + 1);
+                            result.add(key);
+
+                        }
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return result;
+    }
+
+    private void FindAll(final String text, final int index, TrieNode3 ptr, final List<String> result) {
+        for (int i = index; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr.HasKey(t) == false) {
+                if (ptr.HasWildcard) {
+                    FindAll(text, i + 1, ptr.WildcardNode, result);
+                }
+                return;
+            } else {
+                tn = ptr.GetValue(t);
+            }
+            if (tn.End) {
+                for (final Integer item : tn.Results) {
+                    final int length = _keywordLength[item];
+                    final int s = i - length + 1;
+                    if (s >= 0) {
+                        final String key = text.substring(s, i + 1);
+                        result.add(key);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public boolean ContainsAny(final String text) {
+        TrieNode3 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    if (ptr.HasWildcard) {
+                        final boolean result = ContainsAny(text, i + 1, ptr.WildcardNode);
+                        if (result) {
+                            return true;
+                        }
+                    }
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    final int length = _keywordLength[tn.Results.get(0)];
+                    final int s = i - length + 1;
+                    if (s >= 0) {
+                        return true;
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return false;
+    }
+
+    private boolean ContainsAny(final String text, final int index, TrieNode3 ptr) {
+        for (int i = index; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr.HasKey(t) == false) {
+                if (ptr.HasWildcard) {
+                    return ContainsAny(text, i + 1, ptr.WildcardNode);
+                }
+                return false;
+            }
+            tn = ptr.GetValue(t);
+
+            if (tn.End) {
+                final int length = _keywordLength[tn.Results.get(0)];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    return true;
+                }
+            }
+            ptr = tn;
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字, 替换符默认为 *
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 替换符
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        TrieNode3 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    if (ptr.HasWildcard) {
+                        Replace(text, i + 1, ptr.WildcardNode, replaceChar, result);
+                    }
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    final int maxLength = _keywordLength[tn.Results.get(0)];
+                    final int start = i + 1 - maxLength;
+                    if (start >= 0) {
+                        for (int j = start; j <= i; j++) {
+                            result.setCharAt(j, replaceChar);
+                        }
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return result.toString();
+    }
+
+    private void Replace(final String text, final int index, TrieNode3 ptr, final char replaceChar,
+            final StringBuilder result) {
+        for (int i = index; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr.HasKey(t) == false) {
+                if (ptr.HasWildcard) {
+                    Replace(text, i + 1, ptr.WildcardNode, replaceChar, result);
+                }
+                return;
+            }
+            tn = ptr.GetValue(t);
+            if (tn.End) {
+                final int maxLength = _keywordLength[tn.Results.get(0)];
+                final int start = i + 1 - maxLength;
+                if (start >= 0) {
+                    for (int j = start; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/StringMatchEx.java b/src/main/java/org/springblade/modules/words/StringMatchEx.java
new file mode 100644
index 0000000..468b8c8
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/StringMatchEx.java
@@ -0,0 +1,346 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseMatchEx;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StringMatchEx extends BaseMatchEx {
+
+    /// <summary>
+    /// 在文本中查找第一个关键字
+    /// </summary>
+    /// <param name="text">文本</param>
+    /// <returns></returns>
+    public String FindFirst(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    if (_wildcard[p] > 0) {
+                        final String r = FindFirst(text, i + 1, _wildcard[p]);
+                        if (r != null) {
+                            return r;
+                        }
+                    }
+                    next = _firstIndex[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                final int start = _end[next];
+                if (start < _end[next + 1]) {
+                    final int length = _keywordLength[_resultIndex[start]];
+                    final int s = i - length + 1;
+                    if (s >= 0) {
+                        return text.substring(s, i + 1);
+                    }
+                }
+            }
+            p = next;
+        }
+        return null;
+    }
+
+    private String FindFirst(final String text, final int index, int p) {
+        for (int i = index; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                return null;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        final String r = FindFirst(text, i + 1, _wildcard[p]);
+                        if (r != null) {
+                            return r;
+                        }
+                    }
+                    return null;
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+            final int start = _end[next];
+            if (start < _end[next + 1]) {
+                final int length = _keywordLength[_resultIndex[start]];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    return text.substring(s, i + 1);
+                }
+            }
+            p = next;
+        }
+        return null;
+    }
+
+    /// <summary>
+    /// 在文本中查找所有的关键字
+    /// </summary>
+    /// <param name="text">文本</param>
+    /// <returns></returns>
+    public List<String> FindAll(final String text) {
+        final List<String> result = new ArrayList<String>();
+        int p = 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        FindAll(text, i + 1, _wildcard[p], result);
+                    }
+                    next = _firstIndex[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            if (next != 0) {
+                for (int j = _end[next]; j < _end[next + 1]; j++) {
+                    final int length = _keywordLength[_resultIndex[j]];
+                    final int s = i - length + 1;
+                    if (s >= 0) {
+                        final String key = text.substring(s, i + 1);
+                        result.add(key);
+                    }
+                }
+            }
+            p = next;
+        }
+        return result;
+    }
+
+    private void FindAll(final String text, final int index, int p, final List<String> result) {
+        for (int i = index; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                return;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        FindAll(text, i + 1, _wildcard[p], result);
+                    }
+                    return;
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            for (int j = _end[next]; j < _end[next + 1]; j++) {
+                final int length = _keywordLength[_resultIndex[j]];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    final String key = text.substring(s, i + 1);
+                    result.add(key);
+                }
+            }
+            p = next;
+        }
+    }
+
+    /// <summary>
+    /// 判断文本是否包含关键字
+    /// </summary>
+    /// <param name="text">文本</param>
+    /// <returns></returns>
+    public boolean ContainsAny(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    if (_wildcard[p] > 0) {
+                        final boolean r = ContainsAny(text, i + 1, _wildcard[p]);
+                        if (r) {
+                            return true;
+                        }
+                    }
+                    next = _firstIndex[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+
+            if (next != 0) {
+                if (_end[next] < _end[next + 1]) {
+                    return true;
+                }
+            }
+            p = next;
+        }
+        return false;
+    }
+
+    private boolean ContainsAny(final String text, final int index, int p) {
+        for (int i = index; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+
+            final int t = _dict[t1];
+            if (t == 0) {
+                return false;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        final boolean r = ContainsAny(text, i + 1, _wildcard[p]);
+                        if (r) {
+                            return true;
+                        }
+                    }
+                    return false;
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            final int start = _end[next];
+            if (start < _end[next + 1]) {
+                final int length = _keywordLength[_resultIndex[start]];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    return true;
+                }
+            }
+            p = next;
+        }
+        return false;
+    }
+
+    /// <summary>
+    /// 在文本中替换所有的关键字
+    /// </summary>
+    /// <param name="text">文本</param>
+    /// <param name="replaceChar">替换符</param>
+    /// <returns></returns>
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        int p = 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        Replace(text, i + 1, _wildcard[p], replaceChar, result);
+                    }
+                    next = _firstIndex[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            if (next != 0) {
+                final int start = _end[next];
+                if (start < _end[next + 1]) {
+                    final int maxLength = _keywordLength[_resultIndex[start]];
+                    final int start2 = i + 1 - maxLength;
+                    if (start2 >= 0) {
+                        for (int j = start2; j <= i; j++) {
+                            result.setCharAt(j, replaceChar);
+                        }
+                    }
+                }
+            }
+            p = next;
+        }
+        return result.toString();
+    }
+
+    private void Replace(final String text, final int index, int p, final char replaceChar,
+            final StringBuilder result) {
+        for (int i = index; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+
+            final int t = _dict[t1];
+            if (t == 0) {
+                return;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        Replace(text, i + 1, _wildcard[p], replaceChar, result);
+                    }
+                    return;
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            final int start = _end[next];
+            if (start < _end[next + 1]) {
+                final int maxLength = _keywordLength[_resultIndex[start]];
+                final int start2 = i + 1 - maxLength;
+                if (start2 >= 0) {
+                    for (int j = start2; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                    }
+                }
+            }
+            p = next;
+        }
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/StringSearch.java b/src/main/java/org/springblade/modules/words/StringSearch.java
new file mode 100644
index 0000000..355616a
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/StringSearch.java
@@ -0,0 +1,151 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseSearch;
+import org.springblade.modules.words.internals.TrieNode2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StringSearch extends BaseSearch {
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public String FindFirst(final String text) {
+        TrieNode2 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode2 tn = null;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    return _keywords[tn.Results.get(0)];
+                }
+            }
+            ptr = tn;
+        }
+        return null;
+    }
+
+    /**
+     * 在文本中查找所有的关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public List<String> FindAll(final String text) {
+        TrieNode2 ptr = null;
+        final List<String> list = new ArrayList<String>();
+
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode2 tn = null;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    tn.Results.forEach(item -> {
+                        list.add(_keywords[item]);
+                    });
+                }
+            }
+            ptr = tn;
+        }
+        return list;
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public boolean ContainsAny(final String text) {
+        TrieNode2 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode2 tn = null;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    return true;
+                }
+            }
+            ptr = tn;
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字, 替换符默认为 *
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 替换符
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        TrieNode2 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode2 tn = null;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    final int maxLength = _keywords[tn.Results.get(0)].length();
+                    final int start = i + 1 - maxLength;
+                    for (int j = start; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/StringSearchEx.java b/src/main/java/org/springblade/modules/words/StringSearchEx.java
new file mode 100644
index 0000000..dabca20
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/StringSearchEx.java
@@ -0,0 +1,174 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseSearchEx;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StringSearchEx extends BaseSearchEx {
+    /**
+     * 在文本中查找所有的关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public List<String> FindAll(final String text) {
+        final List<String> result = new ArrayList<String>();
+
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final Integer t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    next = _first[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                for (int j = _end[next]; j < _end[next + 1]; j++) {
+                    result.add(_keywords[_resultIndex[j]]);
+                }
+            }
+            p = next;
+        }
+        return result;
+    }
+
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public String FindFirst(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    next = _first[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                final int start = _end[next];
+                if (start < _end[next + 1]) {
+                    return _keywords[_resultIndex[start]];
+                }
+            }
+            p = next;
+        }
+        return null;
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public boolean ContainsAny(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    next = _first[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+
+            if (next != 0) {
+                if (_end[next] < _end[next + 1]) {
+                    return true;
+                }
+            }
+            p = next;
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字, 替换符默认为 *
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 替换符
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    next = _first[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                final int start = _end[next];
+                if (start < _end[next + 1]) {
+                    final int maxLength = _keywords[_resultIndex[start]].length();
+                    for (int j = i + 1 - maxLength; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                    }
+
+                }
+            }
+            p = next;
+        }
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/StringSearchEx2.java b/src/main/java/org/springblade/modules/words/StringSearchEx2.java
new file mode 100644
index 0000000..87eeb8c
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/StringSearchEx2.java
@@ -0,0 +1,164 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseSearchEx2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class StringSearchEx2 extends BaseSearchEx2 {
+
+    /**
+     * 在文本中查找所有的关键字
+     * @param text 文本
+     * @return
+     */
+    public List<String> FindAll(final String text) {
+        final List<String> root = new ArrayList<String>();
+        int p = 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            final int t = _dict[text.charAt(i)];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next = _next[p] + t;
+            boolean find = _key[next] == t;
+            if (find == false && p != 0) {
+                p = 0;
+                next = _next[0] + t;
+                find = _key[next] == t;
+            }
+            if (find) {
+                final int index = _check[next];
+                if (index > 0) {
+                    for (final int item : _guides[index]) {
+                        root.add(_keywords[item]);
+                    }
+                }
+                p = next;
+            }
+        }
+        return root;
+    }
+
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public String FindFirst(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final int t = _dict[text.charAt(i)];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next = _next[p] + t;
+            if (_key[next] == t) {
+                final int index = _check[next];
+                if (index > 0) {
+                    return _keywords[_guides[index][0]];
+                }
+                p = next;
+            } else {
+                p = 0;
+                next = _next[p] + t;
+                if (_key[next] == t) {
+                    final int index = _check[next];
+                    if (index > 0) {
+                        return _keywords[_guides[index][0]];
+                    }
+                    p = next;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     */
+    public boolean ContainsAny(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final int t = _dict[text.charAt(i)];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next = _next[p] + t;
+            if (_key[next] == t) {
+                if (_check[next] > 0) {
+                    return true;
+                }
+                p = next;
+            } else {
+                p = 0;
+                next = _next[p] + t;
+                if (_key[next] == t) {
+                    if (_check[next] > 0) {
+                        return true;
+                    }
+                    p = next;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字, 替换符默认为 *
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 替换符
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        int p = 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            final int t = _dict[text.charAt(i)];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next = _next[p] + t;
+            boolean find = _key[next] == t;
+            if (find == false && p != 0) {
+                p = 0;
+                next = _next[p] + t;
+                find = _key[next] == t;
+            }
+            if (find) {
+                final int index = _check[next];
+                if (index > 0) {
+                    final int maxLength = _keywords[_guides[index][0]].length();
+                    final int start = i + 1 - maxLength;
+                    for (int j = start; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                     }
+                 }
+                 p = next;
+            }
+        }
+        return result.toString();
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/WordsHelper.java b/src/main/java/org/springblade/modules/words/WordsHelper.java
new file mode 100644
index 0000000..47dc692
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/WordsHelper.java
@@ -0,0 +1,284 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.PinyinDict;
+import org.springblade.modules.words.internals.Translate;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class WordsHelper {
+
+    /**
+     * 获取首字母,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证
+     *
+     * @param text 原文本
+     * @return
+     * @throws IOException
+     * @throws NumberFormatException
+     */
+    public static String GetFirstPinyin(String text) throws NumberFormatException, IOException {
+        return PinyinDict.GetFirstPinyin(text, 0);
+    }
+
+    /**
+     * 获取拼音全拼, 不支持多音,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证 请使用GetPinyin方法,此方法不支持多音
+     *
+     * @param text 原文本
+     * @param tone 是否带声调
+     * @return
+     * @throws IOException
+     * @throws NumberFormatException
+     */
+    public static String GetPinyinFast(String text, Boolean tone) throws NumberFormatException, IOException {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < text.length(); i++) {
+            Character c = text.charAt(i);
+            sb.append(PinyinDict.GetPinyinFast(c, tone ? 1 : 0));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 获取拼音全拼, 不支持多音,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证 请使用GetPinyin方法,此方法不支持多音
+     *
+     * @param text
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static String GetPinyinFast(String text) throws NumberFormatException, IOException {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < text.length(); i++) {
+            Character c = text.charAt(i);
+            sb.append(PinyinDict.GetPinyinFast(c, 0));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 获取拼音全拼,支持多音,中文字符集为[0x4E00,0x9FD5]
+     *
+     * @param text 原文本
+     * @param tone 是否带声调
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static String GetPinyin(String text, Boolean tone) throws NumberFormatException, IOException {
+        return PinyinDict.GetPinyin(text, tone ? 1 : 0);
+    }
+
+    /**
+     * 获取拼音全拼,支持多音,中文字符集为[0x4E00,0x9FD5]
+     *
+     * @param text
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static String GetPinyin(String text) throws NumberFormatException, IOException {
+        return PinyinDict.GetPinyin(text, 0);
+    }
+
+    /**
+     * 获取所有拼音,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证
+     *
+     * @param c    原文本
+     * @param tone 是否带声调
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static List<String> GetAllPinyin(char c, Boolean tone) throws NumberFormatException, IOException {
+        return PinyinDict.GetAllPinyin(c, tone ? 1 : 0);
+    }
+
+    /**
+     * 获取所有拼音,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证
+     *
+     * @param c
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static List<String> GetAllPinyin(char c) throws NumberFormatException, IOException {
+        return PinyinDict.GetAllPinyin(c, 0);
+    }
+
+    /**
+     * 获取姓名拼音,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证
+     *
+     * @param name 姓名
+     * @param tone 是否带声调
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static String GetPinyinForName(String name, Boolean tone) throws NumberFormatException, IOException {
+        return String.join("", PinyinDict.GetPinyinForName(name, tone ? 1 : 0));
+    }
+
+    /**
+     * 获取姓名拼音,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证
+     *
+     * @param name
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static String GetPinyinForName(String name) throws NumberFormatException, IOException {
+        return String.join("", PinyinDict.GetPinyinForName(name, 0));
+    }
+
+    /**
+     * 获取姓名拼音,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证
+     *
+     * @param name 姓名
+     * @param tone 是否带声调
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static List<String> GetPinyinListForName(String name, Boolean tone)
+            throws NumberFormatException, IOException {
+        return PinyinDict.GetPinyinForName(name, tone ? 1 : 0);
+    }
+
+    /**
+     * 获取姓名拼音,中文字符集为[0x3400,0x9FD5],注:偏僻汉字很多未验证
+     * @param name
+     * @return
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    public static List<String> GetPinyinListForName(String name) throws NumberFormatException, IOException {
+        return PinyinDict.GetPinyinForName(name, 0);
+    }
+
+
+    /**
+     * 判断输入是否为中文 ,中文字符集为[0x4E00,0x9FA5]
+     *
+     * @param content
+     * @return
+     */
+    public static boolean HasChinese(String content) {
+        return Pattern.matches("[\\u3400-\\u4db5\\u4e00-\\u9fd5]", content);
+    }
+
+    /**
+     * 判断输入是否全为中文,中文字符集为[0x4E00,0x9FA5]
+     *
+     * @param content
+     * @return
+     */
+    public static boolean IsAllChinese(String content) {
+        return Pattern.matches("^[\\u3400-\\u4db5\\u4e00-\\u9fd5]*$", content);
+    }
+
+    /**
+     * 判断含有英语
+     *
+     * @param content
+     * @return
+     */
+    public static boolean HasEnglish(String content) {
+        return Pattern.matches("[A-Za-z]", content);
+    }
+
+    /**
+     * 判断是否全部英语
+     *
+     * @param content
+     * @return
+     */
+    public static boolean IsAllEnglish(String content) {
+        return Pattern.matches("^[A-Za-z]*$", content);
+    }
+
+    /**
+     * 半角转全角
+     *
+     * @param input
+     * @return
+     */
+    public static String ToSBC(String input) {
+        StringBuilder sb = new StringBuilder(input);
+        for (int i = 0; i < input.length(); i++) {
+            char c = input.charAt(i);
+            if (c == 32) {
+                sb.setCharAt(i, (char) 12288);
+            } else if (c < 127) {
+                sb.setCharAt(i, (char) (c + 65248));
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 转半角的函数
+     *
+     * @param input
+     * @return
+     */
+    public static String ToDBC(String input) {
+        StringBuilder sb = new StringBuilder(input);
+        for (int i = 0; i < input.length(); i++) {
+            char c = input.charAt(i);
+            if (c == 12288) {
+                sb.setCharAt(i, (char) 32);
+            } else if (c > 65280 && c < 65375) {
+                sb.setCharAt(i, (char) (c - 65248));
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 转繁体中文
+     *
+     * @param text
+     * @return
+     * @throws Exception
+     */
+    public static String ToTraditionalChinese(String text) throws Exception {
+        return Translate.ToTraditionalChinese(text, 0);
+    }
+
+    /**
+     * 转繁体中文
+     *
+     * @param text
+     * @param type 0、繁体中文,1、港澳繁体,2、台湾正体
+     * @return
+     * @throws Exception
+     */
+    public static String ToTraditionalChinese(String text, int type) throws Exception {
+        return Translate.ToTraditionalChinese(text, type);
+    }
+
+    /***
+     * 转简体中文
+     *
+     * @param text
+     * @return
+     * @throws Exception
+     */
+    public static String ToSimplifiedChinese(String text) throws Exception {
+        return Translate.ToSimplifiedChinese(text, 0);
+    }
+
+    /**
+     * 转简体中文
+     *
+     * @param text
+     * @param srcType 0、繁体中文,1、港澳繁体,2、台湾正体
+     * @return
+     * @throws Exception
+     */
+    public static String ToSimplifiedChinese(String text, int srcType) throws Exception {
+        return Translate.ToSimplifiedChinese(text, srcType);
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/WordsMatch.java b/src/main/java/org/springblade/modules/words/WordsMatch.java
new file mode 100644
index 0000000..b44f180
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/WordsMatch.java
@@ -0,0 +1,304 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseMatch;
+import org.springblade.modules.words.internals.TrieNode3;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 文本搜索匹配, ,支持 部分 正则 如 . ? [ ] \ ( | ) ,不支持( )内再嵌套( )
+ */
+public class WordsMatch extends BaseMatch {
+
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public WordsSearchResult FindFirst(final String text) {
+        TrieNode3 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+
+            TrieNode3 tn;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    if (ptr.HasWildcard) {
+                        final WordsSearchResult result = FindFirst(text, i + 1, ptr.WildcardNode);
+                        if (result != null) {
+                            return result;
+                        }
+                    }
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    final Integer r = tn.Results.get(0);
+                    final int length = _keywordLength[r];
+                    final int start = i - length + 1;
+                    if (start >= 0) {
+                        final int kIndex = _keywordIndex[r];
+                        final String matchKeyword = _matchKeywords[kIndex];
+                        final String keyword = text.substring(start, i + 1);
+                        return new WordsSearchResult(keyword, start, i, kIndex, matchKeyword);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return null;
+    }
+
+    private WordsSearchResult FindFirst(final String text, final int index, TrieNode3 ptr) {
+        for (int i = index; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr.HasKey(t) == false) {
+                if (ptr.HasWildcard) {
+                    final WordsSearchResult result = FindFirst(text, i + 1, ptr.WildcardNode);
+                    if (result != null) {
+                        return result;
+                    }
+                }
+                return null;
+            }
+            tn = ptr.GetValue(t);
+
+            if (tn.End) {
+                final Integer r = tn.Results.get(0);
+                final int length = _keywordLength[r];
+                final int start = i - length + 1;
+                if (start >= 0) {
+                    final int kIndex = _keywordIndex[r];
+                    final String matchKeyword = _matchKeywords[kIndex];
+                    final String keyword = text.substring(start, i + 1);
+                    return new WordsSearchResult(keyword, start, i, kIndex, matchKeyword);
+                }
+            }
+            ptr = tn;
+        }
+        return null;
+    }
+
+    /**
+     * 在文本中查找所有的关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public List<WordsSearchResult> FindAll(final String text) {
+        TrieNode3 ptr = null;
+        final List<WordsSearchResult> result = new ArrayList<WordsSearchResult>();
+
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    if (ptr.HasWildcard) {
+                        FindAll(text, i + 1, ptr.WildcardNode, result);
+                    }
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    for (final Integer r : tn.Results) {
+                        final int length = _keywordLength[r];
+                        final int start = i - length + 1;
+                        if (start >= 0) {
+                            final int kIndex = _keywordIndex[r];
+                            final String matchKeyword = _matchKeywords[kIndex];
+                            final String keyword = text.substring(start, i + 1);
+                            final WordsSearchResult wr = new WordsSearchResult(keyword, start, i, kIndex, matchKeyword);
+                            result.add(wr);
+                        }
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return result;
+    }
+
+    private void FindAll(final String text, final int index, TrieNode3 ptr, final List<WordsSearchResult> result) {
+        for (int i = index; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr.HasKey(t) == false) {
+                if (ptr.HasWildcard) {
+                    FindAll(text, i + 1, ptr.WildcardNode, result);
+                }
+                return;
+            } else {
+                tn = ptr.GetValue(t);
+            }
+            if (tn.End) {
+                for (final Integer r : tn.Results) {
+                    final int length = _keywordLength[r];
+                    final int start = i - length + 1;
+                    if (start >= 0) {
+                        final int kIndex = _keywordIndex[r];
+                        final String matchKeyword = _matchKeywords[kIndex];
+                        final String keyword = text.substring(start, i + 1);
+                        final WordsSearchResult wr = new WordsSearchResult(keyword, start, i, kIndex, matchKeyword);
+                        result.add(wr);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public boolean ContainsAny(final String text) {
+        TrieNode3 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    if (ptr.HasWildcard) {
+                        final boolean result = ContainsAny(text, i + 1, ptr.WildcardNode);
+                        if (result) {
+                            return true;
+                        }
+                    }
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    final int length = _keywordLength[tn.Results.get(0)];
+                    final int s = i - length + 1;
+                    if (s >= 0) {
+                        return true;
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return false;
+    }
+
+    private boolean ContainsAny(final String text, final int index, TrieNode3 ptr) {
+        for (int i = index; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr.HasKey(t) == false) {
+                if (ptr.HasWildcard) {
+                    return ContainsAny(text, i + 1, ptr.WildcardNode);
+                }
+                return false;
+            }
+            tn = ptr.GetValue(t);
+
+            if (tn.End) {
+                final int length = _keywordLength[tn.Results.get(0)];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    return true;
+                }
+            }
+            ptr = tn;
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字, 替换符默认为 *
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 替换符
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        TrieNode3 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    if (ptr.HasWildcard) {
+                        Replace(text, i + 1, ptr.WildcardNode, replaceChar, result);
+                    }
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    final int maxLength = _keywordLength[tn.Results.get(0)];
+                    final int start = i + 1 - maxLength;
+                    if (start >= 0) {
+                        for (int j = start; j <= i; j++) {
+                            result.setCharAt(j, replaceChar);
+                        }
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return result.toString();
+    }
+
+    private void Replace(final String text, final int index, TrieNode3 ptr, final char replaceChar,
+                         final StringBuilder result) {
+        for (int i = index; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode3 tn;
+            if (ptr.HasKey(t) == false) {
+                if (ptr.HasWildcard) {
+                    Replace(text, i + 1, ptr.WildcardNode, replaceChar, result);
+                }
+                return;
+            }
+            tn = ptr.GetValue(t);
+            if (tn.End) {
+                final int maxLength = _keywordLength[tn.Results.get(0)];
+                final int start = i + 1 - maxLength;
+                if (start >= 0) {
+                    for (int j = start; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/WordsMatchEx.java b/src/main/java/org/springblade/modules/words/WordsMatchEx.java
new file mode 100644
index 0000000..abfae5b
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/WordsMatchEx.java
@@ -0,0 +1,358 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseMatchEx;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WordsMatchEx extends BaseMatchEx {
+
+    /// <summary>
+    /// 在文本中查找第一个关键字
+    /// </summary>
+    /// <param name="text">文本</param>
+    /// <returns></returns>
+    public WordsSearchResult FindFirst(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    if (_wildcard[p] > 0) {
+                        final WordsSearchResult r = FindFirst(text, i + 1, _wildcard[p]);
+                        if (r != null) {
+                            return r;
+                        }
+                    }
+                    next = _firstIndex[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                final int start = _end[next];
+                if (start < _end[next + 1]) {
+                    final int length = _keywordLength[_resultIndex[start]];
+                    final int s = i - length + 1;
+                    if (s >= 0) {
+                        final String key = text.substring(s, i + 1);
+                        final int index = _resultIndex[start];
+                        final String matchKeyword = _matchKeywords[index];
+                        return new WordsSearchResult(key, i + 1 - key.length(), i, index, matchKeyword);
+                    }
+                }
+            }
+            p = next;
+        }
+        return null;
+    }
+
+    private WordsSearchResult FindFirst(final String text, final int index, int p) {
+        for (int i = index; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                return null;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        final WordsSearchResult r = FindFirst(text, i + 1, _wildcard[p]);
+                        if (r != null) {
+                            return r;
+                        }
+                    }
+                    return null;
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+            final int start = _end[next];
+            if (start < _end[next + 1]) {
+                final int length = _keywordLength[_resultIndex[start]];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    final String key = text.substring(s, i + 1);
+                    final int index2 = _resultIndex[start];
+                    final String matchKeyword = _matchKeywords[index2];
+                    return new WordsSearchResult(key, i + 1 - key.length(), i, index2, matchKeyword);
+                }
+            }
+            p = next;
+        }
+        return null;
+    }
+
+    /// <summary>
+    /// 在文本中查找所有的关键字
+    /// </summary>
+    /// <param name="text">文本</param>
+    /// <returns></returns>
+    public List<WordsSearchResult> FindAll(final String text) {
+        final List<WordsSearchResult> result = new ArrayList<WordsSearchResult>();
+        int p = 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        FindAll(text, i + 1, _wildcard[p], result);
+                    }
+                    next = _firstIndex[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            if (next != 0) {
+                for (int j = _end[next]; j < _end[next + 1]; j++) {
+                    final int length = _keywordLength[_resultIndex[j]];
+                    final int s = i - length + 1;
+                    if (s >= 0) {
+                        final int kIndex = _keywordIndex[j];
+                        final String matchKeyword = _matchKeywords[kIndex];
+                        final String key = text.substring(s, i + 1);
+                        final WordsSearchResult r = new WordsSearchResult(key, s, i, kIndex, matchKeyword);
+                        result.add(r);
+                    }
+                }
+            }
+            p = next;
+        }
+        return result;
+    }
+
+    private void FindAll(final String text, final int index, int p, final List<WordsSearchResult> result) {
+        for (int i = index; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                return;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        FindAll(text, i + 1, _wildcard[p], result);
+                    }
+                    return;
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            for (int j = _end[next]; j < _end[next + 1]; j++) {
+                final int length = _keywordLength[_resultIndex[j]];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    final int kIndex = _keywordIndex[j];
+                    final String matchKeyword = _matchKeywords[kIndex];
+                    final String key = text.substring(s, i + 1);
+                    final WordsSearchResult r = new WordsSearchResult(key, s, i, kIndex, matchKeyword);
+                    result.add(r);
+                }
+            }
+            p = next;
+        }
+    }
+
+    /// <summary>
+    /// 判断文本是否包含关键字
+    /// </summary>
+    /// <param name="text">文本</param>
+    /// <returns></returns>
+    public boolean ContainsAny(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    if (_wildcard[p] > 0) {
+                        final boolean r = ContainsAny(text, i + 1, _wildcard[p]);
+                        if (r) {
+                            return true;
+                        }
+                    }
+                    next = _firstIndex[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+
+            if (next != 0) {
+                if (_end[next] < _end[next + 1]) {
+                    return true;
+                }
+            }
+            p = next;
+        }
+        return false;
+    }
+
+    private boolean ContainsAny(final String text, final int index, int p) {
+        for (int i = index; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+
+            final int t = _dict[t1];
+            if (t == 0) {
+                return false;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        final boolean r = ContainsAny(text, i + 1, _wildcard[p]);
+                        if (r) {
+                            return true;
+                        }
+                    }
+                    return false;
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            final int start = _end[next];
+            if (start < _end[next + 1]) {
+                final int length = _keywordLength[_resultIndex[start]];
+                final int s = i - length + 1;
+                if (s >= 0) {
+                    return true;
+                }
+            }
+            p = next;
+        }
+        return false;
+    }
+
+    /// <summary>
+    /// 在文本中替换所有的关键字
+    /// </summary>
+    /// <param name="text">文本</param>
+    /// <param name="replaceChar">替换符</param>
+    /// <returns></returns>
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        int p = 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        Replace(text, i + 1, _wildcard[p], replaceChar, result);
+                    }
+                    next = _firstIndex[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            if (next != 0) {
+                final int start = _end[next];
+                if (start < _end[next + 1]) {
+                    final int maxLength = _keywordLength[_resultIndex[start]];
+                    final int start2 = i + 1 - maxLength;
+                    if (start2 >= 0) {
+                        for (int j = start2; j <= i; j++) {
+                            result.setCharAt(j, replaceChar);
+                        }
+                    }
+                }
+            }
+            p = next;
+        }
+        return result.toString();
+    }
+
+    private void Replace(final String text, final int index, int p, final char replaceChar,
+            final StringBuilder result) {
+        for (int i = index; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+
+            final int t = _dict[t1];
+            if (t == 0) {
+                return;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _firstIndex[t];
+            } else {
+                final int index2 = _nextIndex[p].IndexOf(t);
+                if (index2 == -1) {
+                    if (_wildcard[p] > 0) {
+                        Replace(text, i + 1, _wildcard[p], replaceChar, result);
+                    }
+                    return;
+                } else {
+                    next = _nextIndex[p].GetValue(index2);
+                }
+            }
+
+            final int start = _end[next];
+            if (start < _end[next + 1]) {
+                final int maxLength = _keywordLength[_resultIndex[start]];
+                final int start2 = i + 1 - maxLength;
+                if (start2 >= 0) {
+                    for (int j = start2; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                    }
+                }
+            }
+            p = next;
+        }
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/WordsSearch.java b/src/main/java/org/springblade/modules/words/WordsSearch.java
new file mode 100644
index 0000000..bbeed91
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/WordsSearch.java
@@ -0,0 +1,158 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseSearch;
+import org.springblade.modules.words.internals.TrieNode2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WordsSearch extends BaseSearch {
+    public String[] _others;
+
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public WordsSearchResult FindFirst(final String text) {
+        TrieNode2 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode2 tn = null;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    for (final Integer index : tn.Results) {
+                        final String key = _keywords[index];
+                        return new WordsSearchResult(key, i + 1 - key.length(), i, index);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return null;
+    }
+
+    /**
+     * 在文本中查找所有的关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public List<WordsSearchResult> FindAll(final String text) {
+        TrieNode2 ptr = null;
+        final List<WordsSearchResult> list = new ArrayList<WordsSearchResult>();
+
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode2 tn = null;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    for (final Integer index : tn.Results) {
+                        final String key = _keywords[index];
+                        final WordsSearchResult item = new WordsSearchResult(key, i + 1 - key.length(), i, index);
+                        list.add(item);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return list;
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public boolean ContainsAny(final String text) {
+        TrieNode2 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode2 tn = null;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    return true;
+                }
+            }
+            ptr = tn;
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字, 替换符默认为 *
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 替换符
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        TrieNode2 ptr = null;
+        for (int i = 0; i < text.length(); i++) {
+            final char t = text.charAt(i);
+            TrieNode2 tn = null;
+            if (ptr == null) {
+                tn = _first[t];
+            } else {
+                if (ptr.HasKey(t) == false) {
+                    tn = _first[t];
+                } else {
+                    tn = ptr.GetValue(t);
+                }
+            }
+            if (tn != null) {
+                if (tn.End) {
+                    final int maxLength = _keywords[tn.Results.get(0)].length();
+                    final int start = i + 1 - maxLength;
+                    for (int j = start; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                    }
+                }
+            }
+            ptr = tn;
+        }
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/WordsSearchEx.java b/src/main/java/org/springblade/modules/words/WordsSearchEx.java
new file mode 100644
index 0000000..32115fe
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/WordsSearchEx.java
@@ -0,0 +1,178 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseSearchEx;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WordsSearchEx extends BaseSearchEx {
+ /**
+     * 在文本中查找所有的关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public List<WordsSearchResult> FindAll(final String text) {
+        final List<WordsSearchResult> result = new ArrayList<WordsSearchResult>();
+
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    next = _first[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                for (int j = _end[next]; j < _end[next + 1]; j++) {
+                    final int index = _resultIndex[j];
+                    final String key = _keywords[index];
+                    final WordsSearchResult r = new WordsSearchResult(key, i + 1 - key.length(), i, index);
+                    result.add(r);
+                }
+            }
+            p = next;
+        }
+        return result;
+    }
+
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public WordsSearchResult FindFirst(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    next = _first[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                final int start = _end[next];
+                if (start < _end[next + 1]) {
+                    final int index = _resultIndex[start];
+                    final String key = _keywords[index];
+                    return new WordsSearchResult(key, i + 1 - key.length(), i, index);
+                }
+            }
+            p = next;
+        }
+        return null;
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public boolean ContainsAny(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    next = _first[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                if (_end[next] < _end[next + 1]) {
+                    return true;
+                }
+            }
+            p = next;
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字, 替换符默认为 *
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 替换符
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final char t1 = text.charAt(i);
+            final int t = _dict[t1];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next;
+            if (p == 0 || t < _min[p] || t > _max[p]) {
+                next = _first[t];
+            } else {
+                final int index = _nextIndex[p].IndexOf(t);
+                if (index == -1) {
+                    next = _first[t];
+                } else {
+                    next = _nextIndex[p].GetValue(index);
+                }
+            }
+            if (next != 0) {
+                final int start = _end[next];
+                if (start < _end[next + 1]) {
+                    final int maxLength = _keywords[_resultIndex[start]].length();
+                    for (int j = i + 1 - maxLength; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                    }
+
+                }
+            }
+            p = next;
+        }
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/WordsSearchEx2.java b/src/main/java/org/springblade/modules/words/WordsSearchEx2.java
new file mode 100644
index 0000000..b029205
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/WordsSearchEx2.java
@@ -0,0 +1,170 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.internals.BaseSearchEx2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class WordsSearchEx2 extends BaseSearchEx2 {
+    /**
+     * 在文本中查找所有的关键字
+     * @param text 文本
+     * @return
+     */
+    public List<WordsSearchResult> FindAll(final String text) {
+        final List<WordsSearchResult> root = new ArrayList<WordsSearchResult>();
+        int p = 0;
+        final int length = text.length();
+        for (int i = 0; i < length; i++) {
+            final int t = _dict[text.charAt(i)];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next = _next[p] + t;
+            boolean find = _key[next] == t;
+            if (find == false && p != 0) {
+                p = 0;
+                next = _next[0] + t;
+                find = _key[next] == t;
+            }
+            if (find) {
+                final int index = _check[next];
+                if (index > 0) {
+                    for (final int item : _guides[index]) {
+                        final String key = _keywords[item];
+                        final WordsSearchResult r = new WordsSearchResult(key, i + 1 - key.length(), i, item);
+                        root.add(r);
+                    }
+                }
+                p = next;
+            }
+        }
+        return root;
+    }
+
+    /**
+     * 在文本中查找第一个关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public WordsSearchResult FindFirst(final String text) {
+        int p = 0;
+        final int length = text.length();
+        for (int i = 0; i < length; i++) {
+            final int t = _dict[text.charAt(i)];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next = _next[p] + t;
+            if (_key[next] == t) {
+                final int index = _check[next];
+                if (index > 0) {
+                    final String item = _keywords[_guides[index][0]];
+                    return new WordsSearchResult(item, i + 1 - item.length(), i, _guides[index][0]);
+                }
+                p = next;
+            } else {
+                p = 0;
+                next = _next[p] + t;
+                if (_key[next] == t) {
+                    final int index = _check[next];
+                    if (index > 0) {
+                        final String item = _keywords[_guides[index][0]];
+                        return new WordsSearchResult(item, i + 1 - item.length(), i, _guides[index][0]);
+                    }
+                    p = next;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 判断文本是否包含关键字
+     *
+     * @param text 文本
+     * @return
+     */
+    public boolean ContainsAny(final String text) {
+        int p = 0;
+        for (int i = 0; i < text.length(); i++) {
+            final int t = _dict[text.charAt(i)];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next = _next[p] + t;
+            if (_key[next] == t) {
+                if (_check[next] > 0) {
+                    return true;
+                }
+                p = next;
+            } else {
+                p = 0;
+                next = _next[p] + t;
+                if (_key[next] == t) {
+                    if (_check[next] > 0) {
+                        return true;
+                    }
+                    p = next;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 在文本中替换所有的关键字, 替换符默认为 *
+     *
+     * @param text 文本
+     * @return
+     */
+    public String Replace(final String text) {
+        return Replace(text, '*');
+    }
+
+    /**
+     * 在文本中替换所有的关键字
+     *
+     * @param text        文本
+     * @param replaceChar 替换符
+     * @return
+     */
+    public String Replace(final String text, final char replaceChar) {
+        final StringBuilder result = new StringBuilder(text);
+
+        int p = 0;
+
+        for (int i = 0; i < text.length(); i++) {
+            final int t = _dict[text.charAt(i)];
+            if (t == 0) {
+                p = 0;
+                continue;
+            }
+            int next = _next[p] + t;
+            boolean find = _key[next] == t;
+            if (find == false && p != 0) {
+                p = 0;
+                next = _next[p] + t;
+                find = _key[next] == t;
+            }
+            if (find) {
+                final int index = _check[next];
+                if (index > 0) {
+                    final int maxLength = _keywords[_guides[index][0]].length();
+                    final int start = i + 1 - maxLength;
+                    for (int j = start; j <= i; j++) {
+                        result.setCharAt(j, replaceChar);
+                     }
+                 }
+                 p = next;
+            }
+        }
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/WordsSearchResult.java b/src/main/java/org/springblade/modules/words/WordsSearchResult.java
new file mode 100644
index 0000000..f335a04
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/WordsSearchResult.java
@@ -0,0 +1,33 @@
+package org.springblade.modules.words;
+
+public class WordsSearchResult {
+
+    public WordsSearchResult(final String keyword, final int start, final int end, final int index) {
+        Keyword = keyword;
+        End = end;
+        Start = start;
+        Index = index;
+        MatchKeyword = keyword;
+    }
+
+    public WordsSearchResult(final String keyword, final int start, final int end, final int index,
+            final String matchKeyword) {
+        Keyword = keyword;
+        End = end;
+        Start = start;
+        Index = index;
+        MatchKeyword = matchKeyword;
+    }
+
+    /** 开始位置 */
+    public int Start;
+    /** 结束位置 */
+    public int End;
+    /** 关键字 */
+    public String Keyword;
+    /** 索引 */
+    public int Index;
+    /** 匹配关键字 */
+    public String MatchKeyword;
+
+}
diff --git a/src/main/java/org/springblade/modules/words/WorksService.java b/src/main/java/org/springblade/modules/words/WorksService.java
new file mode 100644
index 0000000..15b39f9
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/WorksService.java
@@ -0,0 +1,61 @@
+package org.springblade.modules.words;
+
+import org.springblade.modules.words.sensitiveword.service.ISensitivewordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 敏感词过滤服务
+ * @author zhongrj
+ * @date 2024-01-22
+ */
+@Component
+public class WorksService {
+
+	@Autowired
+	private ISensitivewordService sensitivewordService;
+
+
+	/**
+	 * 敏感词过滤
+	 * @param content
+	 * @return
+	 */
+	public Map<String,Object> interceptWords(String content) {
+		// 查询所有的敏感词数据集合
+		List<String> list = sensitivewordService.getBadSensitivewordList();
+		// 创建查询对象
+		StringSearch iwords = new StringSearch();
+		// 设置敏感词集合
+		iwords.SetKeywords(list);
+		// 判断是否存在敏感词
+		boolean b = iwords.ContainsAny(content);
+		// 创建map 对象返回
+		Map<String,Object> res = new HashMap(3);
+		// 设置是否有敏感词
+		res.put("iswords",String.valueOf(b));
+		if (b) {
+			// 在文本中替换所有的关键字
+			String str = iwords.Replace(content, '*');
+			// 设置替换后的内容
+			res.put("content", str);
+			String text = "";
+			// 在文本中查找所有的关键字
+			List<String> all = iwords.FindAll(content);
+			for (int i = 0; i < all.size(); i++) {
+				text += all.get(i) + ",";
+			}
+			String words = "";
+			if (!text.equals("")) {
+				words = text.substring(0, text.length() - 1);
+			}
+			// 设置找到的敏感关键字
+			res.put("words", words);
+		}
+		// 返回
+		return res;
+	}
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/BaseMatch.java b/src/main/java/org/springblade/modules/words/internals/BaseMatch.java
new file mode 100644
index 0000000..a24716c
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/BaseMatch.java
@@ -0,0 +1,457 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+public class BaseMatch {
+    protected TrieNode3[] _first;
+    protected int[] _keywordLength;
+    protected int[] _keywordIndex;
+    protected String[] _matchKeywords;
+
+    protected List<TrieNode> BuildFirstLayerTrieNode(List<String> keywords) {
+        TrieNode root = new TrieNode();
+
+        Map<Integer, List<TrieNode>> allNodeLayers = new Hashtable<Integer, List<TrieNode>>();
+        // 第一次关键字
+        for (int i = 0; i < keywords.size(); i++) {
+            String p = keywords.get(i);
+            TrieNode nd = root;
+            int start = 0;
+            while (p.charAt(start) == 0) { // 0 为 通配符
+                start++;
+            }
+            for (int j = start; j < p.length(); j++) {
+                nd = nd.Add(p.charAt(j));
+                if (nd.Layer == 0) {
+                    nd.Layer = j + 1 - start;
+                    if (allNodeLayers.containsKey(nd.Layer) == false) {
+                        List<TrieNode> nodes = new ArrayList<TrieNode>();
+                        nodes.add(nd);
+                        allNodeLayers.put(nd.Layer, nodes);
+                    } else {
+                        allNodeLayers.get(nd.Layer).add(nd);
+                    }
+                }
+            }
+            nd.SetResults(i);
+        }
+        Character z = 0;
+        // 第二次关键字 通配符
+        for (int i = 0; i < keywords.size(); i++) {
+            String p = keywords.get(i);
+            if (p.contains(z.toString()) == false) {
+                continue;
+            }
+            int start = 0;
+            while (p.charAt(start) == 0) { // 0 为 通配符
+                start++;
+            }
+            List<TrieNode> trieNodes = new ArrayList<TrieNode>();
+            trieNodes.add(root);
+
+            for (int j = start; j < p.length(); j++) {
+                List<TrieNode> newTrieNodes = new ArrayList<TrieNode>();
+                Character c = p.charAt(j);
+                if (c == 0) {
+                    for (TrieNode nd : trieNodes) {
+                        for (Character key : nd.m_values.keySet()) {
+                            newTrieNodes.add(nd.m_values.get(key));
+                        }
+                    }
+                } else {
+                    for (TrieNode nd : trieNodes) {
+                        TrieNode nd2 = nd.Add(c);
+                        if (nd2.Layer == 0) {
+                            nd2.Layer = j + 1 - start;
+                            if (allNodeLayers.containsKey(nd2.Layer) == false) {
+                                List<TrieNode> nodes = new ArrayList<TrieNode>();
+                                nodes.add(nd2);
+                                allNodeLayers.put(nd2.Layer, nodes);
+                            } else {
+                                allNodeLayers.get(nd2.Layer).add(nd2);
+                            }
+                            // List<TrieNode> tnodes;
+                            // if (allNodeLayers.TryGetValue(nd2.Layer, tnodes) == false) {
+                            // tnodes = new ArrayList<TrieNode>();
+                            // allNodeLayers[nd.Layer] = tnodes;
+                            // }
+                            // tnodes.add(nd2);
+                        }
+                        newTrieNodes.add(nd2);
+                    }
+                }
+                trieNodes = newTrieNodes;
+            }
+            for (TrieNode nd : trieNodes) {
+                nd.SetResults(i);
+            }
+        }
+
+        // 添加到 allNode
+        List<TrieNode> allNode = new ArrayList<TrieNode>();
+        allNode.add(root);
+        for (int i = 0; i < allNodeLayers.size(); i++) { // 注意 这里不能用 keySet()
+            List<TrieNode> nodes = allNodeLayers.get(i + 1);
+            for (int j = 0; j < nodes.size(); j++) {
+                allNode.add(nodes.get(j));
+            }
+        }
+        allNodeLayers.clear();
+        allNodeLayers = null;
+
+        // 第一次 Set Failure
+        for (int i = 1; i < allNode.size(); i++) {
+            TrieNode nd = allNode.get(i);
+            nd.Index = i;
+            TrieNode r = nd.Parent.Failure;
+            char c = nd.Char;
+            while (r != null && !r.m_values.containsKey(c))
+                r = r.Failure;
+            if (r == null)
+                nd.Failure = root;
+            else {
+                nd.Failure = r.m_values.get(c);
+                for (Integer result : nd.Failure.Results)
+                    nd.SetResults(result);
+            }
+        }
+
+        // 第二次 Set Failure
+        Character zore = 0;
+        for (int i = 1; i < allNode.size(); i++) {
+            TrieNode nd = allNode.get(i);
+            if (nd.Layer == 1) {
+                continue;
+            }
+
+            if (nd.m_values.containsKey(zore)) {
+                nd.HasWildcard = true;
+            }
+            if (nd.Failure.HasWildcard) {
+                nd.HasWildcard = true;
+            }
+            if (nd.Char == 0) {
+                nd.IsWildcard = true;
+                continue;
+            } else if (nd.Parent.IsWildcard) {
+                nd.IsWildcard = true;
+                nd.WildcardLayer = nd.Parent.WildcardLayer + 1;
+                if (nd.Failure != root) {
+                    if (nd.Failure.Layer <= nd.WildcardLayer) {
+                        nd.Failure = root;
+                    }
+                }
+                continue;
+            }
+        }
+        root.Failure = root;
+
+        return allNode;
+    }
+
+    protected boolean HasMatch(String keyword) {
+        for (int i = 0; i < keyword.length(); i++) {
+            Character c = keyword.charAt(i);
+            if (c == '.' || c == '?' || c == '\\' || c == '[' || c == '(') {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected List<String> MatchKeywordBuild(String keyword) throws Exception {
+        StringBuilder stringBuilder = new StringBuilder();
+        Map<Integer, List<String>> parameterDict = new Hashtable<Integer, List<String>>();
+        SeparateParameters(keyword, stringBuilder, parameterDict);
+
+        if (parameterDict.size() == 0) {
+            List<String> al = new ArrayList<String>();
+            al.add(stringBuilder.toString());
+            return al;
+        }
+        List<String> parameters = new ArrayList<String>();
+        KeywordBuild(parameterDict, 0, parameterDict.keySet().size() - 1, "", parameters);
+        String keywordFmt = stringBuilder.toString();
+        List<String> list = new ArrayList<String>();
+
+        String z = ((Character) (char) 0).toString();
+        for (int i = 0; i < parameters.size(); i++) {
+            String item = parameters.get(i);
+            String[] items = item.split(z);
+            Object[] ls = new Object[items.length];
+            for (int j = 0; j < ls.length; j++) {
+                ls[j] = items[j];
+            }
+            String t = String.format(keywordFmt, ls);
+            if (list.contains(t) == false) {
+                list.add(t);
+            }
+        }
+        return list;
+    }
+
+    private void SeparateParameters(String keyword, StringBuilder stringBuilder,
+            Map<Integer, List<String>> parameterDict) throws Exception {
+        int index = 0;
+        int parameterIndex = 0;
+        Character zore = 0;
+
+        while (index < keyword.length()) {
+            Character c = keyword.charAt(index);
+            if (c == '.') {
+                if (index + 1 < keyword.length() && keyword.charAt(index + 1) == '?') {
+                    List<String> lt = new ArrayList<String>();
+                    lt.add("");
+                    lt.add(zore.toString());
+                    parameterDict.put(parameterIndex, lt);
+                    stringBuilder.append("%" + parameterIndex + "$s");
+                    parameterIndex++;
+                    index += 2;
+
+                } else {
+                    stringBuilder.append(((char) 0));
+                    index++;
+                }
+            } else if (c == '\\') {
+                if (index + 2 < keyword.length() && keyword.charAt(index + 2) == '?') {
+                    List<String> lt = new ArrayList<String>();
+                    lt.add("");
+                    lt.add(((Character) keyword.charAt(index + 1)).toString());
+                    parameterDict.put(parameterIndex, lt);
+                    stringBuilder.append("%" + parameterIndex + "$s");
+                    parameterIndex++;
+                    index += 3;
+                } else if (index + 1 < keyword.length()) {
+                    stringBuilder.append(keyword.charAt(index + 1));
+                    index += 2;
+                } else {
+                    throw new Exception("【{keyword}】出错了,最后一位为\\");
+                }
+            } else if (c == '[') {
+                index++;
+                List<String> ps = new ArrayList<String>();
+                while (index < keyword.length()) {
+                    c = keyword.charAt(index);
+                    if (c == ']') {
+                        break;
+                    } else if (c == '\\') {
+                        if (index + 1 < keyword.length()) {
+                            ps.add(((Character) keyword.charAt(index + 1)).toString());
+                            index += 2;
+                        }
+                    } else {
+                        ps.add(c.toString());
+                        index++;
+                    }
+                }
+                if (c != ']') {
+                    throw new Exception("【{keyword}】出错了,最后一位不为]");
+                }
+                if (index + 1 < keyword.length() && keyword.charAt(index + 1) == '?') {
+                    ps.add("");
+                    parameterDict.put(parameterIndex, ps);
+                    stringBuilder.append("%" + parameterIndex + "$s");
+                    parameterIndex++;
+                    index += 2;
+                } else {
+                    parameterDict.put(parameterIndex, ps);
+                    stringBuilder.append("%" + parameterIndex + "$s");
+                    parameterIndex++;
+                    index++;
+                }
+            } else if (c == '(') {
+                index++;
+                List<String> ps = new ArrayList<String>();
+                String words = "";
+                while (index < keyword.length()) {
+                    c = keyword.charAt(index);
+                    if (c == ')') {
+                        break;
+                    } else if (c == '|') {
+                        ps.add(words);
+                        words = "";
+                        index++;
+                    } else if (c == '\\') {
+                        if (index + 1 < keyword.length()) {
+                            words += keyword.charAt(index + 1);
+                            index += 2;
+                        }
+                    } else {
+                        words += c;
+                        index++;
+                    }
+                }
+                ps.add(words);
+                if (c != ')') {
+                    throw new Exception("【{keyword}】出错了,最后一位不为)");
+                }
+                if (index + 1 < keyword.length() && keyword.charAt(index + 1) == '?') {
+                    ps.add("");
+                    parameterDict.put(parameterIndex, ps);
+                    stringBuilder.append("%" + parameterIndex + "$s");
+                    parameterIndex++;
+                    index += 2;
+                } else {
+                    parameterDict.put(parameterIndex, ps);
+                    stringBuilder.append("%" + parameterIndex + "$s");
+                    parameterIndex++;
+                    index++;
+                }
+            } else {
+                if (index + 1 < keyword.length() && keyword.charAt(index + 1) == '?') {
+                    List<String> lt = new ArrayList<String>();
+                    lt.add("");
+                    lt.add(c.toString());
+                    parameterDict.put(parameterIndex, lt);
+                    stringBuilder.append("%" + parameterIndex + "$s");
+                    parameterIndex++;
+                    index += 2;
+                } else {
+                    if (c == '{') {
+                        stringBuilder.append("{{");
+                    } else if (c == '}') {
+                        stringBuilder.append("}}");
+                    } else {
+                        stringBuilder.append(c);
+                    }
+                    index++;
+                }
+            }
+        }
+
+    }
+
+    private static void KeywordBuild(Map<Integer, List<String>> parameterDict, int index, int end, String keyword,
+            List<String> result) {
+        Character span = (char) 1;
+        List<String> list = parameterDict.get(index);
+        if (index == end) {
+            for (int i = 0; i < list.size(); i++) {
+                String item = list.get(i);
+                result.add((keyword + span + item).substring(1));
+            }
+        } else {
+            for (int i = 0; i < list.size(); i++) {
+                String item = list.get(i);
+                KeywordBuild(parameterDict, index + 1, end, keyword + span + item, result);
+            }
+        }
+    }
+
+    /**
+     * 设置关键字
+     *
+     * @param keywords 关键字列表
+     * @throws Exception
+     */
+    public void SetKeywords(List<String> keywords) throws Exception {
+        _matchKeywords = keywords.toArray(new String[0]);
+        List<String> newKeyword = new ArrayList<String>();
+        List<Integer> newKeywordLength = new ArrayList<Integer>();
+        List<Integer> newKeywordIndex = new ArrayList<Integer>();
+        Integer index = 0;
+        for (String keyword : keywords) {
+            if (HasMatch(keyword) == false) {
+                newKeyword.add(keyword);
+                newKeywordLength.add(keyword.length());
+                newKeywordIndex.add(index);
+            } else {
+                List<String> list = MatchKeywordBuild(keyword);
+                for (String item : list) {
+                    newKeyword.add(item);
+                    newKeywordLength.add(item.length());
+                    newKeywordIndex.add(index);
+                }
+            }
+            index++;
+        }
+        _keywordLength = new int[newKeywordLength.size()];
+        for (int i = 0; i < _keywordLength.length; i++) {
+            _keywordLength[i] = newKeywordLength.get(i);
+        }
+        _keywordIndex = new int[newKeywordIndex.size()];
+        for (int j = 0; j < _keywordIndex.length; j++) {
+            _keywordIndex[j] = newKeywordIndex.get(j);
+        }
+
+        SetKeywords2(newKeyword);
+    }
+
+    protected void SetKeywords2(List<String> keywords) {
+        List<TrieNode> allNode = BuildFirstLayerTrieNode(keywords);
+        TrieNode root = allNode.get(0);
+
+        List<TrieNode3> allNode2 = new ArrayList<TrieNode3>();
+        for (int i = 0; i < allNode.size(); i++) {
+            allNode2.add(new TrieNode3());
+        }
+
+        for (int i = 0; i < allNode2.size(); i++) {
+            TrieNode oldNode = allNode.get(i);
+            TrieNode3 newNode = allNode2.get(i);
+
+            for (Character key : oldNode.m_values.keySet()) {
+                int index = oldNode.m_values.get(key).Index;
+                if (key == 0) {
+                    newNode.HasWildcard = true;
+                    newNode.WildcardNode = allNode2.get(index);
+                    continue;
+                }
+                newNode.Add(key, allNode2.get(index));
+            }
+            for (Integer item : oldNode.Results) {
+                if (oldNode.IsWildcard) {
+                    if (keywords.get(item).length() > oldNode.WildcardLayer) {
+                        newNode.SetResults(item);
+                    }
+                } else {
+                    newNode.SetResults(item);
+                }
+            }
+
+            TrieNode failure = oldNode.Failure;
+            while (failure != root) {
+                if (oldNode.IsWildcard && failure.Layer <= oldNode.WildcardLayer) {
+                    break;
+                }
+                for (Character key : failure.m_values.keySet()) {
+                    int index = failure.m_values.get(key).Index;
+                    if (key == 0) {
+                        newNode.HasWildcard = true;
+                        if (newNode.WildcardNode == null) {
+                            newNode.WildcardNode = allNode2.get(index);
+                        }
+                        continue;
+                    }
+                    if (newNode.HasKey(key) == false) {
+                        newNode.Add(key, allNode2.get(index));
+                    }
+                }
+                for (Integer item : failure.Results) {
+                    if (oldNode.IsWildcard) {
+                        if (keywords.get(item).length() > oldNode.WildcardLayer) {
+                            newNode.SetResults(item);
+                        }
+                    } else {
+                        newNode.SetResults(item);
+                    }
+                }
+                failure = failure.Failure;
+            }
+        }
+        allNode.clear();
+        allNode = null;
+        root = null;
+
+        // var root2 = allNode2[0];
+        TrieNode3[] first = new TrieNode3[Character.MAX_VALUE + 1];
+        for (Character key : allNode2.get(0).m_values.keySet()) {
+            first[key] = allNode2.get(0).m_values.get(key);
+        }
+        _first = first;
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/BaseMatchEx.java b/src/main/java/org/springblade/modules/words/internals/BaseMatchEx.java
new file mode 100644
index 0000000..214050e
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/BaseMatchEx.java
@@ -0,0 +1,192 @@
+package org.springblade.modules.words.internals;
+
+import java.util.*;
+
+import static java.util.stream.Collectors.toMap;
+
+public class BaseMatchEx extends BaseMatch {
+    protected int[] _dict;
+    protected int[] _firstIndex;
+    protected int[] _min;
+    protected int[] _max;
+
+    protected IntDictionary[] _nextIndex;
+    protected int[] _wildcard;
+    protected int[] _end;
+    protected int[] _resultIndex;
+
+
+    @Override
+    protected   void SetKeywords2(List<String> keywords)
+    {
+        List<TrieNode> allNode = BuildFirstLayerTrieNode(keywords);
+        TrieNode root = allNode.get(0);
+
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 1; i < allNode.size(); i++) {
+            stringBuilder.append(allNode.get(i).Char);
+        }
+        CreateDict(stringBuilder.toString());
+        stringBuilder = null;
+
+
+        List<TrieNode3Ex> allNode2 = new ArrayList<TrieNode3Ex>();
+        for (int i = 0; i < allNode.size(); i++) {
+            TrieNode3Ex node3=new TrieNode3Ex();
+            node3.Index=i;
+            allNode2.add(node3); ;
+        }
+
+        for (int i = 0; i < allNode2.size(); i++) {
+            TrieNode oldNode = allNode.get(i);
+            TrieNode3Ex newNode = allNode2.get(i);
+
+            for (Character item : oldNode.m_values.keySet()) {
+                int key = _dict[item];
+                int index = oldNode.m_values.get(item).Index;
+                if (key == 0) {
+                    newNode.HasWildcard = true;
+                    newNode.WildcardNode = allNode2.get(index) ;
+                    continue;
+                }
+                newNode.Add((char)key, allNode2.get(index) );
+            }
+            for (int item : oldNode.Results) {
+                if (oldNode.IsWildcard) {
+                    if (keywords.get(item).length() > oldNode.WildcardLayer) {
+                        newNode.SetResults(item);
+                    }
+                } else {
+                    newNode.SetResults(item);
+                }
+                //newNode.SetResults(item);
+            }
+
+            TrieNode failure = oldNode.Failure;
+            while (failure != root) {
+                if (oldNode.IsWildcard && failure.Layer <= oldNode.WildcardLayer) {
+                    break;
+                }
+                for (Character item : failure.m_values.keySet()) {
+                    int key = _dict[item];
+                    int index = failure.m_values.get(item).Index;
+                    if (key == 0) {
+                        newNode.HasWildcard = true;
+                        if (newNode.WildcardNode == null) {
+                            newNode.WildcardNode = allNode2.get(index);
+                        }
+                        continue;
+                    }
+                    if (newNode.HasKey((char)key) == false) {
+                        newNode.Add((char)key, allNode2.get(index));
+                    }
+                }
+                for (int item : failure.Results) {
+                    if (oldNode.IsWildcard) {
+                        if (keywords.get(item).length() > oldNode.WildcardLayer) {
+                            newNode.SetResults(item);
+                        }
+                    } else {
+                        newNode.SetResults(item);
+                    }
+                }
+                failure = failure.Failure;
+            }
+        }
+        allNode.clear();
+        allNode = null;
+        root = null;
+
+
+        List<Integer> min = new ArrayList<Integer>();
+        List<Integer> max = new ArrayList<Integer>();
+        List<Integer> wildcard = new ArrayList<Integer>();
+        List<Map<Integer, Integer>> nextIndexs = new ArrayList<Map<Integer, Integer>>();
+        List<Integer> end = new ArrayList<Integer>() ;
+        end.add(0);
+        List<Integer> resultIndex = new ArrayList<Integer>();
+        for (int i = 0; i < allNode2.size(); i++) {
+            Map<Integer, Integer> dict = new HashMap<Integer, Integer>();
+            TrieNode3Ex node = allNode2.get(i);
+            min.add(node.minflag);
+            max.add(node.maxflag);
+
+            if (node.HasWildcard) {
+                wildcard.add(node.WildcardNode.Index);
+            } else {
+                wildcard.add(0);
+            }
+
+            if (i > 0) {
+                for (Character item : node.m_values.keySet()) {
+                    dict.put((Integer)(int)(item) , node.m_values.get(item).Index);
+                }
+            }
+            for (int item : node.Results) {
+                resultIndex.add(item);
+            }
+            end.add(resultIndex.size());
+            nextIndexs.add(dict);
+        }
+        int[] first = new int[Character.MAX_VALUE + 1];
+        for (Character item : allNode2.get(0).m_values.keySet()) {
+            first[item] = allNode2.get(0).m_values.get(item).Index;
+        }
+
+        _firstIndex = first;
+        _min = new int[min.size()];
+        _max = new int[min.size()];
+        for (int i = 0; i < min.size(); i++) {
+            _min[i] = (int) (min.get(i));
+            _max[i] = (int) (max.get(i));
+        }
+        _nextIndex = new IntDictionary[nextIndexs.size()];
+        for (int i = 0; i < nextIndexs.size(); i++) {
+            IntDictionary dictionary = new IntDictionary();
+            dictionary.SetDictionary(nextIndexs.get(i));
+            _nextIndex[i] = dictionary;
+        }
+        _wildcard= new int[wildcard.size()];
+        for (int i = 0; i < wildcard.size(); i++) {
+            _wildcard[i] = (int) (wildcard.get(i));
+        }
+        _end = new int[end.size()];
+        for (int i = 0; i < end.size(); i++) {
+            _end[i] = (int) (end.get(i));
+        }
+        _resultIndex = new int[resultIndex.size()];
+        for (int i = 0; i < resultIndex.size(); i++) {
+            _resultIndex[i] = (int) (resultIndex.get(i));
+        }
+        allNode2.clear();
+        allNode2 = null;
+    }
+
+    private int CreateDict(String keywords) {
+        Map<Character, Integer> dictionary = new Hashtable<Character, Integer>();
+        for (int i = 0; i < keywords.length(); i++) {
+            Character item = keywords.charAt(i);
+            if (dictionary.containsKey(item)) {
+                dictionary.put(item, dictionary.get(item) + 1);
+            } else {
+                dictionary.put(item, 1);
+            }
+        }
+        Map<Character, Integer> dictionary2 = dictionary.entrySet().stream()
+                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
+                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2, LinkedHashMap::new));
+
+        List<Character> list2 = new ArrayList<Character>();
+        for (Character item : dictionary2.keySet()) {
+            list2.add(item);
+        }
+
+        _dict = new int[Character.MAX_VALUE + 1];
+        for (int i = 0; i < list2.size(); i++) {
+            _dict[list2.get(i)] = i + 1;
+        }
+        return dictionary.size();
+    }
+
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/BasePinyinMatch.java b/src/main/java/org/springblade/modules/words/internals/BasePinyinMatch.java
new file mode 100644
index 0000000..107713b
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/BasePinyinMatch.java
@@ -0,0 +1,269 @@
+package org.springblade.modules.words.internals;
+
+import org.springblade.modules.words.WordsSearch;
+import org.springblade.modules.words.WordsSearchResult;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class BasePinyinMatch {
+
+    public class PinyinSearch extends BaseSearch {
+        String[][] _keywordPinyins;
+        int[] _indexs;
+
+        public void SetIndexs(final int[] indexs) {
+            _indexs = indexs;
+        }
+        public void SetIndexs(List<Integer> indexs) {
+            _indexs=new int[indexs.size()];
+            for (int i = 0; i < indexs.size(); i++) {
+                _indexs[i]=indexs.get(i);
+            }
+        }
+
+        public void SetKeywords2(List<TwoTuple<String, String[]>> keywords) {
+            _keywords = new String[keywords.size()];
+            _keywordPinyins = new String[keywords.size()][];
+            for (int i = 0; i < keywords.size(); i++) {
+                _keywords[i] = keywords.get(i).Item1;
+                _keywordPinyins[i] = keywords.get(i).Item2;
+            }
+            SetKeywords();
+        }
+
+        public boolean Find(final String text, final String hz, final String[] pinyins) {
+            TrieNode2 ptr = null;
+            for (int i = 0; i < text.length(); i++) {
+                final Character t = text.charAt(i);
+                TrieNode2 tn;
+                if (ptr == null) {
+                    tn = _first[t];
+                } else {
+                    if (ptr.HasKey(t) == false) {
+                        tn = _first[t];
+                    } else {
+                        tn = ptr.GetValue(t);
+                    }
+                }
+                if (tn != null) {
+                    if (tn.End) {
+                        for (final int result : tn.Results) {
+                            final String keyword = _keywords[result];
+                            final int start = i + 1 - keyword.length();
+                            boolean isok = true;
+                            final String[] keywordPinyins = _keywordPinyins[result];
+
+                            for (int j = 0; j < keyword.length(); j++) {
+                                final int idx = start + j;
+                                final String py = keywordPinyins[j];
+                                if (py.length() == 1 && py.charAt(0) >= 0x3400 && py.charAt(0) <= 0x9fd5) {
+                                    if (hz.charAt(idx) != py.charAt(0)) {
+                                        isok = false;
+                                        break;
+                                    }
+                                } else {
+                                    if (pinyins[idx].startsWith(py) == false) {
+                                        isok = false;
+                                        break;
+                                    }
+                                }
+                            }
+                            if (isok) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+                ptr = tn;
+            }
+            return false;
+        }
+
+        public boolean Find2(final String text, final String hz, final String[] pinyins, final int keysCount) {
+            int findCount = 0;
+            int lastWordsIndex = -1;
+            TrieNode2 ptr = null;
+            for (int i = 0; i < text.length(); i++) {
+                final Character t = text.charAt(i);
+                TrieNode2 tn;
+                if (ptr == null) {
+                    tn = _first[t];
+                } else {
+                    if (ptr.HasKey(t) == false) {
+                        tn = _first[t];
+                    } else {
+                        tn = ptr.GetValue(t);
+                    }
+                }
+                if (tn != null) {
+                    if (tn.End) {
+                        for (final Integer result : tn.Results) {
+                            final int index = _indexs[result];
+                            if (index != findCount) {
+                                continue;
+                            }
+
+                            final String keyword = _keywords[result];
+                            final int start = i + 1 - keyword.length();
+                            if (lastWordsIndex >= start) {
+                                continue;
+                            }
+
+                            boolean isok = true;
+                            final String[] keywordPinyins = _keywordPinyins[result];
+
+                            for (int j = 0; j < keyword.length(); j++) {
+                                final int idx = start + j;
+                                final String py = keywordPinyins[j];
+                                if (py.length() == 1 && py.charAt(0) >= 0x3400 && py.charAt(0) <= 0x9fd5) {
+                                    if (hz.charAt(idx) != py.charAt(0)) {
+                                        isok = false;
+                                        break;
+                                    }
+                                } else {
+                                    if (pinyins[idx].startsWith(py) == false) {
+                                        isok = false;
+                                        break;
+                                    }
+                                }
+                            }
+                            if (isok) {
+                                findCount++;
+                                lastWordsIndex = i;
+                                if (findCount == keysCount) {
+                                    return true;
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+                ptr = tn;
+            }
+            return false;
+        }
+
+    }
+
+    protected void MergeKeywords(String[] keys, int id, String keyword, List<TwoTuple<String, String[]>> list)
+            throws NumberFormatException, IOException {
+        if (id >= keys.length) {
+            TwoTuple<String, String[]> tuple = new TwoTuple<String, String[]>(keyword, keys);
+            list.add(tuple);
+            return;
+        }
+        String key = keys[id];
+        if (key.charAt(0) >= 0x3400 && key.charAt(0) <= 0x9fd5) {
+            List<String> all = PinyinDict.GetAllPinyin(key.charAt(0), 0);
+            Set<Character> fpy = new HashSet<Character>();
+            for (String item : all) {
+                fpy.add(item.charAt(0));
+            }
+            for (Character item : fpy) {
+                MergeKeywords(keys, id + 1, keyword + item, list);
+            }
+        } else {
+            MergeKeywords(keys, id + 1, keyword + key.charAt(0), list);
+        }
+    }
+
+    protected void MergeKeywords(String[] keys, int id, String keyword, List<TwoTuple<String, String[]>> list,
+            int index, List<Integer> indexs) throws NumberFormatException, IOException {
+        if (id >= keys.length) {
+            TwoTuple<String, String[]> tuple = new TwoTuple<String, String[]>(keyword, keys);
+            list.add(tuple);
+            indexs.add(index);
+            return;
+        }
+        String key = keys[id];
+        if (key.charAt(0) >= 0x3400 && key.charAt(0) <= 0x9fd5) {
+            List<String> all = PinyinDict.GetAllPinyin(key.charAt(0), 0);
+            Set<Character> fpy = new HashSet<Character>();
+            for (String item : all) {
+                fpy.add(item.charAt(0));
+            }
+            for (Character item : fpy) {
+                MergeKeywords(keys, id + 1, keyword + item, list, index, indexs);
+            }
+        } else {
+            MergeKeywords(keys, id + 1, keyword + key.charAt(0), list, index, indexs);
+        }
+    }
+
+    protected List<String> SplitKeywords(String key) throws NumberFormatException, IOException {
+        InitPinyinSearch();
+        List<TextNode> textNodes = new ArrayList<TextNode>();
+        for (int i = 0; i <= key.length(); i++) {
+            textNodes.add(new TextNode());
+        }
+        textNodes.get(textNodes.size() - 1).End = true;
+
+        for (int i = 0; i < key.length(); i++) {
+            TextLine line = new TextLine();
+            line.Next = textNodes.get(i + 1);
+            line.Words = ((Character) key.charAt(i)).toString();
+            textNodes.get(i).Children.add(line);
+        }
+
+        List<WordsSearchResult> all = _wordsSearch.FindAll(key);
+        for (WordsSearchResult searchResult : all) {
+            TextLine line = new TextLine();
+            line.Next = textNodes.get(searchResult.End + 1);
+            line.Words = searchResult.Keyword;
+            textNodes.get(searchResult.Start).Children.add(line);
+        }
+
+        List<String> list = new ArrayList<String>();
+        BuildKsywords(textNodes.get(0), 0, "", list);
+        return list;
+    }
+
+    private void BuildKsywords(TextNode textNode, int id, String keywords, List<String> list) {
+        if (textNode.End) {
+            String k = keywords.substring(1);
+            if (list.contains(k) == false) {
+                list.add(k);
+            }
+            return;
+        }
+        for (TextLine item : textNode.Children) {
+            BuildKsywords(item.Next, id + 1, keywords + (char) 0 + item.Words, list);
+        }
+    }
+
+    class TextNode {
+        public boolean End;
+        public List<TextLine> Children = new ArrayList<TextLine>();
+    }
+
+    class TextLine {
+        public String Words;
+        public TextNode Next;
+    }
+
+    private static WordsSearch _wordsSearch;
+
+    private void InitPinyinSearch() throws NumberFormatException, IOException {
+        if (_wordsSearch == null) {
+            List<String> allPinyins = new ArrayList<String>();
+            String[] pys = PinyinDict.getPyShow();
+            for (int i = 1; i < pys.length; i += 2) {
+                String py = pys[i].toUpperCase();
+                for (int j = 1; j <= py.length(); j++) {
+                    String key = py.substring(0, j);
+                    if (allPinyins.contains(key) == false) {
+                        allPinyins.add(key);
+                    }
+
+                }
+            }
+            WordsSearch wordsSearch = new WordsSearch();
+            wordsSearch.SetKeywords(allPinyins);
+            _wordsSearch = wordsSearch;
+        }
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/BaseSearch.java b/src/main/java/org/springblade/modules/words/internals/BaseSearch.java
new file mode 100644
index 0000000..16856d3
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/BaseSearch.java
@@ -0,0 +1,119 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+public class BaseSearch {
+    protected TrieNode2[] _first = new TrieNode2[Character.MAX_VALUE + 1];
+    protected String[] _keywords;
+
+
+    /**
+     * 设置关键字
+     *
+     * @param keywords 关键字列表
+     */
+    public void SetKeywords(List<String> keywords) {
+        _keywords = new String[keywords.size()];
+        _keywords = keywords.toArray(_keywords);
+        SetKeywords();
+    }
+
+    protected void SetKeywords() {
+        TrieNode root = new TrieNode();
+        Map<Integer, List<TrieNode>> allNodeLayers = new Hashtable<Integer, List<TrieNode>>();
+        for (int i = 0; i < _keywords.length; i++) {
+            String p = _keywords[i];
+            TrieNode nd = root;
+            for (int j = 0; j < p.length(); j++) {
+                nd = nd.Add(p.charAt(j));
+                if (nd.Layer == 0) {
+                    nd.Layer = j + 1;
+                    if (allNodeLayers.containsKey(nd.Layer) == false) {
+                        List<TrieNode> nodes = new ArrayList<TrieNode>();
+                        nodes.add(nd);
+                        allNodeLayers.put(nd.Layer, nodes);
+                    } else {
+                        allNodeLayers.get(nd.Layer).add(nd);
+                    }
+                }
+            }
+            nd.SetResults(i);
+        }
+
+        List<TrieNode> allNode = new ArrayList<TrieNode>();
+        allNode.add(root);
+        for (int i = 0; i < allNodeLayers.size(); i++) { // 注意 这里不能用 keySet()
+            List<TrieNode> nodes = allNodeLayers.get(i + 1);
+            for (int j = 0; j < nodes.size(); j++) {
+                allNode.add(nodes.get(j));
+            }
+        }
+        allNodeLayers.clear();
+        allNodeLayers = null;
+
+        for (int i = 1; i < allNode.size(); i++) {
+            TrieNode nd = allNode.get(i);
+            nd.Index = i;
+            TrieNode r = nd.Parent.Failure;
+            Character c = nd.Char;
+            while (r != null && !r.m_values.containsKey(c))
+                r = r.Failure;
+            if (r == null)
+                nd.Failure = root;
+            else {
+                nd.Failure = r.m_values.get(c);
+                for (Integer result : nd.Failure.Results) {
+                    nd.SetResults(result);
+                }
+            }
+        }
+        root.Failure = root;
+
+        List<TrieNode2> allNode2 = new ArrayList<TrieNode2>();
+        for (int i = 0; i < allNode.size(); i++) {
+            allNode2.add(new TrieNode2());
+        }
+        for (int i = 0; i < allNode2.size(); i++) {
+            TrieNode oldNode = allNode.get(i);
+            TrieNode2 newNode = allNode2.get(i);
+
+            for (Character key : oldNode.m_values.keySet()) {
+                TrieNode nd = oldNode.m_values.get(key);
+                newNode.Add(key, allNode2.get(nd.Index));
+            }
+            oldNode.Results.forEach(item -> {
+                newNode.SetResults(item);
+            });
+
+            oldNode = oldNode.Failure;
+            while (oldNode != root) {
+                for (Character key : oldNode.m_values.keySet()) {
+                    TrieNode nd = oldNode.m_values.get(key);
+                    if (newNode.HasKey(key) == false) {
+                        newNode.Add(key, allNode2.get(nd.Index));
+                    }
+                }
+                oldNode.Results.forEach(item -> {
+                    newNode.SetResults(item);
+                });
+                oldNode = oldNode.Failure;
+            }
+        }
+        allNode.clear();
+        allNode = null;
+        root = null;
+
+        TrieNode2[] first = new TrieNode2[Character.MAX_VALUE + 1];
+        TrieNode2 root2 = allNode2.get(0);
+        for (Character key : root2.m_values.keySet()) {
+            TrieNode2 nd = root2.m_values.get(key);
+            first[(int) key] = nd;
+        }
+        _first = first;
+    }
+
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/BaseSearchEx.java b/src/main/java/org/springblade/modules/words/internals/BaseSearchEx.java
new file mode 100644
index 0000000..0520163
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/BaseSearchEx.java
@@ -0,0 +1,347 @@
+package org.springblade.modules.words.internals;
+
+import org.springblade.modules.words.NumHelper;
+
+import java.io.*;
+import java.util.*;
+
+import static java.util.stream.Collectors.toMap;
+
+public class BaseSearchEx {
+    protected int[] _dict;
+    protected int[] _first;
+    protected int[] _min;
+    protected int[] _max;
+
+    protected IntDictionary[] _nextIndex;
+    protected int[] _end;
+    protected int[] _resultIndex;
+    protected String[] _keywords;
+
+    /**
+     * 保存, 修改于2020-08-06,使用utf-8保存,与以前数据可能会不同
+     *
+     * @param filePath 文件地址
+     * @throws IOException
+     */
+    public void Save(String filePath) throws IOException {
+        File fi = new File(filePath);
+        FileOutputStream fs = new FileOutputStream(fi);
+        Save(fs);
+        fs.close();
+    }
+
+    protected void Save(FileOutputStream bw) throws IOException {
+        bw.write(NumHelper.serialize(_keywords.length));
+        for (String item : _keywords) {
+            byte[] bytes = item.getBytes("utf-8");
+            bw.write(NumHelper.serialize(bytes.length));
+            bw.write(bytes);
+        }
+
+        bw.write(NumHelper.serialize(_dict.length));
+        for (int item : _dict) {
+            bw.write(NumHelper.serialize(item));
+        }
+
+        bw.write(NumHelper.serialize(_first.length));
+        for (int item : _first) {
+            bw.write(NumHelper.serialize(item));
+        }
+        bw.write(NumHelper.serialize(_min.length));
+        for (int item : _min) {
+            bw.write(NumHelper.serialize(item));
+        }
+        bw.write(NumHelper.serialize(_max.length));
+        for (int item : _max) {
+            bw.write(NumHelper.serialize(item));
+        }
+        bw.write(NumHelper.serialize(_end.length));
+        for (int item : _end) {
+            bw.write(NumHelper.serialize(item));
+        }
+        bw.write(NumHelper.serialize(_resultIndex.length));
+        for (int item : _resultIndex) {
+            bw.write(NumHelper.serialize(item));
+        }
+
+        bw.write(NumHelper.serialize(_nextIndex.length));
+        for (int i = 0; i < _nextIndex.length; i++) {
+            int[] keys = _nextIndex[i].getKeys();
+            bw.write(NumHelper.serialize(keys.length));
+            for (int item : keys) {
+                bw.write(NumHelper.serialize(item));
+            }
+
+            int[] values = _nextIndex[i].getValues();
+            bw.write(NumHelper.serialize(values.length));
+            for (int item : values) {
+                bw.write(NumHelper.serialize(item));
+            }
+        }
+    }
+
+    /**
+     * 加载, 修改于2020-08-06,使用utf-8加载,加载以前数据可能会出错
+     *
+     * @param filePath
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void Load(String filePath) throws FileNotFoundException, IOException {
+        File fi = new File(filePath);
+        InputStream in = new BufferedInputStream(new FileInputStream(fi));
+        Load(in);
+        in.close();
+    }
+
+    public void Load(InputStream br) throws IOException {
+        int length = NumHelper.read(br);
+        _keywords = new String[length];
+        for (int i = 0; i < length; i++) {
+            int l = NumHelper.read(br);
+            byte[] bytes = new byte[l];
+            br.read(bytes, 0, l);
+            _keywords[i] = new String(bytes,"utf-8");
+        }
+
+        length = NumHelper.read(br);
+        _dict = new int[length];
+        for (int i = 0; i < length; i++) {
+            _dict[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _first = new int[length];
+        for (int i = 0; i < length; i++) {
+            _first[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _min = new int[length];
+        for (int i = 0; i < length; i++) {
+            _min[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _max = new int[length];
+        for (int i = 0; i < length; i++) {
+            _max[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _end = new int[length];
+        for (int i = 0; i < length; i++) {
+            _end[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _resultIndex = new int[length];
+        for (int i = 0; i < length; i++) {
+            _resultIndex[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _nextIndex = new IntDictionary[length];
+        for (int i = 0; i < length; i++) {
+            int l2 = NumHelper.read(br);
+            int[] keys = new int[l2];
+            for (int j = 0; j < keys.length; j++) {
+                keys[j] = NumHelper.read(br);
+            }
+
+            l2 = NumHelper.read(br);
+            int[] values = new int[l2];
+            for (int j = 0; j < values.length; j++) {
+                values[j] = NumHelper.read(br);
+            }
+            _nextIndex[i] = new IntDictionary();
+            _nextIndex[i].SetDictionary(keys, values);
+        }
+    }
+
+    /**
+     * 设置关键字
+     *
+     * @param keywords
+     */
+    public void SetKeywords(List<String> keywords) {
+        _keywords = keywords.toArray(new String[0]);
+        SetKeywords();
+    }
+
+    private void SetKeywords() {
+        TrieNode root = new TrieNode();
+        Map<Integer, List<TrieNode>> allNodeLayers = new TreeMap<Integer, List<TrieNode>>();
+        for (int i = 0; i < _keywords.length; i++) {
+            String p = _keywords[i];
+            TrieNode nd = root;
+            for (int j = 0; j < p.length(); j++) {
+                nd = nd.Add(p.charAt(j));
+                if (nd.Layer == 0) {
+                    nd.Layer = j + 1;
+                    if (allNodeLayers.containsKey(nd.Layer) == false) {
+                        List<TrieNode> nodes = new ArrayList<TrieNode>();
+                        nodes.add(nd);
+                        allNodeLayers.put(nd.Layer, nodes);
+                    } else {
+                        allNodeLayers.get(nd.Layer).add(nd);
+                    }
+                }
+            }
+            nd.SetResults(i);
+        }
+
+        List<TrieNode> allNode = new ArrayList<TrieNode>();
+        allNode.add(root);
+        for (int i = 0; i < allNodeLayers.size(); i++) { // 注意 这里不能用 keySet()
+            List<TrieNode> nodes = allNodeLayers.get(i + 1);
+            for (int j = 0; j < nodes.size(); j++) {
+                allNode.add(nodes.get(j));
+            }
+        }
+        allNodeLayers.clear();
+        allNodeLayers = null;
+
+        for (int i = 1; i < allNode.size(); i++) {
+            TrieNode nd = allNode.get(i);
+            nd.Index = i;
+            TrieNode r = nd.Parent.Failure;
+            Character c = nd.Char;
+            while (r != null && !r.m_values.containsKey(c))
+                r = r.Failure;
+            if (r == null)
+                nd.Failure = root;
+            else {
+                nd.Failure = r.m_values.get(c);
+                for (Integer result : nd.Failure.Results) {
+                    nd.SetResults(result);
+                }
+            }
+        }
+        root.Failure = root;
+
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 1; i < allNode.size(); i++) {
+            stringBuilder.append(allNode.get(i).Char);
+        }
+        CreateDict(stringBuilder.toString());
+        stringBuilder = null;
+
+        List<TrieNode2Ex> allNode2 = new ArrayList<TrieNode2Ex>();
+        for (int i = 0; i < allNode.size(); i++) {
+            TrieNode2Ex nd = new TrieNode2Ex();
+            nd.Index = i;
+            allNode2.add(nd);
+        }
+        for (int i = 0; i < allNode2.size(); i++) {
+            TrieNode oldNode = allNode.get(i);
+            TrieNode2Ex newNode = allNode2.get(i);
+
+            for (Character key : oldNode.m_values.keySet()) {
+                TrieNode nd = oldNode.m_values.get(key);
+                newNode.Add(_dict[key], allNode2.get(nd.Index));
+            }
+            oldNode.Results.forEach(item -> {
+                newNode.SetResults(item);
+            });
+
+            oldNode = oldNode.Failure;
+            while (oldNode != root) {
+                for (Character key : oldNode.m_values.keySet()) {
+                    if (newNode.HasKey(_dict[key]) == false) {
+                        TrieNode nd = oldNode.m_values.get(key);
+                        newNode.Add(_dict[key], allNode2.get(nd.Index));
+                    }
+                }
+                oldNode.Results.forEach(item -> {
+                    newNode.SetResults(item);
+                });
+                oldNode = oldNode.Failure;
+            }
+        }
+        allNode.clear();
+        allNode = null;
+        root = null;
+
+        List<Integer> min = new ArrayList<Integer>();
+        List<Integer> max = new ArrayList<Integer>();
+        List<Map<Integer, Integer>> nextIndexs = new ArrayList<Map<Integer, Integer>>();
+        List<Integer> end = new ArrayList<Integer>();
+        end.add(0);
+        List<Integer> resultIndex = new ArrayList<Integer>();
+        for (int i = 0; i < allNode2.size(); i++) {
+            Map<Integer, Integer> dict = new TreeMap<Integer, Integer>();
+            TrieNode2Ex node = allNode2.get(i);
+            min.add(node.minflag);
+            max.add(node.maxflag);
+
+            if (i > 0) {
+                for (Integer key : node.m_values.keySet()) {
+                    dict.put(key, node.m_values.get(key).Index);
+                }
+            }
+            for (int j = 0; j < node.Results.size(); j++) {
+                resultIndex.add(node.Results.get(j));
+            }
+            end.add(resultIndex.size());
+            nextIndexs.add(dict);
+        }
+        int[] first = new int[Character.MAX_VALUE + 1];
+        for (Integer key : allNode2.get(0).m_values.keySet()) {
+            TrieNode2Ex nd = allNode2.get(0).m_values.get(key);
+            first[(int) key] = nd.Index;
+        }
+
+        _first = first;
+        _min = new int[min.size()];
+        _max = new int[min.size()];
+        for (int i = 0; i < min.size(); i++) {
+            _min[i] = (int) (min.get(i));
+            _max[i] = (int) (max.get(i));
+        }
+        _nextIndex = new IntDictionary[nextIndexs.size()];
+        for (int i = 0; i < nextIndexs.size(); i++) {
+            IntDictionary dictionary = new IntDictionary();
+            dictionary.SetDictionary(nextIndexs.get(i));
+            _nextIndex[i] = dictionary;
+        }
+        _end = new int[end.size()];
+        for (int i = 0; i < end.size(); i++) {
+            _end[i] = (int) (end.get(i));
+        }
+        _resultIndex = new int[resultIndex.size()];
+        for (int i = 0; i < resultIndex.size(); i++) {
+            _resultIndex[i] = (int) (resultIndex.get(i));
+        }
+        allNode2.clear();
+        allNode2 = null;
+    }
+
+    private int CreateDict(String keywords) {
+        Map<Character, Integer> dictionary = new Hashtable<Character, Integer>();
+        for (int i = 0; i < keywords.length(); i++) {
+            Character item = keywords.charAt(i);
+            if (dictionary.containsKey(item)) {
+                dictionary.put(item, dictionary.get(item) + 1);
+            } else {
+                dictionary.put(item, 1);
+            }
+        }
+        Map<Character, Integer> dictionary2 = dictionary.entrySet().stream()
+                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
+                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2, LinkedHashMap::new));
+
+        List<Character> list2 = new ArrayList<Character>();
+        for (Character item : dictionary2.keySet()) {
+            list2.add(item);
+        }
+
+        _dict = new int[Character.MAX_VALUE + 1];
+        for (int i = 0; i < list2.size(); i++) {
+            _dict[list2.get(i)] = i + 1;
+        }
+        return dictionary.size();
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/BaseSearchEx2.java b/src/main/java/org/springblade/modules/words/internals/BaseSearchEx2.java
new file mode 100644
index 0000000..9426e66
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/BaseSearchEx2.java
@@ -0,0 +1,305 @@
+package org.springblade.modules.words.internals;
+
+import org.springblade.modules.words.NumHelper;
+
+import java.io.*;
+import java.util.*;
+
+import static java.util.stream.Collectors.toMap;
+
+public abstract class BaseSearchEx2 {
+    protected String[] _keywords;
+    protected int[][] _guides;
+    protected int[] _key;
+    protected int[] _next;
+    protected int[] _check;
+    protected int[] _dict;
+
+    /**
+     * 保存, 修改于2020-08-06,使用utf-8保存与以前保存的数据不同
+     *
+     * @param filePath 文件地址
+     * @throws IOException
+     */
+    public void Save(String filePath) throws IOException {
+        File fi = new File(filePath);
+        FileOutputStream fs = new FileOutputStream(fi);
+        Save(fs);
+        fs.close();
+    }
+
+    protected void Save(FileOutputStream bw) throws IOException {
+        bw.write(NumHelper.serialize(_keywords.length));
+        for (String item : _keywords) {
+            byte[] bytes = item.getBytes("utf-8");
+            bw.write(NumHelper.serialize(bytes.length));
+            bw.write(bytes);
+        }
+
+        bw.write(NumHelper.serialize(_guides.length));
+        for (int[] guide : _guides) {
+            bw.write(NumHelper.serialize(guide.length));
+            for (int item : guide) {
+                bw.write(NumHelper.serialize(item));
+            }
+        }
+
+        bw.write(NumHelper.serialize(_key.length));
+        for (int item : _key) {
+            bw.write(NumHelper.serialize(item));
+        }
+
+        bw.write(NumHelper.serialize(_next.length));
+        for (int item : _next) {
+            bw.write(NumHelper.serialize(item));
+        }
+
+        bw.write(NumHelper.serialize(_check.length));
+        for (int item : _check) {
+            bw.write(NumHelper.serialize(item));
+        }
+
+        bw.write(NumHelper.serialize(_dict.length));
+        for (int item : _dict) {
+            bw.write(NumHelper.serialize(item));
+        }
+    }
+
+    /**
+     * 加载, 修改于2020-08-06,使用utf-8加载,加载以前数据可能会出错
+     *
+     * @param filePath
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void Load(String filePath) throws FileNotFoundException, IOException {
+        File fi = new File(filePath);
+        InputStream in = new BufferedInputStream(new FileInputStream(fi));
+        Load(in);
+        in.close();
+    }
+
+    public void Load(InputStream br) throws IOException {
+        int length = NumHelper.read(br);
+        _keywords = new String[length];
+        for (int i = 0; i < length; i++) {
+            int l = NumHelper.read(br);
+            byte[] bytes = new byte[l];
+            br.read(bytes, 0, l);
+            _keywords[i] = new String(bytes,"utf-8");
+        }
+        length = NumHelper.read(br);
+        _guides = new int[length][];
+        for (int i = 0; i < length; i++) {
+            int length2 = NumHelper.read(br);
+            _guides[i] = new int[length2];
+            for (int j = 0; j < length2; j++) {
+                _guides[i][j] = NumHelper.read(br);
+            }
+        }
+
+        length = NumHelper.read(br);
+        _key = new int[length];
+        for (int i = 0; i < length; i++) {
+            _key[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _next = new int[length];
+        for (int i = 0; i < length; i++) {
+            _next[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _check = new int[length];
+        for (int i = 0; i < length; i++) {
+            _check[i] = NumHelper.read(br);
+        }
+
+        length = NumHelper.read(br);
+        _dict = new int[length];
+        for (int i = 0; i < length; i++) {
+            _dict[i] = NumHelper.read(br);
+        }
+    }
+
+    /**
+     * 设置关键字
+     *
+     * @param keywords
+     */
+    public void SetKeywords(List<String> keywords) {
+        _keywords = keywords.toArray(new String[0]);
+
+        SetKeywords();
+    }
+
+    private void SetKeywords() {
+        TrieNode root = new TrieNode();
+        Map<Integer,List<TrieNode>> allNodeLayers=new TreeMap<Integer,List<TrieNode>>();
+        for (int i = 0; i < _keywords.length; i++) {
+            String p = _keywords[i];
+            TrieNode nd = root;
+            for (int j = 0; j < p.length(); j++) {
+                nd = nd.Add(p.charAt(j));
+                if (nd.Layer == 0) {
+                    nd.Layer = j + 1;
+                    if(allNodeLayers.containsKey(nd.Layer)==false){
+                        List<TrieNode> nodes=new ArrayList<TrieNode>();
+                        nodes.add(nd);
+                        allNodeLayers.put(nd.Layer, nodes);
+                    }else {
+                        allNodeLayers.get(nd.Layer).add(nd);
+                    }                }
+            }
+            nd.SetResults(i);
+        }
+
+        List<TrieNode> allNode = new ArrayList<TrieNode>();
+        allNode.add(root);
+        for (int i = 0; i < allNodeLayers.size(); i++) { //注意 这里不能用 keySet()
+            List<TrieNode> nodes = allNodeLayers.get(i+1);
+            for (int j = 0; j < nodes.size(); j++) {
+                allNode.add(nodes.get(j));
+            }
+        }
+        allNodeLayers.clear();
+        allNodeLayers=null;
+
+
+        for (int i = 1; i < allNode.size(); i++) {
+            TrieNode nd = allNode.get(i);
+            nd.Index = i;
+            TrieNode r = nd.Parent.Failure;
+            Character c = nd.Char;
+            while (r != null && !r.m_values.containsKey(c)) r = r.Failure;
+            if (r == null)
+                nd.Failure = root;
+            else {
+                nd.Failure = r.m_values.get(c);
+                for (Integer result : nd.Failure.Results) {
+                    nd.SetResults(result);
+                }
+            }
+        }
+        root.Failure = root;
+
+
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 1; i < allNode.size(); i++) {
+            stringBuilder.append(allNode.get(i).Char);
+        }
+        Integer length = CreateDict(stringBuilder.toString());
+        stringBuilder = null;
+
+        List<TrieNodeEx> allNode2 = new ArrayList<TrieNodeEx>();
+        for (int i = 0; i < allNode.size(); i++) {
+            TrieNodeEx nd = new TrieNodeEx();
+            nd.Index = i;
+            allNode2.add(nd);
+        }
+        for (int i = 0; i < allNode2.size(); i++) {
+            TrieNode oldNode = allNode.get(i);
+            TrieNodeEx newNode = allNode2.get(i);
+            newNode.Char = _dict[oldNode.Char];
+
+            for (Character key : oldNode.m_values.keySet()) {
+                TrieNode nd = oldNode.m_values.get(key);
+                newNode.Add(_dict[key], allNode2.get(nd.Index));
+            }
+            oldNode.Results.forEach(item -> {
+                newNode.SetResults(item);
+            });
+            oldNode = oldNode.Failure;
+            while (oldNode != root) {
+                for (Character key : oldNode.m_values.keySet()) {
+                    if (newNode.HasKey(_dict[key]) == false) {
+                        TrieNode nd = oldNode.m_values.get(key);
+                        newNode.Add(_dict[key], allNode2.get(nd.Index));
+                    }
+                }
+                oldNode.Results.forEach(item -> {
+                    newNode.SetResults(item);
+                });
+                oldNode = oldNode.Failure;
+            }
+        }
+        allNode.clear();
+        allNode = null;
+        root = null;
+
+        build(allNode2, length);
+    }
+
+    private void build(List<TrieNodeEx> nodes, int length) {
+        Integer[] has = new Integer[0x00FFFFFF];
+        boolean[] seats = new boolean[0x00FFFFFF];
+        boolean[] seats2 = new boolean[0x00FFFFFF];
+        Integer start = 1;
+        Integer oneStart = 1;
+        for (int i = 0; i < nodes.size(); i++) {
+            TrieNodeEx node = nodes.get(i);
+            node.Rank(oneStart, start, seats, seats2, has);
+        }
+        Integer maxCount = has.length - 1;
+        while (has[maxCount] == null) {
+            maxCount--;
+        }
+        length = maxCount + length + 1;
+
+        // length = root.Rank(has) + length + 1;
+        _key = new int[length];
+        _next = new int[length];
+        _check = new int[length];
+        List<Integer[]> guides = new ArrayList<Integer[]>();
+        guides.add(new Integer[] { 0 });
+        for (int i = 0; i < length; i++) {
+            if (has[i] == null)
+                continue;
+            TrieNodeEx item = nodes.get(has[i]);
+            _key[i] = item.Char;
+            _next[i] = item.Next;
+            if (item.End) {
+                _check[i] = guides.size();
+                Integer[] result = item.Results.toArray(new Integer[0]);
+                guides.add(result);
+            }
+        }
+        _guides = new int[guides.size()][];
+        for (int i = 0; i < guides.size(); i++) {
+            Integer[] array = guides.get(i);
+            _guides[i] = new int[array.length];
+            for (int j = 0; j < array.length; j++) {
+                _guides[i][j] = array[j];
+            }
+        }
+
+    }
+
+    private int CreateDict(String keywords) {
+        Map<Character, Integer> dictionary = new Hashtable<Character, Integer>();
+        for (int i = 0; i < keywords.length(); i++) {
+            Character item = keywords.charAt(i);
+            if (dictionary.containsKey(item)) {
+                dictionary.put(item, dictionary.get(item) + 1);
+            } else {
+                dictionary.put(item, 1);
+            }
+        }
+        Map<Character, Integer> dictionary2 = dictionary.entrySet().stream()
+                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
+                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2, LinkedHashMap::new));
+
+        List<Character> list2 = new ArrayList<Character>();
+        for (Character item : dictionary2.keySet()) {
+            list2.add(item);
+        }
+
+        _dict = new int[Character.MAX_VALUE + 1];
+        for (int i = 0; i < list2.size(); i++) {
+            _dict[list2.get(i)] = i + 1;
+        }
+        return dictionary.size();
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/IntDictionary.java b/src/main/java/org/springblade/modules/words/internals/IntDictionary.java
new file mode 100644
index 0000000..7bbc63c
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/IntDictionary.java
@@ -0,0 +1,78 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class IntDictionary {
+    private int[] _keys;
+    private int[] _values;
+    private int last;
+
+    public IntDictionary() {
+        last = -1;
+    }
+
+    public int[] getKeys() {
+        return _keys;
+    }
+
+    public int[] getValues() {
+        return _values;
+    }
+
+    public void SetDictionary(Map<Integer, Integer> dict) {
+
+        List<Integer> keys = new ArrayList<Integer>();
+        dict.forEach((k, v) -> {
+            keys.add((int) k);
+        });
+
+        _keys = new int[dict.size()];
+        _values = new int[dict.size()];
+        for (int i = 0; i < keys.size(); i++) {
+            _keys[i] = keys.get(i);
+            _values[i] = dict.get(_keys[i]);
+        }
+        last = _keys.length - 1;
+    }
+
+    public void SetDictionary(int[] keys, int[] values) {
+        _keys = keys;
+        _values = values;
+        last = _keys.length - 1;
+    }
+
+    public int IndexOf(int key) {
+        if (last == -1) {
+            return -1;
+        }
+        if (_keys[0] == key) {
+            return 0;
+        }
+        if (_keys[last] == key) {
+            return last;
+        }
+
+        int left = 0;
+        int right = last;
+        while (left + 1 < right) {
+            int mid = (left + right) >> 1;
+            int d = _keys[mid] - key;
+
+            if (d == 0) {
+                return mid;
+            } else if (d > 0) {
+                right = mid;
+            } else {
+                left = mid;
+            }
+        }
+        return -1;
+    }
+
+    public int GetValue(int index){
+        return _values[index];
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/PinyinDict.java b/src/main/java/org/springblade/modules/words/internals/PinyinDict.java
new file mode 100644
index 0000000..96d8035
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/PinyinDict.java
@@ -0,0 +1,277 @@
+package org.springblade.modules.words.internals;
+
+import org.springblade.modules.words.WordsSearch;
+import org.springblade.modules.words.WordsSearchResult;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PinyinDict {
+    private static Map<String, Integer[]> _pyName;
+    private static String[] _pyShow;
+    private static Integer[] _pyIndex;
+    private static Integer[] _pyData;
+    private static Integer[] _wordPyIndex;
+    private static Integer[] _wordPy;
+    private static WordsSearch _search;
+
+    public static String[] getPyShow() throws NumberFormatException, IOException {
+        InitPyIndex();
+        return _pyShow;
+    }
+
+    public static String[] GetPinyinList(String text, int tone) throws NumberFormatException, IOException {
+        InitPyIndex();
+        InitPyWords();
+
+        String[] list = new String[text.length()];
+        List<WordsSearchResult> pos = _search.FindAll(text);
+        Integer pindex = -1;
+
+        for (WordsSearchResult p : pos) {
+            if (p.Start > pindex) {
+                for (int i = 0; i < p.Keyword.length(); i++) {
+                    list[i + p.Start] = _pyShow[_wordPy[i + _wordPyIndex[p.Index]] + tone];
+                }
+                pindex = p.End;
+            }
+        }
+
+        for (int i = 0; i < text.length(); i++) {
+            if (list[i] != null)
+                continue;
+            Character c = text.charAt(i);
+            if (c >= 0x3400 && c <= 0x9fd5) {
+                int index = c - 0x3400;
+                int start = _pyIndex[index];
+                int end = _pyIndex[index + 1];
+                if (end > start) {
+                    list[i] = _pyShow[_pyData[start] + tone];
+                }
+            }
+        }
+        return list;
+    }
+
+    public static String GetPinyin(String text, int tone) throws NumberFormatException, IOException {
+        InitPyIndex();
+
+        String[] list = GetPinyinList(text, tone);
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < list.length; i++) {
+            String s = list[i];
+            if (s != null) {
+                sb.append(list[i]);
+            } else {
+                sb.append(text.charAt(i));
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String GetFirstPinyin(String text, int tone) throws NumberFormatException, IOException {
+        InitPyIndex();
+
+        String[] list = GetPinyinList(text, tone);
+        StringBuilder sb = new StringBuilder(text);
+        for (int i = 0; i < list.length; i++) {
+            String c = list[i];
+            if (c != null) {
+                sb.setCharAt(i, c.charAt(0));
+            }
+        }
+        return sb.toString();
+    }
+
+    public static List<String> GetAllPinyin(Character c, int tone) throws NumberFormatException, IOException {
+        InitPyIndex();
+        if (c >= 0x3400 && c <= 0x9fd5) {
+            int index = c - 0x3400;
+            List<String> list = new ArrayList<String>();
+            int start = _pyIndex[index];
+            int end = _pyIndex[index + 1];
+            if (end > start) {
+                for (int i = start; i < end; i++) {
+                    String py = _pyShow[_pyData[i] + tone];
+                    if (list.contains(py) == false) {
+                        list.add(py);
+                    }
+                }
+            }
+            return list;
+        }
+        return new ArrayList<String>();
+    }
+
+    public static String GetPinyinFast(Character c, int tone) throws NumberFormatException, IOException {
+        InitPyIndex();
+
+        if (c >= 0x3400 && c <= 0x9fd5) {
+            int index = c - 0x3400;
+            int start = _pyIndex[index];
+            int end = _pyIndex[index + 1];
+            if (end > start) {
+                return _pyShow[_pyData[start] + tone];
+            }
+        }
+        return c.toString();
+    }
+
+    public static List<String> GetPinyinForName(String name, int tone) throws NumberFormatException, IOException {
+        InitPyName();
+        InitPyIndex();
+
+        List<String> list = new ArrayList<String>();
+        String xing;
+        String ming;
+        Integer[] indexs;
+        if (name.length() > 1) { // 检查复姓
+            xing = name.substring(0, 2);
+            if (_pyName.containsKey(xing)) {
+                indexs = _pyName.get(xing);
+                for (Integer index : indexs) {
+                    list.add(_pyShow[index + tone]);
+                }
+                if (name.length() > 2) {
+                    ming = name.substring(2);
+                    String[] pys = GetPinyinList(ming, tone);
+                    for (String py : pys) {
+                        list.add(py);
+                    }
+                }
+                return list;
+            }
+        }
+        xing = name.substring(0, 1);
+        if (_pyName.containsKey(xing)) {
+            indexs = _pyName.get(xing);
+            for (Integer index : indexs) {
+                list.add(_pyShow[index + tone]);
+            }
+            if (name.length() > 1) {
+                ming = name.substring(1);
+                String[] pys = GetPinyinList(ming, tone);
+                for (String py : pys) {
+                    list.add(py);
+                }
+            }
+            return list;
+        }
+        String[] pys = GetPinyinList(name, tone);
+        for (String py : pys) {
+            list.add(py);
+        }
+        return list;
+    }
+    private final static Object lockObj = new Object();
+    private static void InitPyIndex() throws NumberFormatException, IOException {
+        if (_pyIndex == null) {
+            synchronized(lockObj){
+                if (_pyIndex == null) {
+                    String resourceName = "pyIndex.txt";
+                    InputStream u1 = WordsSearch.class.getClassLoader().getResourceAsStream(resourceName);
+                    BufferedReader br = new BufferedReader(new InputStreamReader(u1));
+
+                    String tStr = "";
+                    List<Integer> pyIndex = new ArrayList<Integer>();
+                    pyIndex.add(0);
+                    List<Integer> pyData = new ArrayList<Integer>();
+
+                    while ((tStr = br.readLine()) != null) {
+                        if (_pyShow == null) {
+                            String[] ss = tStr.split(",");
+                            _pyShow = ss;
+                        } else {
+                            if (tStr != "0") {
+                                for (String idx : tStr.split(",")) {
+                                    int in = Integer.valueOf(idx, 16);
+                                    pyData.add(in);
+                                }
+                            }
+                            pyIndex.add((int) pyData.size());
+                        }
+                    }
+                    br.close();
+
+                    Integer[] pd = new Integer[pyData.size()];
+                    _pyData = pyData.toArray(pd);
+                    Integer[] pi = new Integer[pyIndex.size()];
+                    _pyIndex = pyIndex.toArray(pi);
+                }
+            }
+        }
+    }
+
+    private static void InitPyName() throws NumberFormatException, IOException {
+        if (_pyName == null) {
+            synchronized(lockObj){
+                if (_pyName == null) {
+                    String resourceName = "pyName.txt";
+                    InputStream u1 = WordsSearch.class.getClassLoader().getResourceAsStream(resourceName);
+                    BufferedReader br = new BufferedReader(new InputStreamReader(u1));
+
+                    Map<String, Integer[]> pyName = new HashMap<String, Integer[]>();
+                    String tStr = "";
+                    while ((tStr = br.readLine()) != null) {
+                        String[] sp = tStr.split(",");
+                        List<Integer> index = new ArrayList<Integer>();
+                        for (int i = 1; i < sp.length; i++) {
+                            String idx = sp[i];
+                            int in = Integer.valueOf(idx, 16);
+                            index.add(in);
+                        }
+                        Integer[] temp = new Integer[index.size()];
+                        pyName.put(sp[0], index.toArray(temp));
+                    }
+                    br.close();
+
+                    _pyName = pyName;
+
+                }
+            }
+        }
+    }
+
+    private static void InitPyWords() throws NumberFormatException, IOException {
+        if (_search == null) {
+            synchronized(lockObj){
+                if (_search == null) {
+                    String resourceName = "pyWords.txt";
+                    InputStream u1 = WordsSearch.class.getClassLoader().getResourceAsStream(resourceName);
+                    BufferedReader br = new BufferedReader(new InputStreamReader(u1));
+
+                    List<String> keywords = new ArrayList<String>();
+                    List<Integer> wordPyIndex = new ArrayList<Integer>();
+                    List<Integer> wordPy = new ArrayList<Integer>();
+
+                    String tStr = "";
+                    while ((tStr = br.readLine()) != null) {
+                        String[] sp = tStr.split(",");
+                        keywords.add(sp[0]);
+                        wordPyIndex.add(wordPy.size());
+                        for (int i = 1; i < sp.length; i++) {
+                            String idx = sp[i];
+                            int in = Integer.valueOf(idx, 16);
+                            wordPy.add(in);
+                        }
+                    }
+                    br.close();
+                    WordsSearch search = new WordsSearch();
+                    search.SetKeywords(keywords);
+                    Integer[] wp = new Integer[wordPy.size()];
+                    _wordPy = wordPy.toArray(wp);
+                    Integer[] wpi = new Integer[wordPyIndex.size()];
+                    _wordPyIndex = wordPyIndex.toArray(wpi);
+                    _search = search;
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/Translate.java b/src/main/java/org/springblade/modules/words/internals/Translate.java
new file mode 100644
index 0000000..45e4822
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/Translate.java
@@ -0,0 +1,212 @@
+package org.springblade.modules.words.internals;
+
+import org.springblade.modules.words.WordsSearch;
+import org.springblade.modules.words.WordsSearchResult;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Translate {
+    private static WordsSearch s2tSearch;
+    private static WordsSearch t2sSearch;
+    private static WordsSearch t2twSearch;
+    private static WordsSearch tw2tSearch;
+    private static WordsSearch t2hkSearch;
+    private static WordsSearch hk2tSearch;
+
+    /**
+     * 转繁体中文
+     *
+     * @param text
+     * @param type 0、繁体中文,1、港澳繁体,2、台湾正体
+     * @return
+     * @throws Exception
+     */
+    public static String ToTraditionalChinese(String text, final int type) throws Exception {
+        if (type > 2 || type < 0) {
+            throw new Exception("type 不支持该类型");
+        }
+
+        final WordsSearch s2t = GetWordsSearch(true, 0);
+        text = TransformationReplace(text, s2t);
+        if (type > 0) {
+            final WordsSearch t2 = GetWordsSearch(true, type);
+            text = TransformationReplace(text, t2);
+        }
+        return text;
+    }
+
+    /**
+     * 转简体中文
+     *
+     * @param text
+     * @param srcType 0、繁体中文,1、港澳繁体,2、台湾正体
+     * @return
+     * @throws Exception
+     */
+    public static String ToSimplifiedChinese(String text, final int srcType) throws Exception {
+        if (srcType > 2 || srcType < 0) {
+            throw new Exception("srcType 不支持该类型");
+        }
+        if (srcType > 0) {
+            final WordsSearch t2 = GetWordsSearch(false, srcType);
+            text = TransformationReplace(text, t2);
+        }
+        final WordsSearch s2t = GetWordsSearch(false, 0);
+        text = TransformationReplace(text, s2t);
+        return text;
+    }
+
+    /**
+     * 清理 简繁转换 缓存
+     */
+    public static void ClearTranslate() {
+        s2tSearch = null;
+        t2sSearch = null;
+        t2twSearch = null;
+        tw2tSearch = null;
+        t2hkSearch = null;
+        hk2tSearch = null;
+    }
+
+    /**
+     *
+     * @param text
+     * @param wordsSearch
+     * @return
+     */
+    private static String TransformationReplace(String text, WordsSearch wordsSearch) {
+        List<WordsSearchResult> ts = wordsSearch.FindAll(text);
+        StringBuilder sb = new StringBuilder();
+        int index = 0;
+        while (index < text.length()) {
+            WordsSearchResult t = null;
+            int end = -1;
+            for (WordsSearchResult wordsSearchResult : ts) {
+                if (wordsSearchResult.Start == index) {
+                    if (end < wordsSearchResult.End) {
+                        end = wordsSearchResult.End;
+                        t = wordsSearchResult;
+                    }
+                }
+            }
+            if (t == null) {
+                sb.append(text.charAt(index));
+                index++;
+            } else {
+                sb.append(wordsSearch._others[t.Index]);
+                index = t.End + 1;
+            }
+        }
+        return sb.toString();
+    }
+
+    private final static Object lockObj = new Object();
+    private static WordsSearch GetWordsSearch(Boolean s2t, int srcType) throws IOException {
+        if (s2t) {
+            if (srcType == 0) {
+                if (s2tSearch == null) {
+                    synchronized(lockObj){
+                        if (s2tSearch == null) {
+                            s2tSearch = BuildWordsSearch("s2t.dat", false);
+                        }
+                    }
+                }
+                return s2tSearch;
+            } else if (srcType == 1) {
+                if (t2hkSearch == null) {
+                    synchronized(lockObj){
+                        if (t2hkSearch == null) {
+                            t2hkSearch = BuildWordsSearch("t2hk.dat", false);
+                        }
+                    }
+                }
+                return t2hkSearch;
+            } else if (srcType == 2) {
+                if (t2twSearch == null) {
+                    synchronized(lockObj){
+                        if (t2twSearch == null) {
+                            t2twSearch = BuildWordsSearch("t2tw.dat", false);
+                        }
+                    }
+                }
+                return t2twSearch;
+            }
+        } else {
+            if (srcType == 0) {
+                if (t2sSearch == null) {
+                    synchronized(lockObj){
+                        if (t2sSearch == null) {
+                            t2sSearch = BuildWordsSearch("t2s.dat", false);
+                        }
+                    }
+                }
+                return t2sSearch;
+            } else if (srcType == 1) {
+                if (hk2tSearch == null) {
+                    synchronized(lockObj){
+                        if (hk2tSearch == null) {
+                            hk2tSearch = BuildWordsSearch("t2hk.dat", true);
+                        }
+                    }
+                }
+                return hk2tSearch;
+            } else if (srcType == 2) {
+                if (tw2tSearch == null) {
+                    synchronized(lockObj){
+                        if (tw2tSearch == null) {
+                            tw2tSearch = BuildWordsSearch("t2tw.dat", true);
+                        }
+                    }
+                }
+                return tw2tSearch;
+            }
+        }
+        return null;
+    }
+
+    private static WordsSearch BuildWordsSearch(String fileName, Boolean reverse) throws IOException {
+        Map<String, String> dict = GetTransformationDict(fileName);
+        List<String> Keys = new ArrayList<String>();
+        List<String> Values = new ArrayList<String>();
+        dict.forEach((k, v) -> {
+            Keys.add(k);
+            Values.add(v);
+        });
+        WordsSearch wordsSearch = new WordsSearch();
+        if (reverse) {
+            wordsSearch.SetKeywords(Values);
+            String[] temp = new String[Keys.size()];
+            wordsSearch._others = Keys.toArray(temp);
+        } else {
+            wordsSearch.SetKeywords(Keys);
+            String[] temp = new String[Keys.size()];
+            wordsSearch._others = Values.toArray(temp);
+        }
+        return wordsSearch;
+    }
+
+    static Map<String, String> GetTransformationDict(String fileName) throws IOException {
+        String resourceName = fileName;
+        InputStream u1 = WordsSearch.class.getClassLoader().getResourceAsStream(resourceName);
+        BufferedReader br = new BufferedReader(new InputStreamReader(u1));
+
+        String tStr = "";
+        Map<String, String> dict = new HashMap<String, String>();
+        while ((tStr = br.readLine()) != null) {
+            String[] ss = tStr.split("\t");
+            if (ss.length < 2) {
+                continue;
+            }
+            dict.put(ss[0], ss[1]);
+        }
+        br.close();
+        return dict;
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/TrieNode.java b/src/main/java/org/springblade/modules/words/internals/TrieNode.java
new file mode 100644
index 0000000..00def8a
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/TrieNode.java
@@ -0,0 +1,51 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class TrieNode implements Comparable<TrieNode> {
+
+    public int Index;
+    public int Layer;
+    public boolean End;
+    public char Char;
+    public List<Integer> Results;
+    public HashMap<Character, TrieNode> m_values;
+    public TrieNode Failure;
+    public TrieNode Parent;
+    public boolean IsWildcard;
+    public int WildcardLayer;
+    public boolean HasWildcard;
+
+
+    public TrieNode() {
+        m_values = new HashMap<Character, TrieNode>();
+        Results = new ArrayList<Integer>();
+    }
+
+    public TrieNode Add(final Character c) {
+        if (m_values.containsKey(c)) {
+            return m_values.get(c);
+        }
+        final TrieNode node = new TrieNode();
+        node.Parent = this;
+        node.Char = c;
+        m_values.put(c, node);
+        return node;
+    }
+
+    public void SetResults(final Integer index) {
+        if (End == false) {
+            End = true;
+        }
+        if (Results.contains(index) == false) {
+            Results.add(index);
+        }
+    }
+
+    @Override
+    public int compareTo(final TrieNode o) {
+        return this.Layer - o.Layer  ;
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/TrieNode2.java b/src/main/java/org/springblade/modules/words/internals/TrieNode2.java
new file mode 100644
index 0000000..ba25aaf
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/TrieNode2.java
@@ -0,0 +1,50 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+
+public class TrieNode2{
+    public boolean End;
+    public List<Integer> Results;
+    public HashMap<Character, TrieNode2> m_values;
+    private int minflag = Integer.MAX_VALUE;
+    private int maxflag = 0;
+
+    public TrieNode2()
+    {
+        Results = new ArrayList<Integer>();
+        m_values = new HashMap<Character, TrieNode2>();
+    }
+
+    public void Add(final char c, final TrieNode2 node3) {
+        if (minflag > c) {
+            minflag = c;
+        }
+        if (maxflag < c) {
+            maxflag = c;
+        }
+        m_values.put(c, node3);
+    }
+
+    public void SetResults(final Integer index) {
+        if (End == false) {
+            End = true;
+        }
+        if (Results.contains(index) == false) {
+            Results.add(index);
+        }
+    }
+
+    public boolean HasKey(final char c) {
+        if (minflag <= c && maxflag >= c) {
+            return m_values.containsKey(c);
+        }
+        return false;
+    }
+
+    public TrieNode2 GetValue(final char c) {
+        return m_values.get(c);
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/TrieNode2Ex.java b/src/main/java/org/springblade/modules/words/internals/TrieNode2Ex.java
new file mode 100644
index 0000000..6ac189f
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/TrieNode2Ex.java
@@ -0,0 +1,51 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class TrieNode2Ex {
+    public int Index;
+    public boolean End;
+    public List<Integer> Results;
+    public HashMap<Integer, TrieNode2Ex> m_values;
+    public int minflag = Integer.MAX_VALUE;
+    public int maxflag = 0;
+
+    public TrieNode2Ex()
+    {
+        Results = new ArrayList<Integer>();
+        m_values = new HashMap<Integer, TrieNode2Ex>();
+    }
+
+    public void Add(final int c, final TrieNode2Ex node3) {
+        if (minflag > c) {
+            minflag = c;
+        }
+        if (maxflag < c) {
+            maxflag = c;
+        }
+        m_values.put(c, node3);
+    }
+
+    public void SetResults(final int index) {
+        if (End == false) {
+            End = true;
+        }
+        if (Results.contains(index) == false) {
+            Results.add(index);
+        }
+    }
+
+    public boolean HasKey(final int c) {
+        if (minflag <= c && maxflag >= c) {
+            return m_values.containsKey(c);
+        }
+        return false;
+    }
+
+    public TrieNode2Ex GetValue(final int c) {
+        return m_values.get(c);
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/TrieNode3.java b/src/main/java/org/springblade/modules/words/internals/TrieNode3.java
new file mode 100644
index 0000000..2569e15
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/TrieNode3.java
@@ -0,0 +1,52 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class TrieNode3 {
+    public boolean End;
+    public boolean HasWildcard;
+    public List<Integer> Results;
+    public HashMap<Character, TrieNode3> m_values;
+    private int minflag = Integer.MAX_VALUE;
+    private int maxflag = 0;
+    public TrieNode3 WildcardNode;
+
+
+    public TrieNode3()
+    {
+        Results = new ArrayList<Integer>();
+        m_values = new HashMap<Character, TrieNode3>();
+    }
+
+    public void Add(final char c, final TrieNode3 node3) {
+        if (minflag > c) {
+            minflag = c;
+        }
+        if (maxflag < c) {
+            maxflag = c;
+        }
+        m_values.put(c, node3);
+    }
+
+    public void SetResults(final int index) {
+        if (End == false) {
+            End = true;
+        }
+        if (Results.contains(index) == false) {
+            Results.add(index);
+        }
+    }
+
+    public boolean HasKey(final char c) {
+        if (minflag <= c && maxflag >= c) {
+            return m_values.containsKey(c);
+        }
+        return false;
+    }
+
+    public TrieNode3 GetValue(final char c) {
+        return m_values.get(c);
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/TrieNode3Ex.java b/src/main/java/org/springblade/modules/words/internals/TrieNode3Ex.java
new file mode 100644
index 0000000..937ae11
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/TrieNode3Ex.java
@@ -0,0 +1,53 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class TrieNode3Ex {
+    public int Index;
+    public boolean End;
+    public boolean HasWildcard;
+    public List<Integer> Results;
+    public HashMap<Character, TrieNode3Ex> m_values;
+    public int minflag = Integer.MAX_VALUE;
+    public int maxflag = 0;
+    public TrieNode3Ex WildcardNode;
+
+
+    public TrieNode3Ex()
+    {
+        Results = new ArrayList<Integer>();
+        m_values = new HashMap<Character, TrieNode3Ex>();
+    }
+
+    public void Add(final char c, final TrieNode3Ex node3) {
+        if (minflag > c) {
+            minflag = c;
+        }
+        if (maxflag < c) {
+            maxflag = c;
+        }
+        m_values.put(c, node3);
+    }
+
+    public void SetResults(final int index) {
+        if (End == false) {
+            End = true;
+        }
+        if (Results.contains(index) == false) {
+            Results.add(index);
+        }
+    }
+
+    public boolean HasKey(final char c) {
+        if (minflag <= c && maxflag >= c) {
+            return m_values.containsKey(c);
+        }
+        return false;
+    }
+
+    public TrieNode3Ex GetValue(final char c) {
+        return m_values.get(c);
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/TrieNodeEx.java b/src/main/java/org/springblade/modules/words/internals/TrieNodeEx.java
new file mode 100644
index 0000000..9ef0e51
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/TrieNodeEx.java
@@ -0,0 +1,140 @@
+package org.springblade.modules.words.internals;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+public class TrieNodeEx {
+    public Integer Char;
+    public boolean End;
+    public Integer Index;
+    public List<Integer> Results;
+    public Map<Integer, TrieNodeEx> m_values;
+    private Integer minflag = Integer.MAX_VALUE;
+    private Integer maxflag = 0;
+    public int Next;
+
+    public TrieNodeEx() {
+        m_values = new Hashtable<Integer, TrieNodeEx>();
+        Results = new ArrayList<Integer>();
+    }
+
+    public void Add(int c, TrieNodeEx node3) {
+        if (minflag > c) {
+            minflag = c;
+        }
+        if (maxflag < c) {
+            maxflag = c;
+        }
+        m_values.put(c, node3);
+    }
+
+    public void SetResults(Integer text) {
+        if (End == false) {
+            End = true;
+        }
+        if (Results.contains(text) == false) {
+            Results.add(text);
+        }
+    }
+
+    public boolean HasKey(Integer c) {
+        if (minflag <= c && maxflag >= c) {
+            return m_values.containsKey((int) c);
+        }
+        return false;
+    }
+
+    public void Rank(Integer oneStart, Integer start, boolean[] seats, boolean[] seats2, Integer[] has) {
+        if (maxflag == 0)
+            return;
+        if (minflag == maxflag) {
+            RankOne(oneStart, seats, has);
+            return;
+        }
+        List<Integer> keys = new ArrayList<Integer>();
+        m_values.forEach((k, v) -> {
+            keys.add((int) k);
+        });
+
+        Integer length = keys.size() - 1;
+        int[] moves = new int[keys.size() - 1];
+        for (int i = 1; i < keys.size(); i++) {
+            moves[i - 1] = maxflag - keys.get(i);
+        }
+
+        while (has[start] != null) {
+            start++;
+        }
+        Integer s = start < minflag ? minflag : start;
+
+        for (int i = s; i < s + (maxflag - minflag); i++) {
+            if (has[i] != null) {
+                for (int j = 0; j < length; j++) {
+                    Integer p = i + moves[j];
+                    if (seats2[p] == false) {
+                        seats2[p] = true;
+                    }
+                }
+            }
+        }
+        Integer max = 0;
+        for (int i = s + (maxflag - minflag); i < has.length; i++) {
+            if (has[i] == null) {
+                if (seats2[i]) {
+                    continue;
+                }
+                Integer next = i - (Integer) maxflag;
+                if (seats[next])
+                    continue;
+                SetSeats(next, seats, has);
+                max = i;
+                break;
+            } else {
+                for (int j = 0; j < length; j++) {
+                    Integer p = i + moves[j];
+                    if (seats2[p] == false) {
+                        seats2[p] = true;
+                    }
+                }
+            }
+        }
+        start += keys.size() / 2;
+        for (int p = start; p < max + maxflag - start + 1; p++) {
+            if (seats2[p] == true) {
+                seats2[p] = false;
+            }
+        }
+    }
+
+    private void RankOne(Integer start, boolean[] seats, Integer[] has) {
+        while (has[start] != null) {
+            start++;
+        }
+        Integer s = start < minflag ? minflag : start;
+
+        for (Integer i = s; i < has.length; i++) {
+            if (has[i] == null) {
+                Integer next = i - (Integer) minflag;
+                if (seats[next])
+                    continue;
+                SetSeats(next, seats, has);
+                break;
+            }
+        }
+        start++;
+    }
+
+
+    private void SetSeats(Integer next, boolean[] seats, Integer[] has) {
+        Next = next;
+        seats[next] = true;
+
+        m_values.forEach((key, value) -> {
+            int position = next + key;
+            has[position] = value.Index;
+        });
+    }
+
+}
diff --git a/src/main/java/org/springblade/modules/words/internals/TwoTuple.java b/src/main/java/org/springblade/modules/words/internals/TwoTuple.java
new file mode 100644
index 0000000..7548446
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/internals/TwoTuple.java
@@ -0,0 +1,11 @@
+package org.springblade.modules.words.internals;
+
+public class TwoTuple<A, B> {
+    public A Item1;
+    public B Item2;
+
+    public TwoTuple(A a, B b) {
+        this.Item1 = a;
+        this.Item2 = b;
+    }
+}
diff --git a/src/main/java/org/springblade/modules/words/sensitiveword/controller/SensitivewordController.java b/src/main/java/org/springblade/modules/words/sensitiveword/controller/SensitivewordController.java
new file mode 100644
index 0000000..af4654a
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/sensitiveword/controller/SensitivewordController.java
@@ -0,0 +1,124 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.words.sensitiveword.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.modules.words.sensitiveword.entity.Sensitiveword;
+import org.springblade.modules.words.sensitiveword.service.ISensitivewordService;
+import org.springblade.modules.words.sensitiveword.vo.SensitivewordVO;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+/**
+ *  控制器
+ *
+ * @author BladeX
+ * @since 2022-01-04
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/sensitiveword")
+@Api(value = "", tags = "接口")
+public class SensitivewordController extends BladeController {
+
+	private final ISensitivewordService sensitivewordService;
+
+	/**
+	 * 详情
+	 */
+	@GetMapping("/detail")
+	@ApiOperationSupport(order = 1)
+	@ApiOperation(value = "详情", notes = "传入sensitiveword")
+	public R<Sensitiveword> detail(Sensitiveword sensitiveword) {
+		Sensitiveword detail = sensitivewordService.getOne(Condition.getQueryWrapper(sensitiveword));
+		return R.data(detail);
+	}
+
+	/**
+	 * 分页
+	 */
+	@GetMapping("/list")
+	@ApiOperationSupport(order = 2)
+	@ApiOperation(value = "分页", notes = "传入sensitiveword")
+	public R<IPage<Sensitiveword>> list(Sensitiveword sensitiveword, Query query) {
+		IPage<Sensitiveword> pages = sensitivewordService.page(Condition.getPage(query), Condition.getQueryWrapper(sensitiveword));
+		return R.data(pages);
+	}
+
+	/**
+	 * 自定义分页
+	 */
+	@GetMapping("/page")
+	@ApiOperationSupport(order = 3)
+	@ApiOperation(value = "分页", notes = "传入sensitiveword")
+	public R<IPage<SensitivewordVO>> page(SensitivewordVO sensitiveword, Query query) {
+		IPage<SensitivewordVO> pages = sensitivewordService.selectSensitivewordPage(Condition.getPage(query), sensitiveword);
+		return R.data(pages);
+	}
+
+	/**
+	 * 新增
+	 */
+	@PostMapping("/save")
+	@ApiOperationSupport(order = 4)
+	@ApiOperation(value = "新增", notes = "传入sensitiveword")
+	public R save(@Valid @RequestBody Sensitiveword sensitiveword) {
+		return R.status(sensitivewordService.save(sensitiveword));
+	}
+
+	/**
+	 * 修改
+	 */
+	@PostMapping("/update")
+	@ApiOperationSupport(order = 5)
+	@ApiOperation(value = "修改", notes = "传入sensitiveword")
+	public R update(@Valid @RequestBody Sensitiveword sensitiveword) {
+		return R.status(sensitivewordService.updateById(sensitiveword));
+	}
+
+	/**
+	 * 新增或修改
+	 */
+	@PostMapping("/submit")
+	@ApiOperationSupport(order = 6)
+	@ApiOperation(value = "新增或修改", notes = "传入sensitiveword")
+	public R submit(@Valid @RequestBody Sensitiveword sensitiveword) {
+		return R.status(sensitivewordService.saveOrUpdate(sensitiveword));
+	}
+
+
+	/**
+	 * 删除
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 8)
+	@ApiOperation(value = "删除", notes = "传入ids")
+	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+		return R.status(sensitivewordService.removeByIds(Func.toLongList(ids)));
+	}
+}
diff --git a/src/main/java/org/springblade/modules/words/sensitiveword/dto/SensitivewordDTO.java b/src/main/java/org/springblade/modules/words/sensitiveword/dto/SensitivewordDTO.java
new file mode 100644
index 0000000..e208730
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/sensitiveword/dto/SensitivewordDTO.java
@@ -0,0 +1,34 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.words.sensitiveword.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.modules.words.sensitiveword.entity.Sensitiveword;
+
+/**
+ * 数据传输对象实体类
+ *
+ * @author BladeX
+ * @since 2022-01-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SensitivewordDTO extends Sensitiveword {
+	private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/main/java/org/springblade/modules/words/sensitiveword/entity/Sensitiveword.java b/src/main/java/org/springblade/modules/words/sensitiveword/entity/Sensitiveword.java
new file mode 100644
index 0000000..014b668
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/sensitiveword/entity/Sensitiveword.java
@@ -0,0 +1,45 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.words.sensitiveword.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 实体类
+ *
+ * @author BladeX
+ * @since 2022-01-04
+ */
+@Data
+@TableName("sys_sensitiveword")
+@ApiModel(value = "Sensitiveword对象", description = "Sensitiveword对象")
+public class Sensitiveword implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	@TableId(value = "id", type = IdType.AUTO)
+	private Integer id;
+	private String badword;
+
+
+}
diff --git a/src/main/java/org/springblade/modules/words/sensitiveword/mapper/SensitivewordMapper.java b/src/main/java/org/springblade/modules/words/sensitiveword/mapper/SensitivewordMapper.java
new file mode 100644
index 0000000..960b0a2
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/sensitiveword/mapper/SensitivewordMapper.java
@@ -0,0 +1,48 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.words.sensitiveword.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.modules.words.sensitiveword.entity.Sensitiveword;
+import org.springblade.modules.words.sensitiveword.vo.SensitivewordVO;
+
+import java.util.List;
+
+/**
+ *  Mapper 接口
+ *
+ * @author BladeX
+ * @since 2022-01-04
+ */
+public interface SensitivewordMapper extends BaseMapper<Sensitiveword> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param sensitiveword
+	 * @return
+	 */
+	List<SensitivewordVO> selectSensitivewordPage(IPage page, SensitivewordVO sensitiveword);
+
+	/**
+	 * 查询所有的敏感词数据集合
+	 * @return
+	 */
+    List<String> getBadSensitivewordList();
+}
diff --git a/src/main/java/org/springblade/modules/words/sensitiveword/mapper/SensitivewordMapper.xml b/src/main/java/org/springblade/modules/words/sensitiveword/mapper/SensitivewordMapper.xml
new file mode 100644
index 0000000..0751069
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/sensitiveword/mapper/SensitivewordMapper.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.modules.words.sensitiveword.mapper.SensitivewordMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="sensitivewordResultMap" type="org.springblade.modules.words.sensitiveword.entity.Sensitiveword">
+        <id column="id" property="id"/>
+        <result column="badword" property="badword"/>
+    </resultMap>
+
+
+    <select id="selectSensitivewordPage" resultMap="sensitivewordResultMap">
+        select * from sys_sensitiveword where is_deleted = 0
+    </select>
+
+    <!--查询所有的敏感词数据集合-->
+    <select id="getBadSensitivewordList" resultType="java.lang.String">
+        select badword from sys_sensitiveword where 1=1
+    </select>
+
+</mapper>
diff --git a/src/main/java/org/springblade/modules/words/sensitiveword/service/ISensitivewordService.java b/src/main/java/org/springblade/modules/words/sensitiveword/service/ISensitivewordService.java
new file mode 100644
index 0000000..3681925
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/sensitiveword/service/ISensitivewordService.java
@@ -0,0 +1,48 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.words.sensitiveword.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.modules.words.sensitiveword.entity.Sensitiveword;
+import org.springblade.modules.words.sensitiveword.vo.SensitivewordVO;
+
+import java.util.List;
+
+/**
+ *  服务类
+ *
+ * @author BladeX
+ * @since 2022-01-04
+ */
+public interface ISensitivewordService extends IService<Sensitiveword> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param sensitiveword
+	 * @return
+	 */
+	IPage<SensitivewordVO> selectSensitivewordPage(IPage<SensitivewordVO> page, SensitivewordVO sensitiveword);
+
+	/**
+	 * 查询所有的敏感词数据集合
+	 * @return
+	 */
+    List<String> getBadSensitivewordList();
+}
diff --git a/src/main/java/org/springblade/modules/words/sensitiveword/service/impl/SensitivewordServiceImpl.java b/src/main/java/org/springblade/modules/words/sensitiveword/service/impl/SensitivewordServiceImpl.java
new file mode 100644
index 0000000..bd3d967
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/sensitiveword/service/impl/SensitivewordServiceImpl.java
@@ -0,0 +1,51 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.words.sensitiveword.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springblade.modules.words.sensitiveword.entity.Sensitiveword;
+import org.springblade.modules.words.sensitiveword.mapper.SensitivewordMapper;
+import org.springblade.modules.words.sensitiveword.service.ISensitivewordService;
+import org.springblade.modules.words.sensitiveword.vo.SensitivewordVO;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ *  服务实现类
+ *
+ * @author BladeX
+ * @since 2022-01-04
+ */
+@Service
+public class SensitivewordServiceImpl extends ServiceImpl<SensitivewordMapper, Sensitiveword> implements ISensitivewordService {
+
+	@Override
+	public IPage<SensitivewordVO> selectSensitivewordPage(IPage<SensitivewordVO> page, SensitivewordVO sensitiveword) {
+		return page.setRecords(baseMapper.selectSensitivewordPage(page, sensitiveword));
+	}
+
+	/**
+	 * 查询所有的敏感词数据集合
+	 * @return
+	 */
+	@Override
+	public List<String> getBadSensitivewordList() {
+		return baseMapper.getBadSensitivewordList();
+	}
+}
diff --git a/src/main/java/org/springblade/modules/words/sensitiveword/vo/SensitivewordVO.java b/src/main/java/org/springblade/modules/words/sensitiveword/vo/SensitivewordVO.java
new file mode 100644
index 0000000..c924213
--- /dev/null
+++ b/src/main/java/org/springblade/modules/words/sensitiveword/vo/SensitivewordVO.java
@@ -0,0 +1,36 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.words.sensitiveword.vo;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.modules.words.sensitiveword.entity.Sensitiveword;
+
+/**
+ * 视图实体类
+ *
+ * @author BladeX
+ * @since 2022-01-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "SensitivewordVO对象", description = "SensitivewordVO对象")
+public class SensitivewordVO extends Sensitiveword {
+	private static final long serialVersionUID = 1L;
+
+}

--
Gitblit v1.9.3