一切的入口:控制器
访问一个 web 服务的入口就是控制器,通俗点说就是 url
链接,我们没有单独的 路由
配置文件,
所有路由的设置都是与控制器代码绑定在一起的,这让代码维护看起来更清晰自然。
现在我们来创建第一个 "Hello World!"
Hello World!
在 src
文件夹下创建一个新的文件夹 controller
。 (必须是这个名称,这与代码扫描加载有关,当然如果你想改也不是不行,只是我们推荐先这么约定)
在 controller
目录下新建一个文件 index.ts
- TypeScript
- JavaScript
src/controller/index.ts
import { mapping } from "zenweb";
// 导出控制器类,类名称没有要求,你甚至可以导出一个匿名类
// 但是我们不推荐你这么做,毕竟代码是要给人看的,起一个贴合实际的名字不更优雅吗?
// 有了名字也方便后续的日志查看
// 当然你也可以在一个文件中导出多个类,都是可以生效的,但是更推荐一个文件只导出一个控制器类
export class IndexController {
@mapping() // 将类方法注册到路由中,在不指定参数的情况下默认为 GET /{方法名称} (index 名称比较特殊默认为 /)
index() {
return "Hello World!"; // 输出结果
}
}
app/controller/index.js
const { mapping } = require("zenweb");
class IndexController {
index() {
return "Hello World!";
}
}
// 依赖注入需要手动处理声明,显然没有 TypeScript 优雅
mapping()(IndexController.prototype, 'index');
// 别忘了导出类
exports = {
IndexController,
};
在你保存文件后应该会发现控制台中信息重新滚动了一次,这是 ts-watch 监测到代码改变而重启了应用,以让新的代码生效。
这时候我们再次打开 http://127.0.0.1:7001 查看是否有:
{"data":"Hello World!"}
为什么默认输出 json 格式
这里我们看到了一段 json
输出,这是 zenweb 的默认输出格式,对于现代 web 开发来说,前后分离已经是一种标配了,
如果你需要传统的服务端渲染可以使用 @zenweb/template
模块,如果你需要直接输出内容可以使用 ctx.body=
这里不做过多介绍。
我可以更改 json 结果的外层包裹吗?
完全可以更改,通过修改 src/index.ts
中的 create({ result: { json: { success } })
即可实现,详情请查看 result 模块
自定义路由 url 和请求方法
src/controller/admin.ts
import { controller, mapping, Context } from "zenweb";
@controller({
prefix: "/admin", // 统一增加控制器下 url 前缀
})
export class AdminController {
// GET /admin
@mapping()
index() {
return "index";
}
// 可以指定多个请求方法
// POST,PUT /admin/login-by-username
@mapping({ path: "/login-by-username", method: ["POST", "PUT"] })
loginByUsername() {
return "login-by-username";
}
// 可以指定多个 url
// /admin/sysname
// /admin/system-name
@mapping({ path: ["/sysname", "/system-name"] })
systemName() {
return "admin system";
}
// 路由参数,冒号后面定义参数名称
// 匹配 /admin/user/123 或者 /admin/user/abc 等等
// 如果只想匹配 整数 类型可以指定自定义正则表达式 "/user/:id(\\d+)"
@mapping({ path: "/user/:id" })
userDetail(ctx: Context) { // 控制器方法中定义的参数会被自动注入
return "user id: " + ctx.params.id;
}
// 也可以在一个方法上指定多个不同的 mapping,每个 mapping 使用自己的中间件和方法
@mapping()
@mapping({ method: 'POST', middleware: checkPost })
many() {
}
}
使用中间件
中间件通常用在控制器方法 之前
或 之后
处理业务逻辑
常见的的用途:用户身份认证,数据输出加工,数据输入处理等等
一个中间件处理函数可以接受两个输入参数,分别是:Context
和 Next
Context
是当前请求上下文,Next
是下一步要处理中间件或控制器方法
下面定义一个典型的中间件代码逻辑:
src/controller/user.ts
import { Context, controller, mapping, Middleware, fail } from "zenweb";
// `之前` 类型演示
// 中间件推荐定义成一个函数返回一个中间件方法
// 统一为这样的风格,方便后续添加参数
function userCheckMiddleware(/* 一些参数 */): Middleware {
// ctx 当前请求上下文
// next 下一步中间件或控制器方法
return function (ctx, next) {
// 从 请求 header 头中取得 token 信息
const token = ctx.get('token');
// 验证 token 是否正确
if (token !== 'demo') {
// token 不正确,抛出异常,终止代码继续执行
fail('need-login');
}
// 设置一些信息到状态上,传递给后续代码使用
ctx.state.username = 'Bob';
// 当业务逻辑没有问题,需要返回下一个
// 此种类型的中间件就属于 `之前` 类型
return next();
}
}
// `之后` 类型演示
function userActionLog(): Middleware {
return async function (ctx, next) {
// 等待之后的所有中间件和控制器执行
await next();
// 打印输出
ctx.log.info('user: %s body: %s', ctx.state.username, ctx.body)
}
}
@controller({
// 可以将中间件定义在整个控制器类上,控制器下每个方法都会经过中间件处理
middleware: userActionLog(),
})
export class UserController {
// 在控制器方法上使用中间件
@mapping({ middleware: userCheckMiddleware() })
my(ctx: Context) {
return "my name is: " + ctx.state.username;
}
}
访问测试:
curl http://127.0.0.1:7001/my
# 输出: {"message":"请登陆!"}
# 添加需要的头信息再次访问
curl -H token:demo http://127.0.0.1:7001/my
# 输出: {"data":"my name is: Bob"}