新增socketio-admin依赖,koa静态资源映射,启动成功后自动启动admin-ui

This commit is contained in:
Pan Qiancheng 2025-05-08 15:32:00 +08:00
parent 7900fce2da
commit d176a09d7c
6 changed files with 120 additions and 7 deletions

View File

@ -17,6 +17,13 @@ const sticky_1 = require("@socket.io/sticky");
const redis_adapter_1 = require("@socket.io/redis-adapter"); const redis_adapter_1 = require("@socket.io/redis-adapter");
const ioredis_1 = tslib_1.__importDefault(require("ioredis")); const ioredis_1 = tslib_1.__importDefault(require("ioredis"));
const socket_io_1 = require("socket.io"); const socket_io_1 = require("socket.io");
const admin_ui_1 = require("@socket.io/admin-ui");
const koa_static_1 = tslib_1.__importDefault(require("koa-static"));
const koa_mount_1 = tslib_1.__importDefault(require("koa-mount"));
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const utils_1 = require("../utils");
const bcryptjs_1 = tslib_1.__importDefault(require("bcryptjs"));
const socketAdminUI = (0, path_1.join)(__dirname, '../../ui/socket-admin');
const DATA_SUBSCRIBE_NAMESPACE = '/dsn'; const DATA_SUBSCRIBE_NAMESPACE = '/dsn';
const SOCKET_NAMESPACE = '/sn'; const SOCKET_NAMESPACE = '/sn';
const SERVER_SUBSCRIBER_NAMESPACE = process.env.OAK_SSUB_NAMESPACE || '/ssub'; const SERVER_SUBSCRIBER_NAMESPACE = process.env.OAK_SSUB_NAMESPACE || '/ssub';
@ -46,8 +53,9 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
koa.use((0, koa_logger_1.default)()); koa.use((0, koa_logger_1.default)());
// socket // socket
const httpServer = (0, http_1.createServer)(koa.callback()); const httpServer = (0, http_1.createServer)(koa.callback());
const socketPath = connector.getSocketPath();
const socketOption = { const socketOption = {
path: connector.getSocketPath(), path: socketPath,
cors: ['development', 'staging'].includes(process.env.NODE_ENV) cors: ['development', 'staging'].includes(process.env.NODE_ENV)
? { ? {
origin: '*', origin: '*',
@ -130,6 +138,16 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
else { else {
console.log('以单实例模式启动'); console.log('以单实例模式启动');
} }
// 密码使用随机字符串
const passwordForAdminUI = process.env.SOCKET_ADMIN_PASSWORD || (0, utils_1.randomString)(16);
(0, admin_ui_1.instrument)(io, {
auth: {
type: "basic", // 使用基本认证,生产建议关闭或换成自定义 auth
username: "admin",
password: bcryptjs_1.default.hashSync(passwordForAdminUI, 10), // 使用 bcrypt 加密密码
},
mode: "development", // 或 "production"
});
const appLoader = clusterInfo.usingCluster const appLoader = clusterInfo.usingCluster
? new oak_backend_base_1.ClusterAppLoader(path, io.of(DATA_SUBSCRIBE_NAMESPACE), io.of(SOCKET_NAMESPACE), io.of(SERVER_SUBSCRIBER_NAMESPACE), connector.getSocketPath()) ? new oak_backend_base_1.ClusterAppLoader(path, io.of(DATA_SUBSCRIBE_NAMESPACE), io.of(SOCKET_NAMESPACE), io.of(SERVER_SUBSCRIBER_NAMESPACE), connector.getSocketPath())
: new oak_backend_base_1.AppLoader(path, io.of(DATA_SUBSCRIBE_NAMESPACE), io.of(SOCKET_NAMESPACE), io.of(SERVER_SUBSCRIBER_NAMESPACE)); : new oak_backend_base_1.AppLoader(path, io.of(DATA_SUBSCRIBE_NAMESPACE), io.of(SOCKET_NAMESPACE), io.of(SERVER_SUBSCRIBER_NAMESPACE));
@ -238,7 +256,6 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
else { else {
url += `:${port}`; url += `:${port}`;
} }
// Example: // Example:
// import { io } from "socket.io-client"; // import { io } from "socket.io-client";
// const socket = io('https://example.com/order', { // const socket = io('https://example.com/order', {
@ -293,13 +310,26 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
router.get(connector.getEndpointRouter(), async (ctx) => { router.get(connector.getEndpointRouter(), async (ctx) => {
ctx.response.body = endpoints; ctx.response.body = endpoints;
}); });
// 注册静态资源
koa.use((0, koa_mount_1.default)('/socket-admin', (0, koa_static_1.default)(socketAdminUI)));
koa.use(router.routes()); koa.use(router.routes());
koa.on('error', (err) => { koa.on('error', (err) => {
console.error(err); console.error(err);
throw err; throw err;
}); });
httpServer.listen(serverConfiguration.port, () => { httpServer.listen(serverConfiguration.port, () => {
console.log(`server will listen on port ${serverConfiguration.port}`); const protocol = nginx?.ssl ? 'https' : 'http';
const host = hostname || 'localhost';
const port = nginx?.port || (clusterInfo.usingCluster ? (process.env.PM2_PORT || 8080) : serverConfiguration.port);
const baseUrl = `${protocol}://${host}:${port}`;
const adminUIUrl = `${baseUrl}/socket-admin`;
console.log(chalk_1.default.greenBright.bold('\n🚀 Server started successfully!\n'));
console.log(`🔗 ${chalk_1.default.cyan('Server URL')}: ${chalk_1.default.underline(baseUrl)}`);
// socketio地址
console.log(`🔗 ${chalk_1.default.cyan('Socket URL')}: ${chalk_1.default.underline(concat(url, socketPath))}`);
console.log(`🛠️ ${chalk_1.default.magenta('Socket Admin UI')}: ${chalk_1.default.underline(adminUIUrl)}`);
// 账号密码
console.log(`🔑 ${chalk_1.default.yellow('Socket Admin UI Password')}: ${chalk_1.default.red(passwordForAdminUI)}\n`);
}); });
if (!omitWatchers) { if (!omitWatchers) {
appLoader.startWatchers(); appLoader.startWatchers();

7
lib/utils.d.ts vendored
View File

@ -57,3 +57,10 @@ export declare function formatJsonByFile<T extends Object>(data: T): string;
* @returns * @returns
*/ */
export declare function deWeight(arr: Array<any> | Set<any>, type: any): Set<any>; export declare function deWeight(arr: Array<any> | Set<any>, type: any): Set<any>;
/**
* @name
* @export
* @param {number} length
* @returns {string}
*/
export declare function randomString(length: number): string;

View File

@ -7,6 +7,7 @@ exports.intersect = intersect;
exports.union = union; exports.union = union;
exports.formatJsonByFile = formatJsonByFile; exports.formatJsonByFile = formatJsonByFile;
exports.deWeight = deWeight; exports.deWeight = deWeight;
exports.randomString = randomString;
/** /**
* @name 从一组路径里查找到所有json文件 * @name 从一组路径里查找到所有json文件
* @export * @export
@ -96,3 +97,18 @@ function deWeight(arr, type) {
} }
return new Set([...map.values()]); return new Set([...map.values()]);
} }
/**
* @name 随机字符串
* @export
* @param {number} length
* @returns {string}
*/
function randomString(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * chars.length);
result += chars[randomIndex];
}
return result;
}

View File

@ -19,7 +19,8 @@
"scripts/**/*", "scripts/**/*",
"template/**/*", "template/**/*",
"templateExample/**/*", "templateExample/**/*",
"templateFiles/**/*" "templateFiles/**/*",
"ui/**/*"
], ],
"author": "", "author": "",
"license": "", "license": "",
@ -33,7 +34,9 @@
"@types/inquirer": "^9.0.3", "@types/inquirer": "^9.0.3",
"@types/koa": "^2.15.0", "@types/koa": "^2.15.0",
"@types/koa-logger": "^3.1.5", "@types/koa-logger": "^3.1.5",
"@types/koa-mount": "^4.0.5",
"@types/koa-router": "^7.4.8", "@types/koa-router": "^7.4.8",
"@types/koa-static": "^4.0.4",
"@types/lodash": "^4.17.13", "@types/lodash": "^4.17.13",
"@types/node": "^20.6.0", "@types/node": "^20.6.0",
"@types/shelljs": "^0.8.8", "@types/shelljs": "^0.8.8",
@ -46,6 +49,7 @@
}, },
"dependencies": { "dependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
"@socket.io/admin-ui": "^0.5.1",
"@socket.io/cluster-adapter": "^0.2.2", "@socket.io/cluster-adapter": "^0.2.2",
"@socket.io/redis-adapter": "^8.3.0", "@socket.io/redis-adapter": "^8.3.0",
"@socket.io/sticky": "^1.0.4", "@socket.io/sticky": "^1.0.4",
@ -56,6 +60,7 @@
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"babel-plugin-named-asset-import": "^0.3.8", "babel-plugin-named-asset-import": "^0.3.8",
"babel-preset-react-app": "^10.0.1", "babel-preset-react-app": "^10.0.1",
"bcrypt": "^5.1.1",
"bfj": "^7.0.2", "bfj": "^7.0.2",
"browser-assert": "^1.2.1", "browser-assert": "^1.2.1",
"browserify-sign": "4.2.2", "browserify-sign": "4.2.2",
@ -64,7 +69,7 @@
"buffer": "^6.0.3", "buffer": "^6.0.3",
"camelcase": "^6.2.1", "camelcase": "^6.2.1",
"case-sensitive-paths-webpack-plugin": "^2.4.0", "case-sensitive-paths-webpack-plugin": "^2.4.0",
"chalk": "^4.1.0", "chalk": "^4.1.2",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"commander": "^6.0.0", "commander": "^6.0.0",
"compression-webpack-plugin": "^10.0.0", "compression-webpack-plugin": "^10.0.0",
@ -98,7 +103,9 @@
"koa": "^2.13.4", "koa": "^2.13.4",
"koa-body": "^5.0.0", "koa-body": "^5.0.0",
"koa-logger": "^3.2.1", "koa-logger": "^3.2.1",
"koa-mount": "^4.2.0",
"koa-router": "^11.0.1", "koa-router": "^11.0.1",
"koa-static": "^5.0.0",
"less": "^4.1.2", "less": "^4.1.2",
"less-loader": "^10.2.0", "less-loader": "^10.2.0",
"loader-utils": "^3.2.0", "loader-utils": "^3.2.0",

View File

@ -19,6 +19,14 @@ import { createAdapter as createRedisAdapter } from "@socket.io/redis-adapter";
import Redis from "ioredis"; import Redis from "ioredis";
import { Server, ServerOptions } from "socket.io"; import { Server, ServerOptions } from "socket.io";
import { ServerConfiguration } from 'oak-domain/lib/types/Configuration'; import { ServerConfiguration } from 'oak-domain/lib/types/Configuration';
import { instrument } from "@socket.io/admin-ui";
import serve from 'koa-static';
import mount from 'koa-mount';
import chalk from 'chalk';
import { randomString } from '../utils';
import bcrypt from 'bcryptjs';
const socketAdminUI = join(__dirname, '../../ui/socket-admin');
const DATA_SUBSCRIBE_NAMESPACE = '/dsn'; const DATA_SUBSCRIBE_NAMESPACE = '/dsn';
const SOCKET_NAMESPACE = '/sn'; const SOCKET_NAMESPACE = '/sn';
@ -65,8 +73,9 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
koa.use(logger()); koa.use(logger());
// socket // socket
const httpServer = createServer(koa.callback()); const httpServer = createServer(koa.callback());
const socketPath = connector.getSocketPath();
const socketOption: Partial<ServerOptions> = { const socketOption: Partial<ServerOptions> = {
path: connector.getSocketPath(), path: socketPath,
cors: ['development', 'staging'].includes(process.env.NODE_ENV!) cors: ['development', 'staging'].includes(process.env.NODE_ENV!)
? { ? {
origin: '*', origin: '*',
@ -153,6 +162,18 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
console.log('以单实例模式启动'); console.log('以单实例模式启动');
} }
// 密码使用随机字符串
const passwordForAdminUI = process.env.SOCKET_ADMIN_PASSWORD || randomString(16);
instrument(io, {
auth: {
type: "basic", // 使用基本认证,生产建议关闭或换成自定义 auth
username: "admin",
password: bcrypt.hashSync(passwordForAdminUI, 10), // 使用 bcrypt 加密密码
},
mode: "development", // 或 "production"
});
const appLoader = clusterInfo.usingCluster const appLoader = clusterInfo.usingCluster
? new ClusterAppLoader<ED, Cxt>( ? new ClusterAppLoader<ED, Cxt>(
path, path,
@ -368,14 +389,30 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
ctx.response.body = endpoints; ctx.response.body = endpoints;
}); });
// 注册静态资源
koa.use(mount('/socket-admin', serve(socketAdminUI)));
koa.use(router.routes()); koa.use(router.routes());
koa.on('error', (err) => { koa.on('error', (err) => {
console.error(err); console.error(err);
throw err; throw err;
}); });
httpServer.listen(serverConfiguration.port, () => { httpServer.listen(serverConfiguration.port, () => {
console.log(`server will listen on port ${serverConfiguration.port}`); const protocol = nginx?.ssl ? 'https' : 'http';
const host = hostname || 'localhost';
const port = nginx?.port || (clusterInfo.usingCluster ? (process.env.PM2_PORT || 8080) : serverConfiguration.port);
const baseUrl = `${protocol}://${host}:${port}`;
const adminUIUrl = `${baseUrl}/socket-admin`;
console.log(chalk.greenBright.bold('\n🚀 Server started successfully!\n'));
console.log(`🔗 ${chalk.cyan('Server URL')}: ${chalk.underline(baseUrl)}`);
// socketio地址
console.log(`🔗 ${chalk.cyan('Socket URL')}: ${chalk.underline(concat(url, socketPath))}`);
console.log(`🛠️ ${chalk.magenta('Socket Admin UI')}: ${chalk.underline(adminUIUrl)}`);
// 账号密码
console.log(`🔑 ${chalk.yellow('Socket Admin UI Password')}: ${chalk.red(passwordForAdminUI)}\n`);
}); });
if (!omitWatchers) { if (!omitWatchers) {

View File

@ -96,3 +96,19 @@ export function deWeight(arr: Array<any> | Set<any>, type: any) {
} }
return new Set([...map.values()]); return new Set([...map.values()]);
} }
/**
* @name
* @export
* @param {number} length
* @returns {string}
*/
export function randomString(length: number): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * chars.length);
result += chars[randomIndex];
}
return result;
}