From 75b604e510705a2a8e5576f0b8cbe56eaf2bb6fe Mon Sep 17 00:00:00 2001 From: zpj80231 Date: Thu, 19 Mar 2026 14:59:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E4=BC=98=E5=8C=96=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=AF=84=E8=AE=BA=E5=8F=AF=E8=83=BD=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84DB500=E9=97=AE=E9=A2=98=EF=BC=9B=E5=85=BC=E5=AE=B9url?= =?UTF-8?q?=E4=B8=AD=E5=8C=85=E5=90=AB=E4=B8=AD=E6=96=87=E3=80=81=E7=A9=BA?= =?UTF-8?q?=E6=A0=BC=E3=80=81=E5=A4=9A=E7=BC=96=E7=A0=81=E7=AD=89=E7=9A=84?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 getComments、getPagePv、like、postComment 和 trackVisit 等 API 中统一使用 decodePostSlug 函数处理文章别名 - 实现更精确的文章别名匹配,支持多种别名格式的查询 - 修复了原始别名处理中的潜在安全问题 - 移除了过时的 LIKE 查询模式,提高查询效率 - 统一了所有涉及文章别名的参数验证逻辑 (cherry picked from commit b9b944d7aff3f2e6008e52ba8f515bc451cfc32d) --- .gitignore | 6 ++++- cwd-api/src/api/public/getComments.ts | 27 ++++++++++--------- cwd-api/src/api/public/getPagePv.ts | 3 ++- cwd-api/src/api/public/like.ts | 16 +++++++----- cwd-api/src/api/public/postComment.ts | 5 ++-- cwd-api/src/api/public/trackVisit.ts | 9 ++++--- cwd-api/src/utils/decodePostSlug.ts | 37 +++++++++++++++++++++++++++ 7 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 cwd-api/src/utils/decodePostSlug.ts diff --git a/.gitignore b/.gitignore index cdb99a5..10e60b6 100644 --- a/.gitignore +++ b/.gitignore @@ -174,4 +174,8 @@ wrangler.jsonc .claude .trae -.npmrc \ No newline at end of file +.npmrc + +# idea + +.idea \ No newline at end of file diff --git a/cwd-api/src/api/public/getComments.ts b/cwd-api/src/api/public/getComments.ts index 452187f..836de90 100644 --- a/cwd-api/src/api/public/getComments.ts +++ b/cwd-api/src/api/public/getComments.ts @@ -1,10 +1,11 @@ import { Context } from 'hono' import { Bindings } from '../../bindings' import { getCravatar } from '../../utils/getAvatar' +import { decodePostSlug, getAllSlugFormats, escapeLikePattern } from '../../utils/decodePostSlug' export const getComments = async (c: Context<{ Bindings: Bindings }>) => { const rawPostSlug = c.req.query('post_slug') || '' - const postSlug = rawPostSlug.trim() + const postSlug = decodePostSlug(rawPostSlug) 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' @@ -42,13 +43,14 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => { } try { - const equalSlugs = Array.from(new Set(slugList)) - const likePatternsSet = new Set() - for (const s of equalSlugs) { - likePatternsSet.add(`${s}#%`) - likePatternsSet.add(`${s}?%`) + const allSlugFormats = new Set() + for (const s of slugList) { + for (const format of getAllSlugFormats(s)) { + allSlugFormats.add(format) + } } - const likePatterns = Array.from(likePatternsSet) + const equalSlugs = Array.from(allSlugFormats) + const whereParts: string[] = [] if (equalSlugs.length === 1) { whereParts.push('post_slug = ?') @@ -56,16 +58,13 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => { const placeholders = equalSlugs.map(() => '?').join(', ') whereParts.push(`post_slug IN (${placeholders})`) } - for (let i = 0; i < likePatterns.length; i += 1) { - whereParts.push('post_slug LIKE ?') - } const whereClause = whereParts.length > 0 - ? `status = "approved" AND (${whereParts.join(' OR ')})` - : 'status = "approved"' + ? `(${whereParts.join(' OR ')})` + : '1=1' - let finalWhereClause = whereClause - const bindParams: unknown[] = [...equalSlugs, ...likePatterns] + let finalWhereClause = `status = "approved" AND ${whereClause}` + const bindParams: unknown[] = [...equalSlugs] if (siteId) { finalWhereClause += ' AND site_id = ?' diff --git a/cwd-api/src/api/public/getPagePv.ts b/cwd-api/src/api/public/getPagePv.ts index cbf99e2..7c8e13d 100644 --- a/cwd-api/src/api/public/getPagePv.ts +++ b/cwd-api/src/api/public/getPagePv.ts @@ -1,10 +1,11 @@ import type { Context } from 'hono'; import type { Bindings } from '../../bindings'; +import { decodePostSlug } from '../../utils/decodePostSlug'; export const getPagePv = async (c: Context<{ Bindings: Bindings }>) => { try { const rawPostSlug = c.req.query('post_slug') || ''; - const postSlug = rawPostSlug.trim(); + const postSlug = decodePostSlug(rawPostSlug); const rawSiteId = c.req.query('siteId') || ''; const siteId = rawSiteId && rawSiteId !== 'default' ? rawSiteId : ''; diff --git a/cwd-api/src/api/public/like.ts b/cwd-api/src/api/public/like.ts index 94ed60e..7c0bc3d 100644 --- a/cwd-api/src/api/public/like.ts +++ b/cwd-api/src/api/public/like.ts @@ -1,5 +1,6 @@ import type { Context } from 'hono'; import type { Bindings } from '../../bindings'; +import { decodePostSlug } from '../../utils/decodePostSlug'; type LikeStatusResponse = { liked: boolean; @@ -36,7 +37,7 @@ export const getLikeStatus = async ( ): Promise => { try { const rawPostSlug = c.req.query('post_slug') || ''; - const postSlug = rawPostSlug.trim(); + const postSlug = decodePostSlug(rawPostSlug); const siteId = c.req.query('siteId') || ''; if (!postSlug) { @@ -92,13 +93,14 @@ export const likePage = async ( const rawPostSlug = typeof body.postSlug === 'string' ? body.postSlug.trim() : ''; + const postSlug = decodePostSlug(rawPostSlug); const rawPostTitle = typeof body.postTitle === 'string' ? body.postTitle.trim() : ''; const rawPostUrl = typeof body.postUrl === 'string' ? body.postUrl.trim() : ''; const siteId = typeof body.siteId === 'string' ? body.siteId.trim() : ''; - if (!rawPostSlug) { + if (!postSlug) { return c.json({ message: 'postSlug is required' }, 400); } @@ -109,7 +111,7 @@ export const likePage = async ( const existingLike = await c.env.CWD_DB.prepare( 'SELECT id FROM Likes WHERE page_slug = ? AND user_id = ? AND site_id = ?' ) - .bind(rawPostSlug, userId, siteId) + .bind(postSlug, userId, siteId) .first<{ id: number }>(); let alreadyLiked = false; @@ -118,7 +120,7 @@ export const likePage = async ( await c.env.CWD_DB.prepare( 'INSERT INTO Likes (page_slug, user_id, created_at, site_id) VALUES (?, ?, ?, ?)' ) - .bind(rawPostSlug, userId, now, siteId) + .bind(postSlug, userId, now, siteId) .run(); } else { alreadyLiked = true; @@ -127,7 +129,7 @@ export const likePage = async ( const pageStatsRow = await c.env.CWD_DB.prepare( 'SELECT id FROM page_stats WHERE post_slug = ? AND site_id = ?' ) - .bind(rawPostSlug, siteId) + .bind(postSlug, siteId) .first<{ id: number }>(); if (!pageStatsRow) { @@ -135,7 +137,7 @@ export const likePage = async ( 'INSERT INTO page_stats (post_slug, post_title, post_url, pv, last_visit_at, created_at, updated_at, site_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' ) .bind( - rawPostSlug, + postSlug, rawPostTitle || null, rawPostUrl || null, 0, @@ -161,7 +163,7 @@ export const likePage = async ( const totalRow = await c.env.CWD_DB.prepare( 'SELECT COUNT(*) AS count FROM Likes WHERE page_slug = ? AND site_id = ?' ) - .bind(rawPostSlug, siteId) + .bind(postSlug, siteId) .first<{ count: number }>(); const totalLikes = totalRow?.count || 0; diff --git a/cwd-api/src/api/public/postComment.ts b/cwd-api/src/api/public/postComment.ts index 3668461..f06079e 100644 --- a/cwd-api/src/api/public/postComment.ts +++ b/cwd-api/src/api/public/postComment.ts @@ -12,8 +12,8 @@ import { EmailNotificationSettings } from '../../utils/email'; import { loadTelegramSettings, sendTelegramMessage } from '../../utils/telegram'; +import { decodePostSlug } from '../../utils/decodePostSlug'; -// 检查内容,将