refactor(comment): 统一字段命名并改用时间戳存储创建时间

将 author 字段重命名为 name,pub_date 改为 created 并使用时间戳存储
更新相关 API、数据库 schema 和前端组件以适配新字段
同时将 user_agent 简化为 ua 并改进日期处理逻辑
This commit is contained in:
anghunk
2026-01-20 13:50:08 +08:00
parent 520cfcc447
commit eb0a7a32aa
15 changed files with 112 additions and 99 deletions

View File

@@ -4,7 +4,7 @@ import { Bindings } from '../../bindings';
export const exportComments = async (c: Context<{ Bindings: Bindings }>) => {
try {
const { results } = await c.env.CWD_DB.prepare(
'SELECT * FROM Comment ORDER BY pub_date DESC'
'SELECT * FROM Comment ORDER BY created DESC'
).all();
return c.json(results);

View File

@@ -25,16 +25,16 @@ export const importComments = async (c: Context<{ Bindings: Bindings }>) => {
const stmts = comments.map((comment: any) => {
const {
id,
pub_date,
created,
post_slug,
author,
name,
email,
url,
ip_address,
device,
os,
browser,
user_agent,
ua,
content_text,
content_html,
parent_id,
@@ -48,15 +48,26 @@ export const importComments = async (c: Context<{ Bindings: Bindings }>) => {
// 构建 SQL。
// 能够处理 id 存在或不存在的情况
const fields = [
'pub_date', 'post_slug', 'author', 'email', 'url',
'ip_address', 'device', 'os', 'browser', 'user_agent',
'created', 'post_slug', 'name', 'email', 'url',
'ip_address', 'device', 'os', 'browser', 'ua',
'content_text', 'content_html', 'parent_id', 'status'
];
const values = [
pub_date, post_slug, author, email, url,
ip_address, device, os, browser, user_agent,
content_text, content_html, parent_id, status
].map(v => v === undefined ? null : v);
created || Date.now(),
post_slug || "",
name || "Anonymous",
email || "",
url || null,
ip_address || null,
device || null,
os || null,
browser || null,
ua || null,
content_text || "",
content_html || "",
parent_id || null,
status || "approved"
];
if (id) {
fields.unshift('id');

View File

@@ -14,7 +14,7 @@ export const listComments = async (c: Context<{ Bindings: Bindings }>) => {
).first<{ count: number }>();
const { results } = await c.env.CWD_DB.prepare(
'SELECT * FROM Comment ORDER BY pub_date DESC LIMIT ? OFFSET ?'
'SELECT * FROM Comment ORDER BY created DESC LIMIT ? OFFSET ?'
)
.bind(limit, offset)
.all();
@@ -30,8 +30,8 @@ export const listComments = async (c: Context<{ Bindings: Bindings }>) => {
const data = await Promise.all(
results.map(async (row: any) => ({
id: row.id,
pubDate: row.pub_date,
author: row.author,
created: row.created,
name: row.name,
email: row.email,
postSlug: row.post_slug,
url: row.url,
@@ -39,6 +39,7 @@ export const listComments = async (c: Context<{ Bindings: Bindings }>) => {
contentText: row.content_text,
contentHtml: row.content_html,
status: row.status,
ua: row.ua,
avatar: await getCravatar(row.email, avatarPrefix || undefined)
}))
);

View File

@@ -15,12 +15,12 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
try {
// 1. 查询审核通过的评论
const query = `
SELECT id, author, email, url, content_text as contentText,
content_html as contentHtml, pub_date as pubDate, parent_id as parentId,
SELECT id, name, email, url, content_text as contentText,
content_html as contentHtml, created, parent_id as parentId,
post_slug as postSlug
FROM Comment
WHERE post_slug = ? AND status = "approved"
ORDER BY pub_date DESC
ORDER BY created DESC
`
const { results } = await c.env.CWD_DB.prepare(query).bind(post_slug).all()
@@ -52,7 +52,7 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
// 获取直接父评论的作者名
const parentComment = commentMap.get(comment.parentId)
if (parentComment) {
comment.replyToAuthor = parentComment.author
comment.replyToAuthor = parentComment.name
}
// 向上查找根评论
@@ -74,7 +74,7 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
// 对每个根评论的 replies 按时间正序排列
rootComments.forEach(root => {
root.replies.sort((a: any, b: any) =>
new Date(a.pubDate).getTime() - new Date(b.pubDate).getTime()
a.created - b.created
)
})

View File

@@ -22,7 +22,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
if (!data || typeof data !== 'object') {
return c.json({ message: '无效的请求体' }, 400);
}
const { post_slug, content: rawContent, author: rawAuthor, email, url, post_title, post_url } = data;
const { post_slug, content: rawContent, name: rawName, email, url, post_title, post_url } = data;
const parentId = (data as any).parent_id ?? (data as any).parentId ?? null;
if (!post_slug || typeof post_slug !== 'string') {
return c.json({ message: 'post_slug 必填' }, 400);
@@ -30,7 +30,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
if (!rawContent || typeof rawContent !== 'string') {
return c.json({ message: '评论内容不能为空' }, 400);
}
if (!rawAuthor || typeof rawAuthor !== 'string') {
if (!rawName || typeof rawName !== 'string') {
return c.json({ message: '昵称不能为空' }, 400);
}
if (!email || typeof email !== 'string') {
@@ -39,7 +39,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
if (!isValidEmail(email)) {
return c.json({ message: '邮箱格式不正确' }, 400);
}
const userAgent = c.req.header('user-agent') || "";
const ua = c.req.header('user-agent') || "";
// 1. 获取 IP (Worker 获取 IP 的标准方式)
const ip = c.req.header('cf-connecting-ip') || "127.0.0.1";
@@ -47,11 +47,11 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
// 2. 检查评论频率控制 (对应 canPostComment)
// 这里建议使用 D1 查最近一条评论的时间,或者直接放行(如果使用了 Cloudflare WAF
const lastComment = await c.env.CWD_DB.prepare(
'SELECT pub_date FROM Comment WHERE ip_address = ? ORDER BY pub_date DESC LIMIT 1'
).bind(ip).first<{ pub_date: string }>();
'SELECT created FROM Comment WHERE ip_address = ? ORDER BY created DESC LIMIT 1'
).bind(ip).first<{ created: number }>();
if (lastComment) {
const lastTime = new Date(lastComment.pub_date).getTime();
const lastTime = lastComment.created;
if (Date.now() - lastTime < 10 * 1000) {
return c.json({ message: "评论频繁等10s后再试" }, 429);
}
@@ -71,7 +71,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
// 3. 准备数据
const cleanedContent = checkContent(rawContent);
const contentText = cleanedContent;
const author = checkContent(rawAuthor);
const name = checkContent(rawName);
// Markdown 渲染与 XSS 过滤
const html = await marked.parse(cleanedContent, { async: true });
@@ -89,32 +89,32 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
console.log('PostComment:request', {
postSlug: post_slug,
hasParent: parentId !== null && parentId !== undefined,
author,
name,
email,
ip
});
const uaParser = new UAParser(userAgent);
const uaParser = new UAParser(ua);
const uaResult = uaParser.getResult();
// 4. 写入 D1 数据库
try {
const { success } = await c.env.CWD_DB.prepare(`
INSERT INTO Comment (
pub_date, post_slug, author, email, url, ip_address,
os, browser, device, user_agent, content_text, content_html,
created, post_slug, name, email, url, ip_address,
os, browser, device, ua, content_text, content_html,
parent_id, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).bind(
new Date().toISOString(),
Date.now(),
post_slug,
author,
name,
email,
url || null,
ip,
`${uaResult.os.name || ""} ${uaResult.os.version || ""}`.trim(),
`${uaResult.browser.name || ""} ${uaResult.browser.version || ""}`.trim(),
uaResult.device.model || uaResult.device.type || "Desktop",
userAgent,
ua,
contentText,
contentHtml,
parentId || null,
@@ -156,8 +156,8 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
const isAdminReply = !!adminEmail && email === adminEmail;
const parentComment = await c.env.CWD_DB.prepare(
"SELECT author, email, content_html FROM Comment WHERE id = ?"
).bind(parentId).first<{ author: string, email: string, content_html: string }>();
"SELECT name, email, content_html FROM Comment WHERE id = ?"
).bind(parentId).first<{ name: string, email: string, content_html: string }>();
if (parentComment && parentComment.email && parentComment.email !== email) {
let canSendUserMail = true;
@@ -173,14 +173,14 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
if (canSendUserMail && isValidEmail(parentComment.email)) {
console.log('PostComment:mailDispatch:userReply:send', {
toEmail: parentComment.email,
toName: parentComment.author
toName: parentComment.name
});
await sendCommentReplyNotification(c.env, {
toEmail: parentComment.email,
toName: parentComment.author,
toName: parentComment.name,
postTitle: data.post_title,
parentComment: parentComment.content_html,
replyAuthor: author,
replyAuthor: name,
replyContent: contentHtml,
postUrl: data.post_url,
}, notifySettings.smtp);
@@ -202,7 +202,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
await sendCommentNotification(c.env, {
postTitle: data.post_title,
postUrl: data.post_url,
commentAuthor: author,
commentAuthor: name,
commentContent: contentHtml
}, notifySettings.smtp);
await c.env.CWD_DB.prepare(