feat(API): 添加消息全局搜索接口

- 新增 /api/messages/search 搜索 API
- 支持关键词模糊搜索消息内容
- 支持角色筛选(用户消息/AI回复/全部)
- 支持分页查询,返回结果总数
- 仅查询当前用户的未归档对话
This commit is contained in:
gaoziman 2025-12-24 22:49:41 +08:00
parent 56b7ffa68d
commit dcd757e584

View File

@ -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<number>`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 }
);
}
}