feat(API): 添加消息全局搜索接口
- 新增 /api/messages/search 搜索 API - 支持关键词模糊搜索消息内容 - 支持角色筛选(用户消息/AI回复/全部) - 支持分页查询,返回结果总数 - 仅查询当前用户的未归档对话
This commit is contained in:
parent
56b7ffa68d
commit
dcd757e584
146
src/app/api/messages/search/route.ts
Normal file
146
src/app/api/messages/search/route.ts
Normal 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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user