refactor(comment): 统一字段命名并改用时间戳存储创建时间
将 author 字段重命名为 name,pub_date 改为 created 并使用时间戳存储 更新相关 API、数据库 schema 和前端组件以适配新字段 同时将 user_agent 简化为 ua 并改进日期处理逻辑
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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)
|
||||
}))
|
||||
);
|
||||
|
||||
@@ -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
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user