Merge branch 'release'

This commit is contained in:
Xu Chang 2025-05-08 16:49:05 +08:00
commit 5da80f06d1
16 changed files with 235 additions and 25 deletions

View File

@ -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,20 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
else {
console.log('以单实例模式启动');
}
const { ui } = serverConfiguration;
const isPasswordSet = !!(process.env.SOCKET_ADMIN_PASSWORD || ui?.password);
// 密码使用随机字符串
const passwordForAdminUI = process.env.SOCKET_ADMIN_PASSWORD || ui?.password || (0, utils_1.randomString)(16);
if (!ui?.disable) {
(0, admin_ui_1.instrument)(io, {
auth: {
type: "basic", // 使用基本认证,生产建议关闭或换成自定义 auth
username: ui?.username || "admin",
password: bcryptjs_1.default.hashSync(passwordForAdminUI, 10), // 使用 bcrypt 加密密码
},
mode: process.env.NODE_ENV === 'production' ? "production" : "development", // 根据环境设置模式
});
}
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 +260,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 +314,37 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
router.get(connector.getEndpointRouter(), async (ctx) => {
ctx.response.body = endpoints;
});
// 注册静态资源
if (!ui?.disable) {
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))}\n`);
if (!ui?.disable) {
console.log(`🛠️ ${chalk_1.default.magenta('Socket Admin UI')}: ${chalk_1.default.underline(adminUIUrl)}`);
// 账号密码
// 是否设置密码
if (isPasswordSet) {
console.log(`🔑 ${chalk_1.default.yellow('Socket Admin UI Password has been set, check the config file\n')}`);
}
else {
console.log(chalk_1.default.yellow('Socket Admin UI Password Generated: ') + chalk_1.default.red(passwordForAdminUI));
console.log(chalk_1.default.yellow('Please set the password when running prod env.\n'));
}
}
});
if (!omitWatchers) {
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
*/
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.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;
}

View File

@ -1,6 +1,6 @@
{
"name": "@xuchangzju/oak-cli",
"version": "4.0.21",
"version": "4.0.22",
"description": "client for oak framework",
"main": "lib/index.js",
"scripts": {
@ -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,16 +103,18 @@
"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",
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.5.3",
"node-watch": "^0.7.4",
"oak-backend-base": "^4.1.18",
"oak-domain": "^5.1.22",
"oak-frontend-base": "^5.3.30",
"oak-backend-base": "^4.1.19",
"oak-domain": "^5.1.24",
"oak-frontend-base": "^5.3.31",
"parse-asn1": "5.1.6",
"postcss": "^8.4.4",
"postcss-flexbugs-fixes": "^5.0.2",

View File

@ -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';
@ -42,7 +50,7 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
omitWatchers?: boolean,
omitTimers?: boolean,
routine?: (context: AsyncContext<ED>) => Promise<void>,
): Promise<(() => Promise<void> )| undefined> {
): Promise<(() => Promise<void>) | undefined> {
const serverConfiguration: ServerConfiguration = require(join(
path,
'lib',
@ -65,14 +73,15 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
koa.use(logger());
// socket
const httpServer = createServer(koa.callback());
const socketPath = connector.getSocketPath();
const socketOption: Partial<ServerOptions> = {
path: connector.getSocketPath(),
path: socketPath,
cors: ['development', 'staging'].includes(process.env.NODE_ENV!)
? {
origin: '*',
allowedHeaders: corsHeaders.concat(connector.getCorsHeader()),
}
: serverConfiguration.cors
? {
origin: '*',
allowedHeaders: corsHeaders.concat(connector.getCorsHeader()),
}
: serverConfiguration.cors
? {
origin: serverConfiguration.cors.origin, //socket.io配置cors origin是支持数组和字符串
allowedHeaders: [
@ -102,15 +111,15 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
}
const isCluster = Array.isArray(redisConfig);
// 创建 Redis 客户端
const pubClient = isCluster ?
const pubClient = isCluster ?
new Redis.Cluster(redisConfig.map((config) => ({
...config,
lazyConnect: true,
})))
: new Redis({
...redisConfig,
lazyConnect: true,
});
: new Redis({
...redisConfig,
lazyConnect: true,
});
const subClient = pubClient.duplicate();
pubClient.on('connect', () => {
console.log('PUB已成功连接到Redis服务器');
@ -153,10 +162,26 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
console.log('以单实例模式启动');
}
const { ui } = serverConfiguration;
const isPasswordSet = !!(process.env.SOCKET_ADMIN_PASSWORD || ui?.password)
// 密码使用随机字符串
const passwordForAdminUI = process.env.SOCKET_ADMIN_PASSWORD || ui?.password || randomString(16);
if (!ui?.disable) {
instrument(io, {
auth: {
type: "basic", // 使用基本认证,生产建议关闭或换成自定义 auth
username: ui?.username || "admin",
password: bcrypt.hashSync(passwordForAdminUI, 10), // 使用 bcrypt 加密密码
},
mode: process.env.NODE_ENV === 'production' ? "production" : "development", // 根据环境设置模式
});
}
const appLoader = clusterInfo.usingCluster
? new ClusterAppLoader<ED, Cxt>(
path,
io.of(DATA_SUBSCRIBE_NAMESPACE ),
io.of(DATA_SUBSCRIBE_NAMESPACE),
io.of(SOCKET_NAMESPACE),
io.of(SERVER_SUBSCRIBER_NAMESPACE),
connector.getSocketPath()
@ -351,13 +376,13 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
(k) => res.setHeader(k, headers2[k])
)
}
if(statusCode){
if (statusCode) {
res.statusCode = statusCode
}
return;
} catch (err) {
console.log(err);
ctx.response.status = 500;
return;
}
@ -368,14 +393,40 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
ctx.response.body = endpoints;
});
// 注册静态资源
if (!ui?.disable) {
koa.use(mount('/socket-admin', serve(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.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))}\n`);
if (!ui?.disable) {
console.log(`🛠️ ${chalk.magenta('Socket Admin UI')}: ${chalk.underline(adminUIUrl)}`);
// 账号密码
// 是否设置密码
if (isPasswordSet) {
console.log(`🔑 ${chalk.yellow('Socket Admin UI Password has been set, check the config file\n')}`)
} else {
console.log(chalk.yellow('Socket Admin UI Password Generated: ') + chalk.red(passwordForAdminUI));
console.log(chalk.yellow('Please set the password when running prod env.\n'));
}
}
});
if (!omitWatchers) {

View File

@ -96,3 +96,19 @@ export function deWeight(arr: Array<any> | Set<any>, 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;
}

View File

@ -0,0 +1 @@
.chart[data-v-0ad5cc14],.chart[data-v-68c0c5d5]{max-width:160px;margin:20px}.selector[data-v-2c330798]{max-width:200px}.row-pointer[data-v-1d29c60a] tbody>tr:hover{cursor:pointer}.select-room[data-v-5631eb89]{max-width:200px}.row-pointer[data-v-5631eb89] tbody>tr:hover{cursor:pointer}.key-column[data-v-3c0dcfcd]{width:30%}.link[data-v-3c0dcfcd]{color:inherit}.key-column[data-v-18284f59]{width:30%}.row-pointer[data-v-57b53591] tbody>tr:hover,.row-pointer[data-v-29992f63] tbody>tr:hover{cursor:pointer}.key-column[data-v-8d2424e4]{width:30%}.row-pointer[data-v-38772079] tbody>tr:hover,.row-pointer[data-v-c9425064] tbody>tr:hover{cursor:pointer}.link[data-v-2c2337d4]{color:inherit}

File diff suppressed because one or more lines are too long

BIN
ui/socket-admin/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,4 @@
<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet">
<circle cx="128" cy="128" r="114" stroke="#FFF" stroke-width="20" fill="none" />
<path d="M97.637 121.69c27.327-22.326 54.058-45.426 81.98-67.097-14.646 22.505-29.708 44.711-44.354 67.215-12.562.06-25.123.06-37.626-.119zM120.737 134.132c12.621 0 25.183 0 37.745.179-27.505 22.206-54.117 45.484-82.099 67.096 14.646-22.505 29.708-44.77 44.354-67.275z" fill="#FFF"/>
</svg>

After

Width:  |  Height:  |  Size: 473 B

View File

@ -0,0 +1,4 @@
<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet">
<circle cx="128" cy="128" r="114" stroke="#010101" stroke-width="20" fill="none" />
<path d="M97.637 121.69c27.327-22.326 54.058-45.426 81.98-67.097-14.646 22.505-29.708 44.711-44.354 67.215-12.562.06-25.123.06-37.626-.119zM120.737 134.132c12.621 0 25.183 0 37.745.179-27.505 22.206-54.117 45.484-82.099 67.096 14.646-22.505 29.708-44.77 44.354-67.275z" fill="#010101"/>
</svg>

After

Width:  |  Height:  |  Size: 478 B

View File

@ -0,0 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/socket-admin/favicon.png"><title>Socket.IO Admin For OAK</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"><link href="/socket-admin/css/app.cc95923b.css" rel="preload" as="style"><link href="/socket-admin/css/chunk-vendors.9f55d012.css" rel="preload" as="style"><link href="/socket-admin/js/app.8f417b7c.js" rel="preload" as="script"><link href="/socket-admin/js/chunk-vendors.1619c6bb.js" rel="preload" as="script"><link href="/socket-admin/css/chunk-vendors.9f55d012.css" rel="stylesheet"><link href="/socket-admin/css/app.cc95923b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but Socket.IO Admin For OAK doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/socket-admin/js/chunk-vendors.1619c6bb.js"></script><script src="/socket-admin/js/app.8f417b7c.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long