之前示例中创建的 publicProcedure
根据名称其实就看出来了定义的这个过程是一个公共的过程,可以让任何人进行调用,但是实际工作中这些过程调用都是需要有指定权限的用户才能访问的,这个时候就需要在调用之前进行身份验证,而在这里的这个身份验证的步骤就是通过使用 TRPC 的中间件来实现的。
中间件使用案例
使用 t.procedure.use()
来将中间件添加到 procedure 中。
假设我们需要定义一个 getUserInfo
的 procedure,只允许 id 为 2 的用户调用,其他用户调用则会返回 401 状态码。
- 在
server/trpc.ts
中定义使用中间件的procedure。
export const privateProcedure = publicProcedure.use(async (opts) => {
const { ctx } = opts;
if (ctx.user.id === 2) {
// 必须传递返回值
return opts.next(opts);
}
throw new TRPCError({code: 'UNAUTHORIZED', message: '未授权'});
});
要注意在中间件中封装的对 procedure 的调用必须传递其返回值。
2. 定义 getUserInfo
的方法(server/index.ts
)
const appRouter = router({
...
// privateProcedure使用
getUserInfo: privateProcedure.query((opt) => {
return opt.ctx.user;
})
});
- 在客户端调用
getUserInfo
。
由于我们仍使用之前的上下文代码,其中我们默认给上下文添加了id 为 1 的用户,所以客户端在调用的时候会返回 401 状态码。
...
trpc.getUserInfo.query().then((data) => {
console.log('UserInfo=', data);
}).catch((err) => {
console.error('Error:', err);
});
4. 修改服务端上下文中默认用户的 id 为 2(server/context.ts
), 并重启服务端。
export const createContext = async({req, res}: CreateHTTPContextOptions) => {
// 模拟从session获取用户信息
const user: User = {
id: 2,
name: 'Alan',
}
return {
user, req, res
}
}
- 再次在客户端调用
getUserInfo
。
此时由于上下文中的用户满足中间件的校验,所以可以继续往下执行最终客户端将获取到用户信息。
通过中间件还可以实现其它的一些作用,必须在过程调用中记录日志,或对上下文进行一些操作都是可以的。
中间件的其它使用方法
上面我们通过在 procedure.use()
中直接写匿名方法来定义中间件,我们还可以通过 t.middleware()
方法来声明一个命名中间件。之前的定义就可以改写成:
const authMiddleware = t.middleware(async (opts) => {
const { ctx } = opts;
if (ctx.user.id === 2) {
return opts.next(opts);
}
throw new TRPCError({code: 'UNAUTHORIZED', message: '未授权'});
})
// 定义需要进行身份验证的procedure
export const privateProcedure = publicProcedure.use(authMiddleware);
unstable_concat
方法
假如我们现在要在中间件中处理的逻辑比较复杂而且对于不同的 procedure
的要使用的中间件处理逻辑也不尽相同,那我们就可以使用 unstable_concat
方法来定义独立的可重用的插件来实现这个功能。
如何理解 独立的
?
独立的
是指我们可以创建与特定 tRPC 实例无关的中间件。比如现在我们的 tRPC 实例是在 server/trpc.ts
中通过 initTPRC
创建的,其中我们指定了这个实例的上下文信息。而通过 unstable_concat
方法我们可以创建一个不包含这些上下文信息的中间件。
假如我们现在要实现一个中间件来监控我们的 procedure
的调用性能监控。这个中间件中其实并不需要我们的用户信息。
- 定义性能监控插件 (
server/trpc.ts
)
const minitorPerformancePlugin = () => {
const t = initTRPC.create();
// 性能监控中间件
const monitorPerformance = t.middleware(async ({ path, next }) => {
const start = performance.now();
const result = await next();
const duration = performance.now() - start;
// 假设这里有一个性能监控服务
console.log(path, `${duration}s`);
return result;
});
return {
minitorProc: t.procedure.use(monitorPerformance),
}
}
- 使用性能监控插件(
server/trpc.ts
)
const plugin = minitorPerformancePlugin();
// 定义需要进行身份验证的procedure
export const privateProcedure = publicProcedure.use(authMiddleware).unstable_concat(plugin.minitorProc);
通过调用定义的 minitorPerformancePlugin
方法创建对应的插件,然后在 procedure
中通过 unstable_concat
方法来将这个中间件进行组合。
现在我们的 getUserInfo
的 procedure
方法既会进行用户校验也会输出这个方法的耗时。
3. 客户端调用,直接运行客户端方法
ts-node client/index.ts
在服务端控制台输出了我们组合后的性能监控中间件的输出内容。
现在修改我们服务端默认的用户 Id,再次调用客户端程序。
可以看到我们的用户校验中间件依然生效。