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

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(),
});

解决方案

问题根源

  1. JSON序列化格式不兼容:前端new Date()生成的日期对象,经过JSON.stringify后会转为ISO字符串(如"2024-05-20T12:34:56.789Z"),但后端Zod的z.date()默认只接受Date对象,无法识别字符串格式,导致校验失败返回400。
  2. 字段规则冲突:表结构中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 00:00:28