From dcd757e584ba989d12524f7cc218bdb8509e5e9d Mon Sep 17 00:00:00 2001 From: gaoziman <2942894660@qq.com> Date: Wed, 24 Dec 2025 22:49:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(API):=20=E6=B7=BB=E5=8A=A0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=85=A8=E5=B1=80=E6=90=9C=E7=B4=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 /api/messages/search 搜索 API - 支持关键词模糊搜索消息内容 - 支持角色筛选(用户消息/AI回复/全部) - 支持分页查询,返回结果总数 - 仅查询当前用户的未归档对话 --- src/app/api/messages/search/route.ts | 146 +++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/app/api/messages/search/route.ts diff --git a/src/app/api/messages/search/route.ts b/src/app/api/messages/search/route.ts new file mode 100644 index 0000000..fd35be0 --- /dev/null +++ b/src/app/api/messages/search/route.ts @@ -0,0 +1,146 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/drizzle/db'; +import { messages, conversations } from '@/drizzle/schema'; +import { getCurrentUser } from '@/lib/auth'; +import { eq, and, ilike, desc, sql, or } from 'drizzle-orm'; + +/** + * 搜索结果类型 + */ +export interface SearchResult { + messageId: string; + conversationId: string; + conversationTitle: string; + role: string; + content: string; + createdAt: Date; +} + +/** + * GET /api/messages/search - 全局消息搜索 + * + * Query Parameters: + * - q: string (必填) - 搜索关键词 + * - role: 'user' | 'assistant' | 'all' (可选) - 角色筛选,默认 'all' + * - page: number (可选) - 页码,默认 1 + * - limit: number (可选) - 每页数量,默认 20,最大 50 + */ +export async function GET(request: Request) { + try { + // 验证用户身份 + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json( + { error: '请先登录' }, + { status: 401 } + ); + } + + // 解析查询参数 + const { searchParams } = new URL(request.url); + const query = searchParams.get('q')?.trim(); + const role = searchParams.get('role') || 'all'; + const page = Math.max(1, parseInt(searchParams.get('page') || '1', 10)); + const limit = Math.min(50, Math.max(1, parseInt(searchParams.get('limit') || '20', 10))); + const offset = (page - 1) * limit; + + // 验证搜索关键词 + if (!query || query.length < 1) { + return NextResponse.json( + { error: '请输入搜索关键词' }, + { status: 400 } + ); + } + + // 关键词长度限制 + if (query.length > 100) { + return NextResponse.json( + { error: '搜索关键词过长,请限制在 100 字符以内' }, + { status: 400 } + ); + } + + // 构建搜索条件 + const searchPattern = `%${query}%`; + + // 基础条件:用户的对话 + const baseConditions = [ + eq(conversations.userId, user.userId), + eq(conversations.isArchived, false), // 排除已归档对话 + ]; + + // 角色筛选条件 + const roleCondition = role === 'all' + ? or(eq(messages.role, 'user'), eq(messages.role, 'assistant')) + : eq(messages.role, role); + + // 内容搜索条件 + const contentCondition = ilike(messages.content, searchPattern); + + // 执行搜索查询 + const results = await db + .select({ + messageId: messages.messageId, + conversationId: messages.conversationId, + conversationTitle: conversations.title, + role: messages.role, + content: messages.content, + createdAt: messages.createdAt, + }) + .from(messages) + .innerJoin( + conversations, + eq(messages.conversationId, conversations.conversationId) + ) + .where( + and( + ...baseConditions, + roleCondition, + contentCondition + ) + ) + .orderBy(desc(messages.createdAt)) + .limit(limit) + .offset(offset); + + // 获取总数(用于分页) + const countResult = await db + .select({ + count: sql`count(*)::int`, + }) + .from(messages) + .innerJoin( + conversations, + eq(messages.conversationId, conversations.conversationId) + ) + .where( + and( + ...baseConditions, + roleCondition, + contentCondition + ) + ); + + const total = countResult[0]?.count || 0; + const totalPages = Math.ceil(total / limit); + + return NextResponse.json({ + success: true, + data: { + results, + total, + page, + limit, + totalPages, + query, + role, + }, + }); + } catch (error) { + console.error('Search messages error:', error); + return NextResponse.json( + { error: '搜索失败,请稍后重试' }, + { status: 500 } + ); + } +}