更新安全配置项目
This commit is contained in:
+9
-27
@@ -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}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
"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,
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 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
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user