更新安全配置项目
This commit is contained in:
+32
-50
@@ -1,61 +1,43 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getConfig, saveDBConfig, saveAIConfig, saveExportConfig, type DBConfig, type AIConfig, type ExportConfig } from "@/lib/config";
|
||||
import {NextResponse} from "next/server";
|
||||
import {getAllConfig, saveAIConfig, saveExportConfig} from "@/lib/config";
|
||||
|
||||
// GET /api/config - 获取完整配置
|
||||
export async function GET() {
|
||||
try {
|
||||
const config = getConfig();
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: config,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "读取配置失败" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
try {
|
||||
const config = getAllConfig();
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: config,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{success: false, error: "读取配置失败"},
|
||||
{status: 500}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/config - 保存配置
|
||||
// Body 格式: { db?: DBConfig, ai?: AIConfig, export?: ExportConfig }
|
||||
// Body 格式: {ai?: AIConfig, export?: ExportConfig }
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { db, ai, export: exportConfig } = body;
|
||||
try {
|
||||
const body = await request.json();
|
||||
const {ai, export: exportConfig} = body;
|
||||
if (ai) {
|
||||
saveAIConfig(ai);
|
||||
}
|
||||
|
||||
if (db) {
|
||||
saveDBConfig(db);
|
||||
}
|
||||
|
||||
if (ai) {
|
||||
saveAIConfig(ai);
|
||||
}
|
||||
|
||||
if (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,
|
||||
if (exportConfig) {
|
||||
saveExportConfig(exportConfig);
|
||||
}
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "配置已保存",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{success: false, error: "保存失败"},
|
||||
{status: 500}
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "配置已保存",
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "保存失败" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,23 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import {
|
||||
Database,
|
||||
Globe,
|
||||
FileText,
|
||||
ChevronRight,
|
||||
UtensilsCrossed,
|
||||
Bandage
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {usePathname} from "next/navigation";
|
||||
import {Bandage, ChevronRight, FileText, Globe, UtensilsCrossed} from "lucide-react";
|
||||
import {Button} from "@/components/ui/button";
|
||||
|
||||
const settingsMenu = [
|
||||
{
|
||||
title: "数据库配置",
|
||||
icon: Database,
|
||||
href: "/settings/database",
|
||||
},
|
||||
{
|
||||
title: "翻译词库",
|
||||
icon: Globe,
|
||||
|
||||
@@ -4,12 +4,12 @@ import {useEffect} from "react";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Loader2} from "lucide-react";
|
||||
|
||||
// 主设置页面 - 重定向到数据库设置
|
||||
// 主设置页面 - 重定向到词库设置
|
||||
export default function SettingsPage() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
router.replace("/settings/database");
|
||||
router.replace("/settings/translation");
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -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
@@ -1,5 +1,5 @@
|
||||
import OpenAI from "openai";
|
||||
import {getConfig} from "@/lib/config";
|
||||
import {getAIConfig} from "@/lib/config";
|
||||
|
||||
function parseTranslation(content: string): string | null {
|
||||
// 第一步:清理内容,移除 Markdown 代码块、换行、多余空格
|
||||
@@ -21,7 +21,7 @@ function parseTranslation(content: string): string | null {
|
||||
// 翻译单个词组(带重试)
|
||||
export async function translateWithAI(word: string): Promise<string | null> {
|
||||
// 获取配置
|
||||
const aiConfig = getConfig().ai
|
||||
const aiConfig = getAIConfig()
|
||||
|
||||
if (!aiConfig.apiKey) {
|
||||
throw new Error("AI API Key 未配置");
|
||||
|
||||
+1
-51
@@ -179,7 +179,7 @@ export function getExportConfig(): ExportConfig {
|
||||
/**
|
||||
* 获取完整配置
|
||||
*/
|
||||
export function getConfig(): AppConfig {
|
||||
export function getAllConfig(): AppConfig {
|
||||
// 如果配置文件不存在,从环境变量初始化
|
||||
if (!configFileExists()) {
|
||||
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翻译配置
|
||||
*/
|
||||
@@ -280,22 +249,3 @@ export function saveExportConfig(config: Partial<ExportConfig>): ExportConfig {
|
||||
|
||||
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
@@ -1,8 +1,8 @@
|
||||
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({
|
||||
host: dataBaseConfig.host || "127.0.0.1",
|
||||
|
||||
Reference in New Issue
Block a user