@yumerijs/core 2.2.2 → 3.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context.d.ts +12 -12
- package/dist/context.js +21 -8
- package/dist/core.d.ts +27 -13
- package/dist/core.js +84 -12
- package/dist/route.d.ts +11 -2
- package/dist/route.js +245 -5
- package/dist/server.d.ts +22 -4
- package/dist/server.js +33 -30
- package/dist/session.d.ts +176 -75
- package/dist/session.js +226 -229
- package/package.json +2 -1
package/dist/session.js
CHANGED
|
@@ -6,96 +6,187 @@
|
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
import * as formidable from 'formidable';
|
|
8
8
|
import fs from 'fs';
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* 会话请求包装类
|
|
11
|
+
* 负责收集和整理所有来自客户端的请求信息
|
|
12
|
+
*/
|
|
13
|
+
class SessionRequest {
|
|
14
|
+
/** 客户端的 IP 地址 */
|
|
10
15
|
ip;
|
|
11
|
-
|
|
16
|
+
/** 请求中携带的原始 Cookie 键值对 */
|
|
17
|
+
cookies;
|
|
18
|
+
/** URL 中的查询字符串参数 */
|
|
12
19
|
query;
|
|
20
|
+
/** 请求的资源路径(不含查询参数) */
|
|
21
|
+
pathname;
|
|
22
|
+
/** 所有的请求头信息 */
|
|
23
|
+
headers;
|
|
24
|
+
/** 客户端支持的语言列表,已按权重排序 */
|
|
25
|
+
languages = [];
|
|
26
|
+
/** 请求使用的协议 */
|
|
27
|
+
protocol = 'http';
|
|
28
|
+
/** 原始的 IncomingMessage 对象 */
|
|
29
|
+
raw;
|
|
30
|
+
constructor(data) {
|
|
31
|
+
this.ip = data.ip;
|
|
32
|
+
this.cookies = data.cookies;
|
|
33
|
+
this.query = data.query;
|
|
34
|
+
this.pathname = data.pathname || '/';
|
|
35
|
+
this.headers = data.headers;
|
|
36
|
+
this.raw = data.raw;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 会话响应包装类
|
|
41
|
+
* 负责管理和准备发往客户端的数据
|
|
42
|
+
*/
|
|
43
|
+
class SessionResponse {
|
|
44
|
+
/** 待发送的 HTTP 状态码 */
|
|
45
|
+
status = 200;
|
|
46
|
+
/** 待发送的响应头 */
|
|
47
|
+
headers = { 'Content-Type': 'text/plain' };
|
|
48
|
+
/** 准备设置到客户端的 Cookie 集合 */
|
|
49
|
+
cookies = {};
|
|
50
|
+
/** 响应体的具体类型 */
|
|
51
|
+
type = "plain";
|
|
52
|
+
/** 响应的具体内容 */
|
|
53
|
+
body;
|
|
54
|
+
/** 标识该响应是否已经处理完毕 */
|
|
55
|
+
handled = false;
|
|
56
|
+
/** 原始的 ServerResponse 对象 */
|
|
57
|
+
raw;
|
|
58
|
+
constructor(res) {
|
|
59
|
+
this.raw = res;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 会话对象 (Session)
|
|
64
|
+
* 是插件开发中最核心的对象,封装了请求、响应及各种便捷工具
|
|
65
|
+
*/
|
|
66
|
+
export class Session {
|
|
67
|
+
/** 封装后的请求对象 */
|
|
68
|
+
request;
|
|
69
|
+
/** 封装后的响应对象 */
|
|
70
|
+
response;
|
|
71
|
+
/** 当前会话的唯一标识符 (UUID) */
|
|
13
72
|
sessionid;
|
|
73
|
+
/** 持久化的会话数据存储 */
|
|
14
74
|
data = {};
|
|
15
|
-
|
|
16
|
-
head = {};
|
|
17
|
-
status = 200;
|
|
18
|
-
_restype = "plain";
|
|
19
|
-
_body;
|
|
75
|
+
/** 供插件或中间件挂载的临时属性 */
|
|
20
76
|
properties = {};
|
|
21
|
-
|
|
77
|
+
/** 对核心服务器实例的引用 */
|
|
22
78
|
server;
|
|
23
|
-
|
|
24
|
-
pathname;
|
|
25
|
-
languages;
|
|
26
|
-
responseHandled = false;
|
|
79
|
+
/** 当前插件的上下文环境 */
|
|
27
80
|
pluginContext;
|
|
28
81
|
/**
|
|
29
|
-
*
|
|
30
|
-
* @param ip
|
|
31
|
-
* @param cookie
|
|
32
|
-
* @param
|
|
82
|
+
* 初始化一个新的会话
|
|
83
|
+
* @param ip 客户端IP
|
|
84
|
+
* @param cookie 请求Cookie
|
|
85
|
+
* @param server 服务器实例
|
|
86
|
+
* @param req 原始请求对象
|
|
87
|
+
* @param res 原始响应对象
|
|
88
|
+
* @param pathname 请求路径
|
|
89
|
+
* @param query 查询参数
|
|
90
|
+
* @param pluginContext 插件上下文
|
|
33
91
|
*/
|
|
34
92
|
constructor(ip, cookie, server, req, res, pathname, query, pluginContext) {
|
|
35
|
-
this.ip = ip;
|
|
36
|
-
this.cookie = cookie;
|
|
37
|
-
this.query = query;
|
|
38
93
|
this.server = server;
|
|
39
|
-
this.pathname = pathname;
|
|
40
94
|
this.pluginContext = pluginContext;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
else if (Array.isArray(value)) {
|
|
48
|
-
header[key] = value.join(', ');
|
|
49
|
-
}
|
|
50
|
-
else if (value !== undefined) {
|
|
51
|
-
header[key] = String(value);
|
|
95
|
+
const headers = {};
|
|
96
|
+
if (req) {
|
|
97
|
+
for (const key in req.headers) {
|
|
98
|
+
const value = req.headers[key];
|
|
99
|
+
headers[key] = Array.isArray(value) ? value.join(', ') : String(value || '');
|
|
52
100
|
}
|
|
53
101
|
}
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
res: res,
|
|
57
|
-
headers: header
|
|
58
|
-
};
|
|
59
|
-
this.head['Content-Type'] = 'text/plain';
|
|
102
|
+
this.request = new SessionRequest({ ip, cookies: cookie, query, pathname, headers, raw: req });
|
|
103
|
+
this.response = new SessionResponse(res);
|
|
60
104
|
if (cookie.sessionid) {
|
|
61
105
|
this.sessionid = cookie.sessionid;
|
|
62
106
|
}
|
|
63
107
|
else {
|
|
64
|
-
this.sessionid = this.generateId(
|
|
108
|
+
this.sessionid = this.generateId();
|
|
65
109
|
this.setCookie('sessionid', this.sessionid);
|
|
66
110
|
}
|
|
67
111
|
if (cookie.lang) {
|
|
68
|
-
this.languages = cookie.lang.split(',');
|
|
112
|
+
this.request.languages = cookie.lang.split(',');
|
|
69
113
|
}
|
|
70
|
-
else if (
|
|
71
|
-
this.languages = this.parseAcceptLanguages(
|
|
72
|
-
this.setCookie('lang', this.languages.join(','));
|
|
114
|
+
else if (headers['accept-language']) {
|
|
115
|
+
this.request.languages = this.parseAcceptLanguages(headers['accept-language']);
|
|
116
|
+
this.setCookie('lang', this.request.languages.join(','));
|
|
73
117
|
}
|
|
74
118
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
119
|
+
/** 获取客户端 IP 地址 */
|
|
120
|
+
get ip() { return this.request.ip; }
|
|
121
|
+
/** 获取请求中的 Cookie 集合 */
|
|
122
|
+
get cookie() { return this.request.cookies; }
|
|
123
|
+
/** 获取 URL 查询参数 */
|
|
124
|
+
get query() { return this.request.query; }
|
|
125
|
+
/** 获取请求路径 */
|
|
126
|
+
get pathname() { return this.request.pathname; }
|
|
127
|
+
/** 获取语言列表 */
|
|
128
|
+
get languages() { return this.request.languages; }
|
|
129
|
+
/** 获取请求协议 */
|
|
130
|
+
get protocol() { return this.request.protocol; }
|
|
131
|
+
set protocol(val) { this.request.protocol = val; }
|
|
132
|
+
/** 获取或设置响应状态码 */
|
|
133
|
+
get status() { return this.response.status; }
|
|
134
|
+
set status(val) { this.response.status = val; }
|
|
135
|
+
/** 获取待发送的新 Cookie */
|
|
136
|
+
get newCookie() { return this.response.cookies; }
|
|
137
|
+
/** 获取响应头对象 */
|
|
138
|
+
get head() { return this.response.headers; }
|
|
139
|
+
/** 获取当前响应内容类型 */
|
|
140
|
+
get _restype() { return this.response.type; }
|
|
141
|
+
/** 获取响应是否已处理的标记 */
|
|
142
|
+
get responseHandled() { return this.response.handled; }
|
|
143
|
+
set responseHandled(val) { this.response.handled = val; }
|
|
144
|
+
/** 组装并获取 Client 兼容对象 */
|
|
145
|
+
get client() {
|
|
146
|
+
return {
|
|
147
|
+
req: this.request.raw,
|
|
148
|
+
res: this.response.raw,
|
|
149
|
+
headers: this.request.headers
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 设置响应体内容
|
|
154
|
+
* @param body 响应内容
|
|
155
|
+
* @param type 内容类型,默认为 'plain'
|
|
156
|
+
*/
|
|
157
|
+
respond(body, type = 'plain') {
|
|
158
|
+
this.response.type = type;
|
|
159
|
+
this.response.body = body;
|
|
78
160
|
}
|
|
161
|
+
/** 获取响应类型 */
|
|
79
162
|
get restype() {
|
|
80
|
-
return this.
|
|
163
|
+
return this.response.type;
|
|
81
164
|
}
|
|
165
|
+
/** 获取响应体内容 */
|
|
82
166
|
get body() {
|
|
83
|
-
return this.
|
|
167
|
+
return this.response.body;
|
|
84
168
|
}
|
|
169
|
+
/** 设置响应体内容,自动推断类型 */
|
|
85
170
|
set body(value) {
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
171
|
+
this.response.body = value;
|
|
172
|
+
this.response.type = typeof value === 'string' ? "plain" : "json";
|
|
88
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* 设置响应 Cookie
|
|
176
|
+
* @param name Cookie 键名
|
|
177
|
+
* @param value Cookie 值
|
|
178
|
+
* @param options 配置项
|
|
179
|
+
*/
|
|
89
180
|
setCookie(name, value, options = {}) {
|
|
90
181
|
if (options.path === undefined) {
|
|
91
182
|
options.path = '/';
|
|
92
183
|
}
|
|
93
|
-
this.
|
|
184
|
+
this.response.cookies[name] = { value, options };
|
|
94
185
|
}
|
|
95
186
|
/**
|
|
96
|
-
* 解析 Accept-Language
|
|
97
|
-
* @param header
|
|
98
|
-
* @returns
|
|
187
|
+
* 解析 Accept-Language 请求头
|
|
188
|
+
* @param header 原始头字符串
|
|
189
|
+
* @returns 排序后的语言数组
|
|
99
190
|
*/
|
|
100
191
|
parseAcceptLanguages(header) {
|
|
101
192
|
if (!header)
|
|
@@ -136,18 +227,14 @@ export class Session {
|
|
|
136
227
|
return result;
|
|
137
228
|
}
|
|
138
229
|
/**
|
|
139
|
-
*
|
|
140
|
-
* @param ip 用户IP
|
|
141
|
-
* @param option 选项
|
|
230
|
+
* 生成会话 ID (UUID)
|
|
142
231
|
*/
|
|
143
|
-
generateId(
|
|
144
|
-
|
|
145
|
-
return sessionId;
|
|
232
|
+
generateId() {
|
|
233
|
+
return crypto.randomUUID();
|
|
146
234
|
}
|
|
147
235
|
/**
|
|
148
|
-
*
|
|
149
|
-
* @
|
|
150
|
-
* @returns Promise<ParsedParams>
|
|
236
|
+
* 异步解析请求体
|
|
237
|
+
* @returns 返回解析后的参数
|
|
151
238
|
*/
|
|
152
239
|
async parseRequestBody(client = this.client) {
|
|
153
240
|
const req = client.req;
|
|
@@ -191,90 +278,58 @@ export class Session {
|
|
|
191
278
|
});
|
|
192
279
|
}
|
|
193
280
|
/**
|
|
194
|
-
*
|
|
195
|
-
* @param req 请求对象
|
|
196
|
-
* @returns Promise<string>
|
|
281
|
+
* 获取请求体文本
|
|
197
282
|
*/
|
|
198
283
|
async getReqBody(req) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
let body = "";
|
|
286
|
+
req.on('data', chunk => body += chunk.toString());
|
|
287
|
+
req.on('end', () => resolve(body));
|
|
288
|
+
});
|
|
202
289
|
}
|
|
203
|
-
|
|
290
|
+
/**
|
|
291
|
+
* MD5 加密
|
|
292
|
+
*/
|
|
204
293
|
md5(str) {
|
|
205
294
|
const hash = crypto.createHash('md5');
|
|
206
295
|
hash.update(str);
|
|
207
296
|
return hash.digest('hex');
|
|
208
297
|
}
|
|
209
|
-
|
|
210
|
-
setData(key, value) {
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
clearData() {
|
|
219
|
-
this.data = {};
|
|
220
|
-
}
|
|
221
|
-
// 销毁会话
|
|
222
|
-
destroy() {
|
|
223
|
-
this.clearData(); // 清理会话数据
|
|
224
|
-
}
|
|
298
|
+
/** 设置会话数据 */
|
|
299
|
+
setData(key, value) { this.data[key] = value; }
|
|
300
|
+
/** 删除会话数据 */
|
|
301
|
+
deleteData(key) { delete this.data[key]; }
|
|
302
|
+
/** 清空会话数据 */
|
|
303
|
+
clearData() { this.data = {}; }
|
|
304
|
+
/** 销毁会话 */
|
|
305
|
+
destroy() { this.clearData(); }
|
|
306
|
+
/** 设置 MIME 类型 */
|
|
225
307
|
setMime(mimeType) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.head['Content-Type'] = 'image/jpeg';
|
|
233
|
-
break;
|
|
234
|
-
case 'pdf':
|
|
235
|
-
this.head['Content-Type'] = 'application/pdf';
|
|
236
|
-
break;
|
|
237
|
-
case 'plain':
|
|
238
|
-
this.head['Content-Type'] = 'text/plain';
|
|
239
|
-
break;
|
|
240
|
-
case 'html':
|
|
241
|
-
this.head['Content-Type'] = 'text/html';
|
|
242
|
-
break;
|
|
243
|
-
case 'json':
|
|
244
|
-
this.head['Content-Type'] = 'application/json';
|
|
245
|
-
break;
|
|
246
|
-
case 'xml':
|
|
247
|
-
this.head['Content-Type'] = 'application/xml';
|
|
248
|
-
break;
|
|
249
|
-
default:
|
|
250
|
-
this.head['Content-Type'] = mimeType;
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
send(data) {
|
|
255
|
-
return this.client.res.write(data);
|
|
308
|
+
const map = {
|
|
309
|
+
'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg',
|
|
310
|
+
'pdf': 'application/pdf', 'plain': 'text/plain', 'html': 'text/html',
|
|
311
|
+
'json': 'application/json', 'xml': 'application/xml'
|
|
312
|
+
};
|
|
313
|
+
this.response.headers['Content-Type'] = map[mimeType] || mimeType;
|
|
256
314
|
}
|
|
315
|
+
/** 向响应流写入数据 */
|
|
316
|
+
send(data) { return this.response.raw.write(data); }
|
|
317
|
+
/** 结束响应 */
|
|
257
318
|
endsession(message) {
|
|
258
|
-
this.
|
|
259
|
-
return this.
|
|
319
|
+
this.response.handled = true;
|
|
320
|
+
return this.response.raw.end(message);
|
|
260
321
|
}
|
|
261
|
-
/**
|
|
262
|
-
* 获取I18n文本
|
|
263
|
-
* @param name 文本点名称
|
|
264
|
-
*/
|
|
322
|
+
/** 获取国际化文本 */
|
|
265
323
|
text(name) {
|
|
266
|
-
return this.server.core.i18n.get(name, this.languages);
|
|
324
|
+
return this.server.core.i18n.get(name, this.request.languages);
|
|
267
325
|
}
|
|
268
|
-
/**
|
|
269
|
-
* 设置缓存标头
|
|
270
|
-
* @param option 选项
|
|
271
|
-
*/
|
|
326
|
+
/** 设置缓存标头 */
|
|
272
327
|
setCache(option) {
|
|
273
328
|
if (option.etag)
|
|
274
|
-
this.
|
|
329
|
+
this.response.headers['ETag'] = `${option.etag}`;
|
|
275
330
|
if (option.modified)
|
|
276
|
-
this.
|
|
277
|
-
|
|
331
|
+
this.response.headers['Last-Modified'] = option.modified.toUTCString();
|
|
332
|
+
const control = [];
|
|
278
333
|
if (option.cacheControl !== undefined)
|
|
279
334
|
control.push(option.cacheControl);
|
|
280
335
|
if (option.maxAge !== undefined)
|
|
@@ -284,148 +339,90 @@ export class Session {
|
|
|
284
339
|
if (option.isImmutable)
|
|
285
340
|
control.push('immutable');
|
|
286
341
|
if (control.length > 0)
|
|
287
|
-
this.
|
|
342
|
+
this.response.headers['Cache-Control'] = control.join(', ');
|
|
288
343
|
if (option.expires)
|
|
289
|
-
this.
|
|
344
|
+
this.response.headers['Expires'] = option.expires.toUTCString();
|
|
290
345
|
}
|
|
291
|
-
/**
|
|
292
|
-
* 设置静态文件
|
|
293
|
-
* @param content 文件内容
|
|
294
|
-
* @param option 选项
|
|
295
|
-
*/
|
|
346
|
+
/** 发送静态内容并处理缓存 */
|
|
296
347
|
static(content, option) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
this.status = 304;
|
|
348
|
+
if (this.request.headers['if-modified-since']) {
|
|
349
|
+
const modified = new Date(this.request.headers['if-modified-since']);
|
|
350
|
+
if (option.modified && modified.getTime() === option.modified.getTime()) {
|
|
351
|
+
this.response.status = 304;
|
|
302
352
|
return;
|
|
303
353
|
}
|
|
304
354
|
}
|
|
305
|
-
if (this.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
355
|
+
if (this.request.headers['if-none-match'] === option.etag) {
|
|
356
|
+
this.response.status = 304;
|
|
357
|
+
return;
|
|
310
358
|
}
|
|
311
359
|
this.setCache(option);
|
|
312
|
-
this.
|
|
360
|
+
this.respond(content);
|
|
313
361
|
}
|
|
314
|
-
/**
|
|
315
|
-
* 发送静态文件
|
|
316
|
-
* @param path 文件路径
|
|
317
|
-
* @param option 缓存选项
|
|
318
|
-
* @returns void
|
|
319
|
-
*/
|
|
362
|
+
/** 发送文件并处理缓存 */
|
|
320
363
|
file(path, option) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
if (modified.getTime() ===
|
|
325
|
-
this.status = 304;
|
|
364
|
+
const stats = fs.statSync(path);
|
|
365
|
+
if (this.request.headers['if-modified-since']) {
|
|
366
|
+
const modified = new Date(this.request.headers['if-modified-since']);
|
|
367
|
+
if (modified.getTime() === stats.mtime.getTime()) {
|
|
368
|
+
this.response.status = 304;
|
|
326
369
|
return;
|
|
327
370
|
}
|
|
328
371
|
}
|
|
329
372
|
const etag = crypto.createHash(option.etagType || 'md5').update(fs.readFileSync(path)).digest('hex');
|
|
330
|
-
if (this.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.status = 304;
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
373
|
+
if (this.request.headers['if-none-match'] === etag) {
|
|
374
|
+
this.response.status = 304;
|
|
375
|
+
return;
|
|
337
376
|
}
|
|
338
377
|
this.setCache({
|
|
339
|
-
modified:
|
|
340
|
-
|
|
341
|
-
maxAge: option.maxAge,
|
|
342
|
-
smaxAge: option.smaxAge,
|
|
343
|
-
cacheControl: 'public'
|
|
378
|
+
modified: stats.mtime, etag,
|
|
379
|
+
maxAge: option.maxAge, smaxAge: option.smaxAge, cacheControl: 'public'
|
|
344
380
|
});
|
|
345
|
-
this.
|
|
381
|
+
this.respond(fs.createReadStream(path), 'stream');
|
|
346
382
|
}
|
|
347
|
-
/**
|
|
348
|
-
* 发送普通文件
|
|
349
|
-
* @param path 文件路径
|
|
350
|
-
* @param isStream 是否流式传输
|
|
351
|
-
*/
|
|
383
|
+
/** 直接发送文件 */
|
|
352
384
|
sendFile(path, isStream = false) {
|
|
353
385
|
if (isStream) {
|
|
354
386
|
this.setMime('application/octet-stream');
|
|
355
|
-
this.
|
|
387
|
+
this.respond(fs.createReadStream(path), 'stream');
|
|
356
388
|
}
|
|
357
389
|
else {
|
|
358
|
-
this.
|
|
390
|
+
this.respond(fs.readFileSync(path), 'buffer');
|
|
359
391
|
}
|
|
360
392
|
}
|
|
361
|
-
/**
|
|
362
|
-
* 渲染模板
|
|
363
|
-
* @template 模板字符串
|
|
364
|
-
* @data 数据
|
|
365
|
-
* @missing 缺失值处理方式,keep-template: 保留模板,keep-key: 保留键,remove: 移除
|
|
366
|
-
* @regex 模板匹配正则,默认为{{ xxx.xxx }}
|
|
367
|
-
*/
|
|
368
|
-
render(template, data, missing = 'keep-template', regex = /\{\{\s*([\w.]+)\s*\}\}/g) {
|
|
369
|
-
const getValue = (obj, path) => {
|
|
370
|
-
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
|
371
|
-
};
|
|
372
|
-
return template.replace(regex, (_, key) => {
|
|
373
|
-
const value = getValue(data, key);
|
|
374
|
-
if (value !== undefined)
|
|
375
|
-
return String(value);
|
|
376
|
-
switch (missing) {
|
|
377
|
-
case 'keep-key':
|
|
378
|
-
return key;
|
|
379
|
-
case 'remove':
|
|
380
|
-
return '';
|
|
381
|
-
case 'keep-template':
|
|
382
|
-
default:
|
|
383
|
-
return _;
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Renders a UI component using the plugin's declared renderer.
|
|
389
|
-
* @param component The component object to render.
|
|
390
|
-
* @param data The data/props to pass to the component.
|
|
391
|
-
*/
|
|
393
|
+
/** 渲染组件视图 */
|
|
392
394
|
async renderView(component, data = {}) {
|
|
393
|
-
if (!this.pluginContext)
|
|
394
|
-
throw new Error(`
|
|
395
|
-
|
|
396
|
-
const renderer = this.pluginContext.renderer || null;
|
|
397
|
-
const pluginName = this.pluginContext.pluginname;
|
|
395
|
+
if (!this.pluginContext)
|
|
396
|
+
throw new Error(`Plugin context missing.`);
|
|
397
|
+
const renderer = this.pluginContext.renderer;
|
|
398
398
|
if (!renderer) {
|
|
399
|
-
this.server.core.logger.error(`Renderer
|
|
400
|
-
|
|
399
|
+
this.server.core.logger.error(`Renderer missing for ${this.pluginContext.pluginname}`);
|
|
400
|
+
return;
|
|
401
401
|
}
|
|
402
|
-
const renderOptions = {
|
|
403
|
-
pluginName,
|
|
404
|
-
};
|
|
405
402
|
try {
|
|
406
|
-
const html = await renderer.render(component, data,
|
|
403
|
+
const html = await renderer.render(component, data, { pluginName: this.pluginContext.pluginname });
|
|
407
404
|
this.setMime('text/html');
|
|
408
|
-
this.
|
|
405
|
+
this.respond(html, 'plain');
|
|
409
406
|
}
|
|
410
407
|
catch (error) {
|
|
411
|
-
this.server.core.logger.error(`
|
|
408
|
+
this.server.core.logger.error(`Render error:`, error);
|
|
412
409
|
throw error;
|
|
413
410
|
}
|
|
414
411
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
412
|
+
/** 渲染文件 */
|
|
413
|
+
async render(filePath, data) {
|
|
414
|
+
const renderer = this.pluginContext?.renderer;
|
|
418
415
|
if (!renderer) {
|
|
419
|
-
this.server.core.logger.error(`Renderer
|
|
420
|
-
|
|
416
|
+
this.server.core.logger.error(`Renderer missing.`);
|
|
417
|
+
return;
|
|
421
418
|
}
|
|
422
419
|
try {
|
|
423
420
|
const html = await renderer.renderFile(filePath, data);
|
|
424
421
|
this.setMime('text/html');
|
|
425
|
-
this.
|
|
422
|
+
this.respond(html, 'plain');
|
|
426
423
|
}
|
|
427
424
|
catch (error) {
|
|
428
|
-
this.server.core.logger.error(`
|
|
425
|
+
this.server.core.logger.error(`File render error:`, error);
|
|
429
426
|
throw error;
|
|
430
427
|
}
|
|
431
428
|
}
|