跳到主要内容

输出数据

输出的结果包含成功失败两种情况。在控制器方法中直接 return 即可返回成功结果,使用 fail() 抛出业务错误。

注意

如果使用空 return 或者不 return,将会返回 404 状态码,除非之前给 ctx.body 设置了值。

成功输出

import { Get, Post, Context } from 'zenweb';

export class TestController {
// 直接 return 返回成功结果
@Get()
test1() {
return 'OK'; // → {"data":"OK"}
}

@Get()
test2() {
return { id: 1, name: 'Alice' }; // → {"data":{"id":1,"name":"Alice"}}
}

@Get()
test3() {
return [1, 2, 3]; // → {"data":[1,2,3]}
}

// 使用 ctx.success() 显式设置结果
@Get()
test4(ctx: Context) {
ctx.success('OK');
// 注意:ctx.success() 不会中断执行,后续再次调用会覆盖之前的结果
}
}

失败输出

使用 fail() 来抛出业务错误,它会立即中断代码执行:

import { Get, fail } from 'zenweb';

export class TestController {
@Get()
test1() {
fail('这是一个错误'); // → {"code":null,"message":"这是一个错误"}
}

@Get()
test2() {
fail(100); // → {"code":100,"message":"request fail"}
}

@Get()
test3() {
fail(1111, '带有错误代码的错误信息'); // → {"code":1111,"message":"带有错误代码的错误信息"}
}

@Get()
test4() {
fail({
code: 123,
message: '自定义错误细节',
data: { field: 'email', reason: 'duplicate' },
}); // → {"code":123,"message":"自定义错误细节","data":{"field":"email","reason":"duplicate"}}
}
}
提示

fail() 是一个异常抛出函数,可以在代码的任何地方使用,不需要 return 就可阻止程序继续执行。

配置 result 模块

create() 中传入 result 选项来控制输出行为:

src/index.ts
import { create } from 'zenweb';

create({
result: {
// 默认失败代码,当 fail() 未指定 code 时使用
failCode: 500,

// 默认失败 HTTP 状态码
// 422 表示"业务错误"(区别于 500 服务器错误)
failStatus: 422,

// 默认失败消息
failMessage: 'request fail',

// 在响应头中输出错误代码
// true → 使用默认头名称 "X-Fail-Code"
// 'X-Error-Code' → 自定义头名称
// false → 关闭
failCodeHeader: true,

// 是否暴露意外错误信息(非 fail() 抛出的 Error)
// 开发环境建议开启,生产环境务必关闭
exposeUnexpected: process.env.NODE_ENV !== 'production',

// 意外错误的 HTTP 状态码
unexpectedStatus: 500,

// 自定义成功结果包装
successWrap(ctx, data) {
return { code: 0, data, timestamp: Date.now() };
},

// 自定义失败结果包装
failWrap(ctx, err) {
return { code: err.code, msg: err.message, data: err.data };
},
},
});

配置选项说明

选项类型默认值说明
failCodenumber-默认失败代码
failStatusnumber422默认失败 HTTP 状态码
failMessagestring'request fail'默认失败消息
failCodeHeaderboolean \| stringtrue在响应头中输出错误代码
exposeUnexpectedboolean开发环境 true是否暴露意外错误详情
unexpectedStatusnumber500意外错误状态码
unexpectedCodenumber500意外错误代码
successWrapfunction-自定义成功结果包装函数
failWrapfunction-自定义失败结果包装函数
rendersResultRender[]-自定义渲染器列表

failCodeHeader 响应头

failCodeHeader: true(默认开启)时,fail() 的错误代码会写入 HTTP 响应头 X-Fail-Code

# 请求一个会 fail(100) 的接口
curl -i http://127.0.0.1:7001/test
# HTTP/1.1 422 Unprocessable Entity
# X-Fail-Code: 100
# Content-Type: application/json; charset=utf-8
#
# {"code":100,"message":"request fail"}

前端可以根据 X-Fail-Code 响应头判断请求是否成功,无需解析响应体。

消息代码管理

当项目规模变大时,散落在代码中的错误字符串难以维护。推荐使用 message-codes.json 集中管理错误消息:

message-codes.json
{
"user.notfound": "用户不存在",
"user.login.fail": "用户名或密码错误",
"user.duplicate": "用户名 {name} 已存在",
"order.stock.insufficient": "商品 {name} 库存不足,当前剩余 {stock} 件"
}

然后在代码中使用消息代码:

import { Get, fail, Context } from 'zenweb';

export class UserController {
@Get()
async detail(ctx: Context) {
const user = await findUser(1);
if (!user) {
// 使用消息代码,会自动从 message-codes.json 中查找对应的错误消息
ctx.messageCodeResolver.fail('user.notfound');
}
return user;
}

@Get()
async register(ctx: Context) {
// 支持参数替换
ctx.messageCodeResolver.fail('user.duplicate', { name: 'Alice' });
// 输出: {"code":null,"message":"用户名 Alice 已存在"}
}
}

也可以使用 format() 只获取消息文本而不抛出错误:

const msg = ctx.messageCodeResolver.format('user.duplicate', { name: 'Bob' });
console.log(msg); // "用户名 Bob 已存在"

自定义结果渲染器

除了默认的 JSON 输出,你还可以注册自定义渲染器来改变输出格式。例如根据请求的 Accept 头返回不同格式:

src/index.ts
import { create } from 'zenweb';
import { ResultRender, Context } from 'zenweb';

// 自定义 XML 渲染器
class XMLRender implements ResultRender {
enwrap = true;
type = 'xml';

match(ctx: Context) {
return ctx.accepts('application/xml') === 'application/xml';
}

render(ctx: Context, data: unknown) {
// 将数据转换为 XML 格式
return toXML(data);
}
}

create({
result: {
renders: [XMLRender],
},
});

渲染器按注册顺序匹配,如果所有自定义渲染器都不匹配,则使用默认的 JSONRender

ctx.isFail 判断

在中间件中可以通过 ctx.isFail 判断当前请求是否产生了业务错误:

import { Middleware } from 'zenweb';

function logMiddleware(): Middleware {
return async (ctx, next) => {
await next();
if (ctx.isFail) {
ctx.log.warn('业务错误: %s', ctx.body);
}
};
}