qyani-web 1.0.0 → 1.0.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/config/type.js +1 -0
- package/dist/controllers/index.js +74 -0
- package/dist/index.js +16 -0
- package/dist/lib/GroupRoute.js +123 -0
- package/dist/lib/app.js +222 -0
- package/dist/lib/middleware/body-parser.js +146 -0
- package/dist/lib/middleware/cors.js +42 -0
- package/dist/lib/middleware/logger.js +76 -0
- package/dist/lib/middleware/static-resources.js +80 -0
- package/dist/lib/middleware/validate.js +137 -0
- package/dist/routes/index.js +43 -0
- package/dist/utils/certs.js +11 -0
- package/dist/utils/tsl.js +45 -0
- package/package.json +1 -1
- package/src/lib/app.ts +2 -3
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,74 @@
|
|
1
|
+
"use strict";
|
2
|
+
/**
|
3
|
+
* 首页
|
4
|
+
* @param req
|
5
|
+
* @param res
|
6
|
+
*/
|
7
|
+
export const index = (req, res) => {
|
8
|
+
const request = req;
|
9
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
10
|
+
res.end(JSON.stringify({
|
11
|
+
message: 'Welcome to Qianrenni\'s WebApp FrameWork',
|
12
|
+
query: Object.fromEntries(request.query ?? {}),
|
13
|
+
params: request.params ?? {}
|
14
|
+
}));
|
15
|
+
return true;
|
16
|
+
};
|
17
|
+
/**
|
18
|
+
* 用户详情
|
19
|
+
* @param req
|
20
|
+
* @param res
|
21
|
+
*/
|
22
|
+
export const userDetail = (req, res) => {
|
23
|
+
const request = req;
|
24
|
+
// 确保 query 存在并转换为 Record 类型
|
25
|
+
const queryParams = request.query
|
26
|
+
? Object.entries(request.query)
|
27
|
+
: {};
|
28
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
29
|
+
res.end(JSON.stringify({
|
30
|
+
id: request.params?.id,
|
31
|
+
query: queryParams
|
32
|
+
}));
|
33
|
+
return true;
|
34
|
+
};
|
35
|
+
import fs from 'fs/promises';
|
36
|
+
import path from 'path';
|
37
|
+
import { fileURLToPath } from 'url';
|
38
|
+
// 替换 __dirname
|
39
|
+
const __filename = fileURLToPath(import.meta.url);
|
40
|
+
const __dirname = path.dirname(__filename);
|
41
|
+
// 确保 public 目录存在
|
42
|
+
const PUBLIC_DIR = path.join(__dirname, '../public');
|
43
|
+
fs.access(PUBLIC_DIR)
|
44
|
+
.then(() => { })
|
45
|
+
.catch(async () => {
|
46
|
+
await fs.mkdir(PUBLIC_DIR, { recursive: true }); // 添加 recursive 避免多级目录问题
|
47
|
+
});
|
48
|
+
/**
|
49
|
+
* 上传文件
|
50
|
+
* @param req
|
51
|
+
* @param res
|
52
|
+
*/
|
53
|
+
export const uploadFiles = async (req, res) => {
|
54
|
+
const request = req;
|
55
|
+
const data = request.body;
|
56
|
+
const savedFiles = [];
|
57
|
+
const keys = Object.keys(data?.fields);
|
58
|
+
if (data?.files) {
|
59
|
+
for (const file of data.files) {
|
60
|
+
const filePath = path.join(PUBLIC_DIR, file.filename);
|
61
|
+
await fs.writeFile(filePath, file.data);
|
62
|
+
savedFiles.push(file.filename);
|
63
|
+
console.log(`文件已保存至: ${filePath}`);
|
64
|
+
}
|
65
|
+
}
|
66
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
67
|
+
res.end(JSON.stringify({
|
68
|
+
status: 'success',
|
69
|
+
message: `${savedFiles.length} 个文件 ${keys.length} 参数上传成功`,
|
70
|
+
files: data?.files?.map(f => f.filename),
|
71
|
+
fields: keys
|
72
|
+
}));
|
73
|
+
return true;
|
74
|
+
};
|
package/dist/index.js
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
// index.js
|
2
|
+
import('./routes/index.js');
|
3
|
+
import app from './lib/app.js';
|
4
|
+
const port = 443;
|
5
|
+
// 捕获未处理的异常
|
6
|
+
process.on('uncaughtException', (err) => {
|
7
|
+
console.error('[FATAL] Uncaught Exception:', err);
|
8
|
+
setTimeout(() => process.exit(1), 1000); // 安全退出
|
9
|
+
});
|
10
|
+
process.on('unhandledRejection', (reason, promise) => {
|
11
|
+
console.error('[WARNING] Unhandled Rejection at:', reason, 'Promise', promise);
|
12
|
+
setTimeout(() => process.exit(1), 1000);
|
13
|
+
});
|
14
|
+
app.listen(port, () => {
|
15
|
+
console.log(`Server running on https://localhost:${port}`);
|
16
|
+
});
|
@@ -0,0 +1,123 @@
|
|
1
|
+
/**
|
2
|
+
* GroupRoute 类用于组织和管理一组具有共同前缀的 HTTP 路由。
|
3
|
+
* 支持在请求处理前后添加中间件,并支持链式调用。
|
4
|
+
*/
|
5
|
+
export class GroupRoute {
|
6
|
+
/**
|
7
|
+
* 构造函数
|
8
|
+
* @param prefix 路由组的前缀,例如 "/api"
|
9
|
+
*/
|
10
|
+
constructor(prefix) {
|
11
|
+
this.prefix = prefix;
|
12
|
+
// 在请求处理前执行的中间件列表
|
13
|
+
this.beforeRequestMiddleWares = [];
|
14
|
+
// 在请求处理后执行的中间件列表
|
15
|
+
this.afterRequestMiddleWares = [];
|
16
|
+
// 注册的路由列表,包含 HTTP 方法、URL 和处理链
|
17
|
+
this.routes = [];
|
18
|
+
}
|
19
|
+
/**
|
20
|
+
* 添加一个在请求处理前执行的中间件
|
21
|
+
* @param middleware 中间件函数
|
22
|
+
* @returns 返回当前 GroupRoute 实例,支持链式调用
|
23
|
+
*/
|
24
|
+
beforeRequest(middleware) {
|
25
|
+
this.beforeRequestMiddleWares.push(middleware);
|
26
|
+
return this;
|
27
|
+
}
|
28
|
+
/**
|
29
|
+
* 添加一个在请求处理后执行的中间件
|
30
|
+
* @param middleware 中间件函数
|
31
|
+
* @returns 返回当前 GroupRoute 实例,支持链式调用
|
32
|
+
*/
|
33
|
+
afterRequest(middleware) {
|
34
|
+
this.afterRequestMiddleWares.push(middleware);
|
35
|
+
return this;
|
36
|
+
}
|
37
|
+
/**
|
38
|
+
* 注册一个 GET 请求路由
|
39
|
+
* @param url 路由路径
|
40
|
+
* @param handler 请求处理函数
|
41
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
42
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
43
|
+
* @returns 返回当前 GroupRoute 实例,支持链式调用
|
44
|
+
*/
|
45
|
+
get(url, handler, beforeRequest = [], afterRequest = []) {
|
46
|
+
this._register('GET', this.prefix + url, handler, beforeRequest, afterRequest);
|
47
|
+
return this;
|
48
|
+
}
|
49
|
+
/**
|
50
|
+
* 注册一个 POST 请求路由
|
51
|
+
* @param url 路由路径
|
52
|
+
* @param handler 请求处理函数
|
53
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
54
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
55
|
+
* @returns 返回当前 GroupRoute 实例,支持链式调用
|
56
|
+
*/
|
57
|
+
post(url, handler, beforeRequest = [], afterRequest = []) {
|
58
|
+
this._register('POST', this.prefix + url, handler, beforeRequest, afterRequest);
|
59
|
+
return this;
|
60
|
+
}
|
61
|
+
/**
|
62
|
+
* 注册一个 PUT 请求路由
|
63
|
+
* @param url 路由路径
|
64
|
+
* @param handler 请求处理函数
|
65
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
66
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
67
|
+
* @returns 返回当前 GroupRoute 实例,支持链式调用
|
68
|
+
*/
|
69
|
+
put(url, handler, beforeRequest = [], afterRequest = []) {
|
70
|
+
this._register('PUT', this.prefix + url, handler, beforeRequest, afterRequest);
|
71
|
+
return this;
|
72
|
+
}
|
73
|
+
/**
|
74
|
+
* 注册一个 DELETE 请求路由
|
75
|
+
* @param url 路由路径
|
76
|
+
* @param handler 请求处理函数
|
77
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
78
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
79
|
+
* @returns 返回当前 GroupRoute 实例,支持链式调用
|
80
|
+
*/
|
81
|
+
delete(url, handler, beforeRequest = [], afterRequest = []) {
|
82
|
+
this._register('DELETE', this.prefix + url, handler, beforeRequest, afterRequest);
|
83
|
+
return this;
|
84
|
+
}
|
85
|
+
/**
|
86
|
+
* 通用方法,用于注册任意 HTTP 方法的路由
|
87
|
+
* @param method HTTP 方法,如 'GET', 'POST' 等
|
88
|
+
* @param url 路由路径
|
89
|
+
* @param handler 请求处理函数
|
90
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
91
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
92
|
+
*/
|
93
|
+
request(method, url, handler, beforeRequest = [], afterRequest = []) {
|
94
|
+
this._register(method, url, handler, beforeRequest, afterRequest);
|
95
|
+
return this;
|
96
|
+
}
|
97
|
+
/**
|
98
|
+
* 内部方法,用于将路由信息注册到路由列表中
|
99
|
+
* @param method HTTP 方法
|
100
|
+
* @param url 路由路径
|
101
|
+
* @param handler 请求处理函数
|
102
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
103
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
104
|
+
*/
|
105
|
+
_register(method, url, handler, beforeRequest, afterRequest) {
|
106
|
+
// 构建完整的中间件链:beforeRequestMiddleWares + beforeRequest + handler + afterRequest + afterRequestMiddleWares
|
107
|
+
const chain = [
|
108
|
+
...this.beforeRequestMiddleWares,
|
109
|
+
...beforeRequest,
|
110
|
+
handler,
|
111
|
+
...afterRequest,
|
112
|
+
...this.afterRequestMiddleWares,
|
113
|
+
];
|
114
|
+
this.routes.push({ method, url, chain });
|
115
|
+
}
|
116
|
+
/**
|
117
|
+
* 获取当前路由组的所有路由信息(可用于调试或注册到框架中)
|
118
|
+
* @returns 路由列表
|
119
|
+
*/
|
120
|
+
getRoutes() {
|
121
|
+
return this.routes;
|
122
|
+
}
|
123
|
+
}
|
package/dist/lib/app.js
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
// app.js
|
2
|
+
import https from 'https';
|
3
|
+
import url from 'url';
|
4
|
+
import certs from '../utils/certs.js';
|
5
|
+
class WebApp {
|
6
|
+
constructor() {
|
7
|
+
this.routes = [];
|
8
|
+
this.beforeRequestMiddleWares = [];
|
9
|
+
this.afterRequestMiddleWares = [];
|
10
|
+
}
|
11
|
+
/**
|
12
|
+
* 添加一个前置中间件
|
13
|
+
* @param middleware 中间件函数
|
14
|
+
* @returns 返回当前 WebApp 实例,支持链式调用
|
15
|
+
*/
|
16
|
+
beforeRequest(middleware) {
|
17
|
+
if (typeof middleware !== 'function') {
|
18
|
+
throw new Error('Middleware must be a function');
|
19
|
+
}
|
20
|
+
if (this.beforeRequestMiddleWares.includes(middleware)) {
|
21
|
+
throw new Error('Middleware already exists');
|
22
|
+
}
|
23
|
+
this.beforeRequestMiddleWares.push(middleware);
|
24
|
+
return this;
|
25
|
+
}
|
26
|
+
/**
|
27
|
+
* 添加一个后置中间件
|
28
|
+
* @param middleware 中间件函数
|
29
|
+
* @returns 返回当前 WebApp 实例,支持链式调用
|
30
|
+
*/
|
31
|
+
afterRequest(middleware) {
|
32
|
+
if (typeof middleware !== 'function') {
|
33
|
+
throw new Error('Middleware must be a function');
|
34
|
+
}
|
35
|
+
if (this.afterRequestMiddleWares.includes(middleware)) {
|
36
|
+
throw new Error('Middleware already exists');
|
37
|
+
}
|
38
|
+
this.afterRequestMiddleWares.push(middleware);
|
39
|
+
return this;
|
40
|
+
}
|
41
|
+
/**
|
42
|
+
* 注册一个路由
|
43
|
+
* @param method HTTP 方法,如 'GET', 'POST' 等
|
44
|
+
* @param url 路由路径
|
45
|
+
* @param handler 请求处理函数
|
46
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
47
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
48
|
+
* @returns 返回当前 WebApp 实例,支持链式调用
|
49
|
+
*/
|
50
|
+
request(method, url, handler, beforeRequest = [], afterRequest = []) {
|
51
|
+
this._register(method, url, handler, beforeRequest, afterRequest);
|
52
|
+
return this;
|
53
|
+
}
|
54
|
+
/**
|
55
|
+
* 注册一个 GET 请求路由
|
56
|
+
* @param url 路由路径
|
57
|
+
* @param handler 请求处理函数
|
58
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
59
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
60
|
+
* @returns 返回当前 WebApp 实例,支持链式调用
|
61
|
+
*/
|
62
|
+
get(url, handler, beforeRequest = [], afterRequest = []) {
|
63
|
+
this._register('GET', url, handler, beforeRequest, afterRequest);
|
64
|
+
return this;
|
65
|
+
}
|
66
|
+
/**
|
67
|
+
* 注册一个 POST 请求路由
|
68
|
+
* @param url 路由路径
|
69
|
+
* @param handler 请求处理函数
|
70
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
71
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
72
|
+
* @returns 返回当前 WebApp 实例,支持链式调用
|
73
|
+
*/
|
74
|
+
post(url, handler, beforeRequest = [], afterRequest = []) {
|
75
|
+
this._register('POST', url, handler, beforeRequest, afterRequest);
|
76
|
+
return this;
|
77
|
+
}
|
78
|
+
/**
|
79
|
+
* 注册一个 PUT 请求路由
|
80
|
+
* @param url 路由路径
|
81
|
+
* @param handler 请求处理函数
|
82
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
83
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
84
|
+
* @returns 返回当前 WebApp 实例,支持链式调用
|
85
|
+
*/
|
86
|
+
put(url, handler, beforeRequest = [], afterRequest = []) {
|
87
|
+
this._register('PUT', url, handler, beforeRequest, afterRequest);
|
88
|
+
return this;
|
89
|
+
}
|
90
|
+
/**
|
91
|
+
* 注册一个 DELETE 请求路由
|
92
|
+
* @param url 路由路径
|
93
|
+
* @param handler 请求处理函数
|
94
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
95
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
96
|
+
* @returns 返回当前 WebApp 实例,支持链式调用
|
97
|
+
*/
|
98
|
+
delete(url, handler, beforeRequest = [], afterRequest = []) {
|
99
|
+
this._register('DELETE', url, handler, beforeRequest, afterRequest);
|
100
|
+
return this;
|
101
|
+
}
|
102
|
+
/**
|
103
|
+
* 内部方法,用于注册路由
|
104
|
+
* @param method HTTP 方法
|
105
|
+
* @param url 路由路径
|
106
|
+
* @param handler 请求处理函数
|
107
|
+
* @param beforeRequest 在处理函数前执行的中间件数组
|
108
|
+
* @param afterRequest 在处理函数后执行的中间件数组
|
109
|
+
*/
|
110
|
+
_register(method, url, handler, beforeRequest, afterRequest) {
|
111
|
+
// 构建完整的中间件链:beforeRequest + handler + afterRequest
|
112
|
+
const chain = [...beforeRequest, handler, ...afterRequest];
|
113
|
+
this.routes.push({ method, url, chain });
|
114
|
+
}
|
115
|
+
/**
|
116
|
+
* 注册一个路由组
|
117
|
+
* @param groupRoute 路由组实例
|
118
|
+
* @returns 返回当前 WebApp 实例,支持链式调用
|
119
|
+
*/
|
120
|
+
registerGroupRoute(groupRoute) {
|
121
|
+
this.routes.push(...groupRoute.getRoutes());
|
122
|
+
return this;
|
123
|
+
}
|
124
|
+
/**
|
125
|
+
* 匹配路由参数
|
126
|
+
* @param routePath 路由路径
|
127
|
+
* @param requestPath 请求路径
|
128
|
+
* @returns 匹配的参数对象
|
129
|
+
*/
|
130
|
+
matchRoute(routePath, requestPath) {
|
131
|
+
const routeParts = routePath.split('/');
|
132
|
+
const requestParts = requestPath.split('/');
|
133
|
+
if (routeParts.length !== requestParts.length)
|
134
|
+
return null;
|
135
|
+
const params = {};
|
136
|
+
for (let i = 0; i < routeParts.length; i++) {
|
137
|
+
if (routeParts[i] !== requestParts[i] && !routeParts[i].startsWith(':')) {
|
138
|
+
return null;
|
139
|
+
}
|
140
|
+
if (routeParts[i].startsWith(':')) {
|
141
|
+
const paramName = routeParts[i].slice(1);
|
142
|
+
params[paramName] = requestParts[i];
|
143
|
+
}
|
144
|
+
}
|
145
|
+
return params;
|
146
|
+
}
|
147
|
+
/**
|
148
|
+
* 启动服务器
|
149
|
+
* @param port 端口号
|
150
|
+
* @param callback 回调函数
|
151
|
+
*/
|
152
|
+
listen(port, callback) {
|
153
|
+
const server = https.createServer(certs, async (req, res) => {
|
154
|
+
const parsedUrl = new url.URL(req.url ?? '', `http://${req.headers.host}`);
|
155
|
+
const path = parsedUrl.pathname;
|
156
|
+
const method = req.method;
|
157
|
+
// 标记是否结束处理
|
158
|
+
let handled = false;
|
159
|
+
try {
|
160
|
+
//执行前置中间件
|
161
|
+
for (const mw of this.beforeRequestMiddleWares) {
|
162
|
+
if (handled !== true)
|
163
|
+
handled = await mw(req, res);
|
164
|
+
}
|
165
|
+
if (handled)
|
166
|
+
return;
|
167
|
+
// 查找匹配的路由
|
168
|
+
let matchedRoute = null;
|
169
|
+
let params = null;
|
170
|
+
for (let route of this.routes) {
|
171
|
+
if (route.method === method) {
|
172
|
+
params = this.matchRoute(route.url, path);
|
173
|
+
if (params) {
|
174
|
+
matchedRoute = route;
|
175
|
+
break;
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
179
|
+
if (params && matchedRoute) {
|
180
|
+
// 设置请求参数
|
181
|
+
req.query = parsedUrl.searchParams;
|
182
|
+
req.params = params;
|
183
|
+
for (const mw of matchedRoute.chain) {
|
184
|
+
if (!handled)
|
185
|
+
handled = await mw(req, res);
|
186
|
+
}
|
187
|
+
}
|
188
|
+
else {
|
189
|
+
// 404
|
190
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
191
|
+
res.end(JSON.stringify({ error: 'Not Found' }));
|
192
|
+
}
|
193
|
+
}
|
194
|
+
catch (error) {
|
195
|
+
// 500
|
196
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
197
|
+
if (error instanceof Error) {
|
198
|
+
res.end(JSON.stringify({
|
199
|
+
error: 'Internal Server Error',
|
200
|
+
message: error.message,
|
201
|
+
}));
|
202
|
+
console.log(error.stack);
|
203
|
+
}
|
204
|
+
else {
|
205
|
+
res.end(JSON.stringify({
|
206
|
+
error: 'Internal Server Error',
|
207
|
+
message: 'An unknown error occurred.',
|
208
|
+
}));
|
209
|
+
}
|
210
|
+
}
|
211
|
+
finally {
|
212
|
+
//执行后置中间件
|
213
|
+
for (const mw of this.afterRequestMiddleWares) {
|
214
|
+
if (!handled)
|
215
|
+
handled = await mw(req, res);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
});
|
219
|
+
server.listen(port, callback);
|
220
|
+
}
|
221
|
+
}
|
222
|
+
export default new WebApp();
|
@@ -0,0 +1,146 @@
|
|
1
|
+
import Bb from 'busboy';
|
2
|
+
function parseJson(rawBody) {
|
3
|
+
try {
|
4
|
+
let result = JSON.parse(rawBody);
|
5
|
+
console.log("parseJson", result);
|
6
|
+
return { fields: result, files: [] };
|
7
|
+
}
|
8
|
+
catch (e) {
|
9
|
+
throw new Error('Invalid JSON');
|
10
|
+
}
|
11
|
+
}
|
12
|
+
// 解析 application/x-www-form-urlencoded
|
13
|
+
/*
|
14
|
+
* 解析 application/x-www-form-urlencoded
|
15
|
+
* @param {string} rawBody - 待解析的表单数据
|
16
|
+
* @returns Record<string, string> 解析后的表单数据
|
17
|
+
*/
|
18
|
+
function parseForm(rawBody) {
|
19
|
+
const params = new URLSearchParams(rawBody);
|
20
|
+
const form = {};
|
21
|
+
for (const [key, value] of params.entries()) {
|
22
|
+
form[key] = value;
|
23
|
+
}
|
24
|
+
console.log("parseForm", form);
|
25
|
+
return { fields: form, files: [] };
|
26
|
+
}
|
27
|
+
/*
|
28
|
+
* 解析 multipart/form-data
|
29
|
+
* @param {http.IncomingMessage} req - 请求对象
|
30
|
+
* @returns {Promise<{fields: Record<string, string>, files: any[]}>} 解析结果
|
31
|
+
*/
|
32
|
+
function parseMultipart(req) {
|
33
|
+
return new Promise((resolve, reject) => {
|
34
|
+
// const bb = import('busboy')({
|
35
|
+
// headers: req.headers,
|
36
|
+
// limits: {
|
37
|
+
// fileSize: 10*1024*1024 ,// 最大 10MB
|
38
|
+
// files:10
|
39
|
+
// }
|
40
|
+
// }
|
41
|
+
// );
|
42
|
+
const bb = Bb({
|
43
|
+
headers: req.headers,
|
44
|
+
limits: {
|
45
|
+
fileSize: 10 * 1024 * 1024, // 最大 10MB
|
46
|
+
files: 10
|
47
|
+
}
|
48
|
+
});
|
49
|
+
const fields = {};
|
50
|
+
const files = [];
|
51
|
+
// 普通字段
|
52
|
+
bb.on('field', (name, value) => {
|
53
|
+
fields[name] = value;
|
54
|
+
});
|
55
|
+
// 文件字段
|
56
|
+
bb.on('file', (fieldname, file, info) => {
|
57
|
+
const { filename, mimeType, encoding } = info;
|
58
|
+
const chunks = [];
|
59
|
+
console.log(`File [${fieldname}]: ${filename}, ${mimeType}`);
|
60
|
+
// ✅ 关键:监听 'limit' 事件
|
61
|
+
file.on('limit', () => {
|
62
|
+
req.unpipe(bb);
|
63
|
+
reject(new Error(`${filename}文件大小超出限制`));
|
64
|
+
});
|
65
|
+
file.on('data', (chunk) => {
|
66
|
+
chunks.push(chunk);
|
67
|
+
});
|
68
|
+
file.on('end', () => {
|
69
|
+
files.push({
|
70
|
+
fieldname,
|
71
|
+
filename,
|
72
|
+
mimetype: mimeType,
|
73
|
+
encoding,
|
74
|
+
data: Buffer.concat(chunks)
|
75
|
+
});
|
76
|
+
console.log(`文件 [${fieldname}] 接收完成`);
|
77
|
+
});
|
78
|
+
file.on('error', (err) => {
|
79
|
+
reject(err);
|
80
|
+
});
|
81
|
+
});
|
82
|
+
// 完成解析
|
83
|
+
bb.on('finish', () => {
|
84
|
+
resolve({ fields, files });
|
85
|
+
});
|
86
|
+
// 错误处理
|
87
|
+
bb.on('error', (err) => {
|
88
|
+
reject(err);
|
89
|
+
});
|
90
|
+
// 文件数量超出限制
|
91
|
+
bb.on('filesLimit', () => {
|
92
|
+
req.unpipe(bb);
|
93
|
+
reject(new Error('文件数量超过限制'));
|
94
|
+
});
|
95
|
+
// 字段数量超出限制
|
96
|
+
bb.on('fieldsLimit', () => {
|
97
|
+
req.unpipe(bb);
|
98
|
+
reject(new Error('字段数量超过限制'));
|
99
|
+
});
|
100
|
+
// 管道输入
|
101
|
+
req.pipe(bb);
|
102
|
+
});
|
103
|
+
}
|
104
|
+
// 映射 Content-Type 到对应的解析函数
|
105
|
+
const parserType = ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data'];
|
106
|
+
/**
|
107
|
+
* 解析请求体
|
108
|
+
* @param req http.IncomingMessage 请求对象
|
109
|
+
* @param res http.ServerResponse 响应对象
|
110
|
+
* @returns {Promise<boolean>} 是否结束处理
|
111
|
+
*/
|
112
|
+
export async function bodyParser(req, res) {
|
113
|
+
const contentType = req.headers['content-type'] || '';
|
114
|
+
req.body = await new Promise((resolve, reject) => {
|
115
|
+
const matchedType = parserType.find(type => type === contentType);
|
116
|
+
if (!matchedType) {
|
117
|
+
return resolve({ fields: {}, files: [] });
|
118
|
+
}
|
119
|
+
if (matchedType === 'multipart/form-data') {
|
120
|
+
parseMultipart(req)
|
121
|
+
.then(data => resolve(data))
|
122
|
+
.catch(err => reject(err));
|
123
|
+
}
|
124
|
+
else {
|
125
|
+
// 处理其他类型
|
126
|
+
let body = [];
|
127
|
+
req.on('data', chunk => {
|
128
|
+
body.push(chunk);
|
129
|
+
});
|
130
|
+
req.on('end', () => {
|
131
|
+
const rawBody = Buffer.concat(body).toString();
|
132
|
+
try {
|
133
|
+
const result = contentType === 'application/json' ? parseJson(rawBody) : parseForm(rawBody);
|
134
|
+
resolve(result);
|
135
|
+
}
|
136
|
+
catch (e) {
|
137
|
+
reject(e);
|
138
|
+
}
|
139
|
+
});
|
140
|
+
req.on('error', (err) => {
|
141
|
+
reject(err);
|
142
|
+
});
|
143
|
+
}
|
144
|
+
});
|
145
|
+
return false; // 继续执行下一个中间件
|
146
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
/*
|
2
|
+
* 跨域中间件
|
3
|
+
* @param options 跨域配置项
|
4
|
+
* @returns 中间件函数
|
5
|
+
*/
|
6
|
+
export function cors(options = {}) {
|
7
|
+
const { origin = '*', methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders = ['Content-Type', 'Authorization'], credentials = false, } = options;
|
8
|
+
return function corsMiddleware(req, res) {
|
9
|
+
const requestOrigin = req.headers.origin;
|
10
|
+
if (requestOrigin) {
|
11
|
+
let allowThisOrigin = false;
|
12
|
+
if (origin === '*') {
|
13
|
+
allowThisOrigin = true;
|
14
|
+
}
|
15
|
+
else if (Array.isArray(origin)) {
|
16
|
+
allowThisOrigin = origin.some(host => host === requestOrigin);
|
17
|
+
}
|
18
|
+
if (allowThisOrigin) {
|
19
|
+
// 设置 CORS 响应头
|
20
|
+
res.setHeader('Access-Control-Allow-Origin', requestOrigin);
|
21
|
+
if (credentials) {
|
22
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
23
|
+
}
|
24
|
+
res.setHeader('Access-Control-Allow-Methods', methods.join(', '));
|
25
|
+
res.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(', '));
|
26
|
+
// 处理 OPTIONS 预检请求
|
27
|
+
if (req.method === 'OPTIONS') {
|
28
|
+
res.writeHead(204); // No Content
|
29
|
+
res.end();
|
30
|
+
return true; // 已处理
|
31
|
+
}
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
console.warn(`Blocked request from disallowed origin: ${requestOrigin}`);
|
35
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
36
|
+
res.end('Forbidden');
|
37
|
+
return true;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
return false; // 继续后续处理
|
41
|
+
};
|
42
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
// middleware/logger.js
|
2
|
+
export function formatLocalTime() {
|
3
|
+
const date = new Date();
|
4
|
+
const year = date.getFullYear();
|
5
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
6
|
+
const day = String(date.getDate()).padStart(2, '0');
|
7
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
8
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
9
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
10
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
11
|
+
}
|
12
|
+
/*
|
13
|
+
* 请求日志记录
|
14
|
+
* @param {http.IncomingMessage} req 请求对象
|
15
|
+
* @param {http.ServerResponse} res 响应对象
|
16
|
+
* @return {boolean} 默认返回 false,表示继续执行后续逻辑
|
17
|
+
*/
|
18
|
+
export const requestLogger = (req, res) => {
|
19
|
+
const method = req.method;
|
20
|
+
const url = req.url;
|
21
|
+
const timestamp = formatLocalTime();
|
22
|
+
console.log(`[${timestamp}] ${req.headers.origin || ''} request ${method} ${url} `);
|
23
|
+
return false; // 继续执行后续逻辑
|
24
|
+
};
|
25
|
+
/*
|
26
|
+
* 响应日志记录
|
27
|
+
* @param {http.IncomingMessage} req 请求对象
|
28
|
+
* @param {http.ServerResponse} res 响应对象
|
29
|
+
* @return {boolean} 默认返回 false,表示继续执行后续逻辑
|
30
|
+
*/
|
31
|
+
export const responseLogger = (req, res) => {
|
32
|
+
const startTime = process.hrtime();
|
33
|
+
// 保存原始 end 方法
|
34
|
+
const originalEnd = res.end;
|
35
|
+
// 覆盖 res.end 方法以捕获响应结束
|
36
|
+
res.end = function (chunk, encoding, callback) {
|
37
|
+
const [seconds, nanoseconds] = process.hrtime(startTime);
|
38
|
+
const durationMs = Math.round(seconds * 1000 + nanoseconds / 1e6);
|
39
|
+
const statusCode = res.statusCode || 500;
|
40
|
+
const timestamp = formatLocalTime();
|
41
|
+
console.log(`[${timestamp}] ${req.headers.origin || ''} response ${req.method} ${req.url} ${statusCode} ${durationMs}ms`);
|
42
|
+
return originalEnd.call(this, chunk, encoding, callback);
|
43
|
+
};
|
44
|
+
return false; // 继续执行后续逻辑
|
45
|
+
};
|
46
|
+
// function errorLoggerInjection(app){
|
47
|
+
// if (!app)
|
48
|
+
// throw new Error('app is required');
|
49
|
+
// // 所有路由注册前自动包装 handler
|
50
|
+
// app.routes = app.routes.map(route => ({
|
51
|
+
// ...route,
|
52
|
+
// handler: wrapHandler(route.handler)
|
53
|
+
// }));
|
54
|
+
// }
|
55
|
+
//
|
56
|
+
// function wrapHandler(handler:Middleware) {
|
57
|
+
// return (req, res) => {
|
58
|
+
// Promise.resolve()
|
59
|
+
// .then(() => handler(req, res))
|
60
|
+
// .catch(error => {
|
61
|
+
// const timestamp = formatLocalTime();
|
62
|
+
// const method = req.method;
|
63
|
+
// const url = req.url;
|
64
|
+
// console.error(`[${timestamp}] ERROR ${method} ${url}`);
|
65
|
+
// console.error('Error Stack:',error.stack);
|
66
|
+
// if(!res.headersSent){
|
67
|
+
// res.writeHead(500, { 'Content-Type': 'application/json' });
|
68
|
+
// }
|
69
|
+
// res.end(JSON.stringify({
|
70
|
+
// error: 'Internal Server Error',
|
71
|
+
// message: error.message
|
72
|
+
// }));
|
73
|
+
// });
|
74
|
+
// };
|
75
|
+
// }
|
76
|
+
console.log('logger.js loaded');
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
/*
|
4
|
+
* 解析文件类型
|
5
|
+
* @param extension 文件扩展名
|
6
|
+
* @returns string 文件类型
|
7
|
+
*/
|
8
|
+
function getContentType(extname) {
|
9
|
+
const map = {
|
10
|
+
'.html': 'text/html',
|
11
|
+
'.js': 'text/javascript',
|
12
|
+
'.css': 'text/css',
|
13
|
+
'.json': 'application/json',
|
14
|
+
'.png': 'image/png',
|
15
|
+
'.jpg': 'image/jpeg',
|
16
|
+
'.gif': 'image/gif',
|
17
|
+
'.svg': 'image/svg+xml',
|
18
|
+
'.txt': 'text/plain',
|
19
|
+
'.woff': 'font/woff',
|
20
|
+
'.woff2': 'font/woff2',
|
21
|
+
'.ttf': 'font/ttf',
|
22
|
+
'.eot': 'application/vnd.ms-fontobject',
|
23
|
+
'.mp3': 'audio/mpeg',
|
24
|
+
'.mp4': 'video/mp4',
|
25
|
+
'.wav': 'audio/wav',
|
26
|
+
'.pdf': 'application/pdf',
|
27
|
+
'.zip': 'application/zip',
|
28
|
+
'.doc': 'application/msword',
|
29
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
30
|
+
'.xls': 'application/vnd.ms-excel',
|
31
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
32
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
33
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
34
|
+
'.webp': 'image/webp'
|
35
|
+
};
|
36
|
+
return map[extname] || 'application/octet-stream';
|
37
|
+
}
|
38
|
+
/*
|
39
|
+
* 静态资源中间件
|
40
|
+
* @param options 配置项
|
41
|
+
* @returns 返回一个中间件函数
|
42
|
+
*/
|
43
|
+
export const staticResources = function (options = {}) {
|
44
|
+
const { prefix = '/public', // URL 前缀
|
45
|
+
rootDir = path.join(process.cwd(), prefix) // 默认 public 目录
|
46
|
+
} = options;
|
47
|
+
console.log('静态资源中间件已启动...', rootDir);
|
48
|
+
return async function (req, res) {
|
49
|
+
const parsedUrl = new URL(req.url ?? '', `http://${req.headers.host}`);
|
50
|
+
const pathname = parsedUrl.pathname;
|
51
|
+
// 检查是否匹配前缀
|
52
|
+
if (!pathname.startsWith(prefix)) {
|
53
|
+
return false; // 继续后续中间件
|
54
|
+
}
|
55
|
+
// 构建实际文件路径
|
56
|
+
const filePath = pathname.replace(prefix, '');
|
57
|
+
const fullPath = path.join(rootDir, filePath);
|
58
|
+
try {
|
59
|
+
const stats = await fs.promises.stat(fullPath);
|
60
|
+
if (!stats.isFile()) {
|
61
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
62
|
+
res.end('Not Found');
|
63
|
+
return true; // 中断中间件链
|
64
|
+
}
|
65
|
+
const extname = path.extname(fullPath).toLowerCase();
|
66
|
+
const contentType = getContentType(extname);
|
67
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
68
|
+
fs.createReadStream(fullPath).pipe(res);
|
69
|
+
return true; // 已响应,中断后续流程
|
70
|
+
}
|
71
|
+
catch (err) {
|
72
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
73
|
+
res.end('Not Found');
|
74
|
+
if (err instanceof Error) {
|
75
|
+
console.error('静态资源访问错误:', err.message);
|
76
|
+
}
|
77
|
+
return true; // 中断流程
|
78
|
+
}
|
79
|
+
};
|
80
|
+
};
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import { formatLocalTime } from './logger.js';
|
2
|
+
/**
|
3
|
+
* 获取字段值
|
4
|
+
* @param section body部分
|
5
|
+
* @param field 字段名
|
6
|
+
* @param req 请求体
|
7
|
+
* @returns 返回字段值
|
8
|
+
*/
|
9
|
+
function getValue(section, field, req) {
|
10
|
+
let value = null;
|
11
|
+
switch (section) {
|
12
|
+
case 'query':
|
13
|
+
value = req.query[field];
|
14
|
+
break;
|
15
|
+
case 'params':
|
16
|
+
value = req.params[field];
|
17
|
+
break;
|
18
|
+
case 'body':
|
19
|
+
value = req.body.fields[field];
|
20
|
+
break;
|
21
|
+
default:
|
22
|
+
break;
|
23
|
+
}
|
24
|
+
return value;
|
25
|
+
}
|
26
|
+
/**
|
27
|
+
*
|
28
|
+
*/
|
29
|
+
function setValue(section, field, value, req) {
|
30
|
+
switch (section) {
|
31
|
+
case 'body':
|
32
|
+
req.body.fields[field] = value;
|
33
|
+
break;
|
34
|
+
case 'query':
|
35
|
+
req.query[field] = value;
|
36
|
+
break;
|
37
|
+
case 'params':
|
38
|
+
req.params[field] = value;
|
39
|
+
break;
|
40
|
+
default:
|
41
|
+
break;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
/**
|
45
|
+
* 参数验证中间件工厂函数
|
46
|
+
* @param options 验证配置
|
47
|
+
* @returns 返回一个中间件函数
|
48
|
+
*/
|
49
|
+
export const validation = (options) => {
|
50
|
+
return (req, res) => {
|
51
|
+
const request = req;
|
52
|
+
const errors = [];
|
53
|
+
// 遍历每个需要校验的部分(body、query、params)
|
54
|
+
for (const [section, fields] of Object.entries(options)) {
|
55
|
+
let data = null;
|
56
|
+
switch (section) {
|
57
|
+
case 'query':
|
58
|
+
data = request.query;
|
59
|
+
break;
|
60
|
+
case 'body':
|
61
|
+
data = request.body.fields;
|
62
|
+
break;
|
63
|
+
case 'params':
|
64
|
+
data = request.params;
|
65
|
+
break;
|
66
|
+
default:
|
67
|
+
errors.push(`Invalid section: ${section}`);
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
if (!data) {
|
71
|
+
errors.push(`Invalid request section: ${section}`);
|
72
|
+
break;
|
73
|
+
}
|
74
|
+
// 遍历每个字段规则
|
75
|
+
for (const [field, rules] of Object.entries(fields)) {
|
76
|
+
let value = getValue(section, field, request);
|
77
|
+
// 判断是否为必填字段
|
78
|
+
if (rules.required === true && value === null) {
|
79
|
+
errors.push(`"${field}" is required in ${section}`);
|
80
|
+
continue;
|
81
|
+
}
|
82
|
+
// 如果是可选字段且未提供,则跳过后续检查
|
83
|
+
if (rules.optional === true && value === null) {
|
84
|
+
continue;
|
85
|
+
}
|
86
|
+
// ======【关键改进】自动类型转换(仅 query / params)======
|
87
|
+
if ((section === 'query' || section === 'params') && value !== null) {
|
88
|
+
if (rules.type === 'number') {
|
89
|
+
const num = parseFloat(typeof value === "string" ? value : 'error');
|
90
|
+
if (!isNaN(num)) {
|
91
|
+
setValue(section, field, num, request); // 写回原始对象
|
92
|
+
}
|
93
|
+
else {
|
94
|
+
errors.push(`"${field}" must be a number in ${section} your's is ${value}`);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
else if (rules.type === 'boolean') {
|
98
|
+
if (value === 'true') {
|
99
|
+
setValue(section, field, true, request);
|
100
|
+
}
|
101
|
+
else if (value === 'false') {
|
102
|
+
setValue(section, field, false, request);
|
103
|
+
}
|
104
|
+
else {
|
105
|
+
errors.push(`"${field}" must be a boolean in ${section}`);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
// 字符串长度校验
|
110
|
+
if (typeof value === 'string') {
|
111
|
+
if (rules.min !== undefined && value.length < rules.min) {
|
112
|
+
errors.push(`"${field}" must be at least ${rules.min} characters long`);
|
113
|
+
}
|
114
|
+
if (rules.max !== undefined && value.length > rules.max) {
|
115
|
+
errors.push(`"${field}" must be at most ${rules.max} characters long`);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
// 格式校验(如 email)
|
119
|
+
if (rules.format === 'email' && value !== null) {
|
120
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
121
|
+
if (!emailRegex.test(typeof value === "string" ? value : 'error')) {
|
122
|
+
errors.push(`"${field}" must be a valid email address`);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
// 如果有错误,返回错误
|
128
|
+
if (errors.length > 0) {
|
129
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
130
|
+
res.end(JSON.stringify({ message: '参数校验失败', errors }));
|
131
|
+
console.log(`[${formatLocalTime()}] 参数校验失败: ${errors.join(', ')}`);
|
132
|
+
return true;
|
133
|
+
}
|
134
|
+
// 否则继续下一个中间件
|
135
|
+
return false;
|
136
|
+
};
|
137
|
+
};
|
@@ -0,0 +1,43 @@
|
|
1
|
+
// routes/index.js
|
2
|
+
import app from '../lib/app.js';
|
3
|
+
import { index, uploadFiles, userDetail } from '../controllers/index.js';
|
4
|
+
import { requestLogger, responseLogger } from '../lib/middleware/logger.js';
|
5
|
+
import { validation } from '../lib/middleware/validate.js';
|
6
|
+
import { staticResources } from '../lib/middleware/static-resources.js';
|
7
|
+
import { cors } from '../lib/middleware/cors.js';
|
8
|
+
import { bodyParser } from '../lib/middleware/body-parser.js';
|
9
|
+
app.beforeRequest(requestLogger);
|
10
|
+
app.beforeRequest(staticResources());
|
11
|
+
app.beforeRequest(cors({
|
12
|
+
origin: ['http://localhost:300', 'https://www.bilibili.com'],
|
13
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
14
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
15
|
+
credentials: true // 如果需要跨域带 cookie,设为 true
|
16
|
+
}));
|
17
|
+
app.beforeRequest(bodyParser);
|
18
|
+
app.beforeRequest(responseLogger);
|
19
|
+
app.get('/', index);
|
20
|
+
app.get('/user/:id', userDetail, [validation({
|
21
|
+
params: {
|
22
|
+
id: {
|
23
|
+
type: 'number',
|
24
|
+
required: true
|
25
|
+
}
|
26
|
+
}
|
27
|
+
})]);
|
28
|
+
app.post('/uploadFiles', uploadFiles);
|
29
|
+
import { GroupRoute } from '../lib/GroupRoute.js';
|
30
|
+
const groupRouter = new GroupRoute('/route1');
|
31
|
+
app.registerGroupRoute(groupRouter);
|
32
|
+
console.log('Routes loaded.');
|
33
|
+
//
|
34
|
+
// app.use(loggerMiddleware); // 日志记录
|
35
|
+
// app.use(corsMiddleware); // CORS 控制
|
36
|
+
// app.use(bodyLimitMiddleware); // 请求体大小限制
|
37
|
+
// app.use(bodyParserMiddleware); // 请求体解析
|
38
|
+
// app.use(authMiddleware); // 身份认证
|
39
|
+
// app.use(validationMiddleware); // 参数校验
|
40
|
+
// app.use(staticMiddleware); // 静态资源服务
|
41
|
+
// app.use(router); // 路由分发
|
42
|
+
// app.use(errorLoggerMiddleware); // 错误日志记录
|
43
|
+
// app.use(errorHandlerMiddleware); // 错误统一处理
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { readFileSync } from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
import { fileURLToPath } from "url";
|
4
|
+
// 获取当前模块文件路径
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
6
|
+
const __dirname = path.dirname(__filename);
|
7
|
+
export default {
|
8
|
+
cert: readFileSync(path.join(__dirname, '../../keys/cert.pem')),
|
9
|
+
key: readFileSync(path.join(__dirname, '../../keys/key.pem'))
|
10
|
+
};
|
11
|
+
console.log('certs loaded');
|
@@ -0,0 +1,45 @@
|
|
1
|
+
"use strict";
|
2
|
+
// const forge = import('node-forge');
|
3
|
+
// const fs = import('fs');
|
4
|
+
//
|
5
|
+
// // 生成密钥对
|
6
|
+
// const keys = forge.pki.rsa.generateKeyPair(2048);
|
7
|
+
//
|
8
|
+
// // 创建证书
|
9
|
+
// const cert = forge.pki.createCertificate();
|
10
|
+
// cert.publicKey = keys.publicKey;
|
11
|
+
// cert.serialNumber = '01';
|
12
|
+
// cert.validity.notBefore = new Date();
|
13
|
+
// cert.validity.notAfter = new Date();
|
14
|
+
// cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
|
15
|
+
//
|
16
|
+
// // 设置主题和颁发者信息
|
17
|
+
// const attrs = [{
|
18
|
+
// name: 'commonName',
|
19
|
+
// value: 'localhost'
|
20
|
+
// }, {
|
21
|
+
// name: 'countryName',
|
22
|
+
// value: 'CN'
|
23
|
+
// }, {
|
24
|
+
// name: 'organizationName',
|
25
|
+
// value: 'Qianrenni'
|
26
|
+
// }, {
|
27
|
+
// name: 'organizationalUnitName',
|
28
|
+
// value: 'Development'
|
29
|
+
// }];
|
30
|
+
//
|
31
|
+
// cert.setSubject(attrs);
|
32
|
+
// cert.setIssuer(attrs); // 通常自签名证书 issuer == subject
|
33
|
+
// cert.sign(keys.privateKey, forge.md.sha256.create()); // ✅ 正确的方法
|
34
|
+
//
|
35
|
+
// // 转换为 PEM 格式
|
36
|
+
// const pem = {
|
37
|
+
// key: forge.pki.privateKeyToPem(keys.privateKey),
|
38
|
+
// cert: forge.pki.certificateToPem(cert)
|
39
|
+
// };
|
40
|
+
//
|
41
|
+
// // 写入文件
|
42
|
+
// fs.writeFileSync('key.pem', pem.key);
|
43
|
+
// fs.writeFileSync('cert.pem', pem.cert);
|
44
|
+
//
|
45
|
+
// console.log('✅ 私钥和证书已生成:key.pem 和 cert.pem');
|
package/package.json
CHANGED
package/src/lib/app.ts
CHANGED
@@ -7,7 +7,7 @@ import * as http from "http";
|
|
7
7
|
import {Handler, Middleware} from "../config/type";
|
8
8
|
|
9
9
|
|
10
|
-
class WebApp {
|
10
|
+
class WebApp {
|
11
11
|
private routes: { method: string; url: string; chain: Middleware[] }[]= [];
|
12
12
|
private beforeRequestMiddleWares: Middleware[]= [];
|
13
13
|
private afterRequestMiddleWares: Middleware[]= [];
|
@@ -228,5 +228,4 @@ class WebApp {
|
|
228
228
|
}
|
229
229
|
}
|
230
230
|
|
231
|
-
|
232
|
-
export default app;
|
231
|
+
export default new WebApp();
|