场景:在微服务中,一般返回数据都会有个返回码、返回信息和返回消息体,但是每次返回时候调用或者是封装,太过麻烦,有没有什么办法不用每次都封装呢?
答案是有的。
返回值对象 ResponseData
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
|
package com.study.auth.comm;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.io.Serializable;
/**
* @Package: com.study.auth.comm
* @Description: <返回数据>
* @Author: MILLA
* @CreateDate: 2018/4/8 9:10
* @UpdateUser: MILLA
* @UpdateDate: 2018/4/8 9:10
* @Version: 1.0
*/
public final class ResponseData<T> implements Serializable {
private static final long serialVersionUID = 7824278330465676943L;
private static final String SUCCESS_CODE = "1000" ;
private static final String SUCCESS_MSG = "success" ;
/**
* 响应编码
*/
@JSONField (serialzeFeatures = {SerializerFeature.WriteMapNullValue}, ordinal = 1 )
private String code;
/**
* 响应提示
*/
@JSONField (serialzeFeatures = {SerializerFeature.WriteMapNullValue}, ordinal = 2 )
private String msg;
/**
* 返回的数据
*/
@JSONField (serialzeFeatures = {SerializerFeature.WriteMapNullValue}, ordinal = 10 )
private T data;
public static ResponseData success() {
return initData(SUCCESS_CODE, SUCCESS_MSG, null );
}
public static ResponseData error(String code) {
String msg = PropertiesReaderUtil.getProperty(code, null );
return initData(code, msg, null );
}
public static ResponseData error(String code, String msg) {
return initData(code, msg, null );
}
public static <T> ResponseData success(T t) {
return initData(SUCCESS_CODE, SUCCESS_MSG, t);
}
public static <T> ResponseData errorData(String code, T data) {
String msg = PropertiesReaderUtil.getProperty(code, null );
return initData(code, msg, data);
}
public static <T> ResponseData errorData(String code, String msg, T data) {
return initData(code, msg, data);
}
private static <T> ResponseData initData(String code, String msg, T t) {
ResponseData data = new ResponseData(SUCCESS_CODE);
if (!isBlank(msg)) {
data.setMsg(msg);
}
if (!isBlank(code)) {
data.setCode(code);
}
if (t != null ) {
data.setData(t);
}
return data;
}
private static boolean isBlank(CharSequence cs) {
int strLen;
if (cs != null && (strLen = cs.length()) != 0 ) {
for ( int i = 0 ; i < strLen; ++i) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false ;
}
}
return true ;
} else {
return true ;
}
}
public ResponseData() {
}
public ResponseData(String code) {
this .code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this .code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this .msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this .data = data;
}
@Override
public String toString() {
return JSON.toJSONString( this );
}
}
|
如上图的包装,还是太繁琐了。
装饰者模式使用-增强类InitializingAdviceDecorator通过实现InitializingBean和装饰者模式对Controller层的返回值进行包装,大致思路:
通过RequestMappingHandlerAdapter获取所有的返回值处理对象HandlerMethodReturnValueHandler创建一个新的集合存储上一步获取的集合(因为上一步的结果是unmodifiableList类型的)遍历该集合找到HandlerMethodReturnValueHandler对象,将这个位置的handler替换程自定义的handler将新获到的集合重新设置到RequestMappingHandlerAdapter的setReturnValueHandlers方法中
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
|
package com.study.auth.config;
import com.study.auth.comm.ResponseData;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import java.util.ArrayList;
import java.util.List;
/**
* @Package: com.study.auth.config.core
* @Description: <增强controller层返回值>
* @Author: milla
* @CreateDate: 2020/09/04 14:42
* @UpdateUser: milla
* @UpdateDate: 2020/09/04 14:42
* @UpdateRemark: <>
* @Version: 1.0
*/
@Configuration
public class InitializingAdviceDecorator implements InitializingBean {
@Autowired
private RequestMappingHandlerAdapter adapter;
@Override
public void afterPropertiesSet() {
//获取所有的handler对象
List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
//因为上面返回的是unmodifiableList,所以需要新建list处理
List<HandlerMethodReturnValueHandler> handlers = new ArrayList(returnValueHandlers);
this .decorateHandlers(handlers);
//将增强的返回值回写回去
adapter.setReturnValueHandlers(handlers);
}
/**
* 使用自定义的返回值控制类
*
* @param handlers
*/
private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
for (HandlerMethodReturnValueHandler handler : handlers) {
if (handler instanceof RequestResponseBodyMethodProcessor) {
//找到返回值的handler并将起包装成自定义的handler
ControllerReturnValueHandler decorator = new ControllerReturnValueHandler((RequestResponseBodyMethodProcessor) handler);
int index = handlers.indexOf(handler);
handlers.set(index, decorator);
break ;
}
}
}
/**
* 自定义返回值的Handler
* 采用装饰者模式
*/
private class ControllerReturnValueHandler implements HandlerMethodReturnValueHandler {
//持有一个被装饰者对象
private HandlerMethodReturnValueHandler handler;
ControllerReturnValueHandler(RequestResponseBodyMethodProcessor handler) {
this .handler = handler;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return true ;
}
/**
* 增强被装饰者的功能
*
* @param returnValue 返回值
* @param returnType 返回类型
* @param mavContainer view
* @param webRequest 请求对象
* @throws Exception 抛出异常
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//如果已经封装了结构体就直接放行
if (returnValue instanceof ResponseData) {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
return ;
}
//正常返回success
ResponseData success = ResponseData.success(returnValue);
handler.handleReturnValue(success, returnType, mavContainer, webRequest);
}
}
}
|
配置文件读取类PropertiesReaderUtil
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
|
package com.study.auth.comm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
/**
* @Package: com.milla.navicat.comm
* @Description: <读取配置properties工具类>
* @Author: MILLA
* @CreateDate: 2018/8/10 10:30
* @UpdateUser: MILLA
* @UpdateDate: 2018/8/10 10:30
* @UpdateRemark: <>
* @Version: 1.0
*/
public final class PropertiesReaderUtil {
private static final String ENCODING = "UTF-8" ;
private static final Logger logger = LoggerFactory.getLogger(PropertiesReaderUtil. class );
private static Properties propsZH;
private static Properties propsCN;
private static String name = null ;
static {
//加载英文
//loadProps(false);
//加载中文
loadProps( true );
}
/**
* 第一种,通过类加载器进行获取properties文件流
* 第二种,通过类进行获取properties文件流
* in = PropertyUtil.class.getResourceAsStream("/properties/message_ZH.properties");
* in = PropertiesReaderUtil.class.getClassLoader().getResourceAsStream("properties/message_ZH.properties");
*/
synchronized static private void loadProps( boolean isZh) {
logger.debug( "start loading properties" );
InputStream in = null ;
if (isZh) {
propsZH = new Properties();
name = "properties/message_ZH.properties" ;
in = PropertiesReaderUtil. class .getClassLoader().getResourceAsStream(name);
} else {
propsCN = new Properties();
name = "properties/message_EN.properties" ;
in = PropertiesReaderUtil. class .getClassLoader().getResourceAsStream(name);
}
try {
if (isZh) {
propsZH.load( new InputStreamReader(in, ENCODING));
} else {
propsCN.load( new InputStreamReader(in, ENCODING));
}
} catch (Exception e) {
logger.debug( "loading properties error :{}" , e);
} finally {
try {
if ( null != in) {
in.close();
}
} catch (IOException e) {
logger.debug( "closing properties io error :{}" , e);
}
}
}
public static String getProperty(String key) {
return getPropertyZH(key);
}
public static String getProperty(String key, String defaultValue) {
return getPropertyZH(key, defaultValue);
}
public static String getPropertyZH(String key) {
if ( null == propsZH) {
loadProps( true );
}
return propsZH.getProperty(key);
}
public static String getPropertyZH(String key, String defaultValue) {
if ( null == propsZH) {
loadProps( true );
}
return propsZH.getProperty(key, defaultValue);
}
public static String getPropertyCN(String key) {
if ( null == propsCN) {
loadProps( false );
}
return propsCN.getProperty(key);
}
public static String getPropertyCN(String key, String defaultValue) {
if ( null == propsCN) {
loadProps( false );
}
return propsCN.getProperty(key, defaultValue);
}
}
|
配置文件message_ZH.properties路径为:properties/message_ZH.properties
也可添加国家化英文或者是其他语言配置
1001=用户未登录
#====非业务返回码=========
1100=服务器内部错误
1101=空指针异常
1102=数据类型转换异常
1103=IO异常
1104=该方法找不到异常
1105=数组越界异常
1106=请求体缺失异常
1107=类型匹配异常
1108=请求参数缺失异常
1109=请求方法不支持异常
1110=请求头类型不支持异常
1111=参数解析异常
1112=必要参数不能为空
#=======================
统一异常捕捉类RestfulExceptionHandler此时,基本能保证增强Controller层的返回值了,如果有需要的话,可能通过@RestControllerAdvice注解,针对抛出的异常使用返回值对象进行包装
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
|
package com.study.auth.exception;
import com.alibaba.fastjson.JSONException;
import com.study.auth.comm.PropertiesReaderUtil;
import com.study.auth.comm.ResponseData;
import com.study.auth.constant.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.boot.json.JsonParseException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.security.auth.login.AccountException;
import java.io.IOException;
import java.sql.SQLException;
/**
* @Package: com.study.auth.exception
* @Description: <所有异常拦截类>
* @Author: milla
* @CreateDate: 2020/09/04 15:35
* @UpdateUser: milla
* @UpdateDate: 2020/09/04 15:35
* @UpdateRemark: <>
* @Version: 1.0
*/
@Slf4j
@RestControllerAdvice
public class RestfulExceptionHandler {
private ResponseData responseData(String code, Exception e) {
log.error( "异常代码:{},异常描述:{},异常堆栈:" , code, PropertiesReaderUtil.getProperty(code), e);
return ResponseData.error(code);
}
private ResponseData<String> responseData(String code, String message, Exception e) {
log.error( "异常代码:{},异常描述:{},异常堆栈:" , code, message, e);
return ResponseData.error(code, message);
}
/**
* 运行时异常
*
* @param e 异常
* @return
*/
@ExceptionHandler (Exception. class )
public ResponseData runtimeExceptionHandler(Exception e) {
return responseData(CommonConstant.EX_RUN_TIME_EXCEPTION, e);
}
/**
* 处理SQLSyntaxErrorException
*
* @param e 异常
* @return
*/
@ExceptionHandler (SQLException. class )
public ResponseData<String> sqlException(SQLException e) {
return responseData(CommonConstant.EX_RUN_TIME_EXCEPTION, e.getMessage(), e);
}
/**
* 处理CustomerMessageException
*
* @param e 异常
* @return
*/
@ExceptionHandler (CustomMessageException. class )
public ResponseData<String> customerMessageException(CustomMessageException e) {
return responseData(CommonConstant.EX_RUN_TIME_EXCEPTION, e.getMessage(), e);
}
/**
* 处理AccountException
*
* @param e 异常
* @return
*/
@ExceptionHandler (AccountException. class )
public ResponseData<String> accountException(AccountException e) {
return responseData(e.getMessage(), e);
}
//---------------------------------------jdk/spring自带的异常----------------------------------
/**
* 处理IllegalArgumentException
*
* @param e 异常
* @return
*/
@ExceptionHandler (IllegalArgumentException. class )
public ResponseData<String> illegalArgumentException(IllegalArgumentException e) {
return responseData(CommonConstant.EX_RUN_TIME_EXCEPTION, e.getMessage(), e);
}
/**
* 空指针异常
*
* @param e 异常
* @return
*/
@ResponseStatus
@ExceptionHandler (NullPointerException. class )
public ResponseData nullPointerExceptionHandler(NullPointerException e) {
return responseData(CommonConstant.EX_NULL_POINTER_EXCEPTION, e);
}
/**
* 类型转换异常
*
* @param e 异常
* @return
*/
@ExceptionHandler (ClassCastException. class )
public ResponseData classCastExceptionHandler(ClassCastException e) {
return responseData(CommonConstant.EX_CLASS_CAST_EXCEPTION, e);
}
/**
* IO异常
*
* @param e 异常
* @return
*/
@ExceptionHandler (IOException. class )
public ResponseData iOExceptionHandler(IOException e) {
return responseData(CommonConstant.EX_IO_EXCEPTION, e);
}
/**
* 未知方法异常
*
* @param e 异常
* @return
*/
@ExceptionHandler (NoSuchMethodException. class )
public ResponseData noSuchMethodExceptionHandler(NoSuchMethodException e) {
return responseData(CommonConstant.EX_NO_SUCH_METHOD_EXCEPTION, e);
}
/**
* 数组越界异常
*
* @param e 异常
* @return
*/
@ExceptionHandler (IndexOutOfBoundsException. class )
public ResponseData indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException e) {
return responseData(CommonConstant.EX_INDEX_OUT_OF_BOUNDS_EXCEPTION, e);
}
/**
* 请求body缺失异常
*
* @param e 异常
* @return
*/
@ExceptionHandler ({HttpMessageNotReadableException. class })
public ResponseData requestNotReadable(HttpMessageNotReadableException e) {
return responseData(CommonConstant.EX_HTTP_MESSAGE_NOT_READABLE_EXCEPTION, e);
}
/**
* 类型匹配异常
*
* @param e 异常
* @return
*/
@ExceptionHandler ({TypeMismatchException. class })
public ResponseData requestTypeMismatch(TypeMismatchException e) {
return responseData(CommonConstant.EX_HTTP_MESSAGE_NOT_READABLE_EXCEPTION, e);
}
/**
* 方法不支持异常
*
* @param e 异常
* @return
*/
@ExceptionHandler ({HttpRequestMethodNotSupportedException. class })
public ResponseData methodNotSupported(HttpRequestMethodNotSupportedException e) {
return responseData(CommonConstant.EX_HTTP_REQUEST_METHOD_NOT_SUPPORTED_EXCEPTION, e);
}
/**
* 请求头不支持异常
*
* @param e 异常
* @return
*/
@ExceptionHandler ({HttpMediaTypeNotSupportedException. class })
public ResponseData mediaTypeNotAcceptable(HttpMediaTypeNotSupportedException e) {
return responseData(CommonConstant.EX_HTTP_MEDIA_TYPE_NOT_ACCEPTABLE_EXCEPTION, e);
}
/**
* 参数解析异常
*
* @param e 异常
* @return
*/
@ExceptionHandler (JSONException. class )
public ResponseData runtimeExceptionHandler(JSONException e) {
return responseData(CommonConstant.PARAMS_PARSE_EXCEPTION, e);
}
/**
* 参数解析异常
*
* @param e 异常
* @return
*/
@ExceptionHandler (JsonParseException. class )
public ResponseData runtimeExceptionHandler(JsonParseException e) {
return responseData(CommonConstant.PARAMS_PARSE_EXCEPTION, e);
}
/**
* 请求参数缺失异常
*
* @param e 异常
* @return
*/
@ExceptionHandler ({MissingServletRequestParameterException. class })
public ResponseData requestMissingServletRequest(MissingServletRequestParameterException e) {
return responseData(CommonConstant.EX_MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION, e);
}
/**
* 参数不能为空
*
* @param e 异常
* @return
*/
@ExceptionHandler (MethodArgumentNotValidException. class )
public ResponseData exceptionHandler(MethodArgumentNotValidException e) {
return responseData(CommonConstant.PARAMS_IS_NULL, e);
}
}
|
常量类 CommonConstant
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
|
package com.study.auth.constant;
/**
* @Package: com.study.auth.constant
* @Description: <公共常量类>
* @Author: milla
* @CreateDate: 2020/09/04 15:37
* @UpdateUser: milla
* @UpdateDate: 2020/09/04 15:37
* @UpdateRemark: <>
* @Version: 1.0
*/
public final class CommonConstant {
/**
* 当前用户名称
*/
public static final String C_CURRENT_ACCOUNT = "current_account" ;
/**
* 用户未登录
*/
public static final String EX_NO_TOKEN_EXCEPTION = "1001" ;
//--------------------------------非业务返回码---------------------------------------
/**
* 运行时异常
*/
public static final String EX_RUN_TIME_EXCEPTION = "1100" ;
/**
* 空指针异常
*/
public static final String EX_NULL_POINTER_EXCEPTION = "1101" ;
/**
* 数据转换异常
*/
public static final String EX_CLASS_CAST_EXCEPTION = "1102" ;
/**
* IO异常
*/
public static final String EX_IO_EXCEPTION = "1103" ;
/**
* 找不到该方法异常
*/
public static final String EX_NO_SUCH_METHOD_EXCEPTION = "1104" ;
/**
* 数组越界异常
*/
public static final String EX_INDEX_OUT_OF_BOUNDS_EXCEPTION = "1105" ;
/**
* 请求体缺失异常
*/
public static final String EX_HTTP_MESSAGE_NOT_READABLE_EXCEPTION = "1106" ;
/**
* TYPE匹配异常
*/
public static final String EX_TYPE_MISMATCH_EXCEPTION = "1107" ;
/**
* 请求参数丢失
*/
public static final String EX_MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION = "1108" ;
/**
* 请求方法类型不支持异常
*/
public static final String EX_HTTP_REQUEST_METHOD_NOT_SUPPORTED_EXCEPTION = "1109" ;
/**
* MEDIA 类型不支持异常
*/
public static final String EX_HTTP_MEDIA_TYPE_NOT_ACCEPTABLE_EXCEPTION = "1110" ;
/**
* 参数解析异常
*/
public static final String PARAMS_PARSE_EXCEPTION = "1111" ;
/**
* 参数不能为空
*/
public static final String PARAMS_IS_NULL = "1112" ;
//-----------------------------------------------------------------------------------
}
|
自定义异常类 CustomMessageException
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
|
package com.study.auth.exception;
/**
* @Package: com.study.auth.exception
* @Description: <自定义异常类>
* @Author: MILLA
* @CreateDate: 2019/8/15 18:39
* @UpdateUser: MILLA
* @UpdateDate: 2019/8/15 18:39
* @UpdateRemark: <>
* @Version: 1.0
*/
public class CustomMessageException extends RuntimeException {
public CustomMessageException() {
super ();
}
public CustomMessageException(String message) {
super (message);
}
public CustomMessageException(String message, Throwable cause) {
super (message, cause);
}
public CustomMessageException(Throwable cause) {
super (cause);
}
protected CustomMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super (message, cause, enableSuppression, writableStackTrace);
}
}
|
所需依赖因为使用了阿里的fastJson工具类还需要进入该类的依赖
1
2
3
4
5
|
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version> 1.2 . 58 </version>
</dependency>
|
至此,可以愉快的使用该返回值的增强类了,在为服务中,还以将该代码重构到comm中,供多个服务共同使用,避免重复早*
到此这篇关于关于Controller 层返回值的公共包装类的问题的文章就介绍到这了,更多相关Controller 层返回值包装类内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://blog.csdn.net/hu10131013/article/details/108453535