feat: 新增CWD评论系统前后端代码及文档

refactor: 移除旧版评论系统代码并重构为Vue3前端

docs: 更新后端配置文档说明

fix: 修复评论提交频率限制和邮件通知逻辑

style: 格式化代码并优化样式

test: 添加Vitest测试配置

build: 更新依赖项和构建配置

chore: 清理无用文件和缓存
This commit is contained in:
anghunk
2026-01-19 14:58:18 +08:00
parent 836ac08296
commit 3d0e3a317a
53 changed files with 1201 additions and 28231 deletions

View File

@@ -0,0 +1,108 @@
import { Context } from 'hono'
import { Bindings } from '../../bindings'
import { getCravatar } from '../../utils/getAvatar'
export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
const post_slug = c.req.query('post_slug')
const page = parseInt(c.req.query('page') || '1')
const limit = Math.min(parseInt(c.req.query('limit') || '20'), 50)
const nested = c.req.query('nested') !== 'false'
const avatar_prefix = c.req.query('avatar_prefix')
const offset = (page - 1) * limit
if (!post_slug) return c.json({ message: "post_slug is required" }, 400)
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,
post_slug as postSlug
FROM Comment
WHERE post_slug = ? AND status = "approved"
ORDER BY pub_date DESC
`
const { results } = await c.env.CWD_DB.prepare(query).bind(post_slug).all()
// 2. 批量处理头像并格式化
const allComments = await Promise.all(results.map(async (row: any) => ({
...row,
avatar: await getCravatar(row.email, avatar_prefix || undefined),
replies: []
})))
// 3. 处理嵌套逻辑扁平化2级往后的回复都放在根评论的 replies 中)
if (nested) {
const commentMap = new Map()
const rootComments: any[] = []
// 建立评论映射
allComments.forEach(comment => commentMap.set(comment.id, comment))
// 找出所有根评论
allComments.forEach(comment => {
if (!comment.parentId) {
rootComments.push(comment)
}
})
// 为每个非根评论找到其根评论,并添加 replyToAuthor 字段
allComments.forEach(comment => {
if (comment.parentId) {
// 获取直接父评论的作者名
const parentComment = commentMap.get(comment.parentId)
if (parentComment) {
comment.replyToAuthor = parentComment.author
}
// 向上查找根评论
let rootId = comment.parentId
let current = commentMap.get(rootId)
while (current && current.parentId) {
rootId = current.parentId
current = commentMap.get(rootId)
}
// 将回复添加到根评论的 replies 中
const rootComment = commentMap.get(rootId)
if (rootComment && !rootComment.parentId) {
rootComment.replies.push(comment)
}
}
})
// 对每个根评论的 replies 按时间正序排列
rootComments.forEach(root => {
root.replies.sort((a: any, b: any) =>
new Date(a.pubDate).getTime() - new Date(b.pubDate).getTime()
)
})
// 对根评论进行分页
const paginatedData = rootComments.slice(offset, offset + limit)
return c.json({
data: paginatedData,
pagination: {
page,
limit,
total: Math.ceil(rootComments.length / limit),
totalCount: allComments.length,
}
})
} else {
// 非嵌套逻辑直接分页
const paginatedData = allComments.slice(offset, offset + limit)
return c.json({
data: paginatedData,
pagination: {
page,
limit,
total: Math.ceil(allComments.length / limit),
totalCount: allComments.length,
}
})
}
} catch (e: any) {
return c.json({ message: e.message }, 500)
}
}