共计 7968 个字符,预计需要花费 20 分钟才能阅读完成。
本文应用了 MongoDB, 还没有集成的能够看一下上篇文章
- Next14 app +Vercel 集成 MongoDB
next13
能够参考 trpc 文档 而且谷歌上曾经有不少问题解答,然而目前next14 app
只看到一个我的项目中有用到 Github 仓库,目前这个仓库中服务端的上下文获取存在问题,目前找到一个有用的能够看 Issus。目前trpc
对next14 app
的反对进度能够看 Issus
好的进入 注释
-
装置依赖包(这里我的依赖包版本是
10.43.1
)yarn add @trpc/serve @trpc/client @trpc/react-query zod
-
创立中间件
context.ts
(我的 trpc 相干文件的门路是src/lib/trpc/
)。这里我有用到JWT
将用户信息挂载在上下文中import {FetchCreateContextFnOptions} from '@trpc/server/adapters/fetch'; import Jwt from 'jsonwebtoken'; type Opts = Partial<FetchCreateContextFnOptions>; /** * 创立上下文 服务端组件中没有 req resHeaders * @see https://trpc.io/docs/server/adapters/fetch#create-the-context */ export function createContext(opts?: Opts): Opts & {userInfo?: Jwt.JwtPayload;} {const userInfo = {}; return {...(opts || {}), userInfo }; } export type Context = Awaited<ReturnType<typeof createContext>>;
-
创立
trpc.ts
文件寄存实例/** * @see https://trpc.io/docs/router * @see https://trpc.io/docs/procedures */ import {TRPCError, initTRPC} from '@trpc/server'; import {Context} from './context'; import {parseCookies} from '@/utils/util'; // 格式化 cookie import jwt from 'jsonwebtoken'; // 能够自行放在 utils/util 文件中 // export function parseCookies(cookieString: string) {// const list: { [key: string]: string } = {}; // cookieString && // cookieString.split(';').forEach((cookie) => {// const parts: string[] = cookie.split('='); // if (parts.length) {// list[parts.shift()!.trim()] = decodeURI(parts.join('=')); // } // }); // return list; // } const t = initTRPC.context<Context>().create(); // 鉴权中间件 const authMiddleware = t.middleware(({ctx, next}) => {const token = parseCookies(ctx.req?.headers.get('cookie') || '').token; const data = jwt.verify(token, process.env.JWT_SECRET!); if (typeof data == 'string') {throw new TRPCError({ code: 'UNAUTHORIZED'}); } return next({ ctx: { ...ctx, userInfo: data, }, }); }); /** * 须要鉴权的路由 * @see https://trpc.nodejs.cn/docs/server/middlewares#authorization */ export const authProcedure = t.procedure.use(authMiddleware.unstable_pipe(({ ctx, next}) => { return next({ctx,}); }) ); /** * Unprotected procedure **/ export const publicProcedure = t.procedure; export const router = t.router; // 创立服务端调用在示例仓库中应用的是 createCaller, 然而 createCaller 在 trpc v11 中曾经废除 // @see https://trpc.io/docs/server/server-side-calls#create-caller export const createCallerFactory = t.createCallerFactory;
- 创立
trpc
路由auth-router.ts
points-router.ts
routers.ts
特地留神,在服务端组件中申请时没有 ctx
// auth-router.ts
// auth-router.ts
import {z} from 'zod';
import {publicProcedure, router} from './trpc';
import {TRPCError} from '@trpc/server';
// 数据库设置 db.ts 放在 lib/db.ts
// db.ts 内容查看连贯 https://juejin.cn/post/7341669201008918565 正题中第 2 点
import clientPromise from '../db';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
export const authRouter = router({
signIn: publicProcedure
.input(
z.object({name: z.string(),
pwd: z.string(),})
)
.mutation(async ({ input, ctx}) => {const { resHeaders} = ctx;
const {name, pwd} = input;
const client = await clientPromise;
const collection = client.db('test').collection('users');
try {
const user = await collection.findOne({name: name,});
if (user) {
// 判断是否有用户存在, 存在间接登录
const isValid = await bcrypt.compare(pwd, user.password);
if (!isValid) {
// 返回 401
return new TRPCError({
code: 'FORBIDDEN',
message: 'Invalid credentials',
});
}
const token = jwt.sign({ userId: user._id, name: user.name},
process.env.JWT_SECRET!, // 这里须要再环境变量中定义
{expiresIn: '12h'}
);
// 设置 cookie
resHeaders?.set('Set-Cookie', 'token=' + token);
return {
code: 200,
data: token,
success: true,
};
}
// 注册逻辑
// 加密用户明码
const hashedPassword = await bcrypt.hash(pwd, 12);
// 存储用户
const result = await collection.insertOne({
name: name,
points: 0, // 积分
password: hashedPassword,
createdAt: new Date(),
updatedAt: new Date(),});
const token = jwt.sign({ userId: result.insertedId, name: name},
process.env.JWT_SECRET!,
{expiresIn: '12h'}
);
resHeaders?.set('Set-Cookie', 'token=' + token);
return {
code: 200,
data: token,
success: true,
};
} catch (error: any) {// console.log(error);
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: error.message,
});
}
}),
login: publicProcedure
.input(
z.object({name: z.string(),
pwd: z.string(),})
)
.mutation(async ({ input, ctx}) => {const { resHeaders} = ctx;
const client = await clientPromise;
const collection = client.db('test').collection('users');
const {name, pwd} = input;
const user = await collection.findOne({name: name,});
// 比拟复原的地址和预期的地址
try {if (user) {const isValid = await bcrypt.compare(pwd, user.password);
if (!isValid) {
return new TRPCError({
code: 'FORBIDDEN',
message: 'Invalid credentials',
});
}
const token = jwt.sign({ userId: user._id, name: name},
process.env.JWT_SECRET!,
{expiresIn: '12h'}
);
resHeaders?.set('Set-Cookie', 'token=' + token);
return {
code: 200,
data: token,
success: true,
};
} else {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'User information not found',
});
}
} catch (error: any) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: error.message,
});
}
}),
// 测试服务端的 trpc 申请
hello: publicProcedure
.input(
z.object({name: z.string(),
})
)
.mutation(async ({ input, ctx}) => {
// ctx 是没有数据的
return input.name;
}),
});
// points-router.ts
import {z} from 'zod';
import {authProcedure, router} from './trpc';
import {TRPCError} from '@trpc/server';
import clientPromise from '../db';
import {ObjectId} from 'mongodb';
export const PointsRouter = router({
// 这里应用的是 authProcedure 中间件路由,须要有携带 token 且鉴权通过才会进入路由,否则返回 401
added: authProcedure
.input(
z.object({count: z.number(),
})
)
.mutation(async ({ input, ctx}) => {const { userInfo} = ctx;
const client = await clientPromise;
const collection = client.db('test').collection('points-records');
const userCollection = client.db('test').collection('users');
try {
// 查问数据
const result = await userCollection.findOne({_id: new ObjectId(userInfo.userId),
});
// 增加积分记录数据
await collection.insertOne({
userId: userInfo.userId,
count: input.count,
points: (result?.points || 0) + input.count!,
operateType: (input.count || 0) >= 0 ? 'added' : 'reduce',
createdAt: new Date(),
updatedAt: new Date(),});
// 批改用户积分数据
await userCollection.updateOne({ _id: new ObjectId(userInfo.userId) },
{
$set: {points: (result?.points || 0) + input.count!,
updatedAt: new Date(),},
}
);
return {
code: 200,
data: {},
success: true,
};
} catch (error: any) {console.log(error.message);
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: error.message,
});
}
}),
});
// routers.ts
import {router} from './trpc';
import {authRouter} from './auth-router';
import {PointsRouter} from './points-router';
export const appRouter = router({
authRouter,
PointsRouter,
});
// export type definition of API
export type AppRouter = typeof appRouter;
至此路由文件曾经定义实现。
-
创立客户端
trpc
申请,client.ts
import {createTRPCReact} from '@trpc/react-query'; import {type AppRouter} from './routers'; export const trpc = createTRPCReact<AppRouter>({});
-
创立
trpc
上下文组件'use client'; import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; import {httpBatchLink} from '@trpc/client'; import React, {useState} from 'react'; import {trpc} from '@/lib/trpc/client'; function getBaseUrl() {if (typeof window !== 'undefined') { // In the browser, we return a relative URL return ''; } // When rendering on the server, we return an absolute URL // reference for vercel.com if (process.env.VERCEL_URL) {return `https://${process.env.VERCEL_URL}`; } // assume localhost return `http://localhost:${process.env.PORT ?? 3000}`; } export function TrpcProviders({children}: {children: React.ReactNode}) {const [queryClient] = useState(() => new QueryClient({})); const [trpcClient] = useState(() => trpc.createClient({ links: [ httpBatchLink({url: getBaseUrl() + '/api/trpc', }), ], }) ); return (<trpc.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> </trpc.Provider> ); }
在
layout.tsx
文件中引入// layout.tsx import {TrpcProviders} from 'xxx' export default function RootLayout({children,}: {children: React.ReactNode;}) { return ( <html lang="en"> <body> <TrpcProviders>{children}</TrpcProviders> </body> </html> ); }
-
在
page.tsx
文件中调用trpc
路由申请'use client'; import {useEffect} from 'react'; import {trpc} from '@/lib/trpc/client'; export function PageHome() {const { mutate} = trpc.authRouter.signIn.useMutation(); useEffect(() => { mutate({ name: 'pxs', pwd: 'pxs', }) }, []); return (<div>1111</div>) }
服务端组件应用
trpc
路由申请 -
定义服务端申请,创立
serverClient.ts
import {appRouter} from './routers'; import {createCallerFactory} from './trpc'; const createCaller = createCallerFactory(appRouter); // 这里目前博主拿不到 req 和可写的 resHeaders export const serverClient = createCaller({});
-
服务端组件调用
import {serverClient} from '@/lib/trpc/serverClient'; export default async function ServerPage() {const res = await serverClient.authRouter.hello({ name: 'pxs'}); return <div>{JSON.stringify(res)}</div>; }
至此 next14 app 应用 trpc 已实现。
分割:1612565136@qq.com
示例仓库:暂无(后续补上)
正文完