全局模式与 $ 快捷方式
从 ZenWeb v4.0 开始,框架引入了全局模式。全局模式基于 Node.js 的 asyncLocalStorage API,让你无需在函数之间层层传递 ctx 参数,就可以在任何位置直接访问当前请求上下文和相关服务。
传统方式下,你需要通过控制器方法注入 ctx,再传递给 Service 层。全局模式让你可以在任意层级的代码中直接使用 $ 前缀的全局函数获取所需对象。
启用全局模式
使用 $initCore() 代替传统的 new Core() 来创建应用实例,全局 Core 会自动启用 asyncLocalStorage:
import { $initCore } from 'zenweb';
const app = $initCore();
app.setup(/* ... */);
app.start();
$initCore() 只能调用一次,重复调用会抛出异常。全局实例保存在 globalThis.__zenweb_core 中。
上下文与 Core
$ctx / $getContext()
获取当前请求的上下文对象(Context),等同于控制器方法中注入的 ctx 参数。
import { $ctx, $getContext } from 'zenweb';
// $ctx 是代理对象,直接使用即可
console.log($ctx.method); // 'GET'
console.log($ctx.path); // '/api/users'
// $getContext() 是函数调用形式
const ctx = $getContext();
ctx.throw(403, '无权访问');
$getContext() 接受一个可选的 force 参数(默认为 true):
// 在请求上下文中调用,返回 Context 对象
const ctx = $getContext(); // force = true,如果不在请求上下文中则抛出异常
// 在请求上下文外调用,返回 undefined 而不是抛出异常
const ctx = $getContext(false);
if (ctx) {
console.log('当前在请求上下文中');
}
$core / $getCore()
获取全局 Core 单例实例。
import { $core, $getCore } from 'zenweb';
// $core 是代理对象,直接访问属性
$core.start();
$core.setup();
// $getCore() 是函数调用形式
const core = $getCore();
console.log(core.env); // 当前环境
$debug
带有请求上下文信息和调用行号的调试输出。在请求上下文中使用时,输出会自动附带 METHOD /path 前缀。
import { $debug } from 'zenweb';
// 基本输出
$debug('用户注册流程开始');
// 输出对象
$debug({ userId: 123, action: 'login' });
// 创建命名空间
const serviceDebug = $debug.extend('service');
serviceDebug('服务层调试信息');
// 条件输出
if ($debug.enabled) {
serviceDebug('当前用户: %o', user);
}
在请求中执行时,输出格式类似:
app POST /api/auth/login +0ms
src/service/auth.ts:15:3
用户登录验证 +2ms
请求参数
$query
获取 URL 查询参数(QueryHelper 快捷方式),支持类型转换和数据校验。
import { Get, $query } from 'zenweb';
export class UserController {
@Get()
async list() {
// 获取并校验查询参数
const { page, size, keyword } = await $query.get({
page: 'int',
size: 'int',
keyword: '!trim',
});
// ...
}
}
// 获取原始 query 数据
const data = await $query.data();
// 分页参数快捷获取
const { offset, limit } = await $query.page();
$param
获取路径参数(ParamHelper 快捷方式),用于 RESTful 风格的路由参数提取。
import { Get, $param } from 'zenweb';
export class UserController {
@Get(':id')
async detail() {
const { id } = await $param.get({ id: '!int' });
// ...
}
}
请求体
$body / $getTextBody / $getRawBody / $getObjectBody
解析请求体的快捷方法集合。
import { Post, $body, $getObjectBody } from 'zenweb';
export class AuthController {
@Post()
async register() {
// 使用 $body.get 获取并校验请求体数据
const { name, password } = await $body.get({
name: '!trim1',
password: '!trim1',
});
// ...
}
@Post()
async uploadAvatar() {
// 获取原始二进制数据
const raw = await $getRawBody();
// 获取文本内容(自动处理编码)
const text = await $getTextBody();
// 获取对象形式的请求体
const obj = await $getObjectBody();
}
}
// $body 同样支持 data() 和 get() 方法
const bodyData = await $body.data(); // 原始数据
const parsed = await $body.get({ // 校验后的数据
name: '!trim1',
email: 'email',
});
日志
$log
获取当前上下文关联的 Logger 对象。在请求上下文中使用时自动关联请求日志,在请求外使用时关联 Core 全局日志。
import { $log } from 'zenweb';
export class AuthService {
async register(name: string, password: string) {
$log.info('用户注册: %s', name);
// ... 注册逻辑 ...
$log.info('用户注册成功: userId=%d', user.id);
return { user, token };
}
}
数据库
$mysql
获取 MySQL 连接池的快捷方式。在请求上下文中使用时会自动使用请求级连接(支持事务),在请求外使用时使用全局连接池。
import { $mysql } from 'zenweb';
export class ChatRoomService {
async getMessages(roomId: number, limit: number = 50) {
const messages = await $mysql.sql`
SELECT m.*, u.name as user_name
FROM chat_room_message m
LEFT JOIN user u ON u.id = m.user_id
WHERE m.room_id = ${roomId}
ORDER BY m.created_at DESC
LIMIT ${limit}
`;
return messages;
}
}
缓存
$cache / $cacheHelper
缓存操作的快捷方式。
import { $cache, $cacheHelper } from '@zenweb/cache';
export class ConfigService {
// 直接使用缓存实例
async getSystemConfig() {
let config = await $cache.get('system:config');
if (!config) {
config = await loadConfigFromDB();
await $cache.set('system:config', config, 3600);
}
return config;
}
// 使用缓存助手(自动获取并缓存)
async getUserInfo(userId: number) {
return $cacheHelper(
['user:info', userId],
async (uid) => {
return User.findByPk(uid);
},
{ ttl: 1800 }
);
}
}
文件上传
$getUpload / $getUploadHelper
文件上传相关操作的快捷方式。
import { Post } from 'zenweb';
import { $getUpload, $getUploadHelper } from '@zenweb/upload';
export class UploadController {
@Post()
async upload() {
const upload = await $getUpload();
// 处理上传文件
// ...
}
}
依赖注入
$getInstance
在任意位置获取注入容器中的实例。如果在请求上下文中,从请求级容器获取;否则从全局容器获取。
import { $getInstance } from 'zenweb';
import { AuthService } from './auth';
export class ChatRoomService {
async createUserMessage(roomId: number, content: string) {
// 在 Service 中获取其他 Service 实例,无需注入
const authService = await $getInstance(AuthService);
const userId = authService.getCurrentUserId();
// ...
}
}
这个特性在 Service 层特别有用——你不再需要通过构造函数或 @Inject 注入依赖的其他 Service:
// 传统方式:需要在控制器中手动注入并传递
export class ChatRoomController {
@Post()
async create(ctx: Context, service: ChatRoomService, authService: AuthService) {
// ...
}
}
// 全局模式:Service 内部直接获取依赖
export class ChatRoomService {
async create(name: string) {
const authService = await $getInstance(AuthService);
const userId = authService.getCurrentUserId();
// ...
}
}
自定义 Helper
$helperBase()
$helperBase 是一个工厂函数,用于为任何继承自 HelperBase 的类创建全局快捷方式。框架内置的 $query 和 $param 就是通过这个函数创建的。
你可以为自己定义的 Helper 类创建全局快捷方式:
import { HelperBase } from '@zenweb/helper';
export class HeaderHelper extends HelperBase {
// 自定义逻辑
}
// 在全局注册文件中
import { $helperBase } from '@zenweb/helper';
import { HeaderHelper } from './helper/header';
export const $header = $helperBase(HeaderHelper);
使用方式与内置 Helper 一致:
const { authorization } = await $header.get({
authorization: '!trim',
});
传统模式 vs 全局模式对比
以下对比展示了两种模式在控制器和 Service 层的写法差异:
控制器层
import { Context, Get, Post, QueryHelper } from 'zenweb';
import { AuthService } from '../service/auth';
export class AuthController {
@Post()
async login(ctx: Context, query: QueryHelper, service: AuthService) {
// ctx、QueryHelper、service 都通过参数注入
const { name, password } = query.get({
name: '!trim1',
password: '!trim1',
});
try {
return await service.login(name, password);
} catch (e: any) {
ctx.throw(400, e.message);
}
}
}
import { Post, $query, $ctx } from 'zenweb';
import { $getInstance } from 'zenweb';
import { AuthService } from '../service/auth';
export class AuthController {
@Post()
async login() {
// 无需注入 ctx 和 service,直接使用全局函数
const { name, password } = await $query.get({
name: '!trim1',
password: '!trim1',
});
const service = await $getInstance(AuthService);
try {
return await service.login(name, password);
} catch (e: any) {
$ctx.throw(400, e.message);
}
}
}
Service 层
import { Context, Inject } from 'zenweb';
export class ChatRoomService {
// 需要通过 @Inject 注入 ctx
@Inject ctx!: Context;
async create(name: string) {
const userId = this.ctx.user!.id!;
// ...
}
async sendMessage(roomId: number, content: string) {
const userId = this.ctx.user!.id!;
// ...
}
}
import { $ctx, $log, $mysql } from 'zenweb';
export class ChatRoomService {
// 无需注入 ctx,直接使用 $ctx
async create(name: string) {
const userId = $ctx.user!.id!;
$log.info('创建聊天室: %s, 操作者: %d', name, userId);
// ...
}
async sendMessage(roomId: number, content: string) {
const userId = $ctx.user!.id!;
$log.info('发送消息到房间 %d', roomId);
// ...
}
}
注意事项
asyncLocalStorage 依赖
全局模式依赖 Node.js 的 async_hooks 模块提供的 AsyncLocalStorage。确保你的运行环境满足以下要求:
- Node.js >= 16.14
- 使用
$initCore()初始化应用(自动启用asyncLocalStorage)
// 正确:使用 $initCore
const app = $initCore();
// 错误:手动 new Core 且未启用 asyncLocalStorage
const app = new Core(); // 全局函数将无法获取请求上下文
请求上下文外使用
部分全局函数可以在请求上下文之外使用(比如启动脚本、定时任务等),它们会回退到全局 Core 实例:
| 全局函数 | 请求上下文内 | 请求上下文外 |
|---|---|---|
$getCore() / $core | 可用 | 可用 |
$debug | 可用(附带请求信息) | 可用(无请求信息) |
$log | 请求级日志 | 全局日志 |
$mysql | 请求级连接 | 全局连接池 |
$getInstance() | 请求级实例 | 全局实例 |
$cache | 可用 | 可用 |
$getContext() / $ctx | 可用 | 抛出异常 |
$query / $param / $body | 可用 | 抛出异常 |
$ctx、$query、$param、$body 等函数必须在请求上下文中使用。在定时任务、消息队列消费者等非 HTTP 请求场景中使用会抛出异常。如果你不确定是否在请求上下文中,可以使用 $getContext(false) 进行安全判断。