refactor(admin): 提取数据库表创建逻辑为共享函数

重构导出和导入统计数据的 API,将重复的 CREATE TABLE 语句提取为独立的 ensureStatsTables 函数,以消除代码重复并提高可维护性。同时统一代码格式和缩进。
This commit is contained in:
anghunk
2026-01-29 20:28:14 +08:00
parent d126e2c4c3
commit 5618dea2a4
2 changed files with 129 additions and 152 deletions

View File

@@ -1,52 +1,39 @@
import { Context } from 'hono'; import { Context } from 'hono';
import { Bindings } from '../../bindings'; import { Bindings } from '../../bindings';
async function ensureStatsTables(env: Bindings) {
await env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS page_stats (id INTEGER PRIMARY KEY AUTOINCREMENT, post_slug TEXT UNIQUE NOT NULL, post_title TEXT, post_url TEXT, pv INTEGER NOT NULL DEFAULT 0, last_visit_at INTEGER, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)'
).run();
await env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS page_visit_daily (id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, domain TEXT, count INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)'
).run();
await env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS Likes (id INTEGER PRIMARY KEY AUTOINCREMENT, page_slug TEXT NOT NULL, user_id TEXT NOT NULL, created_at INTEGER NOT NULL, UNIQUE(page_slug, user_id))'
).run();
}
export const getStatsData = async (env: Bindings) => { export const getStatsData = async (env: Bindings) => {
// Ensure tables exist await ensureStatsTables(env);
await env.CWD_DB.exec(`
CREATE TABLE IF NOT EXISTS page_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_slug TEXT UNIQUE NOT NULL,
post_title TEXT,
post_url TEXT,
pv INTEGER NOT NULL DEFAULT 0,
last_visit_at INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS page_visit_daily (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
domain TEXT,
count INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS Likes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_slug TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
UNIQUE(page_slug, user_id)
);
`);
const { results: pageStats } = await env.CWD_DB.prepare('SELECT * FROM page_stats').all(); const { results: pageStats } = await env.CWD_DB.prepare('SELECT * FROM page_stats').all();
const { results: dailyVisits } = await env.CWD_DB.prepare('SELECT * FROM page_visit_daily').all(); const { results: dailyVisits } = await env.CWD_DB.prepare('SELECT * FROM page_visit_daily').all();
const { results: likes } = await env.CWD_DB.prepare('SELECT * FROM Likes').all(); const { results: likes } = await env.CWD_DB.prepare('SELECT * FROM Likes').all();
return { return {
page_stats: pageStats, page_stats: pageStats,
page_visit_daily: dailyVisits, page_visit_daily: dailyVisits,
likes: likes likes: likes
}; };
}; };
export const exportStats = async (c: Context<{ Bindings: Bindings }>) => { export const exportStats = async (c: Context<{ Bindings: Bindings }>) => {
try { try {
const data = await getStatsData(c.env); const data = await getStatsData(c.env);
return c.json(data); return c.json(data);
} catch (e: any) { } catch (e: any) {
return c.json({ message: e.message || '导出统计数据失败' }, 500); return c.json({ message: e.message || '导出统计数据失败' }, 500);
} }
}; };

View File

@@ -1,125 +1,115 @@
import { Context } from 'hono'; import { Context } from 'hono';
import { Bindings } from '../../bindings'; import { Bindings } from '../../bindings';
async function ensureStatsTables(env: Bindings) {
await env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS page_stats (id INTEGER PRIMARY KEY AUTOINCREMENT, post_slug TEXT UNIQUE NOT NULL, post_title TEXT, post_url TEXT, pv INTEGER NOT NULL DEFAULT 0, last_visit_at INTEGER, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)'
).run();
await env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS page_visit_daily (id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, domain TEXT, count INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)'
).run();
await env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS Likes (id INTEGER PRIMARY KEY AUTOINCREMENT, page_slug TEXT NOT NULL, user_id TEXT NOT NULL, created_at INTEGER NOT NULL, UNIQUE(page_slug, user_id))'
).run();
}
export const saveStatsData = async (env: Bindings, data: any) => { export const saveStatsData = async (env: Bindings, data: any) => {
// Ensure tables exist await ensureStatsTables(env);
await env.CWD_DB.exec(`
CREATE TABLE IF NOT EXISTS page_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_slug TEXT UNIQUE NOT NULL,
post_title TEXT,
post_url TEXT,
pv INTEGER NOT NULL DEFAULT 0,
last_visit_at INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS page_visit_daily (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
domain TEXT,
count INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS Likes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_slug TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
UNIQUE(page_slug, user_id)
);
`);
const stmts: any[] = []; const stmts: any[] = [];
// 1. page_stats if (Array.isArray(data.page_stats)) {
if (Array.isArray(data.page_stats)) { for (const item of data.page_stats) {
for (const item of data.page_stats) { const fields = [
const fields = ['post_slug', 'post_title', 'post_url', 'pv', 'last_visit_at', 'created_at', 'updated_at']; 'post_slug',
const values = [ 'post_title',
item.post_slug, 'post_url',
item.post_title, 'pv',
item.post_url, 'last_visit_at',
item.pv, 'created_at',
item.last_visit_at, 'updated_at'
item.created_at, ];
item.updated_at const values = [
]; item.post_slug,
if (item.id) { item.post_title,
fields.unshift('id'); item.post_url,
values.unshift(item.id); item.pv,
} item.last_visit_at,
const placeholders = fields.map(() => '?').join(', '); item.created_at,
stmts.push( item.updated_at
env.CWD_DB.prepare(`INSERT OR REPLACE INTO page_stats (${fields.join(', ')}) VALUES (${placeholders})`).bind(...values) ];
); if (item.id) {
} fields.unshift('id');
} values.unshift(item.id);
}
const placeholders = fields.map(() => '?').join(', ');
stmts.push(
env.CWD_DB.prepare(
`INSERT OR REPLACE INTO page_stats (${fields.join(', ')}) VALUES (${placeholders})`
).bind(...values)
);
}
}
// 2. page_visit_daily if (Array.isArray(data.page_visit_daily)) {
if (Array.isArray(data.page_visit_daily)) { for (const item of data.page_visit_daily) {
for (const item of data.page_visit_daily) { const fields = ['date', 'domain', 'count', 'created_at', 'updated_at'];
const fields = ['date', 'domain', 'count', 'created_at', 'updated_at']; const values = [item.date, item.domain, item.count, item.created_at, item.updated_at];
const values = [ if (item.id) {
item.date, fields.unshift('id');
item.domain, values.unshift(item.id);
item.count, }
item.created_at, const placeholders = fields.map(() => '?').join(', ');
item.updated_at stmts.push(
]; env.CWD_DB.prepare(
if (item.id) { `INSERT OR REPLACE INTO page_visit_daily (${fields.join(', ')}) VALUES (${placeholders})`
fields.unshift('id'); ).bind(...values)
values.unshift(item.id); );
} }
const placeholders = fields.map(() => '?').join(', '); }
stmts.push(
env.CWD_DB.prepare(`INSERT OR REPLACE INTO page_visit_daily (${fields.join(', ')}) VALUES (${placeholders})`).bind(...values)
);
}
}
// 3. Likes if (Array.isArray(data.likes)) {
if (Array.isArray(data.likes)) { for (const item of data.likes) {
for (const item of data.likes) { const fields = ['page_slug', 'user_id', 'created_at'];
const fields = ['page_slug', 'user_id', 'created_at']; const values = [item.page_slug, item.user_id, item.created_at];
const values = [ if (item.id) {
item.page_slug, fields.unshift('id');
item.user_id, values.unshift(item.id);
item.created_at }
]; const placeholders = fields.map(() => '?').join(', ');
if (item.id) { stmts.push(
fields.unshift('id'); env.CWD_DB.prepare(
values.unshift(item.id); `INSERT OR REPLACE INTO Likes (${fields.join(', ')}) VALUES (${placeholders})`
} ).bind(...values)
const placeholders = fields.map(() => '?').join(', '); );
stmts.push( }
env.CWD_DB.prepare(`INSERT OR REPLACE INTO Likes (${fields.join(', ')}) VALUES (${placeholders})`).bind(...values) }
);
}
}
// Batch execute const BATCH_SIZE = 50;
const BATCH_SIZE = 50; for (let i = 0; i < stmts.length; i += BATCH_SIZE) {
for (let i = 0; i < stmts.length; i += BATCH_SIZE) { const batch = stmts.slice(i, i + BATCH_SIZE);
const batch = stmts.slice(i, i + BATCH_SIZE); await env.CWD_DB.batch(batch);
await env.CWD_DB.batch(batch); }
}
}; };
export const importStats = async (c: Context<{ Bindings: Bindings }>) => { export const importStats = async (c: Context<{ Bindings: Bindings }>) => {
try { try {
const body = await c.req.json(); const body = await c.req.json();
if (!body || typeof body !== 'object') { if (!body || typeof body !== 'object') {
return c.json({ message: '数据格式错误' }, 400); return c.json({ message: '数据格式错误' }, 400);
} }
await saveStatsData(c.env, body); await saveStatsData(c.env, body);
const statsCount = (body.page_stats?.length || 0) + (body.page_visit_daily?.length || 0) + (body.likes?.length || 0); const statsCount =
return c.json({ message: `成功导入 ${statsCount} 条统计数据` }); (body.page_stats?.length || 0) +
} catch (e: any) { (body.page_visit_daily?.length || 0) +
console.error(e); (body.likes?.length || 0);
return c.json({ message: e.message || '导入统计数据失败' }, 500); return c.json({ message: `成功导入 ${statsCount}统计数据` });
} } catch (e: any) {
console.error(e);
return c.json({ message: e.message || '导入统计数据失败' }, 500);
}
}; };