asaihost-nodejs
v0.0.11
Published
AsaiWeb CMS Node.js host plugin — HTTP/WebSocket server, sysserver platform API, IEC 62443 security kernel
Maintainers
Readme
asaihost-nodejs
Node.js HTTP/WebSocket 服务器宿主:多站点监听、静态资源、反向代理、内置 sysserver 平台 API(登录/RBAC/日志/数据库),以及 IEC 62443 安全内核。
| 项 | 值 |
|----|-----|
| npm 包名 | asaihost-nodejs |
| 插件入口 | index.ts(monorepo 源码)/ dist/asaihost-nodejs.es.js(发布后) |
| 运行时配置 | asaihost.json + 密钥侧车 / 环境变量 |
| Node 版本 | >= 20 |
| 对等依赖 | ws ^8.18(mysql / sqlite3 按数据存储选配) |
目录结构
asaihost-nodejs/
├── index.ts # 包入口(默认导出 initAsaiHostNodejs + 命名 API)
├── package.json
├── tsup.config.ts
├── README.md
└── src/
├── index.ts # StartAsaiHostNodejs 启动链
├── host-api.ts # 对外公开 API 聚合
├── types.ts # AsaiRuntime / HostConfig 等类型
├── core/ # init、host-config、network
├── http/ # server、static、security
├── ws/ # server、auth、message
├── proxy/ # 反向代理
├── config/ # validate、secrets
├── infra/ # logger、registry、security
├── common/server/ # ServerApi / ServerSys / ServerWs / WsClient 基类
└── sysserver/ # 内置 api/ws/db/lib目录
快速开始
安装
npm install asaihost-nodejs ws
# 若使用 MySQL / SQLite 数据存储
npm install mysql sqlite3最小引导(app.ts 模式)
import fs from 'node:fs/promises';
import path from 'node:path';
import AsaiHostNodejs from 'asaihost-nodejs';
const $asai: any = {
$lib: { /* 业务工具库 */ },
connectionshttp: {},
connectionsws: {},
taskshttp: {},
tasksws: {},
servershttp: {},
serversws: {},
tm: 0,
tmpdata: {},
};
// 1. 挂载 sysserver 平台能力(api/ws/db/lib);sqlite/mysql 驱动由独立插件注入
import AsaiHostNodejsDbsqlite from './src/plugs/asaihost-nodejs-dbsqlite/index';
import AsaiHostNodejsDbmysql from './src/plugs/asaihost-nodejs-dbmysql/index';
AsaiHostNodejs.sysserver($asai, {
db: { sqlite: AsaiHostNodejsDbsqlite, mysql: AsaiHostNodejsDbmysql },
});
$asai.serverRoot = process.cwd();
const cfg = JSON.parse(
await fs.readFile(path.join($asai.serverRoot, 'websys/sys/asaihost.json'), 'utf8'),
);
// 2. 配置校验与密钥注入
const stripped = AsaiHostNodejs.stripForbiddenSecretsFromHostConfig(cfg);
if (stripped.length) {
console.warn(`[AsaiHostNodejs] 已从 asaihost.json 剥离敏感字段: ${stripped.join(', ')}`);
}
$asai.hostconfig = AsaiHostNodejs.normalizeHostConfig(cfg);
AsaiHostNodejs.normalizeSecurityConfig($asai.hostconfig);
const errors = AsaiHostNodejs.validateHostConfig($asai.hostconfig);
if (errors.length) throw new Error(errors.join('; '));
const { secrets } = await AsaiHostNodejs.loadAsaiSecrets($asai.serverRoot);
AsaiHostNodejs.applySecretsToHostConfig($asai.hostconfig, secrets);
await AsaiHostNodejs.resolveHttpsCertFromPaths($asai.serverRoot, $asai.hostconfig);
// 3. 注册业务 work 处理器并启动
$asai.asaihost = AsaiHostNodejs;
const { sysWork } = sysMod($asai);
const { apiWork } = apiMod($asai);
const { wsWork } = wsMod($asai);
const { jsonpWork } = jsonpMod($asai);
const { StartAsaiHostNodejs } = AsaiHostNodejs.fn($asai, { sysWork, apiWork, wsWork, jsonpWork });
StartAsaiHostNodejs();完整 monorepo 示例见仓库根目录
webserver/app.ts。
在 AsaiCMS monorepo 内使用
本仓库 webserver 通过相对路径引用插件源码,开发时无需先 npm pack:
// webserver/app.ts
import AsaiHostNodejsMod from './src/plugs/asaihost-nodejs/index';本地构建 npm 产物:
cd webserver/src/plugs/asaihost-nodejs
npm install
npm run build构建产物位于 dist/:asaihost-nodejs.es.js(ESM)、asaihost-nodejs.cjs(CJS)、index.d.ts(类型声明)。
独立项目集成
1. 项目结构建议
your-server/
├── app.ts # 引导入口(见上文)
├── websys/sys/
│ ├── asaihost.json # 非敏感配置(可进 Git)
│ └── asaihost.secrets.local.json # 本地密钥(勿提交)
├── .env # 生产推荐:环境变量注入密钥
├── webclient/ # 静态前端(hostdirs 指向)
└── src/work/ # 业务 api/sys/ws 模块2. TypeScript
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true
}
}从 npm 安装后:
import AsaiHostNodejs, { PLUGIN_NAME, type AsaiRuntime, type HostConfig } from 'asaihost-nodejs';3. 运行方式
- 开发:
tsx app.ts或ts-node --esm app.ts - 生产:先
npm run build(若打包业务代码),再node dist/app.js
4. 与传输层插件协作
TCP / gRPC / RS485 等传输插件通过 $asai.asaihost 挂载,禁止跨包 deep import src/。在 asaihost.json 中配置 tcp / grpc / rs485 块,由对应 npm 插件读取。
启动与校验流程
读取 asaihost.json
→ stripForbiddenSecretsFromHostConfig(敏感字段不得写在 JSON)
→ normalizeHostConfig + normalizeSecurityConfig
→ validateHostConfig(结构 fail-fast)
→ validateProductionSecurity(生产 IEC 62443 检查)
→ loadAsaiSecrets(.env / secrets.local.json / process.env)
→ applySecretsToHostConfig + resolveHttpsCertFromPaths
→ sysserver($asai) 已提前执行
→ fn($asai, { sysWork, apiWork, wsWork, jsonpWork })
→ StartAsaiHostNodejs() → 多端口 HTTP/WS + 反向代理默认导出 API 形状
interface AsaiNodejsHostPlugin {
readonly name: 'asaihost-nodejs';
fn(runtime, options): { StartAsaiHostNodejs; asaiWs };
sysserver(runtime, opt?: SysserverBootOptions): void;
AsaiCommon: { ServerApi; ServerWs; ServerSys; WsClient };
AsaiUtils: { deUserInfoWithAsai; wsMock };
// …配置、路由、安全、传输桥接等(见 host-api.ts)
}配置文件总览
本插件的配置由 两份 组成,启动时合并到 $asai.hostconfig:
| 文件 | 位置(monorepo 惯例) | 进 Git | 说明 |
|------|----------------------|--------|------|
| asaihost.json | webserver/websys/sys/asaihost.json | ✅ 是 | 结构与非敏感运维项 |
| asaihost.secrets.local.json | webserver/websys/sys/ | ❌ 否 | 本地开发密钥(复制 example) |
| .env | webserver/.env | ❌ 否 | 环境变量注入(生产推荐) |
加载顺序(后者覆盖前者):process.env → .env → asaihost.secrets.local.json → 注入 hostconfig。
敏感字段(password / asaisn / aes.keybase64 / PEM 内联 / sessionSecret / wssec[0])禁止写入 asaihost.json,启动时会剥离并报错。
用户数据存储(dataStores.user)
默认 file(webdata/webdb/users/users.json + sessions.json)。在 websys/sys/asaihost.json 中修改 dataStores.user 即可切换,重启 webserver 后生效(app.ts 须已注入 sqlite/mysql 驱动)。
| type | 说明 | opt 主要字段 |
|------|------|----------------|
| file | 默认,JSON 文件 | dir, ext, create |
| sqlite | 单文件库,表 auth_users / auth_sessions | database, create |
| mysql | 远程 MySQL | host, user, password, database, create |
"dataStores": {
"user": {
"type": "file",
"opt": { "dir": "webdata/webdb/users/", "ext": ".json", "create": 1 }
}
}切换 SQLite:
"user": {
"type": "sqlite",
"opt": { "database": "./webdata/webdb/users/users.db", "create": 1 }
}切换 MySQL:
"user": {
"type": "mysql",
"opt": {
"host": "127.0.0.1",
"user": "root",
"password": "",
"database": "asai",
"create": 1
}
}运行时可通过 GET /api/user/login/config/ 查看 userStore.type(file / sqlite / mysql)。兼容旧字段 userStore,但推荐只维护 dataStores.user。
完整配置 — asaihost.json(合成示例)
{
"website": { "title": "WEB", "ver": "1.0.0.0" },
"dataStores": {
"user": {
"type": "file",
"opt": { "dir": "webdata/webdb/users/", "ext": ".json", "create": 1 }
}
},
"security": {
"requireAuth": 0,
"csp": 0,
"auditLog": 1,
"authSkipPaths": [
"/api/asailog/*",
"/api/sys/errorcodes/*",
"/api/user/login/auth/*",
"/api/user/login/register/*",
"/sys/info/*",
"/jsonp/*"
],
"minUserLevel": 0,
"allowRegister": 1,
"lockoutMaxFails": 5,
"lockoutMinutes": 15,
"sessionTtlHours": 24,
"passwordMinLength": 8,
"quota": {
"enabled": 0,
"maxApiConcurrent": 500,
"maxApiPerIp": 30,
"maxApiPerIpPerMin": 120,
"maxWsPerIp": 5,
"maxLoginSessions": 100,
"maxSessionsPerUser": 3,
"onUserSessionOverflow": "reject"
}
},
"logger": {
"lv": { "view": 2, "record": 1 },
"path": {
"maxsize": 1048576,
"client": "./webdata/webdb/log/client/",
"server": "./webdata/webdb/log/server/"
},
"intervention": {
"path": "./webdata/webdb/log/intervention/",
"retentionYears": 5
},
"sample": { "API": 0.15, "WS": 0.08, "REQ_OTHER": 0.2 },
"skip": ["/sys/info/metrics", "/sys/guard/msg", "/api/asailog/*"]
},
"hosts": {
"default": {
"port": 9198,
"httptype": "http://",
"maxhttp": 2000,
"maxws": 2000,
"maxonline": 100,
"hostdirs": ["webclient", "websys", "webdata/upload"]
},
"asai": {
"ws": 1,
"port": 9199,
"hostdirs": ["webclient", "websys", "webdata/upload"]
}
},
"proxyhosts": {
"default": {
"proxyport": 9298,
"port": 9198,
"httptype": "http://",
"proxyhttptype": "http://"
}
},
"aes": {
"lv": 1,
"aad": "asai aes aad",
"keylength": 256,
"ivlength": 12,
"algorithm": "aes-256-gcm"
},
"httpscert": {
"keyPath": "websys/sys/certs/local.key",
"certPath": "websys/sys/certs/local.crt"
},
"ip": "asai",
"index": "index.html"
}完整字段模板见 monorepo
webserver/websys/sys/asaihost.json;校验规则以validateHostConfig源码为准。
密钥侧车
复制 websys/sys/asaihost.secrets.example.json 为 asaihost.secrets.local.json:
{
"_comment": "禁止提交仓库。生产请改用环境变量。",
"password": "asai",
"asaisn": "<随机盐>",
"aesKeyBase64": "<与前端 VITE_ASAI_AES_KEYBASE64 一致>",
"sessionSecret": "<随机密钥>",
"wssecToken": "asai"
}环境变量(等价)
ASAI_HOST_PASSWORD=asai
ASAI_ASAISN=
ASAI_AES_KEYBASE64=
ASAI_SESSION_SECRET=
ASAI_WSSEC_TOKEN=
ASAI_HTTPS_KEY_PATH=websys/sys/certs/local.key
ASAI_HTTPS_CERT_PATH=websys/sys/certs/local.crt| 环境变量 | 注入到 hostconfig | 说明 |
|----------|-------------------|------|
| ASAI_HOST_PASSWORD | password | 系统 bootstrap 密码 |
| ASAI_ASAISN | asaisn | Userinfo 编解码盐 |
| ASAI_AES_KEYBASE64 | aes.keybase64 | AES-256-GCM 密钥 |
| ASAI_SESSION_SECRET | security.sessionSecret | 会话 HMAC |
| ASAI_WSSEC_TOKEN | wssec[0] | WS 安全令牌 |
配置 → 运行时映射
asaihost.json + secrets
├── website / ip / index / mimetypes / pkg → 全局
├── logger.* → infra/logger, http/log
├── tm.* → init, ws/auth, process
├── security.* → access-control, user-store, quota
├── userStore.* → sysserver/api/login/store
├── aes.* (+ secrets.keybase64) → API/WS 加解密
├── httpscert.* → https.createServer
├── hosts.default + hosts.<site> → http/*, ws/*
│ ├── weburls.* → SPA fallback
│ └── ckhttp / ckws / max* → 连接监控与限流
├── proxyhosts.* → proxy/server
└── tcp|grpc|rs485 → 传输插件(可选)站点合并规则
- 实际站点 =
hosts.default浅合并hosts.<name> default仅作模板,不单独监听asai为内置守护站点;9199 端口冲突时process.exit(1)
请求路由
| URL | 处理器 |
|-----|--------|
| /sys/* | sysWork |
| /api/* | apiWork(前缀匹配 hostserverapis) |
| /jsonp/* | jsonpWork |
| 其它 | 静态(hostdirs 顺序查找) |
| WebSocket | wsWork(msg.ty 最长前缀) |
公开 API 参考
npm 消费者仅使用包入口导出,禁止 deep import src/。
配置与密钥
| 导出 | 说明 |
|------|------|
| normalizeHostConfig | 填充默认值、规范化结构 |
| validateHostConfig | 结构校验,返回错误字符串数组 |
| validateProductionSecurity | 生产环境 IEC 62443 检查 |
| normalizeSecurityConfig | 安全策略默认值 |
| stripForbiddenSecretsFromHostConfig | 剥离 JSON 中的敏感字段 |
| loadAsaiSecrets / loadDotEnvFile | 加载密钥 |
| applySecretsToHostConfig | 合并密钥到 hostconfig |
| resolveHttpsCertFromPaths | 从路径读取 PEM |
| validateAsaiSecrets / isDevDefaultPassword | 密钥校验 |
路由注册
| 导出 | 说明 |
|------|------|
| registerApi / registerWs | 注册业务路由工厂 |
| initHostserverApi / initHostserverWs | 初始化平台路由表 |
| syncApiRouteKeys / syncWsRouteKeys | 同步路由键列表 |
安全与基础设施
| 导出 | 说明 |
|------|------|
| secureCompare | 恒定时间字符串比较 |
| resolvePathUnderRoots / getHostAllowedRoots | 防目录穿越 |
| validateHttpAuth | HTTP Userinfo 鉴权 |
| logSecurityEvent / getClientIp | 安全审计 |
| sanitizeString 等 | 输入净化 |
传输桥接(供 TCP/gRPC/RS485 插件)
| 导出 | 说明 |
|------|------|
| getTransportBlock / getTransportDefault | 读取 hostconfig 传输配置 |
| initTransportBridge | 初始化 $asai 传输桥 |
| ensureDefaultConnection / createTransportDataHandler | 连接与数据回调 |
类型导出
AsaiRuntime、HostConfig、AsaiHostNodejsOptions、HostBootResult、AsaiSecrets、SecurityConfig 等见 import type { … } from 'asaihost-nodejs'。
字段速查
security
| 字段 | 默认 | 说明 |
|------|------|------|
| requireAuth | 0 | 1=API 需登录(authSkipPaths 除外) |
| csp | 0 | 1=启用 Content-Security-Policy |
| auditLog | 1 | 安全审计日志 |
| quota.enabled | 0 | 连接/频率配额 |
hosts.default 常用
| 字段 | 默认 | 说明 |
|------|------|------|
| port | 9198 | 监听端口 |
| httptype | http:// | 或 https:// |
| ws | — | 1=同端口挂载 WebSocket |
| maxhttp / maxws / maxonline | 2000/2000/100 | 限流 |
| hostdirs | — | 静态目录(按序查找) |
| close | 0 | 1=跳过启动 |
aes
| 字段 | 说明 |
|------|------|
| lv | 1=可选 AES;>1=全量 AES(须 secrets 注入 keybase64) |
| aad / algorithm / keylength / ivlength | GCM 参数 |
开发与发布 npm 包
cd webserver/src/plugs/asaihost-nodejs
npm install
npm run typecheck # 仅类型检查
npm run build # tsup 打包 + tsc 声明
npm pack # 本地验证 .tgz 内容
npm publish --access publicpackage.json exports 约定
| 条件 | 解析到 |
|------|--------|
| development | ./index.ts(monorepo + tsx 开发) |
| import | ./dist/asaihost-nodejs.es.js |
| require | ./dist/asaihost-nodejs.cjs |
| types | ./dist/asaihost-nodejs.d.ts |
注意事项
- 修改
asaihost.json后需重启 Node 进程。 - 端口被占用 →
EADDRINUSE→ 静默退出;排查 9198/9199。 CONFIG_WARN表示使用 dev 默认密码,生产请设ASAI_HOST_PASSWORD。- 业务处理器经模块级
workHandlersMap 注册,不污染global。 - 静态路径经
resolvePathUnderRoots防目录穿越。
约束与前端对应
- 与
asaihost-vue(前端)配对使用时,保持asaihost.json/aes密钥 /VITE_ASAI_AES_KEYBASE64一致。 - 插件间禁止跨包
plugs/*/src/引用;消费方只走import from 'asaihost-nodejs'。 - 传输层插件仅通过
$asai.asaihost.*与宿主协作。
| 前端 | 后端 |
|------|------|
| asaihost-vue | asaihost-nodejs |
| public/webmodel/cfg.js | websys/sys/asaihost.json |
| VITE_ASAI_AES_KEYBASE64 | ASAI_AES_KEYBASE64 |
配置以本 README 与 validateHostConfig 源码为准;monorepo 实参见 webserver/websys/sys/asaihost.json 与 webserver/app.ts。
