@wutiange/log-listener-plugin 1.3.0-alpha.2 → 1.3.0-alpha.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +10 -2
  3. package/dist/src/HTTPInterceptor.d.ts +3 -2
  4. package/dist/src/HTTPInterceptor.js +29 -8
  5. package/dist/src/HTTPInterceptor.js.map +1 -1
  6. package/dist/src/{server.d.ts → Server.d.ts} +1 -0
  7. package/dist/src/{server.js → Server.js} +5 -2
  8. package/dist/src/Server.js.map +1 -0
  9. package/dist/src/__mocks__/react-native/Libraries/Blob/FileReader.d.ts +14 -0
  10. package/dist/src/__mocks__/react-native/Libraries/Blob/FileReader.js +42 -0
  11. package/dist/src/__mocks__/react-native/Libraries/Blob/FileReader.js.map +1 -0
  12. package/dist/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.d.ts +17 -0
  13. package/dist/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.js +35 -0
  14. package/dist/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.js.map +1 -0
  15. package/dist/src/__tests__/HTTPInterceptor.test.d.ts +9 -0
  16. package/dist/src/__tests__/HTTPInterceptor.test.js +281 -0
  17. package/dist/src/__tests__/HTTPInterceptor.test.js.map +1 -0
  18. package/dist/src/__tests__/Server.test.d.ts +1 -0
  19. package/dist/src/__tests__/Server.test.js +155 -0
  20. package/dist/src/__tests__/Server.test.js.map +1 -0
  21. package/dist/src/__tests__/utils.test.d.ts +1 -0
  22. package/dist/src/__tests__/utils.test.js +108 -0
  23. package/dist/src/__tests__/utils.test.js.map +1 -0
  24. package/dist/src/logPlugin.d.ts +2 -1
  25. package/dist/src/logPlugin.js +13 -8
  26. package/dist/src/logPlugin.js.map +1 -1
  27. package/dist/src/utils.d.ts +4 -1
  28. package/dist/src/utils.js +75 -12
  29. package/dist/src/utils.js.map +1 -1
  30. package/package.json +54 -47
  31. package/dist/server.d.ts +0 -10
  32. package/dist/server.js +0 -45
  33. package/dist/server.js.map +0 -1
  34. package/dist/src/__tests__/console.test.d.ts +0 -1
  35. package/dist/src/__tests__/console.test.js +0 -29
  36. package/dist/src/__tests__/console.test.js.map +0 -1
  37. package/dist/src/server.js.map +0 -1
  38. package/src/HTTPInterceptor.ts +0 -319
  39. package/src/__tests__/console.test.ts +0 -26
  40. package/src/common.ts +0 -4
  41. package/src/logPlugin.ts +0 -238
  42. package/src/server.ts +0 -66
  43. package/src/utils.ts +0 -47
@@ -1,319 +0,0 @@
1
- // @ts-ignore
2
- import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
3
- // @ts-ignore
4
- import BlobFileReader from 'react-native/Libraries/Blob/FileReader';
5
-
6
- type StartNetworkLoggingOptions = {
7
- /** List of hosts to ignore, e.g. `services.test.com` */
8
- ignoredHosts?: string[];
9
- /** List of urls to ignore, e.g. `https://services.test.com/test` */
10
- ignoredUrls?: string[];
11
- /**
12
- * List of url patterns to ignore, e.g. `/^GET https://test.com\/pages\/.*$/`
13
- *
14
- * Url to match with is in the format: `${method} ${url}`, e.g. `GET https://test.com/pages/123`
15
- */
16
- ignoredPatterns?: RegExp[];
17
- /**
18
- * Force the network logger to start even if another program is using the network interceptor
19
- * e.g. a dev/debuging program
20
- */
21
- forceEnable?: boolean;
22
- };
23
-
24
- interface HttpRequestInfo {
25
- id: string;
26
- method: RequestMethod;
27
- url: string;
28
- timeout: number;
29
- requestHeaders: Record<string, string>;
30
- requestData: string;
31
- startTime: number;
32
- endTime: number;
33
- responseHeaders: Headers;
34
- responseData: string;
35
- status: number;
36
- duration: number;
37
- responseContentType: string;
38
- responseSize: number;
39
- responseURL: string;
40
- responseType: string;
41
- }
42
-
43
- type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
44
-
45
- type XHR = {
46
- uniqueId: string;
47
- responseHeaders?: Headers;
48
- };
49
-
50
- type EventName =
51
- | 'open'
52
- | 'requestHeader'
53
- | 'headerReceived'
54
- | 'send'
55
- | 'response';
56
-
57
- const extractHost = (url: string) => {
58
- const host = url.split('//')[1]?.split(':')[0]?.split('/')[0] || undefined;
59
-
60
- return host;
61
- };
62
-
63
- const generateUniqueId = () => {
64
- return Date.now().toString(36) + Math.random().toString(36).substr(2);
65
- };
66
-
67
- const parseResponseBlob = async (response: string) => {
68
- const blobReader = new BlobFileReader();
69
- blobReader.readAsText(response);
70
-
71
- return await new Promise<string>((resolve, reject) => {
72
- const handleError = () => reject(blobReader.error);
73
-
74
- blobReader.addEventListener('load', () => {
75
- resolve(blobReader.result);
76
- });
77
- blobReader.addEventListener('error', handleError);
78
- blobReader.addEventListener('abort', handleError);
79
- });
80
- }
81
-
82
- const getResponseBody = async (responseType: string, response: string) => {
83
- try {
84
- const body = await (responseType !== 'blob'
85
- ? response
86
- : parseResponseBlob(response));
87
- return JSON.parse(body)
88
- } catch (error) {
89
- return null
90
- }
91
- }
92
-
93
- class HTTPInterceptor {
94
- private static _index = 0;
95
- private ignoredHosts: Set<string> | undefined;
96
- private ignoredUrls: Set<string> | undefined;
97
- private ignoredPatterns: RegExp[] | undefined;
98
- // 只保存正在请求中的
99
- private allRequests = new Map<string, Partial<HttpRequestInfo>>();
100
-
101
- private userListeners: [
102
- EventName,
103
- (data: Partial<HttpRequestInfo>) => Promise<void> | void,
104
- ][] = [];
105
-
106
- private enabled = false;
107
-
108
- addListener = (
109
- eventName: EventName,
110
- listener: (data: Partial<HttpRequestInfo>) => Promise<void> | void,
111
- ) => {
112
- // 如果之前已经订阅过了就过滤掉
113
- if (
114
- this.userListeners.find(
115
- ([name, tempListener]) =>
116
- name === eventName && tempListener === listener,
117
- )
118
- ) {
119
- return;
120
- }
121
- this.userListeners.push([eventName, listener]);
122
-
123
- return () => {
124
- this.userListeners = this.userListeners.filter(
125
- ([name, tempListener]) =>
126
- name !== eventName || tempListener !== listener,
127
- );
128
- };
129
- };
130
-
131
- removeListener = (
132
- eventName: EventName,
133
- listener: (data: Partial<HttpRequestInfo>) => Promise<void> | void,
134
- ) => {
135
- this.userListeners = this.userListeners.filter(
136
- ([name, tempListener]) => name !== eventName || tempListener !== listener,
137
- );
138
- };
139
-
140
- removeAllListener() {
141
- this.userListeners = [];
142
- }
143
-
144
- private listenerHandle = (
145
- eventName: EventName,
146
- data: Partial<HttpRequestInfo>,
147
- ) => {
148
- this.userListeners.forEach(async ([name, listener]) => {
149
- if (name === eventName) {
150
- await listener(data);
151
- }
152
- });
153
- };
154
-
155
-
156
-
157
- private openHandle = (method: RequestMethod, url: string, xhr: XHR) => {
158
- if (this.ignoredHosts) {
159
- const host = extractHost(url);
160
- if (host && this.ignoredHosts.has(host)) {
161
- return;
162
- }
163
- }
164
- if (this.ignoredUrls && this.ignoredUrls.has(url)) {
165
- return;
166
- }
167
-
168
- if (this.ignoredPatterns) {
169
- if (
170
- this.ignoredPatterns.some(pattern => pattern.test(`${method} ${url}`))
171
- ) {
172
- return;
173
- }
174
- }
175
- xhr.uniqueId = HTTPInterceptor._index + generateUniqueId();
176
- const newRequest = {
177
- id: xhr.uniqueId,
178
- method,
179
- url,
180
- };
181
- this.allRequests.set(xhr.uniqueId, newRequest);
182
- this.listenerHandle('open', newRequest);
183
- };
184
-
185
- private requestHeaderHandle = (header: string, value: string, xhr: XHR) => {
186
- const currentRequest = this.allRequests.get(xhr.uniqueId);
187
- if (!currentRequest) {
188
- return;
189
- }
190
- if (!currentRequest.requestHeaders) {
191
- currentRequest.requestHeaders = {};
192
- }
193
- currentRequest.requestHeaders[header] = value;
194
- this.listenerHandle('requestHeader', currentRequest);
195
- };
196
-
197
- private headerReceivedHandle = (
198
- responseContentType: string,
199
- responseSize: number,
200
- responseHeaders: Headers,
201
- xhr: XHR,
202
- ) => {
203
- const currentRequest = this.allRequests.get(xhr.uniqueId);
204
- if (!currentRequest) {
205
- return;
206
- }
207
- currentRequest.responseContentType = responseContentType;
208
- currentRequest.responseSize = responseSize;
209
- currentRequest.responseHeaders = xhr.responseHeaders;
210
- this.listenerHandle('headerReceived', currentRequest);
211
- };
212
-
213
- private responseHandle = async (
214
- status: number,
215
- timeout: number,
216
- response: string,
217
- responseURL: string,
218
- responseType: string,
219
- xhr: XHR,
220
- ) => {
221
- const currentRequest = this.allRequests.get(xhr.uniqueId);
222
- if (!currentRequest) {
223
- return;
224
- }
225
- currentRequest.endTime = Date.now();
226
- currentRequest.status = status;
227
- currentRequest.timeout = timeout;
228
- currentRequest.responseData = await getResponseBody(responseType, response);
229
- currentRequest.responseURL = responseURL;
230
- currentRequest.responseType = responseType;
231
- currentRequest.duration =
232
- currentRequest.endTime - (currentRequest.startTime ?? 0);
233
- this.listenerHandle('response', currentRequest);
234
- this.allRequests.delete(xhr.uniqueId);
235
- };
236
-
237
- private sendHandle = (data: string, xhr: XHR) => {
238
- const currentRequest = this.allRequests.get(xhr.uniqueId);
239
- if (!currentRequest) {
240
- return;
241
- }
242
- try {
243
- currentRequest.requestData = JSON.parse(data);
244
- } catch (error) {
245
- currentRequest.requestData = null;
246
- }
247
- currentRequest.startTime = Date.now();
248
- this.listenerHandle('send', currentRequest);
249
- };
250
-
251
- enable = (options?: StartNetworkLoggingOptions) => {
252
- try {
253
- if (
254
- this.enabled ||
255
- (XHRInterceptor.isInterceptorEnabled() && !options?.forceEnable)
256
- ) {
257
- if (!this.enabled) {
258
- console.warn(
259
- 'network interceptor has not been enabled as another interceptor is already running (e.g. another debugging program). Use option `forceEnable: true` to override this behaviour.',
260
- );
261
- }
262
- return;
263
- }
264
-
265
- if (options?.ignoredHosts) {
266
- if (
267
- !Array.isArray(options.ignoredHosts) ||
268
- typeof options.ignoredHosts[0] !== 'string'
269
- ) {
270
- console.warn(
271
- 'ignoredHosts must be an array of strings. The logger has not been started.',
272
- );
273
- return;
274
- }
275
- this.ignoredHosts = new Set(options.ignoredHosts);
276
- }
277
-
278
- if (options?.ignoredPatterns) {
279
- this.ignoredPatterns = options.ignoredPatterns;
280
- }
281
-
282
- if (options?.ignoredUrls) {
283
- if (
284
- !Array.isArray(options.ignoredUrls) ||
285
- typeof options.ignoredUrls[0] !== 'string'
286
- ) {
287
- console.warn(
288
- 'ignoredUrls must be an array of strings. The logger has not been started.',
289
- );
290
- return;
291
- }
292
- this.ignoredUrls = new Set(options.ignoredUrls);
293
- }
294
- XHRInterceptor.setOpenCallback(this.openHandle);
295
- XHRInterceptor.setRequestHeaderCallback(this.requestHeaderHandle);
296
- XHRInterceptor.setHeaderReceivedCallback(this.headerReceivedHandle);
297
- XHRInterceptor.setSendCallback(this.sendHandle);
298
- XHRInterceptor.setResponseCallback(this.responseHandle);
299
- XHRInterceptor.enableInterception();
300
- this.enabled = true;
301
- } catch (error) {}
302
- };
303
-
304
- disable = () => {
305
- if (!this.enabled) {
306
- return;
307
- }
308
- XHRInterceptor.disableInterception();
309
- this.enabled = false;
310
- }
311
- }
312
-
313
- const httpInterceptor = new HTTPInterceptor();
314
- export {
315
- type StartNetworkLoggingOptions,
316
- httpInterceptor,
317
- type EventName,
318
- type RequestMethod,
319
- };
@@ -1,26 +0,0 @@
1
- import '../../console'
2
- import logger from '../../index'
3
- describe("重写日志", () => {
4
- it("log 没有提前设置 url", () => {
5
- console.log("log 是否正常打印")
6
- expect(1).toBe(1)
7
- })
8
-
9
- it("log 提前设置了 url", () => {
10
- logger.setBaseUrl("http://192.168.118.103")
11
- console.log("log 是否正常打印")
12
- expect(1).toBe(1)
13
- })
14
-
15
- it('warn', () => {
16
- logger.setBaseUrl("http://192.168.118.103")
17
- console.warn("warn 是否正常打印")
18
- expect(1).toBe(1)
19
- })
20
-
21
- it('error', () => {
22
- logger.setBaseUrl("http://192.168.118.103")
23
- console.error("error 是否正常打印")
24
- expect(1).toBe(1)
25
- })
26
- })
package/src/common.ts DELETED
@@ -1,4 +0,0 @@
1
- export const [log, warn, error] = [console.log, console.warn, console.error];
2
-
3
- // @ts-ignore
4
- export const tempFetch = global.fetch as typeof fetch;
package/src/logPlugin.ts DELETED
@@ -1,238 +0,0 @@
1
- import Server from './server';
2
- import { extractDomain } from './utils';
3
- import { httpInterceptor } from './HTTPInterceptor';
4
-
5
- class LogPlugin {
6
- private server: Server | null = null;
7
- private baseData: Record<string, any> = {};
8
- private timeout: number | null = null;
9
- private host = '';
10
- private isAuto = false
11
-
12
- auto = () => {
13
- if (this.host) {
14
- this.startRecordNetwork();
15
- this.startRecordLog();
16
- }
17
- this.isAuto = true
18
- }
19
-
20
- unAuto = () => {
21
- this.stopRecordLog()
22
- httpInterceptor.disable()
23
- httpInterceptor.removeAllListener()
24
- this.isAuto = false
25
- }
26
-
27
- startRecordLog = () => {
28
- const common = require('./common')
29
- console.log = (...data: any[]) => {
30
- this.log(...data);
31
- common.log(...data);
32
- };
33
-
34
- console.warn = (...data: any[]) => {
35
- this.warn(...data);
36
- common.warn(...data);
37
- };
38
-
39
- console.error = (...data: any[]) => {
40
- this.error(...data);
41
- common.error(...data);
42
- };
43
- }
44
-
45
- stopRecordLog = () => {
46
- const common = require('./common')
47
- console.log = common.log
48
- console.warn = common.warn
49
- console.error = common.error
50
- }
51
-
52
- startRecordNetwork = () => {
53
- httpInterceptor.addListener("send", (data) => {
54
- this.server?.network({
55
- ...this.baseData,
56
- url: data.url,
57
- id: data.id,
58
- method: data.method,
59
- headers: data.requestHeaders,
60
- body: data.requestData,
61
- createTime: data.startTime
62
- })
63
- })
64
- httpInterceptor.addListener("response", (data) => {
65
- this.server?.network({
66
- ...this.baseData,
67
- headers: data.responseHeaders,
68
- body: data.responseData,
69
- requestId: data.id,
70
- statusCode: data.status,
71
- endTime: data.endTime
72
- })
73
- })
74
- httpInterceptor.enable({
75
- ignoredHosts: [extractDomain(this.host)]
76
- })
77
- }
78
-
79
- setBaseUrl = (url: string) => {
80
- if (!url?.trim()) {
81
- httpInterceptor.disable()
82
- this.stopRecordLog()
83
- return
84
- }
85
- this.host = url.includes("http") ? url : `http://${url}`;
86
- if (this.server) {
87
- this.server.updateUrl(url);
88
- } else {
89
- this.server = new Server(url);
90
- }
91
- if (this.isAuto) {
92
- this.startRecordNetwork();
93
- this.startRecordLog()
94
- }
95
- }
96
-
97
- /**
98
- * @deprecated 不需要手动上报,日志插件会自动收集日志
99
- */
100
- setTimeout = (timeout: number) => {
101
- if (typeof timeout === 'number') {
102
- this.timeout = timeout;
103
- this.server?.updateTimeout(this.timeout);
104
- }
105
- }
106
-
107
- /**
108
- * @deprecated 不需要手动上报,日志插件会自动收集日志
109
- */
110
- getTimeout = () => {
111
- if (typeof this.timeout === 'number') {
112
- return this.timeout;
113
- }
114
- return null;
115
- }
116
-
117
- setBaseData = (data: Record<string, any> = {}) => {
118
- this.baseData = data;
119
- }
120
-
121
- private _log = (level: string, tag: string, ...data: any[]) => {
122
- const sendData = {
123
- ...this.baseData,
124
- message: data,
125
- tag,
126
- level: level ?? 'log',
127
- createTime: Date.now(),
128
- };
129
- this.server?.log(sendData);
130
- }
131
-
132
- tag = (tag: string, ...data: any[]) => {
133
- this._log('log', tag, ...data);
134
- }
135
-
136
- log = (...data: any[]) => {
137
- this._log('log', 'default', ...data);
138
- }
139
-
140
- warn = (...data: any[]) => {
141
- this._log('warn', 'default', ...data);
142
- }
143
-
144
- error = (...data: any[]) => {
145
- this._log('error', 'default', ...data);
146
- }
147
-
148
- /**
149
- * @deprecated 不需要手动上报,日志插件会自动收集日志
150
- */
151
- uniqueReq = async (
152
- uniqueId: string | undefined,
153
- input: RequestInfo | URL,
154
- init?: RequestInit
155
- ) => {
156
- let url: string | null = null;
157
- let method = init?.method ?? 'get';
158
- let headers = init?.headers;
159
- let body = init?.body;
160
- if (input instanceof Request) {
161
- url = input.url;
162
- method = input.method ?? 'get';
163
- headers = (input.headers as Record<string, any>).map;
164
- body = input.body;
165
- } else if (input instanceof URL) {
166
- url = input.href;
167
- } else {
168
- url = input;
169
- }
170
- return this.server?.network({
171
- ...this.baseData,
172
- url,
173
- id: uniqueId,
174
- method,
175
- headers,
176
- body,
177
- createTime: Date.now(),
178
- });
179
- }
180
-
181
- private _res = async (uniqueId?: string, id?: number, response?: Response) => {
182
- const body = await response?.text();
183
- return this.server?.network({
184
- ...this.baseData,
185
- headers: (response?.headers as Record<string, any>).map,
186
- body,
187
- requestId: uniqueId ?? Number(id),
188
- statusCode: response?.status,
189
- endTime: Date.now(),
190
- });
191
- }
192
-
193
- /**
194
- * @deprecated 不需要手动上报,日志插件会自动收集日志
195
- */
196
- resTimeout = async (uniqueId: string) => {
197
- return this.server?.network({
198
- ...this.baseData,
199
- isTimeout: true,
200
- requestId: uniqueId,
201
- });
202
- }
203
-
204
- /**
205
- * @deprecated 不需要手动上报,日志插件会自动收集日志
206
- */
207
- resResponseError = async (uniqueId: string) => {
208
- return this.server?.network({
209
- ...this.baseData,
210
- isResponseError: true,
211
- requestId: uniqueId,
212
- });
213
- }
214
-
215
- /**
216
- * @deprecated 不需要手动上报,日志插件会自动收集日志
217
- */
218
- uniqueRes = async (uniqueId: string, response?: Response) => {
219
- return this._res(uniqueId, undefined, response);
220
- }
221
-
222
- /**
223
- * @deprecated 不需要手动上报,日志插件会自动收集日志
224
- */
225
- req = async (input: RequestInfo | URL, init?: RequestInit) => {
226
- return this.uniqueReq(undefined, input, init);
227
- }
228
-
229
- /**
230
- * @deprecated 不需要手动上报,日志插件会自动收集日志
231
- */
232
- res = async (id: number, response?: Response) => {
233
- return this._res(undefined, id, response);
234
- }
235
- }
236
- const logPlugin = new LogPlugin();
237
- export { LogPlugin };
238
- export default logPlugin;
package/src/server.ts DELETED
@@ -1,66 +0,0 @@
1
- import {hasPort, sleep} from './utils';
2
- const DEFAULT_PORT = 27751
3
- class Server {
4
- private baseUrl = '';
5
- private timeout: number;
6
-
7
- constructor(url: string, timeout: number = 3000) {
8
- this.updateUrl(url);
9
- this.timeout = timeout;
10
- }
11
-
12
- updateTimeout(timeout = 3000) {
13
- this.timeout = timeout;
14
- }
15
-
16
- private getPort() {
17
- if (hasPort(this.baseUrl)) {
18
- return ''
19
- }
20
- return DEFAULT_PORT;
21
- }
22
-
23
- private async send(path: string, data: Record<string, any>) {
24
- try {
25
- if (!this.baseUrl) {
26
- return null;
27
- }
28
- const common = require('./common');
29
- const result = await Promise.race([
30
- common.tempFetch(`${this.baseUrl}:${this.getPort()}/${path}`, {
31
- method: 'POST',
32
- headers: {
33
- 'Content-Type': 'application/json;charset=utf-8',
34
- },
35
- body: JSON.stringify(data, (_, val) => {
36
- if (val instanceof Error) {
37
- return val.toString();
38
- }
39
- return val;
40
- }),
41
- }),
42
- sleep(this.timeout, true),
43
- ]);
44
- if (result instanceof Response) {
45
- return result.text();
46
- }
47
- return null;
48
- } catch (error) {
49
- return null;
50
- }
51
- }
52
-
53
- updateUrl(url: string) {
54
- this.baseUrl = url;
55
- }
56
-
57
- async log(data: Record<string, any>) {
58
- return this.send('log', data);
59
- }
60
-
61
- async network(data: Record<string, any>) {
62
- return this.send('network', data);
63
- }
64
- }
65
-
66
- export default Server;
package/src/utils.ts DELETED
@@ -1,47 +0,0 @@
1
- export function sleep(ms: number, isReject: boolean = false) {
2
- return new Promise((resolve, reject) => {
3
- setTimeout(isReject ? () => reject({
4
- code: 11001,
5
- key: '@wutiange/log-listener-plugin%%timeout',
6
- msg: 'Timeout'
7
- }) : resolve, ms)
8
- })
9
- }
10
-
11
- export function extractDomain(url: string) {
12
- // 如果 url 是空的或不是字符串,直接返回
13
- if (!url || typeof url !== 'string') {
14
- return url;
15
- }
16
-
17
- // 使用正则表达式匹配 URL
18
- const match = url.match(/^(https?:\/\/)?([^/:]+)/i);
19
-
20
- // 如果没有匹配到,返回原始输入
21
- if (!match) {
22
- return url;
23
- }
24
-
25
- // 返回匹配到的域名部分
26
- return match[2];
27
- }
28
-
29
-
30
- export function hasPort(url: string) {
31
- // 如果 url 是空的或不是字符串,返回 false
32
- if (!url || typeof url !== 'string') {
33
- return false;
34
- }
35
-
36
- try {
37
- // 使用 URL 构造函数解析 URL
38
- const parsedUrl = new URL(url);
39
-
40
- // 检查 port 属性是否为空
41
- // 注意:如果使用默认端口(如 HTTP 的 80 或 HTTPS 的 443),port 会是空字符串
42
- return parsedUrl.port !== '';
43
- } catch (error) {
44
- // 如果 URL 无效,捕获错误并返回 false
45
- return false;
46
- }
47
- }