如何在GraphQL Pothos Builder中实现登录接口的自定义Mutation输出?
修复Pothos Builder中自定义UserAndToken类型的问题
我使用GraphQL Pothos Builder开发了登录组件,ORM采用Prisma。目前仅能返回User类型的响应,希望实现包含用户信息与令牌的自定义输出。尝试创建UserAndToken ObjectRef但未成功,已尝试一周仍无法解决,请求帮助修复该问题。
原代码如下:
import prisma from "@/prisma/client"; import { User as UserModel } from "@prisma/client"; import bcrypt from "bcrypt"; import { GraphQLError } from "graphql"; import ms from "ms"; import { cookies } from "next/headers"; import { builder } from "../builder"; import { User } from "../types/User"; import { generateAccessToken, generateRefreshToken, } from "../utils/generateToken"; const UserAndToken = builder .objectRef<{ user: UserModel, accessToken: string }>("UserAndToken") .implement({ fields: t => ({ user: t.field({ type: User, resolve: (parent) => parent.user }) }) }) builder.mutationFields((t) => ({ login: t.prismaField({ description: "Login to time boxing platform.", type: UserAndToken, args: { email: t.arg({ type: "Email", required: true }), password: t.arg.string({ required: true }), }, resolve: async (query, _, args) => { const user = await prisma.user.findUnique({ where: { email: args.email }, }); if ( !user || !bcrypt.compare(args.password, user.password) ) { return Promise.reject( new GraphQLError( "Either email or password is incorrect." ) ); } try { const accessToken = generateAccessToken(user); const refreshToken = generateRefreshToken(user); cookies().set({ name: "X-REFRESH-TOKEN", value: refreshToken, httpOnly: true, sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", secure: process.env.NODE_ENV === "production", expires: new Date(Date.now() + ms("7d")), //7d }); await prisma.session.create({ data: { refreshToken, expiresAt: new Date(new Date().getDate() + 7), userId: user.id, }, }); // update user's last login. await prisma.user.update({ where: { id: user.id }, data: { lastLogin: new Date() }, }); return { user, accessToken }; } catch (err) { console.log(err) return Promise.reject( new GraphQLError( "Unable to login. Try again later." ) ); } }, }), }));
问题分析
- 自定义类型字段缺失:定义的
UserAndToken类型只实现了user字段,但返回的对象包含user和accessToken,GraphQL无法识别未定义的accessToken字段。 - bcrypt验证错误:
bcrypt.compare是异步方法,原代码用同步调用会导致密码验证逻辑失效,永远返回false。 - 会话过期时间错误:
new Date(new Date().getDate() + 7)只会修改日期部分,不会正确设置7天后的过期时间,应该用Date.now() + ms("7d")统一逻辑。
修复后的代码
import prisma from "@/prisma/client"; import { User as UserModel } from "@prisma/client"; import bcrypt from "bcrypt"; import { GraphQLError } from "graphql"; import ms from "ms"; import { cookies } from "next/headers"; import { builder } from "../builder"; import { User } from "../types/User"; import { generateAccessToken, generateRefreshToken, } from "../utils/generateToken"; // 正确实现包含user和accessToken的自定义类型 const UserAndToken = builder .objectRef<{ user: UserModel, accessToken: string }>("UserAndToken") .implement({ fields: t => ({ user: t.field({ type: User, resolve: (parent) => parent.user }), // 添加accessToken字段定义 accessToken: t.string({ resolve: (parent) => parent.accessToken }) }) }) builder.mutationFields((t) => ({ login: t.prismaField({ description: "Login to time boxing platform.", type: UserAndToken, args: { email: t.arg({ type: "Email", required: true }), password: t.arg.string({ required: true }), }, resolve: async (query, _, args) => { const user = await prisma.user.findUnique({ where: { email: args.email }, }); // 修复bcrypt异步调用问题 if (!user || !(await bcrypt.compare(args.password, user.password))) { throw new GraphQLError("Either email or password is incorrect."); } try { const accessToken = generateAccessToken(user); const refreshToken = generateRefreshToken(user); cookies().set({ name: "X-REFRESH-TOKEN", value: refreshToken, httpOnly: true, sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", secure: process.env.NODE_ENV === "production", expires: new Date(Date.now() + ms("7d")), }); await prisma.session.create({ data: { refreshToken, // 修复会话过期时间计算 expiresAt: new Date(Date.now() + ms("7d")), userId: user.id, }, }); await prisma.user.update({ where: { id: user.id }, data: { lastLogin: new Date() }, }); // 返回与自定义类型匹配的结构 return { user, accessToken }; } catch (err) { console.error(err); throw new GraphQLError("Unable to login. Try again later."); } }, }), }));
关键修复点
- 给
UserAndToken类型添加accessToken字符串字段,确保返回的对象结构与类型定义完全匹配。 - 将
bcrypt.compare改为异步调用(添加await),保证密码验证逻辑正确执行。 - 统一会话过期时间的计算方式,避免日期设置错误。
- 用
throw替代Promise.reject,代码更简洁符合异步函数规范。
内容的提问来源于stack exchange,提问作者Jenny Patel
相关产品推荐
相关产品推荐

