feat(评论系统): 新增评论审核功能及界面优化
实现新评论需审核后显示的功能,包括后台设置开关、API支持、前端状态提示 优化管理后台界面样式和分页组件 添加成功消息提示组件及样式 更新相关文档说明
This commit is contained in:
@@ -40,15 +40,24 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
||||
return c.json({ message: '邮箱格式不正确' }, 400);
|
||||
}
|
||||
const ua = c.req.header('user-agent') || "";
|
||||
|
||||
// 1. 获取 IP (Worker 获取 IP 的标准方式)
|
||||
|
||||
const ip = c.req.header('cf-connecting-ip') || "127.0.0.1";
|
||||
|
||||
// 1.5 管理员身份验证
|
||||
const adminEmail = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?').bind('comment_admin_email').first<string>('value');
|
||||
|
||||
const adminEmail = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
|
||||
.bind('comment_admin_email')
|
||||
.first<string>('value');
|
||||
|
||||
const requireReviewRaw = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
|
||||
.bind('comment_require_review')
|
||||
.first<string>('value');
|
||||
const requireReview = requireReviewRaw === '1';
|
||||
|
||||
let isAdminComment = false;
|
||||
|
||||
if (adminEmail && email === adminEmail) {
|
||||
const adminKey = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?').bind('comment_admin_key_hash').first<string>('value');
|
||||
const adminKey = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
|
||||
.bind('comment_admin_key_hash')
|
||||
.first<string>('value');
|
||||
|
||||
if (adminKey) {
|
||||
const lockKey = `admin_lock:${ip}`;
|
||||
@@ -76,12 +85,11 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
||||
return c.json({ message: "密钥错误" }, 401);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证成功,清除失败记录
|
||||
|
||||
await c.env.CWD_AUTH_KV.delete(`admin_fail:${ip}`);
|
||||
isAdminComment = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查评论频率控制 (对应 canPostComment)
|
||||
// 这里建议使用 D1 查最近一条评论的时间,或者直接放行(如果使用了 Cloudflare WAF)
|
||||
const lastComment = await c.env.CWD_DB.prepare(
|
||||
@@ -123,7 +131,8 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
||||
const uaParser = new UAParser(ua);
|
||||
const uaResult = uaParser.getResult();
|
||||
|
||||
// 4. 写入 D1 数据库
|
||||
const defaultStatus = requireReview && !isAdminComment ? "pending" : "approved";
|
||||
|
||||
try {
|
||||
const { success } = await c.env.CWD_DB.prepare(`
|
||||
INSERT INTO Comment (
|
||||
@@ -145,7 +154,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
||||
contentText,
|
||||
contentHtml,
|
||||
parentId || null,
|
||||
"approved" // 或者从环境变量读取默认状态
|
||||
defaultStatus
|
||||
).run();
|
||||
|
||||
if (!success) throw new Error("Database insert failed");
|
||||
|
||||
@@ -31,6 +31,7 @@ const COMMENT_AVATAR_PREFIX_KEY = 'comment_avatar_prefix';
|
||||
const COMMENT_ADMIN_ENABLED_KEY = 'comment_admin_enabled';
|
||||
const COMMENT_ALLOWED_DOMAINS_KEY = 'comment_allowed_domains';
|
||||
const COMMENT_ADMIN_KEY_HASH_KEY = 'comment_admin_key_hash';
|
||||
const COMMENT_REQUIRE_REVIEW_KEY = 'comment_require_review';
|
||||
|
||||
|
||||
async function loadCommentSettings(env: Bindings) {
|
||||
@@ -43,10 +44,11 @@ async function loadCommentSettings(env: Bindings) {
|
||||
COMMENT_AVATAR_PREFIX_KEY,
|
||||
COMMENT_ADMIN_ENABLED_KEY,
|
||||
COMMENT_ALLOWED_DOMAINS_KEY,
|
||||
COMMENT_ADMIN_KEY_HASH_KEY
|
||||
COMMENT_ADMIN_KEY_HASH_KEY,
|
||||
COMMENT_REQUIRE_REVIEW_KEY
|
||||
];
|
||||
const { results } = await env.CWD_DB.prepare(
|
||||
'SELECT key, value FROM Settings WHERE key IN (?, ?, ?, ?, ?, ?)'
|
||||
'SELECT key, value FROM Settings WHERE key IN (?, ?, ?, ?, ?, ?, ?)'
|
||||
)
|
||||
.bind(...keys)
|
||||
.all<{ key: string; value: string }>();
|
||||
@@ -61,6 +63,9 @@ async function loadCommentSettings(env: Bindings) {
|
||||
const enabledRaw = map.get(COMMENT_ADMIN_ENABLED_KEY) ?? null;
|
||||
const adminEnabled = enabledRaw === '1';
|
||||
|
||||
const requireReviewRaw = map.get(COMMENT_REQUIRE_REVIEW_KEY) ?? null;
|
||||
const requireReview = requireReviewRaw === '1';
|
||||
|
||||
// 解析允许的域名列表
|
||||
const allowedDomainsRaw = map.get(COMMENT_ALLOWED_DOMAINS_KEY) ?? '';
|
||||
const allowedDomains = allowedDomainsRaw
|
||||
@@ -73,6 +78,7 @@ async function loadCommentSettings(env: Bindings) {
|
||||
avatarPrefix: map.get(COMMENT_AVATAR_PREFIX_KEY) ?? null,
|
||||
adminEnabled,
|
||||
allowedDomains,
|
||||
requireReview,
|
||||
adminKey: map.get(COMMENT_ADMIN_KEY_HASH_KEY) ?? null,
|
||||
adminKeySet: !!map.get(COMMENT_ADMIN_KEY_HASH_KEY)
|
||||
};
|
||||
@@ -87,6 +93,7 @@ async function saveCommentSettings(
|
||||
adminEnabled?: boolean;
|
||||
allowedDomains?: string[];
|
||||
adminKey?: string;
|
||||
requireReview?: boolean;
|
||||
}
|
||||
) {
|
||||
await env.CWD_DB.prepare(
|
||||
@@ -118,6 +125,15 @@ async function saveCommentSettings(
|
||||
{
|
||||
key: COMMENT_ADMIN_KEY_HASH_KEY,
|
||||
value: adminKeyValue
|
||||
},
|
||||
{
|
||||
key: COMMENT_REQUIRE_REVIEW_KEY,
|
||||
value:
|
||||
typeof settings.requireReview === 'boolean'
|
||||
? settings.requireReview
|
||||
? '1'
|
||||
: '0'
|
||||
: undefined
|
||||
}
|
||||
];
|
||||
|
||||
@@ -240,6 +256,7 @@ app.put('/admin/settings/comments', async (c) => {
|
||||
const rawAdminEnabled = body.adminEnabled;
|
||||
const rawAllowedDomains = Array.isArray(body.allowedDomains) ? body.allowedDomains : [];
|
||||
const rawAdminKey = typeof body.adminKey === 'string' ? body.adminKey : undefined;
|
||||
const rawRequireReview = body.requireReview;
|
||||
|
||||
const adminEmail = rawAdminEmail.trim();
|
||||
const adminBadge = rawAdminBadge.trim();
|
||||
@@ -252,6 +269,10 @@ app.put('/admin/settings/comments', async (c) => {
|
||||
.map((d: any) => (typeof d === 'string' ? d.trim() : ''))
|
||||
.filter(Boolean);
|
||||
const adminKey = rawAdminKey; // Can be undefined or empty string
|
||||
const requireReview =
|
||||
typeof rawRequireReview === 'boolean'
|
||||
? rawRequireReview
|
||||
: rawRequireReview === '1' || rawRequireReview === 1;
|
||||
|
||||
if (adminEmail && !isValidEmail(adminEmail)) {
|
||||
return c.json({ message: '邮箱格式不正确' }, 400);
|
||||
@@ -263,7 +284,8 @@ app.put('/admin/settings/comments', async (c) => {
|
||||
avatarPrefix,
|
||||
adminEnabled,
|
||||
allowedDomains,
|
||||
adminKey
|
||||
adminKey,
|
||||
requireReview
|
||||
});
|
||||
|
||||
return c.json({ message: '保存成功' });
|
||||
|
||||
Reference in New Issue
Block a user