You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Fastify/Sessions+Prisma自定义会话存储时请求无响应排查

问题:Fastify会话认证请求无响应,数据库已创建会话

我用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返回值。

修复步骤

  1. 修改存储方法为回调模式
    必须手动调用框架传入的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方法同理修改
    
  2. 优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 06:53:18