PostgreSQL(Drizzle-ORM)中timestamp能否存储并更新new Date()?
问题:PostgreSQL(Drizzle-ORM)更新timestamp字段返回400错误
我通过Drizzle-ORM在PostgreSQL中定义了documents表,其中latestUpdatedAt是notNull的timestamp类型字段,默认值为当前时间。需求是每次更新latestText字段时,同步将latestUpdatedAt设为当前时间(new Date()),但前端传入该字段后接口返回400错误,注释掉该字段则更新正常。想确认PostgreSQL是否支持存储并更新new Date(),以及如何解决这个问题。
表结构
export const documentsTable = pgTable( "documents", { id: serial("id").primaryKey(), displayId: uuid("display_id").defaultRandom().notNull().unique(), title: varchar("title", { length: 100 }).notNull(), content: text("content").notNull(), latestText: text("latestText").notNull(), pinnedMessageId: varchar("pinned_message_id").default(""), retrivedMessage: integer("retrived_message").default(-100000), latestUpdatedAt: timestamp("latestUpdatedAt").notNull().defaultNow(), // 目标字段 }, (table) => ({ displayIdIndex: index("display_id_index").on(table.displayId), }), );
更新逻辑
const updateDocument = async () => { if (!debouncedDocument) return; const res = await fetch(`/api/documents/${documentId}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ title: debouncedDocument.title, content: debouncedDocument.content, latestText: debouncedDocument.latestText, messages: debouncedDocument.messages, pinnedMessageId: debouncedDocument.pinnedMessageId, retrivedMessage: debouncedDocument.retrivedMessage, latestUpdatedAt: debouncedDocument.latestUpdatedAt, // 传入该字段导致400 }), }); if (!res.ok) { return; } const data: Document = await res.json(); else if(dbDocument?.latestText!== data.latestText){ router.refresh(); console.log("refreshed_message"); } }; const setContent = (newContent: string, newLatestText:string) => { if (document === null) return; console.log(new Date()); setDocument({ ...document, latestText: newLatestText, latestUpdatedAt: new Date(), // 怀疑此处是问题点 }); };
校验Schema
import { z } from "zod"; export const updateDocSchema = z.object({ title: z.string().optional(), content: z.string().optional(), latestText: z.string().optional(), messages: z.array(z.object({ text: z.string().optional(), author: z.string().optional(), sendAt: z.date().optional() })).optional(), pinnedMessageId: z.string().optional(), retrivedMessage: z.number().optional(), latestUpdatedAt: z.date().optional(), });
解决方案
问题根源
- JSON序列化格式不兼容:前端
new Date()生成的日期对象,经过JSON.stringify后会转为ISO字符串(如"2024-05-20T12:34:56.789Z"),但后端Zod的z.date()默认只接受Date对象,无法识别字符串格式,导致校验失败返回400。 - 字段规则冲突:表结构中
latestUpdatedAt是notNull,但更新Schema设为optional,若前端传入格式错误的值,会直接触发校验报错。
具体解决方法
方法1:修改Zod Schema,支持字符串转Date
使用Zod的coerce功能,自动将合法的日期字符串转为Date对象:
// 替换原Schema中的latestUpdatedAt字段 latestUpdatedAt: z.coerce.date().optional(),
z.coerce.date()会自动处理ISO字符串、时间戳等格式,解决序列化后的格式不匹配问题。
方法2:后端自动维护更新时间(推荐)
让后端在检测到latestText更新时,自动设置latestUpdatedAt为数据库当前时间,前端无需传递该字段:
- 前端修改:删除
updateDocument中latestUpdatedAt的传递,setContent里也不用设置该字段。 - 后端Drizzle更新逻辑:
await update(documentsTable) .set({ latestText: updatedData.latestText, // 其他需要更新的字段... latestUpdatedAt: sql`CURRENT_TIMESTAMP` // 使用数据库当前时间 }) .where(eq(documentsTable.displayId, documentId));
这种方式更可靠,避免前后端时间不一致,也彻底规避了前端传递的格式问题。
方法3:前端传递标准ISO字符串
前端将new Date()转为ISO字符串后再传递:
setDocument({ ...document, latestText: newLatestText, latestUpdatedAt: new Date().toISOString(), // 转为标准字符串 });
同时修改Zod Schema接受字符串格式:
latestUpdatedAt: z.string().datetime().optional(),
PostgreSQL的timestamp类型可以直接识别ISO字符串,后端无需额外转换即可存入数据库。
内容的提问来源于stack exchange,提问作者susanliu
相关产品推荐
相关产品推荐

