diff --git a/functions/api/messages.js b/functions/api/messages.js
index ce6bee6..cddd739 100644
--- a/functions/api/messages.js
+++ b/functions/api/messages.js
@@ -2,7 +2,7 @@
* Cloudflare Pages Functions
* GET /api/messages → 返回最新留言列表
* POST /api/messages → 新增一条留言
- * 依赖绑定:环境变量 DB(D1 数据库)
+ * 依赖绑定:环境变量 DB(D1 数据库,建表语句见 /schema.sql)
*/
const MAX_NAME = 12;
@@ -103,6 +103,9 @@ export async function onRequestPost({ request, env }) {
request.headers.get('CF-Connecting-IP') ||
request.headers.get('X-Forwarded-For') ||
'';
+ // 审计信息:入库保存,但列表接口只返回 id/name/text/time,不会外泄
+ const ua = (request.headers.get('User-Agent') || '').slice(0, 400);
+ const country = request.headers.get('CF-IPCountry') || '';
const now = Math.floor(Date.now() / 1000);
try {
@@ -130,10 +133,10 @@ export async function onRequestPost({ request, env }) {
const time = today();
const res = await env.DB
.prepare(
- `INSERT INTO messages (name, text, time, ip, created_at)
- VALUES (?, ?, ?, ?, ?)`
+ `INSERT INTO messages (name, text, time, ip, ua, country, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
)
- .bind(name, text, time, ip, now)
+ .bind(name, text, time, ip, ua, country, now)
.run();
// 5. 顺手裁剪:只保留最近 500 条,避免无限增长
diff --git a/functions/api/submit.ts b/functions/api/submit.ts
index b1bb4c3..a823562 100644
--- a/functions/api/submit.ts
+++ b/functions/api/submit.ts
@@ -153,6 +153,38 @@ function renderMarkdown(f: {
return `${fm.join('\n')}\n\n${f.body.trim()}\n`;
}
+// ---------- 请求方环境信息(仅写入 PR 描述,不写入仓库文件) ----------
+interface SubmitMeta {
+ ip: string;
+ ua: string;
+ country: string;
+ time: string;
+}
+
+function requestMeta(request: Request): SubmitMeta {
+ const fwd = request.headers.get('X-Forwarded-For')?.split(',')[0]?.trim();
+ return {
+ ip: request.headers.get('CF-Connecting-IP') ?? fwd ?? '',
+ ua: (request.headers.get('User-Agent') ?? '').slice(0, 400),
+ country: request.headers.get('CF-IPCountry') ?? '',
+ time: new Date().toISOString(),
+ };
+}
+
+function metaSection(m: SubmitMeta): string {
+ return [
+ '',
+ '提交环境信息(仅记录于 PR,未写入仓库文件)
',
+ '',
+ `- **IP:** \`${m.ip || '未知'}\``,
+ `- **User-Agent:** \`${m.ua || '未知'}\``,
+ `- **国家/地区:** \`${m.country || '未知'}\``,
+ `- **提交时间:** \`${m.time}\``,
+ '',
+ ' ',
+ ].join('\n');
+}
+
// ---------- 音乐投稿 ----------
function renderSongJson(f: {
title: string; artist: string; link?: string;
@@ -171,6 +203,7 @@ function renderSongJson(f: {
async function handleSongSubmit(
env: Env, fd: FormData, json: (data: unknown, status?: number) => Response,
+ meta: SubmitMeta,
): Promise {
const title = (fd.get('title') as string | null)?.trim() ?? '';
const artist = (fd.get('artist') as string | null)?.trim() ?? '';
@@ -259,6 +292,7 @@ async function handleSongSubmit(
'',
message ? `**留言:**\n\n${message}` : '',
'',
+ metaSection(meta),
'---',
'由音乐投稿表单自动创建,请审核后合并。',
].filter(Boolean).join('\n');
@@ -288,6 +322,8 @@ export const onRequestPost: PagesFunction = async ({ request, env }) => {
headers: { 'content-type': 'application/json; charset=utf-8' },
});
+ const meta = requestMeta(request);
+
try {
// 1) 解析 multipart
const ct = request.headers.get('content-type') || '';
@@ -314,7 +350,7 @@ export const onRequestPost: PagesFunction = async ({ request, env }) => {
// 1.7) 音乐投稿分支(type=music)
const submitType = (fd.get('type') as string | null)?.trim() ?? 'chronicle';
if (submitType === 'music') {
- return await handleSongSubmit(env, fd, json);
+ return await handleSongSubmit(env, fd, json, meta);
}
const title = (fd.get('title') as string | null)?.trim() ?? '';
@@ -449,6 +485,7 @@ export const onRequestPost: PagesFunction = async ({ request, env }) => {
'',
message ? `**留言:**\n\n${message}` : '',
'',
+ metaSection(meta),
'---',
'由 Astro 投稿表单自动创建,请审核后合并。',
].filter(Boolean).join('\n');
diff --git a/schema.sql b/schema.sql
new file mode 100644
index 0000000..97642c0
--- /dev/null
+++ b/schema.sql
@@ -0,0 +1,19 @@
+-- D1 数据库结构(留言墙 /api/messages 使用,绑定名 DB)
+--
+-- 全新部署:直接执行下面的 CREATE TABLE。
+-- 已有部署:老表缺少 ua / country 列时,执行文件末尾注释里的 ALTER TABLE。
+
+CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL, -- 留言人昵称
+ text TEXT NOT NULL, -- 留言内容
+ time TEXT NOT NULL, -- 展示用日期 YYYY.MM.DD
+ ip TEXT, -- 访客 IP(限流 + 审计)
+ ua TEXT, -- User-Agent(审计)
+ country TEXT, -- 国家/地区代码,来自 CF-IPCountry(审计)
+ created_at INTEGER NOT NULL -- Unix 秒,排序与限流用
+);
+
+-- 已有部署的增量迁移:
+-- ALTER TABLE messages ADD COLUMN ua TEXT;
+-- ALTER TABLE messages ADD COLUMN country TEXT;