mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
refactor:优化WebUI后端代码格式(无新功能添加)
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
"@log4js-node/log4js-api": "^1.0.2",
|
"@log4js-node/log4js-api": "^1.0.2",
|
||||||
"@napneko/nap-proto-core": "^0.0.4",
|
"@napneko/nap-proto-core": "^0.0.4",
|
||||||
|
"@protobuf-ts/runtime": "^2.9.4",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
@@ -52,9 +53,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"piscina": "^4.7.0",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"silk-wasm": "^3.6.1",
|
"silk-wasm": "^3.6.1",
|
||||||
"ws": "^8.18.0",
|
"ws": "^8.18.0"
|
||||||
"piscina": "^4.7.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @file WebUI服务入口文件
|
||||||
|
*/
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { ALLRouter } from './src/router';
|
|
||||||
import { LogWrapper } from '@/common/log';
|
import { LogWrapper } from '@/common/log';
|
||||||
import { NapCatPathWrapper } from '@/common/path';
|
import { NapCatPathWrapper } from '@/common/path';
|
||||||
import { WebUiConfigWrapper } from './src/helper/config';
|
|
||||||
import { RequestUtil } from '@/common/request';
|
import { RequestUtil } from '@/common/request';
|
||||||
import { isIP } from "node:net";
|
|
||||||
|
|
||||||
|
import { WebUiConfigWrapper } from '@webapi/helper/config';
|
||||||
|
import { ALLRouter } from '@webapi/router';
|
||||||
|
import { cors } from '@webapi/middleware/cors';
|
||||||
|
import { createUrl } from '@webapi/utils/url';
|
||||||
|
import { sendSuccess } from '@webapi/utils/response';
|
||||||
|
|
||||||
|
// 实例化Express
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,49 +35,51 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
|
|||||||
log('[NapCat] [WebUi] Current WebUi is not run.');
|
log('[NapCat] [WebUi] Current WebUi is not run.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------注册中间件------------
|
||||||
|
// 使用express的json中间件
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
// 初始服务
|
|
||||||
|
// CORS中间件
|
||||||
|
// TODO:
|
||||||
|
app.use(cors);
|
||||||
|
// ------------中间件结束------------
|
||||||
|
|
||||||
|
// ------------挂载路由------------
|
||||||
|
// 挂载静态路由(前端),路径为 [/前缀]/webui
|
||||||
|
app.use(config.prefix + '/webui', express.static(pathWrapper.staticPath));
|
||||||
|
// 挂载API接口
|
||||||
|
app.use(config.prefix + '/api', ALLRouter);
|
||||||
|
|
||||||
|
// 初始服务(先放个首页)
|
||||||
// WebUI只在config.prefix所示路径上提供服务,可配合Nginx挂载到子目录中
|
// WebUI只在config.prefix所示路径上提供服务,可配合Nginx挂载到子目录中
|
||||||
app.all(config.prefix + '/', (_req, res) => {
|
app.all(config.prefix + '/', (_req, res) => {
|
||||||
res.json({
|
sendSuccess(res, null, 'NapCat WebAPI is now running!');
|
||||||
msg: 'NapCat WebAPI is now running!',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
// 配置静态文件服务,提供./static目录下的文件服务,访问路径为/webui
|
// ------------路由挂载结束------------
|
||||||
app.use(config.prefix + '/webui', express.static(pathWrapper.staticPath));
|
|
||||||
//挂载API接口
|
// ------------启动服务------------
|
||||||
// 添加CORS支持
|
|
||||||
// TODO:
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
res.header('Access-Control-Allow-Origin', '*');
|
|
||||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
||||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
app.use(config.prefix + '/api', ALLRouter);
|
|
||||||
app.listen(config.port, config.host, async () => {
|
app.listen(config.port, config.host, async () => {
|
||||||
const normalizeHost = (host: string) => {
|
// 启动后打印出相关地址
|
||||||
if (host === '0.0.0.0') return '127.0.0.1';
|
|
||||||
if (isIP(host) === 6) return `[${host}]`;
|
const port = config.port.toString(),
|
||||||
return host;
|
searchParams = { token: config.token },
|
||||||
};
|
path = `${config.prefix}/webui`;
|
||||||
const createUrl = (host: string, path: string, token: string) => {
|
|
||||||
const url = new URL(`http://${normalizeHost(host)}`);
|
// 打印日志(地址、token)
|
||||||
url.port = config.port.toString();
|
|
||||||
url.pathname = `${config.prefix}${path}`;
|
|
||||||
url.searchParams.set('token', token);
|
|
||||||
return url.toString();
|
|
||||||
};
|
|
||||||
log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`);
|
log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`);
|
||||||
log(`[NapCat] [WebUi] Login Token is ${config.token}`);
|
log(`[NapCat] [WebUi] Login Token is ${config.token}`);
|
||||||
log(`[NapCat] [WebUi] WebUi User Panel Url: ${createUrl(config.host, '/webui', config.token)}`);
|
log(`[NapCat] [WebUi] WebUi User Panel Url: ${createUrl(config.host, port, path, searchParams)}`);
|
||||||
log(`[NapCat] [WebUi] WebUi Local Panel Url: ${createUrl('127.0.0.1', '/webui', config.token)}`);
|
log(`[NapCat] [WebUi] WebUi Local Panel Url: ${createUrl('127.0.0.1', port, path, searchParams)}`);
|
||||||
|
|
||||||
|
// 获取公网地址
|
||||||
try {
|
try {
|
||||||
const publishUrl = 'https://ip.011102.xyz/';
|
const publishUrl = 'https://ip.011102.xyz/';
|
||||||
const data = await RequestUtil.HttpGetJson<{ IP: { IP: string } }>(publishUrl, 'GET', {}, {}, true, true);
|
const data = await RequestUtil.HttpGetJson<{ IP: { IP: string } }>(publishUrl, 'GET', {}, {}, true, true);
|
||||||
log(`[NapCat] [WebUi] WebUi Publish Panel Url: ${createUrl(data.IP.IP, '/webui', config.token)}`);
|
log(`[NapCat] [WebUi] WebUi Publish Panel Url: ${createUrl(data.IP.IP, port, path, searchParams)}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.logError(`[NapCat] [WebUi] Get Publish Panel Url Error: ${err}`);
|
logger.logError(`[NapCat] [WebUi] Get Publish Panel Url Error: ${err}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// ------------Over!------------
|
||||||
}
|
}
|
||||||
|
@@ -1,69 +1,64 @@
|
|||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { AuthHelper } from '../helper/SignToken';
|
|
||||||
import { WebUiDataRuntime } from '../helper/Data';
|
|
||||||
import { WebUiConfig } from '@/webui';
|
import { WebUiConfig } from '@/webui';
|
||||||
|
|
||||||
const isEmpty = (data: any) => data === undefined || data === null || data === '';
|
import { AuthHelper } from '@webapi/helper/SignToken';
|
||||||
|
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
||||||
|
import { sendSuccess, sendError } from '@webapi/utils/response';
|
||||||
|
import { isEmpty } from '@webapi/utils/check';
|
||||||
|
|
||||||
|
// 登录
|
||||||
export const LoginHandler: RequestHandler = async (req, res) => {
|
export const LoginHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 获取WebUI配置
|
||||||
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
||||||
|
// 获取请求体中的token
|
||||||
const { token } = req.body;
|
const { token } = req.body;
|
||||||
|
// 如果token为空,返回错误信息
|
||||||
if (isEmpty(token)) {
|
if (isEmpty(token)) {
|
||||||
res.json({
|
return sendError(res, 'token is empty');
|
||||||
code: -1,
|
|
||||||
message: 'token is empty',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!await WebUiDataRuntime.checkLoginRate(WebUiConfigData.loginRate)) {
|
// 检查登录频率
|
||||||
res.json({
|
if (!(await WebUiDataRuntime.checkLoginRate(WebUiConfigData.loginRate))) {
|
||||||
code: -1,
|
return sendError(res, 'login rate limit');
|
||||||
message: 'login rate limit',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
//验证config.token是否等于token
|
//验证config.token是否等于token
|
||||||
if (WebUiConfigData.token !== token) {
|
if (WebUiConfigData.token !== token) {
|
||||||
res.json({
|
return sendError(res, 'token is invalid');
|
||||||
code: -1,
|
|
||||||
message: 'token is invalid',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const signCredential = Buffer.from(JSON.stringify(await AuthHelper.signCredential(WebUiConfigData.token))).toString('base64');
|
// 签发凭证
|
||||||
res.json({
|
const signCredential = Buffer.from(JSON.stringify(await AuthHelper.signCredential(WebUiConfigData.token))).toString(
|
||||||
code: 0,
|
'base64'
|
||||||
message: 'success',
|
);
|
||||||
data: {
|
// 返回成功信息
|
||||||
'Credential': signCredential,
|
return sendSuccess(res, {
|
||||||
},
|
Credential: signCredential,
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
export const LogoutHandler: RequestHandler = (req, res) => {
|
|
||||||
// 这玩意无状态销毁个灯 得想想办法
|
// 退出登录
|
||||||
res.json({
|
export const LogoutHandler: RequestHandler = (_, res) => {
|
||||||
code: 0,
|
// TODO: 这玩意无状态销毁个灯 得想想办法
|
||||||
message: 'success',
|
return sendSuccess(res, null);
|
||||||
});
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 检查登录状态
|
||||||
export const checkHandler: RequestHandler = async (req, res) => {
|
export const checkHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 获取WebUI配置
|
||||||
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
||||||
|
// 获取请求头中的Authorization
|
||||||
const authorization = req.headers.authorization;
|
const authorization = req.headers.authorization;
|
||||||
|
// 检查凭证
|
||||||
try {
|
try {
|
||||||
|
// 从Authorization中获取凭证
|
||||||
const CredentialBase64: string = authorization?.split(' ')[1] as string;
|
const CredentialBase64: string = authorization?.split(' ')[1] as string;
|
||||||
|
// 解析凭证
|
||||||
const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString());
|
const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString());
|
||||||
|
// 验证凭证是否在一小时内有效
|
||||||
await AuthHelper.validateCredentialWithinOneHour(WebUiConfigData.token, Credential);
|
await AuthHelper.validateCredentialWithinOneHour(WebUiConfigData.token, Credential);
|
||||||
res.json({
|
// 返回成功信息
|
||||||
code: 0,
|
return sendSuccess(res, null);
|
||||||
message: 'success',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.json({
|
// 返回错误信息
|
||||||
code: -1,
|
return sendError(res, 'Authorization Faild');
|
||||||
message: 'failed',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
|
|
||||||
export const LogFileListHandler: RequestHandler = async (req, res) => {
|
import { sendSuccess } from '@webapi/utils/response';
|
||||||
res.send({
|
|
||||||
code: 0,
|
// TODO: Implement LogFileListHandler
|
||||||
data: {
|
export const LogFileListHandler: RequestHandler = async (_, res) => {
|
||||||
uin: 0,
|
const fakeData = {
|
||||||
nick: 'NapCat',
|
uin: 0,
|
||||||
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=0&s=640',
|
nick: 'NapCat',
|
||||||
status: 'online',
|
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=0&s=640',
|
||||||
boottime: Date.now()
|
status: 'online',
|
||||||
}
|
boottime: Date.now(),
|
||||||
});
|
};
|
||||||
|
sendSuccess(res, fakeData);
|
||||||
};
|
};
|
||||||
|
@@ -1,79 +1,58 @@
|
|||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { WebUiDataRuntime } from '../helper/Data';
|
|
||||||
import { existsSync, readFileSync } from 'node:fs';
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
import { OneBotConfig } from '@/onebot/config/config';
|
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { webUiPathWrapper } from '@/webui';
|
|
||||||
|
|
||||||
const isEmpty = (data: any) => data === undefined || data === null || data === '';
|
import { OneBotConfig } from '@/onebot/config/config';
|
||||||
export const OB11GetConfigHandler: RequestHandler = async (req, res) => {
|
|
||||||
|
import { webUiPathWrapper } from '@/webui';
|
||||||
|
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
||||||
|
import { sendError, sendSuccess } from '@webapi/utils/response';
|
||||||
|
import { isEmpty } from '@webapi/utils/check';
|
||||||
|
|
||||||
|
// 获取OneBot11配置
|
||||||
|
export const OB11GetConfigHandler: RequestHandler = async (_, res) => {
|
||||||
|
// 获取QQ登录状态
|
||||||
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
||||||
|
// 如果未登录,返回错误
|
||||||
if (!isLogin) {
|
if (!isLogin) {
|
||||||
res.send({
|
return sendError(res, 'Not Login');
|
||||||
code: -1,
|
|
||||||
message: 'Not Login',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 获取登录的QQ号
|
||||||
const uin = await WebUiDataRuntime.getQQLoginUin();
|
const uin = await WebUiDataRuntime.getQQLoginUin();
|
||||||
|
// 读取配置文件
|
||||||
const configFilePath = resolve(webUiPathWrapper.configPath, `./onebot11_${uin}.json`);
|
const configFilePath = resolve(webUiPathWrapper.configPath, `./onebot11_${uin}.json`);
|
||||||
//console.log(configFilePath);
|
// 尝试解析配置文件
|
||||||
let data: OneBotConfig;
|
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(
|
// 读取配置文件
|
||||||
|
const data = JSON.parse(
|
||||||
existsSync(configFilePath)
|
existsSync(configFilePath)
|
||||||
? readFileSync(configFilePath).toString()
|
? readFileSync(configFilePath).toString()
|
||||||
: readFileSync(resolve(webUiPathWrapper.configPath, './onebot11.json')).toString()
|
: readFileSync(resolve(webUiPathWrapper.configPath, './onebot11.json')).toString()
|
||||||
);
|
) as OneBotConfig;
|
||||||
|
// 返回配置文件
|
||||||
|
return sendSuccess(res, data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
data = {} as OneBotConfig;
|
return sendError(res, 'Config Get Error');
|
||||||
res.send({
|
|
||||||
code: -1,
|
|
||||||
message: 'Config Get Error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
res.send({
|
|
||||||
code: 0,
|
|
||||||
message: 'success',
|
|
||||||
data: data,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 写入OneBot11配置
|
||||||
export const OB11SetConfigHandler: RequestHandler = async (req, res) => {
|
export const OB11SetConfigHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 获取QQ登录状态
|
||||||
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
||||||
|
// 如果未登录,返回错误
|
||||||
if (!isLogin) {
|
if (!isLogin) {
|
||||||
res.send({
|
return sendError(res, 'Not Login');
|
||||||
code: -1,
|
|
||||||
message: 'Not Login',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 如果配置为空,返回错误
|
||||||
if (isEmpty(req.body.config)) {
|
if (isEmpty(req.body.config)) {
|
||||||
res.send({
|
return sendError(res, 'config is empty');
|
||||||
code: -1,
|
|
||||||
message: 'config is empty',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
let SetResult;
|
// 写入配置
|
||||||
try {
|
try {
|
||||||
await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config));
|
await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config));
|
||||||
SetResult = true;
|
return sendSuccess(res, null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
SetResult = false;
|
return sendError(res, 'Config Set Error');
|
||||||
}
|
}
|
||||||
if (SetResult) {
|
|
||||||
res.send({
|
|
||||||
code: 0,
|
|
||||||
message: 'success',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.send({
|
|
||||||
code: -1,
|
|
||||||
message: 'Config Set Error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
@@ -1,78 +1,64 @@
|
|||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { WebUiDataRuntime } from '../helper/Data';
|
|
||||||
|
|
||||||
const isEmpty = (data: any) => data === undefined || data === null || data === '';
|
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
||||||
|
import { isEmpty } from '@webapi/utils/check';
|
||||||
|
import { sendError, sendSuccess } from '@webapi/utils/response';
|
||||||
|
|
||||||
|
// 获取QQ登录二维码
|
||||||
export const QQGetQRcodeHandler: RequestHandler = async (req, res) => {
|
export const QQGetQRcodeHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 判断是否已经登录
|
||||||
if (await WebUiDataRuntime.getQQLoginStatus()) {
|
if (await WebUiDataRuntime.getQQLoginStatus()) {
|
||||||
res.send({
|
// 已经登录
|
||||||
code: -1,
|
return sendError(res, 'QQ Is Logined');
|
||||||
message: 'QQ Is Logined',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 获取二维码
|
||||||
const qrcodeUrl = await WebUiDataRuntime.getQQLoginQrcodeURL();
|
const qrcodeUrl = await WebUiDataRuntime.getQQLoginQrcodeURL();
|
||||||
|
// 判断二维码是否为空
|
||||||
if (isEmpty(qrcodeUrl)) {
|
if (isEmpty(qrcodeUrl)) {
|
||||||
res.send({
|
return sendError(res, 'QRCode Get Error');
|
||||||
code: -1,
|
|
||||||
message: 'QRCode Get Error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
res.send({
|
// 返回二维码URL
|
||||||
code: 0,
|
const data = {
|
||||||
message: 'success',
|
qrcode: qrcodeUrl,
|
||||||
data: {
|
};
|
||||||
qrcode: qrcodeUrl,
|
return sendSuccess(res, data);
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取QQ登录状态
|
||||||
export const QQCheckLoginStatusHandler: RequestHandler = async (req, res) => {
|
export const QQCheckLoginStatusHandler: RequestHandler = async (req, res) => {
|
||||||
res.send({
|
const data = {
|
||||||
code: 0,
|
isLogin: await WebUiDataRuntime.getQQLoginStatus(),
|
||||||
message: 'success',
|
qrcodeurl: await WebUiDataRuntime.getQQLoginQrcodeURL(),
|
||||||
data: {
|
};
|
||||||
isLogin: await WebUiDataRuntime.getQQLoginStatus(),
|
return sendSuccess(res, data);
|
||||||
qrcodeurl: await WebUiDataRuntime.getQQLoginQrcodeURL()
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 快速登录
|
||||||
export const QQSetQuickLoginHandler: RequestHandler = async (req, res) => {
|
export const QQSetQuickLoginHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 获取QQ号
|
||||||
const { uin } = req.body;
|
const { uin } = req.body;
|
||||||
|
// 判断是否已经登录
|
||||||
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
res.send({
|
return sendError(res, 'QQ Is Logined');
|
||||||
code: -1,
|
|
||||||
message: 'QQ Is Logined',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 判断QQ号是否为空
|
||||||
if (isEmpty(uin)) {
|
if (isEmpty(uin)) {
|
||||||
res.send({
|
return sendError(res, 'uin is empty');
|
||||||
code: -1,
|
|
||||||
message: 'uin is empty',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取快速登录状态
|
||||||
const { result, message } = await WebUiDataRuntime.requestQuickLogin(uin);
|
const { result, message } = await WebUiDataRuntime.requestQuickLogin(uin);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
res.send({
|
return sendError(res, message);
|
||||||
code: -1,
|
|
||||||
message: message,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
//本来应该验证 但是http不宜这么搞 建议前端验证
|
//本来应该验证 但是http不宜这么搞 建议前端验证
|
||||||
//isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
//isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
||||||
res.send({
|
return sendSuccess(res, null);
|
||||||
code: 0,
|
|
||||||
message: 'success',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
export const QQGetQuickLoginListHandler: RequestHandler = async (req, res) => {
|
|
||||||
|
// 获取快速登录列表
|
||||||
|
export const QQGetQuickLoginListHandler: RequestHandler = async (_, res) => {
|
||||||
const quickLoginList = await WebUiDataRuntime.getQQQuickLoginList();
|
const quickLoginList = await WebUiDataRuntime.getQQQuickLoginList();
|
||||||
res.send({
|
return sendSuccess(res, quickLoginList);
|
||||||
code: 0,
|
|
||||||
data: quickLoginList,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
13
src/webui/src/const/status.ts
Normal file
13
src/webui/src/const/status.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export enum HttpStatusCode {
|
||||||
|
OK = 200,
|
||||||
|
BadRequest = 400,
|
||||||
|
Unauthorized = 401,
|
||||||
|
Forbidden = 403,
|
||||||
|
NotFound = 404,
|
||||||
|
InternalServerError = 500,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ResponseCode {
|
||||||
|
Success = 0,
|
||||||
|
Error = -1,
|
||||||
|
}
|
@@ -1,18 +1,5 @@
|
|||||||
import { OneBotConfig } from '@/onebot/config/config';
|
import { OneBotConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
interface LoginRuntimeType {
|
|
||||||
LoginCurrentTime: number;
|
|
||||||
LoginCurrentRate: number;
|
|
||||||
QQLoginStatus: boolean;
|
|
||||||
QQQRCodeURL: string;
|
|
||||||
QQLoginUin: string;
|
|
||||||
NapCatHelper: {
|
|
||||||
onQuickLoginRequested: (uin: string) => Promise<{ result: boolean; message: string }>;
|
|
||||||
onOB11ConfigChanged: (ob11: OneBotConfig) => Promise<void>;
|
|
||||||
QQLoginList: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoginRuntime: LoginRuntimeType = {
|
const LoginRuntime: LoginRuntimeType = {
|
||||||
LoginCurrentTime: Date.now(),
|
LoginCurrentTime: Date.now(),
|
||||||
LoginCurrentRate: 0,
|
LoginCurrentRate: 0,
|
||||||
|
@@ -1,15 +1,5 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
interface WebUiCredentialInnerJson {
|
|
||||||
CreatedTime: number;
|
|
||||||
TokenEncoded: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WebUiCredentialJson {
|
|
||||||
Data: WebUiCredentialInnerJson;
|
|
||||||
Hmac: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AuthHelper {
|
export class AuthHelper {
|
||||||
private static readonly secretKey = Math.random().toString(36).slice(2);
|
private static readonly secretKey = Math.random().toString(36).slice(2);
|
||||||
|
|
||||||
@@ -24,9 +14,7 @@ export class AuthHelper {
|
|||||||
TokenEncoded: token,
|
TokenEncoded: token,
|
||||||
};
|
};
|
||||||
const jsonString = JSON.stringify(innerJson);
|
const jsonString = JSON.stringify(innerJson);
|
||||||
const hmac = crypto.createHmac('sha256', AuthHelper.secretKey)
|
const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex');
|
||||||
.update(jsonString, 'utf8')
|
|
||||||
.digest('hex');
|
|
||||||
return { Data: innerJson, Hmac: hmac };
|
return { Data: innerJson, Hmac: hmac };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +26,8 @@ export class AuthHelper {
|
|||||||
public static async checkCredential(credentialJson: WebUiCredentialJson): Promise<boolean> {
|
public static async checkCredential(credentialJson: WebUiCredentialJson): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const jsonString = JSON.stringify(credentialJson.Data);
|
const jsonString = JSON.stringify(credentialJson.Data);
|
||||||
const calculatedHmac = crypto.createHmac('sha256', AuthHelper.secretKey)
|
const calculatedHmac = crypto
|
||||||
|
.createHmac('sha256', AuthHelper.secretKey)
|
||||||
.update(jsonString, 'utf8')
|
.update(jsonString, 'utf8')
|
||||||
.digest('hex');
|
.digest('hex');
|
||||||
return calculatedHmac === credentialJson.Hmac;
|
return calculatedHmac === credentialJson.Hmac;
|
||||||
@@ -53,7 +42,10 @@ export class AuthHelper {
|
|||||||
* @param credentialJson 已签名的凭证JSON对象。
|
* @param credentialJson 已签名的凭证JSON对象。
|
||||||
* @returns 布尔值,表示凭证是否有效且token匹配。
|
* @returns 布尔值,表示凭证是否有效且token匹配。
|
||||||
*/
|
*/
|
||||||
public static async validateCredentialWithinOneHour(token: string, credentialJson: WebUiCredentialJson): Promise<boolean> {
|
public static async validateCredentialWithinOneHour(
|
||||||
|
token: string,
|
||||||
|
credentialJson: WebUiCredentialJson
|
||||||
|
): Promise<boolean> {
|
||||||
const isValid = await AuthHelper.checkCredential(credentialJson);
|
const isValid = await AuthHelper.checkCredential(credentialJson);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -3,7 +3,6 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|||||||
import * as net from 'node:net';
|
import * as net from 'node:net';
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
|
||||||
// 限制尝试端口的次数,避免死循环
|
// 限制尝试端口的次数,避免死循环
|
||||||
const MAX_PORT_TRY = 100;
|
const MAX_PORT_TRY = 100;
|
||||||
|
|
||||||
@@ -64,14 +63,6 @@ async function tryUsePort(port: number, host: string, tryCount: number = 0): Pro
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebUiConfigType {
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
prefix: string;
|
|
||||||
token: string;
|
|
||||||
loginRate: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
|
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
|
||||||
export class WebUiConfigWrapper {
|
export class WebUiConfigWrapper {
|
||||||
WebUiConfigData: WebUiConfigType | undefined = undefined;
|
WebUiConfigData: WebUiConfigType | undefined = undefined;
|
||||||
@@ -114,14 +105,18 @@ export class WebUiConfigWrapper {
|
|||||||
// 不希望回写的配置放后面
|
// 不希望回写的配置放后面
|
||||||
|
|
||||||
// 查询主机地址是否可用
|
// 查询主机地址是否可用
|
||||||
const [host_err, host] = await tryUseHost(parsedConfig.host).then(data => [null, data]).catch(err => [err, null]);
|
const [host_err, host] = await tryUseHost(parsedConfig.host)
|
||||||
|
.then((data) => [null, data])
|
||||||
|
.catch((err) => [err, null]);
|
||||||
if (host_err) {
|
if (host_err) {
|
||||||
console.log('host不可用', host_err);
|
console.log('host不可用', host_err);
|
||||||
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
||||||
} else {
|
} else {
|
||||||
parsedConfig.host = host;
|
parsedConfig.host = host;
|
||||||
// 修正端口占用情况
|
// 修正端口占用情况
|
||||||
const [port_err, port] = await tryUsePort(parsedConfig.port, parsedConfig.host).then(data => [null, data]).catch(err => [err, null]);
|
const [port_err, port] = await tryUsePort(parsedConfig.port, parsedConfig.host)
|
||||||
|
.then((data) => [null, data])
|
||||||
|
.catch((err) => [err, null]);
|
||||||
if (port_err) {
|
if (port_err) {
|
||||||
console.log('port不可用', port_err);
|
console.log('port不可用', port_err);
|
||||||
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
||||||
@@ -137,4 +132,3 @@ export class WebUiConfigWrapper {
|
|||||||
return defaultconfig; // 理论上这行代码到不了,到了只能返回默认配置了
|
return defaultconfig; // 理论上这行代码到不了,到了只能返回默认配置了
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
46
src/webui/src/middleware/auth.ts
Normal file
46
src/webui/src/middleware/auth.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
|
||||||
|
import { WebUiConfig } from '@/webui';
|
||||||
|
|
||||||
|
import { AuthHelper } from '@webapi/helper/SignToken';
|
||||||
|
import { sendError } from '@webapi/utils/response';
|
||||||
|
|
||||||
|
// 鉴权中间件
|
||||||
|
export async function auth(req: Request, res: Response, next: NextFunction) {
|
||||||
|
// 判断当前url是否为/login 如果是跳过鉴权
|
||||||
|
if (req.url == '/auth/login') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否有Authorization头
|
||||||
|
if (req.headers?.authorization) {
|
||||||
|
// 切割参数以获取token
|
||||||
|
const authorization = req.headers.authorization.split(' ');
|
||||||
|
// 当Bearer后面没有参数时
|
||||||
|
if (authorization.length < 2) {
|
||||||
|
return sendError(res, 'Unauthorized');
|
||||||
|
}
|
||||||
|
// 获取token
|
||||||
|
const token = authorization[1];
|
||||||
|
// 解析token
|
||||||
|
let Credential: WebUiCredentialJson;
|
||||||
|
try {
|
||||||
|
Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8'));
|
||||||
|
} catch (e) {
|
||||||
|
return sendError(res, 'Unauthorized');
|
||||||
|
}
|
||||||
|
// 获取配置
|
||||||
|
const config = await WebUiConfig.GetWebUIConfig();
|
||||||
|
// 验证凭证在1小时内有效且token与原始token相同
|
||||||
|
const credentialJson = await AuthHelper.validateCredentialWithinOneHour(config.token, Credential);
|
||||||
|
if (credentialJson) {
|
||||||
|
// 通过验证
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
// 验证失败
|
||||||
|
return sendError(res, 'Unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有Authorization头
|
||||||
|
return sendError(res, 'Unauthorized');
|
||||||
|
}
|
9
src/webui/src/middleware/cors.ts
Normal file
9
src/webui/src/middleware/cors.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { RequestHandler } from 'express';
|
||||||
|
|
||||||
|
// CORS 中间件,跨域用
|
||||||
|
export const cors: RequestHandler = (_, res, next) => {
|
||||||
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||||
|
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
||||||
|
next();
|
||||||
|
};
|
@@ -1,7 +1,11 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { OB11GetConfigHandler, OB11SetConfigHandler } from '../api/OB11Config';
|
|
||||||
|
import { OB11GetConfigHandler, OB11SetConfigHandler } from '@webapi/api/OB11Config';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
// router:读取配置
|
||||||
router.post('/GetConfig', OB11GetConfigHandler);
|
router.post('/GetConfig', OB11GetConfigHandler);
|
||||||
|
// router:写入配置
|
||||||
router.post('/SetConfig', OB11SetConfigHandler);
|
router.post('/SetConfig', OB11SetConfigHandler);
|
||||||
|
|
||||||
export { router as OB11ConfigRouter };
|
export { router as OB11ConfigRouter };
|
||||||
|
@@ -1,14 +1,20 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
QQCheckLoginStatusHandler,
|
QQCheckLoginStatusHandler,
|
||||||
QQGetQRcodeHandler,
|
QQGetQRcodeHandler,
|
||||||
QQGetQuickLoginListHandler,
|
QQGetQuickLoginListHandler,
|
||||||
QQSetQuickLoginHandler,
|
QQSetQuickLoginHandler,
|
||||||
} from '../api/QQLogin';
|
} from '@webapi/api/QQLogin';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
// router:获取快速登录列表
|
||||||
router.all('/GetQuickLoginList', QQGetQuickLoginListHandler);
|
router.all('/GetQuickLoginList', QQGetQuickLoginListHandler);
|
||||||
|
// router:检查QQ登录状态
|
||||||
router.post('/CheckLoginStatus', QQCheckLoginStatusHandler);
|
router.post('/CheckLoginStatus', QQCheckLoginStatusHandler);
|
||||||
|
// router:获取QQ登录二维码
|
||||||
router.post('/GetQQLoginQrcode', QQGetQRcodeHandler);
|
router.post('/GetQQLoginQrcode', QQGetQRcodeHandler);
|
||||||
|
// router:设置QQ快速登录
|
||||||
router.post('/SetQuickLogin', QQSetQuickLoginHandler);
|
router.post('/SetQuickLogin', QQSetQuickLoginHandler);
|
||||||
|
|
||||||
export { router as QQLoginRouter };
|
export { router as QQLoginRouter };
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { checkHandler, LoginHandler, LogoutHandler } from '../api/Auth';
|
|
||||||
|
import { checkHandler, LoginHandler, LogoutHandler } from '@webapi/api/Auth';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
// router:登录
|
||||||
router.post('/login', LoginHandler);
|
router.post('/login', LoginHandler);
|
||||||
|
// router:检查登录状态
|
||||||
router.post('/check', checkHandler);
|
router.post('/check', checkHandler);
|
||||||
|
// router:注销
|
||||||
router.post('/logout', LogoutHandler);
|
router.post('/logout', LogoutHandler);
|
||||||
|
|
||||||
export { router as AuthRouter };
|
export { router as AuthRouter };
|
||||||
|
@@ -1,67 +1,30 @@
|
|||||||
import { NextFunction, Request, Response, Router } from 'express';
|
/**
|
||||||
import { AuthHelper } from '../../src/helper/SignToken';
|
* @file 所有路由的入口文件
|
||||||
import { QQLoginRouter } from './QQLogin';
|
*/
|
||||||
import { AuthRouter } from './auth';
|
|
||||||
import { OB11ConfigRouter } from './OB11Config';
|
import { Router } from 'express';
|
||||||
import { WebUiConfig } from '@/webui';
|
|
||||||
|
import { OB11ConfigRouter } from '@webapi/router/OB11Config';
|
||||||
|
import { auth } from '@webapi/middleware/auth';
|
||||||
|
import { sendSuccess } from '@webapi/utils/response';
|
||||||
|
|
||||||
|
import { QQLoginRouter } from '@webapi/router/QQLogin';
|
||||||
|
import { AuthRouter } from '@webapi/router/auth';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
export async function AuthApi(req: Request, res: Response, next: NextFunction) {
|
// 鉴权中间件
|
||||||
//判断当前url是否为/login 如果是跳过鉴权
|
router.use(auth);
|
||||||
if (req.url == '/auth/login') {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (req.headers?.authorization) {
|
|
||||||
const authorization = req.headers.authorization.split(' ');
|
|
||||||
if (authorization.length < 2) {
|
|
||||||
res.json({
|
|
||||||
code: -1,
|
|
||||||
msg: 'Unauthorized',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const token = authorization[1];
|
|
||||||
let Credential: any;
|
|
||||||
try {
|
|
||||||
Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8'));
|
|
||||||
} catch (e) {
|
|
||||||
res.json({
|
|
||||||
code: -1,
|
|
||||||
msg: 'Unauthorized',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const config = await WebUiConfig.GetWebUIConfig();
|
|
||||||
const credentialJson = await AuthHelper.validateCredentialWithinOneHour(config.token, Credential);
|
|
||||||
if (credentialJson) {
|
|
||||||
//通过验证
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
code: -1,
|
|
||||||
msg: 'Unauthorized',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
// router:测试用
|
||||||
code: -1,
|
router.all('/test', (_, res) => {
|
||||||
msg: 'Server Error',
|
return sendSuccess(res);
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
router.use(AuthApi);
|
|
||||||
router.all('/test', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
code: 0,
|
|
||||||
msg: 'ok',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
// router:WebUI登录相关路由
|
||||||
router.use('/auth', AuthRouter);
|
router.use('/auth', AuthRouter);
|
||||||
|
// router:QQ登录相关路由
|
||||||
router.use('/QQLogin', QQLoginRouter);
|
router.use('/QQLogin', QQLoginRouter);
|
||||||
|
// router:OB11配置相关路由
|
||||||
router.use('/OB11Config', OB11ConfigRouter);
|
router.use('/OB11Config', OB11ConfigRouter);
|
||||||
|
|
||||||
export { router as ALLRouter };
|
export { router as ALLRouter };
|
||||||
|
7
src/webui/src/types/config.d.ts
vendored
Normal file
7
src/webui/src/types/config.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
interface WebUiConfigType {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
prefix: string;
|
||||||
|
token: string;
|
||||||
|
loginRate: number;
|
||||||
|
}
|
12
src/webui/src/types/data.d.ts
vendored
Normal file
12
src/webui/src/types/data.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
interface LoginRuntimeType {
|
||||||
|
LoginCurrentTime: number;
|
||||||
|
LoginCurrentRate: number;
|
||||||
|
QQLoginStatus: boolean;
|
||||||
|
QQQRCodeURL: string;
|
||||||
|
QQLoginUin: string;
|
||||||
|
NapCatHelper: {
|
||||||
|
onQuickLoginRequested: (uin: string) => Promise<{ result: boolean; message: string }>;
|
||||||
|
onOB11ConfigChanged: (ob11: OneBotConfig) => Promise<void>;
|
||||||
|
QQLoginList: string[];
|
||||||
|
};
|
||||||
|
}
|
7
src/webui/src/types/server.d.ts
vendored
Normal file
7
src/webui/src/types/server.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
interface APIResponse<T> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Protocol = 'http' | 'https' | 'ws' | 'wss';
|
9
src/webui/src/types/sign_token.d.ts
vendored
Normal file
9
src/webui/src/types/sign_token.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
interface WebUiCredentialInnerJson {
|
||||||
|
CreatedTime: number;
|
||||||
|
TokenEncoded: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebUiCredentialJson {
|
||||||
|
Data: WebUiCredentialInnerJson;
|
||||||
|
Hmac: string;
|
||||||
|
}
|
1
src/webui/src/utils/check.ts
Normal file
1
src/webui/src/utils/check.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const isEmpty = <T>(data: T) => data === undefined || data === null || data === '';
|
26
src/webui/src/utils/response.ts
Normal file
26
src/webui/src/utils/response.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { Response } from 'express';
|
||||||
|
|
||||||
|
import { ResponseCode, HttpStatusCode } from '@webapi/const/status';
|
||||||
|
|
||||||
|
export const sendResponse = <T>(res: Response, data?: T, code: ResponseCode = 0, message = 'success') => {
|
||||||
|
res.status(HttpStatusCode.OK).json({
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendError = (res: Response, message = 'error') => {
|
||||||
|
res.status(HttpStatusCode.OK).json({
|
||||||
|
code: ResponseCode.Error,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendSuccess = <T>(res: Response, data?: T, message = 'success') => {
|
||||||
|
res.status(HttpStatusCode.OK).json({
|
||||||
|
code: ResponseCode.Success,
|
||||||
|
data,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
};
|
47
src/webui/src/utils/url.ts
Normal file
47
src/webui/src/utils/url.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* @file URL工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { isIP } from 'node:net';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 host(主机地址) 转换为标准格式
|
||||||
|
* @param host 主机地址
|
||||||
|
* @returns 标准格式的IP地址
|
||||||
|
* @example normalizeHost('10.0.3.2') => '10.0.3.2'
|
||||||
|
* @example normalizeHost('0.0.0.0') => '127.0.0.1'
|
||||||
|
* @example normalizeHost('2001:4860:4801:51::27') => '[2001:4860:4801:51::27]'
|
||||||
|
*/
|
||||||
|
export const normalizeHost = (host: string) => {
|
||||||
|
if (host === '0.0.0.0') return '127.0.0.1';
|
||||||
|
if (isIP(host) === 6) return `[${host}]`;
|
||||||
|
return host;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建URL
|
||||||
|
* @param host 主机地址
|
||||||
|
* @param port 端口
|
||||||
|
* @param path URL路径
|
||||||
|
* @param search URL参数
|
||||||
|
* @returns 完整URL
|
||||||
|
* @example createUrl('127.0.0.1', '8080', '/api', { token: '123456' }) => 'http://127.0.0.1:8080/api?token=123456'
|
||||||
|
* @example createUrl('baidu.com', '80', void 0, void 0, 'https') => 'https://baidu.com:80/'
|
||||||
|
*/
|
||||||
|
export const createUrl = (
|
||||||
|
host: string,
|
||||||
|
port: string,
|
||||||
|
path = '/',
|
||||||
|
search?: Record<string, any>,
|
||||||
|
protocol: Protocol = 'http'
|
||||||
|
) => {
|
||||||
|
const url = new URL(`${protocol}://${normalizeHost(host)}`);
|
||||||
|
url.port = port;
|
||||||
|
url.pathname = path;
|
||||||
|
if (search) {
|
||||||
|
for (const key in search) {
|
||||||
|
url.searchParams.set(key, search[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
};
|
@@ -1,34 +1,37 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2020",
|
"ES2020",
|
||||||
"DOM",
|
"DOM",
|
||||||
"DOM.Iterable"
|
"DOM.Iterable"
|
||||||
],
|
],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@*": [
|
"@/*": [
|
||||||
"./src*"
|
"./src/*"
|
||||||
]
|
],
|
||||||
}
|
"@webapi/*": [
|
||||||
},
|
"./src/webui/src/*"
|
||||||
"include": [
|
],
|
||||||
"src/**/*.ts"
|
}
|
||||||
]
|
},
|
||||||
}
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
@@ -50,59 +50,62 @@ const ShellBaseConfigPlugin: PluginOption[] = [
|
|||||||
nodeResolve(),
|
nodeResolve(),
|
||||||
];
|
];
|
||||||
|
|
||||||
const ShellBaseConfig = () => defineConfig({
|
const ShellBaseConfig = () =>
|
||||||
resolve: {
|
defineConfig({
|
||||||
conditions: ['node', 'default'],
|
resolve: {
|
||||||
alias: {
|
conditions: ['node', 'default'],
|
||||||
'@/core': resolve(__dirname, './src/core'),
|
alias: {
|
||||||
'@': resolve(__dirname, './src'),
|
'@/core': resolve(__dirname, './src/core'),
|
||||||
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
'@': resolve(__dirname, './src'),
|
||||||
},
|
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
||||||
},
|
'@webapi': resolve(__dirname, './src/webui/src'),
|
||||||
build: {
|
|
||||||
sourcemap: false,
|
|
||||||
target: 'esnext',
|
|
||||||
minify: false,
|
|
||||||
lib: {
|
|
||||||
entry: {
|
|
||||||
'napcat': 'src/shell/napcat.ts',
|
|
||||||
'audio-worker': 'src/common/audio-worker.ts',
|
|
||||||
},
|
},
|
||||||
formats: ['es'],
|
|
||||||
fileName: (_, entryName) => `${entryName}.mjs`,
|
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
build: {
|
||||||
external: [...nodeModules, ...external],
|
sourcemap: false,
|
||||||
|
target: 'esnext',
|
||||||
|
minify: false,
|
||||||
|
lib: {
|
||||||
|
entry: {
|
||||||
|
napcat: 'src/shell/napcat.ts',
|
||||||
|
'audio-worker': 'src/common/audio-worker.ts',
|
||||||
|
},
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: (_, entryName) => `${entryName}.mjs`,
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...nodeModules, ...external],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const FrameworkBaseConfig = () => defineConfig({
|
const FrameworkBaseConfig = () =>
|
||||||
resolve: {
|
defineConfig({
|
||||||
conditions: ['node', 'default'],
|
resolve: {
|
||||||
alias: {
|
conditions: ['node', 'default'],
|
||||||
'@/core': resolve(__dirname, './src/core'),
|
alias: {
|
||||||
'@': resolve(__dirname, './src'),
|
'@/core': resolve(__dirname, './src/core'),
|
||||||
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
'@': resolve(__dirname, './src'),
|
||||||
},
|
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
||||||
},
|
|
||||||
build: {
|
|
||||||
sourcemap: false,
|
|
||||||
target: 'esnext',
|
|
||||||
minify: false,
|
|
||||||
lib: {
|
|
||||||
entry: {
|
|
||||||
'napcat': 'src/framework/napcat.ts',
|
|
||||||
'audio-worker': 'src/common/audio-worker.ts',
|
|
||||||
},
|
},
|
||||||
formats: ['es'],
|
|
||||||
fileName: (_, entryName) => `${entryName}.mjs`,
|
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
build: {
|
||||||
external: [...nodeModules, ...external],
|
sourcemap: false,
|
||||||
|
target: 'esnext',
|
||||||
|
minify: false,
|
||||||
|
lib: {
|
||||||
|
entry: {
|
||||||
|
napcat: 'src/framework/napcat.ts',
|
||||||
|
'audio-worker': 'src/common/audio-worker.ts',
|
||||||
|
},
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: (_, entryName) => `${entryName}.mjs`,
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...nodeModules, ...external],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
export default defineConfig(({ mode }): UserConfig => {
|
export default defineConfig(({ mode }): UserConfig => {
|
||||||
if (mode === 'shell') {
|
if (mode === 'shell') {
|
||||||
|
Reference in New Issue
Block a user