alemonjs 2.1.0-alpha.53 → 2.1.0-alpha.54
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/lib/adapter.js +111 -0
- package/lib/app/load.d.ts +14 -0
- package/lib/app/load.js +167 -0
- package/lib/cbp/actions.js +48 -0
- package/lib/cbp/api.js +48 -0
- package/lib/cbp/client.d.ts +10 -0
- package/lib/cbp/client.js +159 -0
- package/lib/cbp/config.js +43 -0
- package/lib/cbp/connect.d.ts +27 -0
- package/lib/cbp/connect.js +62 -0
- package/lib/cbp/connects/client.js +0 -1
- package/lib/cbp/hello.html.js +47 -0
- package/lib/cbp/platform.d.ts +18 -0
- package/lib/cbp/platform.js +170 -0
- package/lib/cbp/router.js +297 -0
- package/lib/cbp/routers/router.js +1 -1
- package/lib/cbp/server/main.js +0 -1
- package/lib/cbp/server.d.ts +8 -0
- package/lib/cbp/server.js +449 -0
- package/lib/cbp/testone.js +288 -0
- package/lib/core/code.d.ts +16 -0
- package/lib/core/code.js +17 -0
- package/lib/datastructure/SinglyLinkedList.d.ts +17 -0
- package/lib/datastructure/SinglyLinkedList.js +73 -0
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/typing/actions.d.ts +93 -0
- package/lib/typing/apis.d.ts +18 -0
- package/lib/typing/client/index.d.ts +67 -0
- package/lib/typing/cycle/index.d.ts +42 -0
- package/lib/typing/event/actions.d.ts +37 -0
- package/lib/typing/event/actions.js +72 -0
- package/lib/typing/event/base/expansion.d.ts +5 -0
- package/lib/typing/event/base/guild.d.ts +18 -0
- package/lib/typing/event/base/message.d.ts +28 -0
- package/lib/typing/event/base/platform.d.ts +16 -0
- package/lib/typing/event/base/user.d.ts +34 -0
- package/lib/typing/event/channal/index.d.ts +13 -0
- package/lib/typing/event/guild/index.d.ts +13 -0
- package/lib/typing/event/index.d.ts +94 -0
- package/lib/typing/event/interaction/index.d.ts +14 -0
- package/lib/typing/event/map.d.ts +36 -0
- package/lib/typing/event/member/index.d.ts +14 -0
- package/lib/typing/event/message/message.d.ts +23 -0
- package/lib/typing/event/message/private.message.d.ts +16 -0
- package/lib/typing/event/request/index.d.ts +13 -0
- package/lib/typing/logger/index.d.ts +49 -0
- package/lib/typing/message/ark.d.ts +46 -0
- package/lib/typing/message/button.d.ts +32 -0
- package/lib/typing/message/image.d.ts +23 -0
- package/lib/typing/message/index.d.ts +16 -0
- package/lib/typing/message/link.d.ts +12 -0
- package/lib/typing/message/markdown.d.ts +91 -0
- package/lib/typing/message/mention.d.ts +16 -0
- package/lib/typing/message/text.d.ts +12 -0
- package/lib/typing/package/index.d.ts +12 -0
- package/lib/typing/state/index.d.ts +5 -0
- package/lib/typing/store/res.d.ts +75 -0
- package/lib/typing/subscribe/index.d.ts +30 -0
- package/package.json +1 -1
- package/lib/app.d.ts +0 -18
- package/lib/app.js +0 -82
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const html = `
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<title>欢迎使用 AlemonJS!</title>
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
width: 35em;
|
|
9
|
+
margin: 0 auto;
|
|
10
|
+
font-family: Tahoma, Verdana, Arial, sans-serif;
|
|
11
|
+
background-color: #f8f8f8;
|
|
12
|
+
color: #333;
|
|
13
|
+
}
|
|
14
|
+
h1 {
|
|
15
|
+
color: #0099cc;
|
|
16
|
+
margin-top: 2em;
|
|
17
|
+
}
|
|
18
|
+
.footer {
|
|
19
|
+
margin-top: 3em;
|
|
20
|
+
font-size: 0.9em;
|
|
21
|
+
color: #888;
|
|
22
|
+
}
|
|
23
|
+
a {
|
|
24
|
+
color: #0099cc;
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<h1>AlemonJS 启动成功!</h1>
|
|
30
|
+
<p>
|
|
31
|
+
恭喜,您的服务已成功通过 <a href="https://alemonjs.com" target="_blank">AlemonJS 框架</a> 启动。
|
|
32
|
+
</p>
|
|
33
|
+
<p>
|
|
34
|
+
如果想访问主应用,请访问, <a href="/app" target="_blank">/app</a>(对应根目录index.html)
|
|
35
|
+
</p>
|
|
36
|
+
<p>
|
|
37
|
+
如果想访问其他应用,请访问 /apps/[package-name]</a>。(对应/packages/[package-name]/index.html)
|
|
38
|
+
</p>
|
|
39
|
+
<div class="footer">
|
|
40
|
+
<p>感谢选择 AlemonJS。</p>
|
|
41
|
+
</div>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
44
|
+
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
export { html };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EventsEnum } from '../types/event/map.js';
|
|
2
|
+
import { ActionReplyFunc, ApiReplyFunc } from './typings.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CBP 平台端
|
|
6
|
+
* @param url
|
|
7
|
+
* @param options
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
declare const cbpPlatform: (url: string, options?: {
|
|
11
|
+
open: () => void;
|
|
12
|
+
}) => {
|
|
13
|
+
send: (data: EventsEnum) => void;
|
|
14
|
+
onactions: (reply: ActionReplyFunc) => void;
|
|
15
|
+
onapis: (reply: ApiReplyFunc) => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { cbpPlatform };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { ResultCode } from '../core/variable.js';
|
|
3
|
+
import { deviceId, DEVICE_ID_HEADER, USER_AGENT_HEADER, reconnectInterval } from './config.js';
|
|
4
|
+
import * as flattedJSON from 'flatted';
|
|
5
|
+
import { useHeartbeat } from './connect.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CBP 平台端
|
|
9
|
+
* @param url
|
|
10
|
+
* @param options
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
const cbpPlatform = (url, options = {
|
|
14
|
+
open: () => { }
|
|
15
|
+
}) => {
|
|
16
|
+
if (global.chatbotPlatform) {
|
|
17
|
+
delete global.chatbotPlatform;
|
|
18
|
+
}
|
|
19
|
+
const { open = () => { } } = options;
|
|
20
|
+
const [heartbeatControl] = useHeartbeat({
|
|
21
|
+
ping: () => {
|
|
22
|
+
global?.chatbotPlatform?.ping?.();
|
|
23
|
+
},
|
|
24
|
+
isConnected: () => {
|
|
25
|
+
return global?.chatbotPlatform && global?.chatbotPlatform?.readyState === WebSocket.OPEN;
|
|
26
|
+
},
|
|
27
|
+
terminate: () => {
|
|
28
|
+
try {
|
|
29
|
+
global?.chatbotPlatform?.terminate?.();
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.debug({
|
|
33
|
+
code: ResultCode.Fail,
|
|
34
|
+
message: '强制断开连接失败',
|
|
35
|
+
data: error
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* 发送数据
|
|
42
|
+
* @param data
|
|
43
|
+
*/
|
|
44
|
+
const send = (data) => {
|
|
45
|
+
if (global.chatbotPlatform && global.chatbotPlatform.readyState === WebSocket.OPEN) {
|
|
46
|
+
data.DeviceId = deviceId; // 设置设备 ID
|
|
47
|
+
global.chatbotPlatform.send(flattedJSON.stringify(data));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const actionReplys = [];
|
|
51
|
+
const apiReplys = [];
|
|
52
|
+
/**
|
|
53
|
+
* 消费数据
|
|
54
|
+
* @param data
|
|
55
|
+
* @param payload
|
|
56
|
+
*/
|
|
57
|
+
const replyAction = (data, payload) => {
|
|
58
|
+
if (global.chatbotPlatform && global.chatbotPlatform.readyState === WebSocket.OPEN) {
|
|
59
|
+
// 透传消费。也就是对应的设备进行处理消费。
|
|
60
|
+
global.chatbotPlatform.send(flattedJSON.stringify({
|
|
61
|
+
action: data.action,
|
|
62
|
+
payload: payload,
|
|
63
|
+
actionId: data.actionId,
|
|
64
|
+
DeviceId: data.DeviceId
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const replyApi = (data, payload) => {
|
|
69
|
+
if (global.chatbotPlatform && global.chatbotPlatform.readyState === WebSocket.OPEN) {
|
|
70
|
+
// 透传消费。也就是对应的设备进行处理消费。
|
|
71
|
+
global.chatbotPlatform.send(flattedJSON.stringify({
|
|
72
|
+
action: data.action,
|
|
73
|
+
apiId: data.apiId,
|
|
74
|
+
DeviceId: data.DeviceId,
|
|
75
|
+
payload: payload
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* 接收行为
|
|
81
|
+
* @param reply
|
|
82
|
+
*/
|
|
83
|
+
const onactions = (reply) => {
|
|
84
|
+
actionReplys.push(reply);
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* 接收接口
|
|
88
|
+
*/
|
|
89
|
+
const onapis = (reply) => {
|
|
90
|
+
apiReplys.push(reply);
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* 启动 WebSocket 连接
|
|
94
|
+
*/
|
|
95
|
+
const start = () => {
|
|
96
|
+
global.chatbotPlatform = new WebSocket(url, {
|
|
97
|
+
headers: {
|
|
98
|
+
[USER_AGENT_HEADER]: 'platform',
|
|
99
|
+
[DEVICE_ID_HEADER]: deviceId
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
global.chatbotPlatform.on('open', () => {
|
|
103
|
+
open();
|
|
104
|
+
heartbeatControl.start(); // 启动心跳
|
|
105
|
+
});
|
|
106
|
+
global.chatbotPlatform.on('pong', () => {
|
|
107
|
+
heartbeatControl.pong(); // 更新 pong 时间
|
|
108
|
+
});
|
|
109
|
+
global.chatbotPlatform.on('message', message => {
|
|
110
|
+
try {
|
|
111
|
+
const data = flattedJSON.parse(message.toString());
|
|
112
|
+
logger.debug({
|
|
113
|
+
code: ResultCode.Ok,
|
|
114
|
+
message: '平台端接收消息',
|
|
115
|
+
data: data
|
|
116
|
+
});
|
|
117
|
+
if (data.apiId) {
|
|
118
|
+
for (const cb of apiReplys) {
|
|
119
|
+
void cb(data,
|
|
120
|
+
// 传入一个消费函数
|
|
121
|
+
val => replyApi(data, val));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else if (data.actionId) {
|
|
125
|
+
for (const cb of actionReplys) {
|
|
126
|
+
void cb(data,
|
|
127
|
+
// 传入一个消费函数
|
|
128
|
+
val => replyAction(data, val));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
logger.error({
|
|
134
|
+
code: ResultCode.Fail,
|
|
135
|
+
message: '解析消息失败',
|
|
136
|
+
data: error
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
global.chatbotPlatform.on('close', err => {
|
|
141
|
+
heartbeatControl.stop(); // 停止心跳
|
|
142
|
+
logger.warn({
|
|
143
|
+
code: ResultCode.Fail,
|
|
144
|
+
message: '平台端连接关闭,尝试重新连接...',
|
|
145
|
+
data: err
|
|
146
|
+
});
|
|
147
|
+
delete global.chatbotPlatform;
|
|
148
|
+
// 重新连接逻辑
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
start(); // 重新连接
|
|
151
|
+
}, reconnectInterval); // 6秒后重连
|
|
152
|
+
});
|
|
153
|
+
global.chatbotPlatform.on('error', err => {
|
|
154
|
+
logger.error({
|
|
155
|
+
code: ResultCode.Fail,
|
|
156
|
+
message: '平台端错误',
|
|
157
|
+
data: err
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
start();
|
|
162
|
+
const client = {
|
|
163
|
+
send,
|
|
164
|
+
onactions,
|
|
165
|
+
onapis
|
|
166
|
+
};
|
|
167
|
+
return client;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export { cbpPlatform };
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import KoaRouter from 'koa-router';
|
|
2
|
+
import fs, { existsSync } from 'fs';
|
|
3
|
+
import path, { join, dirname } from 'path';
|
|
4
|
+
import mime from 'mime-types';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { html } from './hello.html.js';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const mainDirMap = new Map();
|
|
10
|
+
const formatPath = (path) => {
|
|
11
|
+
if (!path || path === '/') {
|
|
12
|
+
return '/index.html';
|
|
13
|
+
}
|
|
14
|
+
const pates = path.split('/');
|
|
15
|
+
const lastPath = pates[pates.length - 1];
|
|
16
|
+
if (lastPath.includes('.')) {
|
|
17
|
+
return path;
|
|
18
|
+
}
|
|
19
|
+
path += '.html';
|
|
20
|
+
return path;
|
|
21
|
+
};
|
|
22
|
+
// 输入一个文件路径。
|
|
23
|
+
const getModuelFile = (dir) => {
|
|
24
|
+
const dirMap = {
|
|
25
|
+
'.js': `${dir}.js`,
|
|
26
|
+
'.jsx': `${dir}.jsx`,
|
|
27
|
+
'.mjs': `${dir}.mjs`,
|
|
28
|
+
'.cjs': `${dir}.cjs`,
|
|
29
|
+
'/index.js': `${dir}/index.js`,
|
|
30
|
+
'/index.jsx': `${dir}/index.jsx`,
|
|
31
|
+
'/index.mjs': `${dir}/index.mjs`,
|
|
32
|
+
'/index.cjs': `${dir}/index.cjs`,
|
|
33
|
+
'.ts': `${dir}.ts`,
|
|
34
|
+
'.tsx': `${dir}.tsx`,
|
|
35
|
+
'/index.ts': `${dir}/index.ts`,
|
|
36
|
+
'/index.tsx': `${dir}/index.tsx`
|
|
37
|
+
};
|
|
38
|
+
for (const key in dirMap) {
|
|
39
|
+
const filePath = dirMap[key];
|
|
40
|
+
if (existsSync(filePath) && fs.statSync(filePath)) {
|
|
41
|
+
return filePath;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return '';
|
|
45
|
+
};
|
|
46
|
+
const router = new KoaRouter({
|
|
47
|
+
prefix: '/'
|
|
48
|
+
});
|
|
49
|
+
router.get('/', ctx => {
|
|
50
|
+
ctx.status = 200;
|
|
51
|
+
ctx.set('Content-Type', 'text/html; charset=utf-8');
|
|
52
|
+
ctx.body = html;
|
|
53
|
+
});
|
|
54
|
+
// 响应服务在线
|
|
55
|
+
router.get('api/online', ctx => {
|
|
56
|
+
ctx.status = 200;
|
|
57
|
+
ctx.body = {
|
|
58
|
+
code: 200,
|
|
59
|
+
message: 'service online',
|
|
60
|
+
data: null
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
router.all('app/{*path}', async (ctx) => {
|
|
64
|
+
if (!process.env.input) {
|
|
65
|
+
ctx.status = 400;
|
|
66
|
+
ctx.body = {
|
|
67
|
+
code: 400,
|
|
68
|
+
message: '没有主要入口文件',
|
|
69
|
+
data: null
|
|
70
|
+
};
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const rootPath = process.cwd();
|
|
74
|
+
const apiPath = '/app/api';
|
|
75
|
+
if (ctx.path.startsWith(apiPath)) {
|
|
76
|
+
const mainPath = join(rootPath, process.env.input);
|
|
77
|
+
// 路径
|
|
78
|
+
if (!existsSync(mainPath)) {
|
|
79
|
+
ctx.status = 400;
|
|
80
|
+
ctx.body = {
|
|
81
|
+
code: 400,
|
|
82
|
+
message: '未找到主要入口文件',
|
|
83
|
+
data: 'existsSync input'
|
|
84
|
+
};
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const mainDir = dirname(mainPath);
|
|
88
|
+
try {
|
|
89
|
+
const dir = join(mainDir, 'route', ctx.path?.replace(apiPath, '/api') || '');
|
|
90
|
+
const dirs = dir.split('/');
|
|
91
|
+
const fileName = dirs[dirs.length - 1];
|
|
92
|
+
if (fileName.includes('.')) {
|
|
93
|
+
ctx.status = 404;
|
|
94
|
+
ctx.body = {
|
|
95
|
+
code: 404,
|
|
96
|
+
message: `API '${ctx.path}' 未找到。`,
|
|
97
|
+
data: 'existsSync route filename'
|
|
98
|
+
};
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const modulePath = getModuelFile(dir);
|
|
102
|
+
if (!modulePath) {
|
|
103
|
+
ctx.status = 404;
|
|
104
|
+
ctx.body = {
|
|
105
|
+
code: 404,
|
|
106
|
+
message: `API '${ctx.path}' 未找到。`,
|
|
107
|
+
data: 'existsSync modulePath'
|
|
108
|
+
};
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const apiModule = await import(`file://${modulePath}`);
|
|
112
|
+
if (!apiModule[ctx.method] || typeof apiModule[ctx.method] !== 'function') {
|
|
113
|
+
ctx.status = 405;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
await apiModule[ctx.method](ctx);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error(`Error handling API request ${ctx.path}`);
|
|
120
|
+
ctx.status = 500;
|
|
121
|
+
ctx.body = {
|
|
122
|
+
code: 500,
|
|
123
|
+
message: '处理 API 请求时发生错误。',
|
|
124
|
+
error: err.message
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// 如果不是 get请求。即不响应
|
|
130
|
+
if (ctx.method !== 'GET') {
|
|
131
|
+
ctx.status = 405;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let root = '';
|
|
135
|
+
const resourcePath = formatPath(ctx.params?.path);
|
|
136
|
+
try {
|
|
137
|
+
const pkg = require(path.join(rootPath, 'package.json')) ?? {};
|
|
138
|
+
root = pkg.alemonjs?.web?.root ?? '';
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
ctx.status = 500;
|
|
142
|
+
ctx.body = {
|
|
143
|
+
code: 500,
|
|
144
|
+
message: '加载 package.json 时发生错误。',
|
|
145
|
+
error: err.message
|
|
146
|
+
};
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const fullPath = root ? path.join(rootPath, root, resourcePath) : path.join(rootPath, resourcePath);
|
|
150
|
+
try {
|
|
151
|
+
// 返回文件
|
|
152
|
+
const file = await fs.promises.readFile(fullPath);
|
|
153
|
+
const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
|
|
154
|
+
ctx.set('Content-Type', mimeType); // 自动设置响应头
|
|
155
|
+
ctx.body = file;
|
|
156
|
+
ctx.status = 200;
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (err?.status === 404) {
|
|
160
|
+
ctx.status = 404;
|
|
161
|
+
ctx.body = {
|
|
162
|
+
code: 404,
|
|
163
|
+
message: '资源中未找到。',
|
|
164
|
+
data: null
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
ctx.status = 500;
|
|
169
|
+
ctx.body = {
|
|
170
|
+
code: 500,
|
|
171
|
+
message: '加载资源时发生服务器错误。',
|
|
172
|
+
error: err.message
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
router.all('app', ctx => {
|
|
178
|
+
ctx.redirect('/app/');
|
|
179
|
+
});
|
|
180
|
+
router.all('apps/:app/{*path}', async (ctx) => {
|
|
181
|
+
const appName = ctx.params.app;
|
|
182
|
+
const apiPath = `/apps/${appName}/api`;
|
|
183
|
+
if (ctx.path.startsWith(apiPath)) {
|
|
184
|
+
try {
|
|
185
|
+
if (!mainDirMap.has(appName)) {
|
|
186
|
+
const mainPath = require.resolve(appName);
|
|
187
|
+
// 不存在 main
|
|
188
|
+
if (!existsSync(mainPath)) {
|
|
189
|
+
ctx.status = 400;
|
|
190
|
+
ctx.body = {
|
|
191
|
+
code: 400,
|
|
192
|
+
message: '未找到主要入口文件',
|
|
193
|
+
data: null
|
|
194
|
+
};
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const mainDir = dirname(mainPath);
|
|
198
|
+
mainDirMap.set(appName, mainDir);
|
|
199
|
+
}
|
|
200
|
+
const dir = join(mainDirMap.get(appName), 'route', ctx.path?.replace(apiPath, '/api') || '');
|
|
201
|
+
const dirs = dir.split('/');
|
|
202
|
+
const fileName = dirs[dirs.length - 1];
|
|
203
|
+
if (fileName.includes('.')) {
|
|
204
|
+
ctx.status = 404;
|
|
205
|
+
ctx.body = {
|
|
206
|
+
code: 404,
|
|
207
|
+
message: `API 'route/${ctx.path}' 未找到。`,
|
|
208
|
+
data: null
|
|
209
|
+
};
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const modulePath = getModuelFile(dir);
|
|
213
|
+
if (!modulePath) {
|
|
214
|
+
ctx.status = 404;
|
|
215
|
+
ctx.body = {
|
|
216
|
+
code: 404,
|
|
217
|
+
message: `API 'route/${ctx.path}' 未找到。`,
|
|
218
|
+
data: null
|
|
219
|
+
};
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const apiModule = await import(`file://${modulePath}`);
|
|
223
|
+
if (!apiModule[ctx.method] || typeof apiModule[ctx.method] !== 'function') {
|
|
224
|
+
ctx.status = 405;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
await apiModule[ctx.method](ctx);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
logger.warn(`Error request ${ctx.path}:`, err?.message || '');
|
|
231
|
+
ctx.status = 500;
|
|
232
|
+
ctx.body = {
|
|
233
|
+
code: 500,
|
|
234
|
+
message: '处理 API 请求时发生错误。',
|
|
235
|
+
error: err.message
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// 如果不是 get请求。即不响应
|
|
241
|
+
if (ctx.method !== 'GET') {
|
|
242
|
+
ctx.status = 405;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// 不是 packages,而是 node_modules。需要是模块化
|
|
246
|
+
const rootPath = path.join(process.cwd(), 'node_modules', appName);
|
|
247
|
+
const resourcePath = formatPath(ctx.params?.path);
|
|
248
|
+
let root = '';
|
|
249
|
+
try {
|
|
250
|
+
const pkg = require(`${appName}/package`) ?? {};
|
|
251
|
+
root = pkg?.alemonjs?.web?.root ?? '';
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
ctx.status = 500;
|
|
255
|
+
ctx.body = {
|
|
256
|
+
code: 500,
|
|
257
|
+
message: '加载 package.json 时发生错误。',
|
|
258
|
+
error: err.message
|
|
259
|
+
};
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const fullPath = root ? path.join(rootPath, root, resourcePath) : path.join(rootPath, resourcePath);
|
|
263
|
+
try {
|
|
264
|
+
// 返回文件
|
|
265
|
+
const file = await fs.promises.readFile(fullPath);
|
|
266
|
+
const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
|
|
267
|
+
ctx.set('Content-Type', mimeType); // 自动设置响应头
|
|
268
|
+
ctx.body = file;
|
|
269
|
+
ctx.status = 200;
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
if (err?.status === 404) {
|
|
273
|
+
ctx.status = 404;
|
|
274
|
+
ctx.body = {
|
|
275
|
+
code: 404,
|
|
276
|
+
message: `资源 '${resourcePath}' 在子应用 '${appName}' 中未找到。`,
|
|
277
|
+
data: null
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
logger.warn(`Error request ${ctx.path}:`, err?.message || '');
|
|
282
|
+
ctx.status = 500;
|
|
283
|
+
ctx.body = {
|
|
284
|
+
code: 500,
|
|
285
|
+
message: `加载子应用 '${appName}' 资源时发生服务器错误。`,
|
|
286
|
+
error: err.message
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
router.all('apps/:name', ctx => {
|
|
292
|
+
if (ctx.path === `/apps/${ctx.params.name}`) {
|
|
293
|
+
ctx.redirect(`/apps/${ctx.params.name}/`);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
export { router as default };
|
|
@@ -80,7 +80,7 @@ router.all('app/{*path}', async (ctx) => {
|
|
|
80
80
|
await runMiddlewares(middlewares, ctx, handler);
|
|
81
81
|
}
|
|
82
82
|
catch (err) {
|
|
83
|
-
|
|
83
|
+
logger.error(`Error handling API request ${ctx.path}`);
|
|
84
84
|
ctx.status = 500;
|
|
85
85
|
ctx.body = {
|
|
86
86
|
code: 500,
|
package/lib/cbp/server/main.js
CHANGED