diff --git a/lib/server/start.js b/lib/server/start.js index 9b54d0f..383e44b 100644 --- a/lib/server/start.js +++ b/lib/server/start.js @@ -17,6 +17,13 @@ const sticky_1 = require("@socket.io/sticky"); const redis_adapter_1 = require("@socket.io/redis-adapter"); const ioredis_1 = tslib_1.__importDefault(require("ioredis")); 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 SOCKET_NAMESPACE = '/sn'; 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)()); // socket const httpServer = (0, http_1.createServer)(koa.callback()); + const socketPath = connector.getSocketPath(); const socketOption = { - path: connector.getSocketPath(), + path: socketPath, cors: ['development', 'staging'].includes(process.env.NODE_ENV) ? { origin: '*', @@ -130,6 +138,16 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) { else { 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 ? 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)); @@ -238,7 +256,6 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) { else { url += `:${port}`; } - // Example: // import { io } from "socket.io-client"; // 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) => { ctx.response.body = endpoints; }); + // 注册静态资源 + koa.use((0, koa_mount_1.default)('/socket-admin', (0, koa_static_1.default)(socketAdminUI))); koa.use(router.routes()); koa.on('error', (err) => { console.error(err); throw err; }); 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) { appLoader.startWatchers(); diff --git a/lib/utils.d.ts b/lib/utils.d.ts index e332d51..2b94a7b 100644 --- a/lib/utils.d.ts +++ b/lib/utils.d.ts @@ -57,3 +57,10 @@ export declare function formatJsonByFile(data: T): string; * @returns */ export declare function deWeight(arr: Array | Set, type: any): Set; +/** + * @name 随机字符串 + * @export + * @param {number} length + * @returns {string} + */ +export declare function randomString(length: number): string; diff --git a/lib/utils.js b/lib/utils.js index 857ccdb..f919eb7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,6 +7,7 @@ exports.intersect = intersect; exports.union = union; exports.formatJsonByFile = formatJsonByFile; exports.deWeight = deWeight; +exports.randomString = randomString; /** * @name 从一组路径里查找到所有json文件 * @export @@ -96,3 +97,18 @@ function deWeight(arr, type) { } 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; +} diff --git a/package.json b/package.json index 5923f0c..f673be8 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "scripts/**/*", "template/**/*", "templateExample/**/*", - "templateFiles/**/*" + "templateFiles/**/*", + "ui/**/*" ], "author": "", "license": "", @@ -33,7 +34,9 @@ "@types/inquirer": "^9.0.3", "@types/koa": "^2.15.0", "@types/koa-logger": "^3.1.5", + "@types/koa-mount": "^4.0.5", "@types/koa-router": "^7.4.8", + "@types/koa-static": "^4.0.4", "@types/lodash": "^4.17.13", "@types/node": "^20.6.0", "@types/shelljs": "^0.8.8", @@ -46,6 +49,7 @@ }, "dependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@socket.io/admin-ui": "^0.5.1", "@socket.io/cluster-adapter": "^0.2.2", "@socket.io/redis-adapter": "^8.3.0", "@socket.io/sticky": "^1.0.4", @@ -56,6 +60,7 @@ "babel-loader": "^8.2.3", "babel-plugin-named-asset-import": "^0.3.8", "babel-preset-react-app": "^10.0.1", + "bcrypt": "^5.1.1", "bfj": "^7.0.2", "browser-assert": "^1.2.1", "browserify-sign": "4.2.2", @@ -64,7 +69,7 @@ "buffer": "^6.0.3", "camelcase": "^6.2.1", "case-sensitive-paths-webpack-plugin": "^2.4.0", - "chalk": "^4.1.0", + "chalk": "^4.1.2", "clean-webpack-plugin": "^4.0.0", "commander": "^6.0.0", "compression-webpack-plugin": "^10.0.0", @@ -98,7 +103,9 @@ "koa": "^2.13.4", "koa-body": "^5.0.0", "koa-logger": "^3.2.1", + "koa-mount": "^4.2.0", "koa-router": "^11.0.1", + "koa-static": "^5.0.0", "less": "^4.1.2", "less-loader": "^10.2.0", "loader-utils": "^3.2.0", diff --git a/src/server/start.ts b/src/server/start.ts index 2035016..41bae9a 100644 --- a/src/server/start.ts +++ b/src/server/start.ts @@ -19,6 +19,14 @@ import { createAdapter as createRedisAdapter } from "@socket.io/redis-adapter"; import Redis from "ioredis"; import { Server, ServerOptions } from "socket.io"; 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 SOCKET_NAMESPACE = '/sn'; @@ -65,8 +73,9 @@ export async function startup = { - path: connector.getSocketPath(), + path: socketPath, cors: ['development', 'staging'].includes(process.env.NODE_ENV!) ? { origin: '*', @@ -153,6 +162,18 @@ export async function startup( path, @@ -368,14 +389,30 @@ export async function startup { console.error(err); throw err; }); + 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) { diff --git a/src/utils.ts b/src/utils.ts index 91bbdbd..a18c51d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -96,3 +96,19 @@ export function deWeight(arr: Array | Set, type: any) { } 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; +} \ No newline at end of file