feat(api): auto-create and migrate messages table on first request

ensureSchema runs idempotent CREATE TABLE / ALTER TABLE (skipping
duplicate-column errors), cached once per isolate with retry on failure;
schema.sql becomes reference-only documentation
This commit is contained in:
hanyixuanten committed 2026-10-25 12:15:52 +00:00
1 parent ca422df0c3
commit 4b489a3ebb
2 files changed
+46 -7

No files matched your search

+43 -1
View File
@@ -2,7 +2,8 @@
* Cloudflare Pages Functions * Cloudflare Pages Functions
* GET /api/messages → 返回最新留言列表 * GET /api/messages → 返回最新留言列表
* POST /api/messages → 新增一条留言 * POST /api/messages → 新增一条留言
* 依赖绑定:环境变量 DB(D1 数据库,建表语句见 /schema.sql) * 依赖绑定:环境变量 DB(D1 数据库)
* 表结构由 ensureSchema 在首次访问时自动创建/迁移,无需手动执行 SQL
*/ */
const MAX_NAME = 12; const MAX_NAME = 12;
@@ -40,6 +41,43 @@ function clean(str, max) {
.slice(0, max); .slice(0, max);
} }
/* ---------- 表结构:自动建表/迁移(幂等,旧表缺列时自动补齐) ---------- */
const SCHEMA_SQL = [
`CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
text TEXT NOT NULL,
time TEXT NOT NULL,
ip TEXT,
ua TEXT,
country TEXT,
created_at INTEGER NOT NULL
)`,
'ALTER TABLE messages ADD COLUMN ua TEXT',
'ALTER TABLE messages ADD COLUMN country TEXT',
];
let schemaPromise = null; // 每个 isolate 只迁移一次;失败不缓存,下个请求重试
function ensureSchema(env) {
if (!schemaPromise) {
schemaPromise = (async () => {
for (const sql of SCHEMA_SQL) {
try {
await env.DB.prepare(sql).run();
} catch (err) {
// 列已存在时 SQLite 报 duplicate column name,属预期
if (!/duplicate column/i.test(String(err && err.message))) throw err;
}
}
})().catch((err) => {
schemaPromise = null;
throw err;
});
}
return schemaPromise;
}
/* ---------- OPTIONS(预检) ---------- */ /* ---------- OPTIONS(预检) ---------- */
export async function onRequestOptions() { export async function onRequestOptions() {
return new Response(null, { return new Response(null, {
@@ -58,6 +96,8 @@ export async function onRequestGet({ env }) {
if (!env.DB) return json({ ok: false, error: '数据库未绑定' }, 500); if (!env.DB) return json({ ok: false, error: '数据库未绑定' }, 500);
try { try {
await ensureSchema(env);
const { results } = await env.DB const { results } = await env.DB
.prepare( .prepare(
`SELECT id, name, text, time `SELECT id, name, text, time
@@ -109,6 +149,8 @@ export async function onRequestPost({ request, env }) {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
try { try {
await ensureSchema(env);
if (ip) { if (ip) {
const last = await env.DB const last = await env.DB
.prepare( .prepare(
+3 -6
View File
@@ -1,7 +1,8 @@
-- D1 数据库结构(留言墙 /api/messages 使用,绑定名 DB) -- D1 数据库结构(留言墙 /api/messages 使用,绑定名 DB)
-- --
-- 全新部署:直接执行下面的 CREATE TABLE。 -- 无需手动执行:Functions 首次访问会自动建表并为旧表补齐缺失的列
-- 已有部署:老表缺少 ua / country 列时,执行文件末尾注释里的 ALTER TABLE。 -- (见 functions/api/messages.js 的 ensureSchema)。本文件仅作结构参考,
-- 需要手工干预(如排查问题)时再执行。
CREATE TABLE IF NOT EXISTS messages ( CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -13,7 +14,3 @@ CREATE TABLE IF NOT EXISTS messages (
country TEXT, -- 国家/地区代码,来自 CF-IPCountry(审计) country TEXT, -- 国家/地区代码,来自 CF-IPCountry(审计)
created_at INTEGER NOT NULL -- Unix 秒,排序与限流用 created_at INTEGER NOT NULL -- Unix 秒,排序与限流用
); );
-- 已有部署的增量迁移:
-- ALTER TABLE messages ADD COLUMN ua TEXT;
-- ALTER TABLE messages ADD COLUMN country TEXT;