Skip to content

Repository files navigation

EvaEngine for Node.js

NPM versionCIcodecovnpmLicense

面向 Node.js 微服务的 Application Runtime:同一套引擎覆盖 HTTPCLI定时任务,并提供 DI、Provider、中间件、配置、缓存、鉴权辅助、实体(Sequelize)、异常体系与 Swagger 生成。

消费方(人与 agent): 本 README 即为完整对外说明。使用本包不需要阅读仓库内的 docs/

环境要求

  • Node.js ≥ 24
  • ESM("type": "module"
  • npm(或其它可从 npm registry 安装的客户端)

安装

npm install evaengine

可选脚手架:EvaSkeleton.js

导入方式(重要)

包的 default 导出 是一个 core 对象。顶层具名导出只有 defaultcore(同一对象)。

importevafrom'evaengine';// 或:import { core as eva } from 'evaengine';const{
EvaEngine,
Command,DI,
Entities,
express,
wrapper,
services,
middlewares,
providers,
exceptions,
swagger,
utils,
commands,// 内置 CLI 命令
Joi,
sequelize
}=eva;

不要依赖 import { EvaEngine } from 'evaengine'——该具名导出不存在。


心智模型

new EvaEngine(meta, mode?)
→ 绑定 base 服务(env, config, logger, namespace, now, event_manager)
→ bootstrap() # web 服务 + 中间件 Provider
→ use(...) / registerCommands
→ run() | runHttps() | runCLI() | runCrontab() | runCommand()
模式典型流程
web(默认)bootstrap()use()run() / runHttps()
cliregisterCommands()runCLI() / runCrontab() / runCommand()

进程级事实(按每进程一个 Engine 规划):

  • DI全局容器。
  • EvaEngine.getApp()模块级 Express app 单例。
  • bootstrap() 注册 web 服务与中间件 Provider;CLI 路径在 getCLI / runCrontab 内注册 CLI 服务。
  • 内置 EventManager仅进程内(不是消息队列)。

快速开始

Web

importevafrom'evaengine';const{ EvaEngine,DI, wrapper, exceptions }=eva;const{ UnauthorizedException }=exceptions;constengine=newEvaEngine({projectRoot: process.cwd(),port: Number(process.env.PORT)||3000// configPath、sourceRoot 可选});engine.bootstrap();// 可选横切中间件(须在 bootstrap 之后)engine.use(DI.get('trace')());// engine.use(DI.get('session')());// engine.use(DI.get('auth')());engine.use('/health',(req,res)=>{res.json({ok: true});});engine.use('/me',wrapper(async(req,res)=>{if(!req.auth?.uid){thrownewUnauthorizedException('Login required');}res.json({uid: req.auth.uid});}));engine.run();

CLI

importevafrom'evaengine';import*asUserCommandsfrom'./commands/user.js';const{ EvaEngine }=eva;constengine=newEvaEngine({projectRoot: process.cwd()},'cli');engine.registerCommands(UserCommands);awaitengine.runCLI();// node app.js user:create --name=Ada

定时任务

importevafrom'evaengine';import*asJobsfrom'./commands/jobs.js';const{ EvaEngine }=eva;constengine=newEvaEngine({projectRoot: process.cwd()},'cli');engine.registerCommands([Jobs]);// 六段 cron(含秒)等细节见 runCrontab 第三参数 useSecondsengine.runCrontab('0/10 * * * * *','hello:world --id=EvaEngine');

内置 CLI 二进制

npx engine
npx engine make:entity
npx engine make:dbview
npx engine make:graphql
npx engine tramp:dump-config

配合 Spring Cloud Config(仅 bin):

  • SPRING_CONFIG_ENDPOINT(设置后启用)
  • SPRING_CONFIG_NAMESPRING_CONFIG_PROFILESSPRING_CONFIG_LABEL

推荐项目结构

project/
package.json # "type": "module"
config/
config.default.cjs
config.development.cjs
config.production.cjs
config.local.development.cjs # 本地覆盖,建议 gitignore
src/
app.js # web 入口
cli.js
commands/
entities/
routes/
test/

配置

配置目录为 {projectRoot}/config(可用构造参数 configPath 覆盖),按以下顺序合并:

  1. 引擎内置默认(随包提供)
  2. config.default.cjs
  3. config.<NODE_ENV>.cjs
  4. 可选 config.local.<NODE_ENV>.cjs(不存在则忽略)

配置文件使用 CommonJS.cjs(经 require 加载)。

// config/config.default.cjsmodule.exports={app: {name: 'my-service'},redis: {host: '127.0.0.1',port: 6379,lazyConnect: true},cache: {prefix: 'myapp',driver: 'redis'},token: {secret: process.env.TOKEN_SECRET||'',provider: undefined,// 设为 'kong' 时使用 Kong JWT 与对应 auth 中间件faker: {enable: false,key: 'eva',uid: 1}},session: {secret: process.env.SESSION_SECRET||'change-me',resave: true,saveUninitialized: true,cookie: {path: '/',httpOnly: true,secure: false,maxAge: 3600_000}},db: {dialect: 'mysql',port: 3306,database: '',replication: {write: {host: '',username: '',password: '',pool: {}},read: []}}};

运行时读取:

constconfig=DI.get('config');config.get('redis.host');config.get();// 完整对象

环境变量

变量作用
NODE_ENV选择 config.<env>.cjs
PORT常见应用端口(使用时传入构造参数)
LOG_LEVEL覆盖日志级别
TZmoment 默认时区(未设置时为 Asia/Shanghai
CLI_NAMECLI 模式下 logger 标签
MAX_REQUEST_DEBUG_BODYdebug 中间件 body 限制
SEQUELIZE_REPLICATION_CONFIG_KEYdb 下 replication 配置的替代键名
SPRING_CONFIG_*bin 远程配置(见上文)

DI 与服务

DI.get('logger').info('hello');DI.get('redis').getInstance();DI.get('cache');// 缓存门面DI.get('jwt');DI.get('http_client');DI.get('rest_client');DI.get('event_manager');DI.get('namespace');DI.get('now');DI.get('env');DI.get('validator_base');
DI 名绑定时机
envconfigloggernamespacenowevent_manager构造时(base)
rediscachehttp_clientrest_clientvalidator_basejwtbootstrap()(web)或 CLI 执行路径
下文中间件名bootstrap()

自定义 Provider:

importevafrom'evaengine';const{DI, providers }=eva;const{ ServiceProvider }=providers.services;classMyApiProviderextendsServiceProvider{getname(){return'my_api';}register(){DI.bindValue(this.name,{ping: ()=>'pong'});}}engine.registerService(MyApiProvider);// 或替换整表:// EvaEngine.setServiceProvidersForWeb([...EvaEngine.getServiceProvidersForWeb(), MyApiProvider]);

测试辅助:DI.reset()DI.registerMockedProviders(providers, configPath)DI.bindClass / bindValue / bindMethod


中间件

bootstrap() 之后按名称绑定工厂。需要调用工厂(注意部分场景二次调用):

engine.use(DI.get('trace')());engine.use(DI.get('session')());engine.use(DI.get('auth')());// validator 是高阶工厂:engine.use('/items',DI.get('validator')(()=>({query: eva.Joi.object({page: eva.Joi.number().integer().required()})})),handler);
名称作用
sessionexpress-session(经 connect-redis 的 Redis 存储)
authX-Tokenapi_key 取 JWT,或 session uid;可选 faker token
trace请求追踪(与 namespace 协作)
validatorJoi 请求校验
view_cache响应缓存辅助
debug调试输出

config.token.provider === 'kong' 时,jwt 服务与 auth 中间件均切换为 Kong 实现。

使用 wrapper(async (req,res) => …),以便抛出的 exceptions.* 进入默认错误处理器。


命令(Command)

importevafrom'evaengine';const{ Command,DI}=eva;exportclassHelloWorldextendsCommand{staticgetName(){return'hello:world';}staticgetDescription(){return'Say hello';}staticgetSpec(){return{id: {type: 'string',description: 'Who to greet'}};}asyncrun(){const{ id ='world'}=this.getOptions();DI.get('logger').info(`Hello ${id}`);}}

通过 engine.registerCommands(moduleExports) 或模块数组注册。名称来自 getName()

Engine API:runCLI()runCommand('name --flag=1')runCrontab(expression, 'name --flag=1', useSeconds?)clearCommands()clearCrontabs()


实体(Sequelize)

importpathfrom'path';importevafrom'evaengine';const{ Entities,DI}=eva;constentities=newEntities(path.join(process.cwd(),'src/entities'));entities.init();// 按 config.db 构建 Sequelize 并扫描目录constUser=entities.get('user');constall=entities.getAll();awaitentities.getTransaction(async(t)=>{/* … */});

实体文件(经 require 加载的 CJS 或 ESM 工厂):

// src/entities/user.cjsmodule.exports=(sequelize,DataTypes)=>sequelize.define('user',{id: {type: DataTypes.INTEGER.UNSIGNED,primaryKey: true,autoIncrement: true},name: {type: DataTypes.STRING,allowNull: false}},{tableName: 'users'});

异常

importevafrom'evaengine';const{
StandardException,
LogicException,
InvalidArgumentException,
UnauthorizedException,
ResourceNotFoundException,
RuntimeException
// …完整列表见包导出 exceptions}=eva.exceptions;

默认 HTTP 错误处理器在 run / runHttps 时挂载:将 StandardException 子类映射为 JSON 与状态码;生产环境会剥离 stack 等细节。


Swagger

使用 eva.swaggerExSwagger、注解辅助等)从源码注释与模型生成 Swagger 2.0。在应用脚本中自行接入生成流程;UI 资源来自依赖 swagger-ui-dist


EvaEngine API 一览

constructor({ projectRoot, configPath?, sourceRoot?, port?, config?, logger?, namespace? }, mode?='web')
getMeta() getDI()
bootstrap() use(...args) run(port?) runHttps(port?, options?) getServer()
registerCommands(commands) getCommands() clearCommands() getCommand() getCommandName()
runCLI(name?) runCommand(commandString) runCrontab(seq, commandString, useSeconds?) clearCrontabs()
registerServiceProviders(providers) registerService(ProviderClass)
setDefaultErrorHandler / getDefaultErrorHandler
setUncaughtExceptionHandler / getUncaughtExceptionHandler
setServerErrorHandler / getServerErrorHandler
static getApp() createRouter() getVersion()
static get/set BaseServiceProviders | ServiceProvidersForWeb | ServiceProvidersForCLI | MiddlewareProviders

本库不是什么

  • 不只是 Express 薄封装——HTTP 只是入口之一
  • 不是业务领域框架或业务规则层
  • 不是消息总线(可靠投递请用真正的 MQ)
  • 不是完整 ORM 产品——仅提供 Sequelize 集成辅助

本仓库开发

git clone https://github.com/EvaEngine/EvaEngine.js.git
cd EvaEngine.js
npm install
npm run lint
npm run build
npm test# 部分测试需要本机 Redis 127.0.0.1:6379

发版:在 main 上由 semantic-release(Conventional Commits)执行——仅发布 npm,不创建 GitHub Release。维护者文档在 git 的 docs/ 下,npm 消费方无需阅读

Releases

Packages

Used by

Contributors

Languages