使用Fastify/Sessions+Prisma自定义会话存储时请求无响应排查
我用TypeScript开发基于会话的认证系统,选择fastify/session框架并通过Prisma存储会话。目前遇到的问题是:访问根路径/时,Postman一直显示“发送请求中”,无任何响应返回,但数据库已成功创建会话,且控制台打印的sessionId与数据库中的记录一致。
我注意到fastify/session源码的index.d.ts文件中,get、set、destroy方法的声明均返回void,这是否会导致问题?另外,我的Prisma Session模型是否存在问题?
相关代码
index.ts文件
import Fastify, { FastifyReply, FastifyRequest } from "fastify"; import fastifyCookie from "@fastify/cookie"; import fastifySession from "@fastify/session"; import { db } from "./libs/db"; const fastify = Fastify(); const PORT = Number(process.env.PORT) || 3000; fastify.register(fastifyCookie); fastify.register(fastifySession, { store: { set: async (sid: string, session: object) => { await db.session.upsert({ where: { sid }, update: { session: JSON.stringify(session) }, create: { sid, session: JSON.stringify(session) }, }); }, get: async (sid: string) => { try { const storedSession = await db.session.findUnique({ where: { sid } }); return storedSession ? JSON.parse(storedSession.session) : null; } catch (error) { return null; } }, destroy: async (sid: string) => { await db.session.delete({ where: { sid } }); }, }, cookieName: "sid", secret: "supersecretkeysupersecretkeysupersecretkeysupersecretkey", cookie: { maxAge: 1000 * 60 * 60, secure: process.env.NODE_ENV === "production", }, }); fastify.get("/", (req: FastifyRequest, res: FastifyReply) => { console.log(req.session.sessionId); return { success: "Test sucess!" }; }); try { fastify.listen({ port: PORT }); console.log(`Server is listening port ${PORT}`); } catch (error) { fastify.log.error(error); process.exit(1); }
schema.prisma文件
generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model Session { sid String @id @default(uuid()) session String }
fastify/session的index.d.ts方法声明
set( sessionId: string, session: Fastify.Session, callback: Callback ): void; get( sessionId: string, callback: CallbackSession ): void; destroy(sessionId: string, callback: Callback): void;
核心原因
fastify/session的自定义存储要求回调式API,你当前用async/await的Promise风格实现,框架无法感知异步操作完成,导致请求一直被挂起。虽然类型声明显示方法返回void,本质是因为框架依赖回调传递结果,而非Promise返回值。
修复步骤
修改存储方法为回调模式
必须手动调用框架传入的callback参数,通知操作成功或失败:store: { set: (sid: string, session: object, callback: (err?: Error) => void) => { db.session.upsert({ where: { sid }, update: { session: JSON.stringify(session) }, create: { sid, session: JSON.stringify(session) }, }) .then(() => callback()) // 操作成功,无参调用回调 .catch(err => callback(err)); // 操作失败,传递错误 }, get: (sid: string, callback: (err?: Error, session?: object | null) => void) => { db.session.findUnique({ where: { sid } }) .then(storedSession => { const sessionData = storedSession ? JSON.parse(storedSession.session) : null; callback(null, sessionData); // 成功时传递null错误+会话数据 }) .catch(err => callback(err)); // 失败时传递错误 }, destroy: (sid: string, callback: (err?: Error) => void) => { db.session.delete({ where: { sid } }) .then(() => callback()) .catch(err => callback(err)); }, },若想保留
async/await,可以包装成回调形式:set: async (sid: string, session: object, callback: (err?: Error) => void) => { try { await db.session.upsert({ where: { sid }, update: { session: JSON.stringify(session) }, create: { sid, session: JSON.stringify(session) }, }); callback(); // 成功通知 } catch (err) { callback(err as Error); // 错误通知 } }, // get和destroy方法同理修改优化Prisma Session模型(可选但推荐)
现有模型可正常工作,但添加expires字段能支持会话过期自动清理:model Session { sid String @id @default(uuid()) session String expires DateTime // 新增过期时间字段 }对应修改
set方法,存储会话过期时间:set: async (sid: string, session: { expires?: number } & object, callback: (err?: Error) => void) => { try { await db.session.upsert({ where: { sid }, update: { session: JSON.stringify(session), expires: session.expires ? new Date(session.expires) : new Date(Date.now() + 3600000) }, create: { sid, session: JSON.stringify(session), expires: session.expires ? new Date(session.expires) : new Date(Date.now() + 3600000) }, }); callback(); } catch (err) { callback(err as Error); } },
关于类型声明的说明
index.d.ts中方法返回void是因为框架采用回调式设计,方法本身不返回Promise,所有结果和错误都通过传入的callback函数传递。你之前的async函数虽然返回Promise,但框架不会处理这个Promise,导致异步操作完成后框架未收到通知,请求一直处于挂起状态。
内容的提问来源于stack exchange,提问作者Kaan

