feat(设置): 实现用户级别设置隔离

- 设置 API 改为基于当前登录用户
- 注册时自动创建默认用户设置
- API Key 加密后存储到数据库
- 添加默认设置常量和格式化函数
This commit is contained in:
gaoziman 2025-12-21 14:03:55 +08:00
parent 2e8033a8ae
commit 058ea85daa
2 changed files with 109 additions and 57 deletions

View File

@ -122,9 +122,19 @@ export async function POST(request: NextRequest) {
}) })
.returning(); .returning();
// 创建默认用户设置 // 创建默认用户设置API Key 为空,需要用户自行配置)
await db.insert(userSettings).values({ await db.insert(userSettings).values({
userId, userId,
cchUrl: process.env.CCH_DEFAULT_URL || 'https://claude.leocoder.cn/',
cchApiKey: null,
cchApiKeyConfigured: false,
defaultModel: 'claude-sonnet-4-20250514',
defaultTools: ['web_search', 'code_execution', 'web_fetch'],
theme: 'light',
language: 'zh-CN',
fontSize: 15,
enableThinking: false,
saveChatHistory: true,
}); });
// 生成 JWT Token // 生成 JWT Token

View File

@ -2,45 +2,81 @@ import { NextResponse } from 'next/server';
import { db } from '@/drizzle/db'; import { db } from '@/drizzle/db';
import { userSettings } from '@/drizzle/schema'; import { userSettings } from '@/drizzle/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { getCurrentUser } from '@/lib/auth';
import { encryptApiKey } from '@/lib/crypto';
// GET /api/settings - 获取用户设置 // 默认设置值
const DEFAULT_SETTINGS = {
cchUrl: process.env.CCH_DEFAULT_URL || 'https://claude.leocoder.cn/',
cchApiKeyConfigured: false,
defaultModel: 'claude-sonnet-4-20250514',
defaultTools: ['web_search', 'code_execution', 'web_fetch'],
systemPrompt: '',
temperature: '0.7',
theme: 'light',
language: 'zh-CN',
fontSize: 15,
enableThinking: false,
saveChatHistory: true,
};
// 格式化设置响应(不返回 API Key 本身)
function formatSettingsResponse(settings: typeof userSettings.$inferSelect | null) {
if (!settings) {
return DEFAULT_SETTINGS;
}
return {
cchUrl: settings.cchUrl || DEFAULT_SETTINGS.cchUrl,
cchApiKeyConfigured: settings.cchApiKeyConfigured || false,
defaultModel: settings.defaultModel || DEFAULT_SETTINGS.defaultModel,
defaultTools: settings.defaultTools || DEFAULT_SETTINGS.defaultTools,
systemPrompt: settings.systemPrompt || '',
temperature: settings.temperature || '0.7',
theme: settings.theme || DEFAULT_SETTINGS.theme,
language: settings.language || DEFAULT_SETTINGS.language,
fontSize: settings.fontSize || 15,
enableThinking: settings.enableThinking || false,
saveChatHistory: settings.saveChatHistory ?? true,
};
}
// GET /api/settings - 获取当前用户的设置
export async function GET() { export async function GET() {
try { try {
const settings = await db.query.userSettings.findFirst({ // 1. 获取当前登录用户
where: eq(userSettings.id, 1), const user = await getCurrentUser();
}); if (!user) {
// 未登录用户返回默认设置(允许查看但无法保存)
if (!settings) { return NextResponse.json(DEFAULT_SETTINGS);
// 如果没有设置,返回默认值
return NextResponse.json({
cchUrl: process.env.CCH_DEFAULT_URL || 'http://localhost:13500',
cchApiKeyConfigured: false,
defaultModel: 'claude-sonnet-4-20250514',
defaultTools: ['web_search', 'code_execution', 'web_fetch'],
systemPrompt: '',
temperature: '0.7',
theme: 'light',
language: 'zh-CN',
fontSize: 15,
enableThinking: false,
saveChatHistory: true,
});
} }
// 不返回 API Key 本身,只返回是否已配置 // 2. 查询该用户的设置
return NextResponse.json({ const settings = await db.query.userSettings.findFirst({
cchUrl: settings.cchUrl, where: eq(userSettings.userId, user.userId),
cchApiKeyConfigured: settings.cchApiKeyConfigured,
defaultModel: settings.defaultModel,
defaultTools: settings.defaultTools,
systemPrompt: settings.systemPrompt || '',
temperature: settings.temperature || '0.7',
theme: settings.theme,
language: settings.language,
fontSize: settings.fontSize || 15,
enableThinking: settings.enableThinking,
saveChatHistory: settings.saveChatHistory,
}); });
// 3. 如果没有设置记录,创建默认设置
if (!settings) {
const newSettings = await db
.insert(userSettings)
.values({
userId: user.userId,
cchUrl: DEFAULT_SETTINGS.cchUrl,
defaultModel: DEFAULT_SETTINGS.defaultModel,
defaultTools: DEFAULT_SETTINGS.defaultTools,
theme: DEFAULT_SETTINGS.theme,
language: DEFAULT_SETTINGS.language,
fontSize: DEFAULT_SETTINGS.fontSize,
enableThinking: DEFAULT_SETTINGS.enableThinking,
saveChatHistory: DEFAULT_SETTINGS.saveChatHistory,
})
.returning();
return NextResponse.json(formatSettingsResponse(newSettings[0]));
}
return NextResponse.json(formatSettingsResponse(settings));
} catch (error) { } catch (error) {
console.error('Failed to get settings:', error); console.error('Failed to get settings:', error);
return NextResponse.json( return NextResponse.json(
@ -50,9 +86,18 @@ export async function GET() {
} }
} }
// PUT /api/settings - 更新用户设置 // PUT /api/settings - 更新当前用户设置
export async function PUT(request: Request) { export async function PUT(request: Request) {
try { try {
// 1. 获取当前登录用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '请先登录后再修改设置' },
{ status: 401 }
);
}
const body = await request.json(); const body = await request.json();
const { const {
cchUrl, cchUrl,
@ -68,7 +113,7 @@ export async function PUT(request: Request) {
saveChatHistory, saveChatHistory,
} = body; } = body;
// 构建更新对象 // 2. 构建更新对象
const updateData: Record<string, unknown> = { const updateData: Record<string, unknown> = {
updatedAt: new Date(), updatedAt: new Date(),
}; };
@ -77,14 +122,15 @@ export async function PUT(request: Request) {
updateData.cchUrl = cchUrl; updateData.cchUrl = cchUrl;
} }
// 如果提供了 API Key更新它 // 如果提供了 API Key加密后存储
if (cchApiKey !== undefined) { if (cchApiKey !== undefined) {
if (cchApiKey === '') { if (cchApiKey === '') {
// 清除 API Key // 清除 API Key
updateData.cchApiKey = null; updateData.cchApiKey = null;
updateData.cchApiKeyConfigured = false; updateData.cchApiKeyConfigured = false;
} else { } else {
updateData.cchApiKey = cchApiKey; // 加密存储 API Key
updateData.cchApiKey = encryptApiKey(cchApiKey);
updateData.cchApiKeyConfigured = true; updateData.cchApiKeyConfigured = true;
} }
} }
@ -126,15 +172,23 @@ export async function PUT(request: Request) {
updateData.saveChatHistory = saveChatHistory; updateData.saveChatHistory = saveChatHistory;
} }
// 检查是否存在设置记录 // 3. 检查是否存在该用户的设置记录
const existing = await db.query.userSettings.findFirst({ const existing = await db.query.userSettings.findFirst({
where: eq(userSettings.id, 1), where: eq(userSettings.userId, user.userId),
}); });
if (!existing) { if (!existing) {
// 创建新的设置记录 // 创建新的设置记录
await db.insert(userSettings).values({ await db.insert(userSettings).values({
id: 1, userId: user.userId,
cchUrl: DEFAULT_SETTINGS.cchUrl,
defaultModel: DEFAULT_SETTINGS.defaultModel,
defaultTools: DEFAULT_SETTINGS.defaultTools,
theme: DEFAULT_SETTINGS.theme,
language: DEFAULT_SETTINGS.language,
fontSize: DEFAULT_SETTINGS.fontSize,
enableThinking: DEFAULT_SETTINGS.enableThinking,
saveChatHistory: DEFAULT_SETTINGS.saveChatHistory,
...updateData, ...updateData,
}); });
} else { } else {
@ -142,27 +196,15 @@ export async function PUT(request: Request) {
await db await db
.update(userSettings) .update(userSettings)
.set(updateData) .set(updateData)
.where(eq(userSettings.id, 1)); .where(eq(userSettings.userId, user.userId));
} }
// 返回更新后的设置(不包含 API Key // 4. 返回更新后的设置(不包含 API Key
const updatedSettings = await db.query.userSettings.findFirst({ const updatedSettings = await db.query.userSettings.findFirst({
where: eq(userSettings.id, 1), where: eq(userSettings.userId, user.userId),
}); });
return NextResponse.json({ return NextResponse.json(formatSettingsResponse(updatedSettings ?? null));
cchUrl: updatedSettings?.cchUrl,
cchApiKeyConfigured: updatedSettings?.cchApiKeyConfigured,
defaultModel: updatedSettings?.defaultModel,
defaultTools: updatedSettings?.defaultTools,
systemPrompt: updatedSettings?.systemPrompt || '',
temperature: updatedSettings?.temperature || '0.7',
theme: updatedSettings?.theme,
language: updatedSettings?.language,
fontSize: updatedSettings?.fontSize || 15,
enableThinking: updatedSettings?.enableThinking,
saveChatHistory: updatedSettings?.saveChatHistory,
});
} catch (error) { } catch (error) {
console.error('Failed to update settings:', error); console.error('Failed to update settings:', error);
return NextResponse.json( return NextResponse.json(