更新安全配置项目

This commit is contained in:
2026-04-13 22:10:43 +08:00
parent 8e245076fe
commit 70d3020035
8 changed files with 43 additions and 284 deletions
+9 -27
View File
@@ -1,33 +1,28 @@
import { NextResponse } from "next/server"; import {NextResponse} from "next/server";
import { getConfig, saveDBConfig, saveAIConfig, saveExportConfig, type DBConfig, type AIConfig, type ExportConfig } from "@/lib/config"; import {getAllConfig, saveAIConfig, saveExportConfig} from "@/lib/config";
// GET /api/config - 获取完整配置 // GET /api/config - 获取完整配置
export async function GET() { export async function GET() {
try { try {
const config = getConfig(); const config = getAllConfig();
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
data: config, data: config,
}); });
} catch (error) { } catch (error) {
return NextResponse.json( return NextResponse.json(
{ success: false, error: "读取配置失败" }, {success: false, error: "读取配置失败"},
{ status: 500 } {status: 500}
); );
} }
} }
// POST /api/config - 保存配置 // POST /api/config - 保存配置
// Body 格式: { db?: DBConfig, ai?: AIConfig, export?: ExportConfig } // Body 格式: {ai?: AIConfig, export?: ExportConfig }
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const body = await request.json(); const body = await request.json();
const { db, ai, export: exportConfig } = body; const {ai, export: exportConfig} = body;
if (db) {
saveDBConfig(db);
}
if (ai) { if (ai) {
saveAIConfig(ai); saveAIConfig(ai);
} }
@@ -35,27 +30,14 @@ export async function POST(request: Request) {
if (exportConfig) { if (exportConfig) {
saveExportConfig(exportConfig); saveExportConfig(exportConfig);
} }
// 如果同时发送了完整配置
if (!db && !ai && !exportConfig && body.db === undefined && body.ai === undefined && body.export === undefined) {
// 可能是旧的扁平格式,尝试兼容
if (body.apiKey !== undefined || body.baseUrl !== undefined || body.model !== undefined) {
saveAIConfig({
apiKey: body.apiKey,
baseUrl: body.baseUrl,
model: body.model,
});
}
}
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
message: "配置已保存", message: "配置已保存",
}); });
} catch (error) { } catch (error) {
return NextResponse.json( return NextResponse.json(
{ success: false, error: "保存失败" }, {success: false, error: "保存失败"},
{ status: 500 } {status: 500}
); );
} }
} }
-152
View File
@@ -1,152 +0,0 @@
"use client";
import {useState, useEffect} from "react";
import {useToast} from "@/components/ui/toast";
import {Loader2} from "lucide-react";
import {Button} from "@/components/ui/button";
interface DBConfig {
host: string;
port: number;
user: string;
password: string;
database: string;
}
const defaultDBConfig: DBConfig = {
host: "127.0.0.1",
port: 3306,
user: "root",
password: "",
database: "recipe_tools",
};
export default function DatabasePage() {
const [config, setConfig] = useState<DBConfig>(defaultDBConfig);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const {addToast} = useToast();
useEffect(() => {
fetch("/api/config")
.then((res) => res.json())
.then((result) => {
if (result.success && result.data) {
setConfig({
host: result.data.db?.host || defaultDBConfig.host,
port: result.data.db?.port || defaultDBConfig.port,
user: result.data.db?.user || defaultDBConfig.user,
password: result.data.db?.password || defaultDBConfig.password,
database: result.data.db?.database || defaultDBConfig.database,
});
}
})
.catch(console.error)
.finally(() => setLoading(false));
}, []);
const handleSave = async () => {
setSaving(true);
try {
const response = await fetch("/api/config", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({db: config}),
});
const result = await response.json();
if (result.success) {
addToast({type: "success", title: "数据库配置已保存"});
} else {
addToast({type: "error", title: "保存失败", description: result.error});
}
} catch {
addToast({type: "error", title: "保存失败"});
} finally {
setSaving(false);
}
};
if (loading) {
return (
<div className="space-y-6">
<div>
<h2 className="text-lg font-semibold"></h2>
<p className="text-sm text-muted-foreground"> MySQL </p>
</div>
<div className="h-40 animate-pulse rounded-lg bg-muted"/>
</div>
);
}
return (
<div className="space-y-6">
<div className={"flex items-center justify-between"}>
<div>
<h2 className="text-lg font-semibold"></h2>
<p className="text-sm text-muted-foreground"> MySQL </p>
</div>
<div className="flex gap-3">
<Button onClick={handleSave} disabled={saving}>
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin"/>}
{saving ? "保存中..." : "保存配置"}
</Button>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<input
type="text"
value={config.host}
onChange={(e) => setConfig({...config, host: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="127.0.0.1"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<input
type="text"
value={config.port}
onChange={(e) => setConfig({...config, port: parseInt(e.target.value) || 3306})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="3306"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<input
type="text"
value={config.user}
onChange={(e) => setConfig({...config, user: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="root"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<input
type="password"
value={config.password}
onChange={(e) => setConfig({...config, password: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="******"
/>
</div>
<div className="space-y-2 md:col-span-2">
<label className="text-sm font-medium"></label>
<input
type="text"
value={config.database}
onChange={(e) => setConfig({...config, database: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="recipe_tools"
/>
</div>
</div>
</div>
);
}
+3 -15
View File
@@ -1,23 +1,11 @@
"use client"; "use client";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import {usePathname} from "next/navigation";
import { import {Bandage, ChevronRight, FileText, Globe, UtensilsCrossed} from "lucide-react";
Database, import {Button} from "@/components/ui/button";
Globe,
FileText,
ChevronRight,
UtensilsCrossed,
Bandage
} from "lucide-react";
import { Button } from "@/components/ui/button";
const settingsMenu = [ const settingsMenu = [
{
title: "数据库配置",
icon: Database,
href: "/settings/database",
},
{ {
title: "翻译词库", title: "翻译词库",
icon: Globe, icon: Globe,
+2 -2
View File
@@ -4,12 +4,12 @@ import {useEffect} from "react";
import {useRouter} from "next/navigation"; import {useRouter} from "next/navigation";
import {Loader2} from "lucide-react"; import {Loader2} from "lucide-react";
// 主设置页面 - 重定向到数据库设置 // 主设置页面 - 重定向到库设置
export default function SettingsPage() { export default function SettingsPage() {
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
router.replace("/settings/database"); router.replace("/settings/translation");
}, [router]); }, [router]);
return ( return (
-9
View File
@@ -1,9 +0,0 @@
# 数据库配置
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=123456
DB_NAME=recipe_tools
# 服务器配置
PORT=3000
+2 -2
View File
@@ -1,5 +1,5 @@
import OpenAI from "openai"; import OpenAI from "openai";
import {getConfig} from "@/lib/config"; import {getAIConfig} from "@/lib/config";
function parseTranslation(content: string): string | null { function parseTranslation(content: string): string | null {
// 第一步:清理内容,移除 Markdown 代码块、换行、多余空格 // 第一步:清理内容,移除 Markdown 代码块、换行、多余空格
@@ -21,7 +21,7 @@ function parseTranslation(content: string): string | null {
// 翻译单个词组(带重试) // 翻译单个词组(带重试)
export async function translateWithAI(word: string): Promise<string | null> { export async function translateWithAI(word: string): Promise<string | null> {
// 获取配置 // 获取配置
const aiConfig = getConfig().ai const aiConfig = getAIConfig()
if (!aiConfig.apiKey) { if (!aiConfig.apiKey) {
throw new Error("AI API Key 未配置"); throw new Error("AI API Key 未配置");
+1 -51
View File
@@ -179,7 +179,7 @@ export function getExportConfig(): ExportConfig {
/** /**
* 获取完整配置 * 获取完整配置
*/ */
export function getConfig(): AppConfig { export function getAllConfig(): AppConfig {
// 如果配置文件不存在,从环境变量初始化 // 如果配置文件不存在,从环境变量初始化
if (!configFileExists()) { if (!configFileExists()) {
return initConfigFromEnv(); return initConfigFromEnv();
@@ -192,37 +192,6 @@ export function getConfig(): AppConfig {
}; };
} }
/**
* 保存数据库配置
*/
export function saveDBConfig(config: Partial<DBConfig>): DBConfig {
// 确保配置文件存在
if (!configFileExists()) {
initConfigFromEnv();
}
const current = getDBConfig();
const newConfig: DBConfig = {
host: config.host ?? current.host,
port: config.port ?? current.port,
user: config.user ?? current.user,
password: config.password ?? current.password,
database: config.database ?? current.database,
};
// 读取现有配置,更新 db 部分
const fileConfig = readConfigFile();
const updatedConfig: AppConfig = {
ai: fileConfig.ai ?? { apiKey: "", baseUrl: "https://api.openai.com/v1", model: "gpt-3.5-turbo" },
export: fileConfig.export ?? { untranslatedColor: "CC0000", secondPageHeader: false, orientation: "landscape" },
db: newConfig,
};
writeConfigFile(updatedConfig);
return newConfig;
}
/** /**
* 保存AI翻译配置 * 保存AI翻译配置
*/ */
@@ -280,22 +249,3 @@ export function saveExportConfig(config: Partial<ExportConfig>): ExportConfig {
return newConfig; return newConfig;
} }
/**
* 保存完整配置
*/
export function saveConfig(config: Partial<AppConfig>): AppConfig {
// 确保配置文件存在
if (!configFileExists()) {
initConfigFromEnv();
}
const current = getConfig();
const fileConfig = readConfigFile();
return {
db: config.db ? saveDBConfig(config.db) : current.db,
ai: config.ai ? saveAIConfig(config.ai) : current.ai,
export: config.export ? saveExportConfig(config.export) : current.export,
};
}
+3 -3
View File
@@ -1,8 +1,8 @@
import mysql from "mysql2/promise"; import mysql from "mysql2/promise";
import {getConfig} from "@/lib/config"; import {getDBConfig} from "@/lib/config";
// 获取配置文件中的数据链接 // 获取配置文件中的数据
const dataBaseConfig = getConfig().db const dataBaseConfig = getDBConfig()
const pool = mysql.createPool({ const pool = mysql.createPool({
host: dataBaseConfig.host || "127.0.0.1", host: dataBaseConfig.host || "127.0.0.1",