From ac39b76ffc92de156a583d3ef15587f83b5919bb Mon Sep 17 00:00:00 2001 From: shinya Date: Thu, 3 Jul 2025 22:05:12 +0800 Subject: [PATCH] fix: search history isolation --- src/app/api/searchhistory/route.ts | 43 +++++++++++++++++++++++------ src/lib/db.client.ts | 23 ++++++++++++---- src/lib/db.ts | 44 ++++++++++++++++-------------- 3 files changed, 76 insertions(+), 34 deletions(-) diff --git a/src/app/api/searchhistory/route.ts b/src/app/api/searchhistory/route.ts index 1bf7100..d1cd904 100644 --- a/src/app/api/searchhistory/route.ts +++ b/src/app/api/searchhistory/route.ts @@ -10,12 +10,22 @@ export const runtime = 'edge'; const HISTORY_LIMIT = 20; /** - * GET /api/searchhistory + * GET /api/searchhistory?user= * 返回 string[] */ -export async function GET() { +export async function GET(request: NextRequest) { try { - const history = await db.getSearchHistory(); + const { searchParams } = new URL(request.url); + const user = searchParams.get('user')?.trim(); + + if (!user) { + return NextResponse.json( + { error: 'User parameter is required' }, + { status: 400 } + ); + } + + const history = await db.getSearchHistory(user); return NextResponse.json(history, { status: 200 }); } catch (err) { console.error('获取搜索历史失败', err); @@ -28,12 +38,14 @@ export async function GET() { /** * POST /api/searchhistory - * body: { keyword: string } + * body: { keyword: string, user: string } */ export async function POST(request: NextRequest) { try { const body = await request.json(); const keyword: string = body.keyword?.trim(); + const user: string = body.user?.trim(); + if (!keyword) { return NextResponse.json( { error: 'Keyword is required' }, @@ -41,10 +53,17 @@ export async function POST(request: NextRequest) { ); } - await db.addSearchHistory(keyword); + if (!user) { + return NextResponse.json( + { error: 'User parameter is required' }, + { status: 400 } + ); + } + + await db.addSearchHistory(user, keyword); // 再次获取最新列表,确保客户端与服务端同步 - const history = await db.getSearchHistory(); + const history = await db.getSearchHistory(user); return NextResponse.json(history.slice(0, HISTORY_LIMIT), { status: 200 }); } catch (err) { console.error('添加搜索历史失败', err); @@ -56,7 +75,7 @@ export async function POST(request: NextRequest) { } /** - * DELETE /api/searchhistory + * DELETE /api/searchhistory?user=&keyword= * * 1. 不带 keyword -> 清空全部搜索历史 * 2. 带 keyword= -> 删除单条关键字 @@ -64,9 +83,17 @@ export async function POST(request: NextRequest) { export async function DELETE(request: NextRequest) { try { const { searchParams } = new URL(request.url); + const user = searchParams.get('user')?.trim(); const kw = searchParams.get('keyword')?.trim(); - await db.deleteSearchHistory(kw || undefined); + if (!user) { + return NextResponse.json( + { error: 'User parameter is required' }, + { status: 400 } + ); + } + + await db.deleteSearchHistory(user, kw || undefined); return NextResponse.json({ success: true }, { status: 200 }); } catch (err) { diff --git a/src/lib/db.client.ts b/src/lib/db.client.ts index b2a7c8a..90bd32c 100644 --- a/src/lib/db.client.ts +++ b/src/lib/db.client.ts @@ -206,7 +206,10 @@ export async function getSearchHistory(): Promise { // 如果配置为使用数据库,则从后端 API 获取 if (STORAGE_TYPE !== 'localstorage') { try { - return fetchFromApi('/api/searchhistory'); + const user = getUsername(); + return fetchFromApi( + `/api/searchhistory?user=${encodeURIComponent(user ?? '')}` + ); } catch (err) { console.error('获取搜索历史失败:', err); return []; @@ -240,12 +243,13 @@ export async function addSearchHistory(keyword: string): Promise { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { + const user = getUsername(); await fetch('/api/searchhistory', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ keyword: trimmed }), + body: JSON.stringify({ keyword: trimmed, user: user ?? '' }), }); } catch (err) { console.error('保存搜索历史失败:', err); @@ -276,7 +280,8 @@ export async function clearSearchHistory(): Promise { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { - await fetch('/api/searchhistory', { + const user = getUsername(); + await fetch(`/api/searchhistory?user=${encodeURIComponent(user ?? '')}`, { method: 'DELETE', }); } catch (err) { @@ -300,9 +305,15 @@ export async function deleteSearchHistory(keyword: string): Promise { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { - await fetch(`/api/searchhistory?keyword=${encodeURIComponent(trimmed)}`, { - method: 'DELETE', - }); + const user = getUsername(); + await fetch( + `/api/searchhistory?user=${encodeURIComponent( + user ?? '' + )}&keyword=${encodeURIComponent(trimmed)}`, + { + method: 'DELETE', + } + ); } catch (err) { console.error('删除搜索历史失败:', err); } diff --git a/src/lib/db.ts b/src/lib/db.ts index 971ed4d..3ce966a 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -58,9 +58,9 @@ export interface IStorage { checkUserExist(userName: string): Promise; // 搜索历史相关 - getSearchHistory(): Promise; - addSearchHistory(keyword: string): Promise; - deleteSearchHistory(keyword?: string): Promise; + getSearchHistory(userName: string): Promise; + addSearchHistory(userName: string, keyword: string): Promise; + deleteSearchHistory(userName: string, keyword?: string): Promise; // 用户列表 getAllUsers(): Promise; @@ -184,26 +184,30 @@ class RedisStorage implements IStorage { } // ---------- 搜索历史 ---------- - private shKey = 'moontv:search_history'; - - async getSearchHistory(): Promise { - return (await this.client.lRange(this.shKey, 0, -1)) as string[]; + private shKey(user: string) { + return `u:${user}:sh`; // u:username:sh } - async addSearchHistory(keyword: string): Promise { + async getSearchHistory(userName: string): Promise { + return (await this.client.lRange(this.shKey(userName), 0, -1)) as string[]; + } + + async addSearchHistory(userName: string, keyword: string): Promise { + const key = this.shKey(userName); // 先去重 - await this.client.lRem(this.shKey, 0, keyword); + await this.client.lRem(key, 0, keyword); // 插入到最前 - await this.client.lPush(this.shKey, keyword); + await this.client.lPush(key, keyword); // 限制最大长度 - await this.client.lTrim(this.shKey, 0, SEARCH_HISTORY_LIMIT - 1); + await this.client.lTrim(key, 0, SEARCH_HISTORY_LIMIT - 1); } - async deleteSearchHistory(keyword?: string): Promise { + async deleteSearchHistory(userName: string, keyword?: string): Promise { + const key = this.shKey(userName); if (keyword) { - await this.client.lRem(this.shKey, 0, keyword); + await this.client.lRem(key, 0, keyword); } else { - await this.client.del(this.shKey); + await this.client.del(key); } } @@ -371,16 +375,16 @@ export class DbManager { } // ---------- 搜索历史 ---------- - async getSearchHistory(): Promise { - return this.storage.getSearchHistory(); + async getSearchHistory(userName: string): Promise { + return this.storage.getSearchHistory(userName); } - async addSearchHistory(keyword: string): Promise { - await this.storage.addSearchHistory(keyword); + async addSearchHistory(userName: string, keyword: string): Promise { + await this.storage.addSearchHistory(userName, keyword); } - async deleteSearchHistory(keyword?: string): Promise { - await this.storage.deleteSearchHistory(keyword); + async deleteSearchHistory(userName: string, keyword?: string): Promise { + await this.storage.deleteSearchHistory(userName, keyword); } // 获取全部用户名