黑发不知勤学早,白首方恨读书迟。 ——颜真卿

我们可能想使用本地配置词库、过滤器的方式去做全局屏蔽词处理

这里针对三种参数情况

1.requestParam传参:http://localhost:8080/test?keywords=屏蔽词2号

2.requestBody传参:请求体内传json格式的数据,请求头的Content-Typeapplication/json

3.pathvariable传参:http://localhost:8080/test/屏蔽词3号

这三种应该概括了绝大多数情况下参数传递与接收

代码如下:

首先是过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.ruben.simplescaffold.filter;


import com.alibaba.fastjson.JSON;
import com.ruben.simplescaffold.filter.wrappers.RequestWrapper;
import com.ruben.simplescaffold.filter.wrappers.ResponseWrapper;
import com.ruben.simplescaffold.utils.sensitive.SensitiveWordUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

/**
* 屏蔽词过滤器
*
* @author <achao1441470436@gmail.com>
* @since 2021/8/6 17:10
*/
@Slf4j
@Component
@WebFilter(filterName = "SensitiveWordFilter", urlPatterns = "/**")
public class SensitiveWordFilter implements Filter {

@Resource
private SensitiveWordUtils sensitiveWordUtils;

/**
* The <code>doFilter</code> method of the Filter is called by the container
* each time a request/response pair is passed through the chain due to a
* client request for a resource at the end of the chain. The FilterChain
* passed in to this method allows the Filter to pass on the request and
* response to the next entity in the chain.
* <p>
* A typical implementation of this method would follow the following
* pattern:- <br>
* 1. Examine the request<br>
* 2. Optionally wrap the request object with a custom implementation to
* filter content or headers for input filtering <br>
* 3. Optionally wrap the response object with a custom implementation to
* filter content or headers for output filtering <br>
* 4. a) <strong>Either</strong> invoke the next entity in the chain using
* the FilterChain object (<code>chain.doFilter()</code>), <br>
* 4. b) <strong>or</strong> not pass on the request/response pair to the
* next entity in the filter chain to block the request processing<br>
* 5. Directly set headers on the response after invocation of the next
* entity in the filter chain.
*
* @param request The request to process
* @param response The response associated with the request
* @param chain Provides access to the next filter in the chain for this
* filter to pass the request and response to for further
* processing
* @throws IOException if an I/O error occurs during this filter's
* processing of the request
* @throws ServletException if the processing fails for any other reason
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
long startTime = System.nanoTime();

// 过滤
RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest, sensitiveWordUtils, null, null);
String bodyString = RequestWrapper.BodyHelper.getBodyString(requestWrapper);

String uri = URLDecoder.decode(requestWrapper.getRequestURI(), StandardCharsets.UTF_8.displayName());
if (sensitiveWordUtils.isContainSensitiveWord(uri, SensitiveWordUtils.MAX_MATCH_TYPE)) {
requestWrapper.getRequestDispatcher(sensitiveWordUtils.replaceSensitiveWord(uri, SensitiveWordUtils.MAX_MATCH_TYPE, "0")).forward(requestWrapper, response);
return;
}
// 执行
chain.doFilter(requestWrapper, responseWrapper);

// 获取response返回的内容并重新写入response
String result = responseWrapper.getResponseData(response.getCharacterEncoding());
response.getOutputStream().write(result.getBytes());

log.info("method:{}", requestWrapper.getMethod());
log.info("uri:{}", requestWrapper.getRequestURI());
log.info("parameterMap:{}", JSON.toJSONString(requestWrapper.getParameterMap()));
log.info("bodyString:{}", bodyString);
log.info("responseCode:{}", responseWrapper.getStatus());
log.info("result:{}", result);
log.info("timeCost:{}", (System.nanoTime() - startTime) / (1000.0 * 1000.0) + "ms");
}
}

中间穿插了几个类:

第一个是屏蔽词过滤处理的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package com.ruben.simplescaffold.utils.sensitive;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
* 敏感词过滤
*
* @author chenming
* @version 1.0
* @since 2014年4月20日 下午4:17:15
*/
@Slf4j
@Component
public class SensitiveWordUtils {
public static final String REPLACE_CHAR = "*"; // 替换后的字符
public static int MIN_MATCH_TYPE = 1; //最小匹配规则
public static int MAX_MATCH_TYPE = 2; //最大匹配规则
@SuppressWarnings("rawtypes")
private static Map sensitiveWordMap = null;
@Value("${sensitive.path}")
private String sensitivePath;

public SensitiveWordUtils() {
}

/**
* 初始化敏感词库
*/
@PostConstruct
void init() {
sensitiveWordMap = SensitiveWordInit.getInstance().initKeyWord(sensitivePath);
}

/**
* 判断文字是否包含敏感字符
*
* @param txt 文字
* @param matchType 匹配规则 1:最小匹配规则,2:最大匹配规则
* @return 若包含返回true,否则返回false
* @author chenming
* @since 2014年4月20日 下午4:28:30
*/
public boolean isContainSensitiveWord(String txt, int matchType) {
boolean flag = false;
for (int i = 0; i < txt.length(); i++) {
int matchFlag = this.CheckSensitiveWord(txt, i, matchType); //判断是否包含敏感字符
if (matchFlag > 0) { //大于0存在,返回true
flag = true;
}
}
return flag;
}

/**
* 获取文字中的敏感词
*
* @param txt 文字
* @param matchType 匹配规则&nbsp;1:最小匹配规则,2:最大匹配规则
* @return 匹配的敏感词
* @author chenming
* @since 2014年4月20日 下午5:10:52
*/
public Set<String> getSensitiveWord(String txt, int matchType) {
Set<String> sensitiveWordList = new HashSet<>();

for (int i = 0; i < txt.length(); i++) {
int length = CheckSensitiveWord(txt, i, matchType); //判断是否包含敏感字符
if (length > 0) { //存在,加入list中
sensitiveWordList.add(txt.substring(i, i + length));
i = i + length - 1; //减1的原因,是因为for会自增
}
}

return sensitiveWordList;
}

/**
* 替换敏感字字符
*
* @param txt 字符串
* @param matchType 匹配规则
* @param replaceChar 替换字符,默认*
* @author chenming
* @since 2014年4月20日 下午5:12:07
*/
public String replaceSensitiveWord(String txt, int matchType, String replaceChar) {
String resultTxt = txt;
Set<String> set = getSensitiveWord(txt, matchType); //获取所有的敏感词
Iterator<String> iterator = set.iterator();
String word;
String replaceString;
while (iterator.hasNext()) {
word = iterator.next();
replaceString = getReplaceChars(replaceChar, word.length());
resultTxt = resultTxt.replaceAll(word, replaceString);
}

return resultTxt;
}

/**
* 获取替换字符串
*
* @param replaceChar 要替换的字符串
* @param length 长度
* @return 替换后的字符串
* @author chenming
* @since 2014年4月20日 下午5:21:19
*/
private String getReplaceChars(String replaceChar, int length) {
StringBuilder resultReplace = new StringBuilder(replaceChar);
for (int i = 1; i < length; i++) {
resultReplace.append(replaceChar);
}

return resultReplace.toString();
}

/**
* 检查文字中是否包含敏感字符,检查规则如下:<br>
*
* @param txt 需要检测的词
* @param beginIndex 开始下标
* @param matchType 匹配规则
* @return 如果存在,则返回敏感词字符的长度,不存在返回0
* @author chenming
* @since 2014年4月20日 下午4:31:03
*/
@SuppressWarnings({"rawtypes"})
public int CheckSensitiveWord(String txt, int beginIndex, int matchType) {
boolean flag = false; //敏感词结束标识位:用于敏感词只有1位的情况
int matchFlag = 0; //匹配标识数默认为0
char word;
Map nowMap = sensitiveWordMap;
for (int i = beginIndex; i < txt.length(); i++) {
word = txt.charAt(i);
nowMap = (Map) nowMap.get(word); //获取指定key
if (nowMap != null) { //存在,则判断是否为最后一个
matchFlag++; //找到相应key,匹配标识+1
if ("1".equals(nowMap.get("isEnd"))) { //如果为最后一个匹配规则,结束循环,返回匹配标识数
flag = true; //结束标志位为true
if (SensitiveWordUtils.MIN_MATCH_TYPE == matchType) { //最小规则,直接返回,最大规则还需继续查找
break;
}
}
} else { //不存在,直接返回
break;
}
}
if (matchFlag < 2 || !flag) { //长度必须大于等于1,为词
matchFlag = 0;
}
return matchFlag;
}

}

这个工具类需要初始化加载词库,因此我们还有一个初始化的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package com.ruben.simplescaffold.utils.sensitive;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
* 初始化敏感词库,将敏感词加入到HashMap中,构建DFA算法模型
*
* @author : chenming
* @since 2014年4月20日 下午2:27:06
*/
public class SensitiveWordInit {
private volatile static SensitiveWordInit lazyMan;//volatile避免指令重排
private static boolean SINGLE_SIGN = false;//红绿等解决通过反射创建对象(反编译可以破解该方法)
@SuppressWarnings("rawtypes")
public HashMap sensitiveWordMap;

private SensitiveWordInit() {
synchronized (SensitiveWordInit.class) {
if (!SINGLE_SIGN) {
SINGLE_SIGN = true;
} else {
throw new RuntimeException("不要试图使用反射破坏单例");
}
}
}

//双重检测锁模式的 懒汉式单例 DCL懒汉式
public static SensitiveWordInit getInstance() {
if (lazyMan == null) {
lazyMan = new SensitiveWordInit();//不是一个原子性操作
}
return lazyMan;
}

/**
* @author chenming
* @since 2014年4月20日 下午2:28:32
*/
@SuppressWarnings("rawtypes")
public Map initKeyWord(String filePath) {
try {
//读取敏感词库
Set<String> keyWordSet = readSensitiveWordFile(filePath);
//将敏感词库加入到HashMap中
addSensitiveWordToHashMap(keyWordSet);
//spring获取application,然后application.setAttribute("sensitiveWordMap",sensitiveWordMap);
} catch (Exception e) {
e.printStackTrace();
return Collections.emptyMap();
}
return sensitiveWordMap;
}

/**
* 读取敏感词库,将敏感词放入HashSet中,构建一个DFA算法模型:<br>
* 中 = {
* isEnd = 0
* 国 = {<br>
* isEnd = 1
* 人 = {isEnd = 0
* 民 = {isEnd = 1}
* }
* 男 = {
* isEnd = 0
* 人 = {
* isEnd = 1
* }
* }
* }
* }
* 五 = {
* isEnd = 0
* 星 = {
* isEnd = 0
* 红 = {
* isEnd = 0
* 旗 = {
* isEnd = 1
* }
* }
* }
* }
*
* @param keyWordSet 敏感词库
* @author chenming
* @since 2014年4月20日 下午3:04:20
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private void addSensitiveWordToHashMap(Set<String> keyWordSet) {
sensitiveWordMap = new HashMap(keyWordSet.size()); //初始化敏感词容器,减少扩容操作
String key;
Map nowMap;
Map<String, String> newWorMap;
//迭代keyWordSet
for (String s : keyWordSet) {
key = s; //关键字
nowMap = sensitiveWordMap;
for (int i = 0; i < key.length(); i++) {
char keyChar = key.charAt(i); //转换成char型
Object wordMap = nowMap.get(keyChar); //获取

if (wordMap != null) { //如果存在该key,直接赋值
nowMap = (Map) wordMap;
} else { //不存在则,则构建一个map,同时将isEnd设置为0,因为他不是最后一个
newWorMap = new HashMap<>();
newWorMap.put("isEnd", "0"); //不是最后一个
nowMap.put(keyChar, newWorMap);
nowMap = newWorMap;
}

if (i == key.length() - 1) {
nowMap.put("isEnd", "1"); //最后一个
}
}
}
}

/**
* 读取敏感词库中的内容,将内容添加到set集合中
*
* @throws FileNotFoundException,NullPointerException 抛出空指针或者文件未找到异常
* @author chenming
* @since 2014年4月20日 下午2:31:18
*/
private Set<String> readSensitiveWordFile(String filePath) throws Exception {
Set<String> set;
InputStream inputStream;
try {
inputStream = new FileInputStream(filePath);
} catch (FileNotFoundException e) {
inputStream = Objects.requireNonNull(ClassLoader.getSystemResourceAsStream("sensitive.txt"));
}
try (InputStreamReader read = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
set = new HashSet<>();
BufferedReader bufferedReader = new BufferedReader(read);
String txt;
while ((txt = bufferedReader.readLine()) != null) { //读取文件,将文件内容放入到set中
set.add(txt);
}
}
//关闭文件流
return set;
}
}

这里注意,首先我们会去找配置文件中配置的sensitive.path

image-20210808003727126

找不到则加载resources下的sensitive.txt

image-20210808003800965

然后是过滤器中需要的过滤请求的封装好的request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package com.ruben.simplescaffold.filter.wrappers;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.ruben.simplescaffold.utils.sensitive.SensitiveWordUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
* 封装请求
*
* @author <achao1441470436@gmail.com>
* @since 2021/8/7 21:50
*/
public class RequestWrapper extends HttpServletRequestWrapper {

private final Map<String, String[]> parameterMap;
private final byte[] body;

public RequestWrapper(HttpServletRequest request, SensitiveWordUtils sensitiveWordUtils, Map<String, String[]> parameterMap, String bodyString) {
super(request);
if (sensitiveWordUtils == null) {
this.parameterMap = Optional.ofNullable(parameterMap).orElseGet(super::getParameterMap);
queryStringPutParameterMap(super.getQueryString());
this.body = Optional.ofNullable(bodyString).orElseGet(() -> BodyHelper.getBodyString(request)).getBytes(StandardCharsets.UTF_8);
} else {
String paramString = JSON.toJSONString(Optional.ofNullable(parameterMap).orElseGet(super::getParameterMap));
paramString = sensitiveWordUtils.replaceSensitiveWord(paramString, SensitiveWordUtils.MAX_MATCH_TYPE, SensitiveWordUtils.REPLACE_CHAR);
this.parameterMap = JSON.parseObject(paramString, new TypeReference<Map<String, String[]>>() {
});
Optional.ofNullable(super.getQueryString()).ifPresent(queryString -> queryStringPutParameterMap(sensitiveWordUtils.replaceSensitiveWord(queryString, SensitiveWordUtils.MAX_MATCH_TYPE, SensitiveWordUtils.REPLACE_CHAR)));
this.body = sensitiveWordUtils.replaceSensitiveWord(Optional.ofNullable(bodyString).orElseGet(() -> BodyHelper.getBodyString(request)), SensitiveWordUtils.MAX_MATCH_TYPE, SensitiveWordUtils.REPLACE_CHAR).getBytes(StandardCharsets.UTF_8);
}

}

@Override
public String getParameter(String name) {
String[] values = parameterMap.get(name);
if (Objects.isNull(values) || values.length == 0) {
return null;
}
return values[0];
}

@Override
public Map<String, String[]> getParameterMap() {
if (parameterMap == null) {
return super.getParameterMap();
}
return parameterMap;
}

@Override
public Enumeration<String> getParameterNames() {
if (parameterMap == null) {
return super.getParameterNames();
}
return new Vector<>(parameterMap.keySet()).elements();
}

@Override
public String[] getParameterValues(String name) {
if (parameterMap == null) {
return super.getParameterValues(name);
}
return parameterMap.get(name);
}

@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return byteArrayInputStream.read();
}

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener arg0) {
}
};
}

@Override
public String getHeader(String name) {
return super.getHeader(name);
}

@Override
public Enumeration<String> getHeaderNames() {
return super.getHeaderNames();
}

@Override
public Enumeration<String> getHeaders(String name) {
return super.getHeaders(name);
}

private void queryStringPutParameterMap(String queryString) {
if (queryString != null && queryString.trim().length() > 0) {
String[] params = queryString.split("&");
for (String param : params) {
int splitIndex = param.indexOf("=");
if (splitIndex == -1) {
continue;
}
String key = param.substring(0, splitIndex);
if (!this.parameterMap.containsKey(key)) {
if (splitIndex < param.length()) {
String value = param.substring(splitIndex + 1);
this.parameterMap.put(key, new String[]{value});
}
}
}
}
}


public static class BodyHelper {
/**
* 获取请求Body
*
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
}

还有个获取响应结果的封装好的response,虽然此处我们没用到,但如果我们要获取filter中的响应体执行过滤,也可以用这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package com.ruben.simplescaffold.filter.wrappers;

import lombok.SneakyThrows;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;

/**
* 过滤器获取响应结果
*
* @author <achao1441470436@gmail.com>
* @since 2021/7/29 10:13
*/
public class ResponseWrapper extends HttpServletResponseWrapper {

private ByteArrayOutputStream buffer = null;
private ServletOutputStream outputStream = null;
private PrintWriter writer = null;

/**
* Constructs a response adaptor wrapping the given response.
*
* @param response The response to be wrapped
* @throws IllegalArgumentException if the response is null
*/
@SneakyThrows
public ResponseWrapper(HttpServletResponse response) {
super(response);
buffer = new ByteArrayOutputStream();
outputStream = new WrapperOutputStream(buffer);
writer = new PrintWriter(new OutputStreamWriter(buffer, StandardCharsets.UTF_8));
}

/**
* The default behavior of this method is to return getOutputStream() on the
* wrapped response object.
*/
@Override
public ServletOutputStream getOutputStream() throws IOException {
return outputStream;
}

/**
* The default behavior of this method is to return getWriter() on the
* wrapped response object.
*/
@Override
public PrintWriter getWriter() throws IOException {
return writer;
}

/**
* The default behavior of this method is to call flushBuffer() on the
* wrapped response object.
*/
@Override
@SneakyThrows
public void flushBuffer() {
if (outputStream != null) {
outputStream.flush();
}
if (writer != null) {
writer.flush();
}
}

/**
* The default behavior of this method is to call reset() on the wrapped
* response object.
*/
@Override
public void reset() {
buffer.reset();
}

/**
* Get response content
*
* @param charset HttpServletResponse#getCharacterEncoding()
* @return response content
*/
@SneakyThrows
public String getResponseData(String charset) {
// 将out、writer中的数据强制输出到WrapperResponse的buffer里面,否则取不到数据
flushBuffer();
return buffer.toString(StandardCharsets.UTF_8.displayName());
}

/**
* 内部类,对ServletOutputStream进行包装,指定输出流的输出端
*/
private static class WrapperOutputStream extends ServletOutputStream {

private ByteArrayOutputStream bos = null;

public WrapperOutputStream(ByteArrayOutputStream stream) throws IOException {
bos = stream;
}


/**
* Writes the specified byte to this output stream. The general
* contract for <code>write</code> is that one byte is written
* to the output stream. The byte to be written is the eight
* low-order bits of the argument <code>b</code>. The 24
* high-order bits of <code>b</code> are ignored.
* <p>
* Subclasses of <code>OutputStream</code> must provide an
* implementation for this method.
*
* @param b the <code>byte</code>.
* @throws IOException if an I/O error occurs. In particular,
* an <code>IOException</code> may be thrown if the
* output stream has been closed.
*/
@Override
public void write(int b) throws IOException {
bos.write(b);
}


/**
* Checks if a non-blocking write will succeed. If this returns
* <code>false</code>, it will cause a callback to
* {@link WriteListener#onWritePossible()} when the buffer has emptied. If
* this method returns <code>false</code> no further data must be written
* until the contain calls {@link WriteListener#onWritePossible()}.
*
* @return <code>true</code> if data can be written, else <code>false</code>
* @since Servlet 3.1
*/
@Override
public boolean isReady() {
return false;
}

/**
* Sets the {@link WriteListener} for this {@link ServletOutputStream} and
* thereby switches to non-blocking IO. It is only valid to switch to
* non-blocking IO within async processing or HTTP upgrade processing.
*
* @param listener The non-blocking IO write listener
* @throws IllegalStateException If this method is called if neither
* async nor HTTP upgrade is in progress or
* if the {@link WriteListener} has already
* been set
* @throws NullPointerException If listener is null
* @since Servlet 3.1
*/
@Override
public void setWriteListener(WriteListener listener) {

}
}

}

最后是测试用的controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.ruben.simplescaffold.controller.rest;

import com.ruben.simplescaffold.pojo.common.Result;
import com.ruben.simplescaffold.pojo.dto.CommonDTO;
import org.springframework.web.bind.annotation.*;

/**
* 测试控制层
*
* @author <achao1441470436@gmail.com>
* @since 2021/8/7 22:58
*/
@RestController
@RequestMapping("test")
public class TestController {

@PostMapping
public Result testBody(@RequestBody CommonDTO commonDTO) {
return Result.ok().data(commonDTO);
}

@GetMapping
public Result testQueryParam(CommonDTO commonDTO) {
return Result.ok().data(commonDTO);
}

@GetMapping("{path}")
public Result testPath(@PathVariable String path) {
return Result.ok().data(path);
}

}

这里还用到两个类,一个响应结果Result,一个CommonDTOCommonDTO大伙可以使用Map<String,Object>代替下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.ruben.simplescaffold.pojo.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.http.HttpStatus;

import java.util.HashMap;
import java.util.Optional;

/**
* 自定义返回响应
*
* @author <achao1441470436@gmail.com>
* @since 2021/6/23 10:40
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(fluent = true)
@EqualsAndHashCode(callSuper = true)
public class Result extends HashMap<String, Object> {
public static final String DATA_KEY = "data";
private static final long serialVersionUID = 8639693790921600208L;
/**
* 成功状态
*/
private Boolean success;
/**
* 消息
*/
private String msg;
/**
* 状态码
*/
private Integer code;

public static Result ok() {
return httpStatus(HttpStatus.OK);
}

public static Result error(String msg) {
return httpStatus(HttpStatus.INTERNAL_SERVER_ERROR).msg(msg);
}

public static Result paramError() {
return httpStatus(HttpStatus.BAD_REQUEST);
}

public static Result notLogin() {
return httpStatus(HttpStatus.UNAUTHORIZED);
}

public static Result httpStatus(HttpStatus httpStatus) {
httpStatus = Optional.ofNullable(httpStatus).orElse(HttpStatus.OK);
HttpStatus.Series series = httpStatus.series();
return new Result().success(httpStatus.is2xxSuccessful()).msg(series.name()).code(HttpStatus.OK.value());
}

/**
* 放入key和value
*
* @param key 键
* @param value 值
* @return com.kuang.honghaisyweb.pojo.common.Result
* @author <achao1441470436@gmail.com>
* @since 2021/6/23 17:07
*/
@Override
public Result put(String key, Object value) {
super.put(key, value);
return this;
}

/**
* 返回数据
*
* @param value 数据
* @return com.kuang.honghaisyweb.pojo.common.Result
* @author <achao1441470436@gmail.com>
* @since 2021/6/23 17:07
*/
public Result data(Object value) {
this.put(DATA_KEY, value);
return this;
}

}

最后是屏蔽词文本

1
2
3
屏蔽词1号
屏蔽词2号
屏蔽词3号

以及测试案例:

image-20210808005622525

image-20210808005634211

image-20210808005641787

可以看到bodyparam传参都替换成了*号,urlpath传参也替换成了0

完整源码:https://gitee.com/VampireAchao/simple-scaffold.git