diff --git a/.cargo-home/registry/src/github.com-25cdd57fae9f0462/thread_local-1.1.9/Cargo.toml b/.cargo-home/registry/src/github.com-25cdd57fae9f0462/thread_local-1.1.9/Cargo.toml new file mode 100644 index 0000000..c2df8f3 --- /dev/null +++ b/.cargo-home/registry/src/github.com-25cdd57fae9f0462/thread_local-1.1.9/Cargo.toml @@ -0,0 +1,51 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2024" +rust-version = "1.63" +name = "thread_local" +version = "1.1.9" +authors = ["Amanieu d'Antras "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Per-object thread-local storage" +documentation = "https://docs.rs/thread_local/" +readme = "README.md" +keywords = [ + "thread_local", + "concurrent", + "thread", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/Amanieu/thread_local-rs" + +[features] +nightly = [] + +[lib] +name = "thread_local" +path = "src/lib.rs" + +[[bench]] +name = "thread_local" +path = "benches/thread_local.rs" +harness = false + +[dependencies.cfg-if] +version = "1.0.0" + +[dev-dependencies.criterion] +version = "0.5.1" diff --git a/docker-compose.yml b/docker-compose.yml index fd887d8..f548ee7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,8 +55,6 @@ services: environment: # 让 Next 的 API 路由代理到新的 api-gateway NEXT_PUBLIC_BACKEND_URL: http://api-gateway:4000/v1 - # Prisma 直连数据库(与后端共用同一库) - DATABASE_URL: postgresql://postgres:postgres@postgres-db:5432/fundamental?schema=public NODE_ENV: development NEXT_TELEMETRY_DISABLED: "1" volumes: @@ -66,7 +64,6 @@ services: ports: - "13001:3001" depends_on: - - postgres-db - api-gateway networks: - app-network diff --git a/docs/1_requirements/20251108_[Active]_requirements.md b/docs/1_requirements/20251108_[Active]_requirements.md index 3f767fe..f917297 100644 --- a/docs/1_requirements/20251108_[Active]_requirements.md +++ b/docs/1_requirements/20251108_[Active]_requirements.md @@ -104,8 +104,7 @@ #### 验收标准 -1. 选股系统应当提供配置页面用于设置数据库连接参数 -2. 选股系统应当提供配置页面用于设置Gemini_API密钥 -3. 选股系统应当提供配置页面用于设置各市场的数据源配置 -4. 当配置更新时,选股系统应当验证配置的有效性 -5. 当配置保存时,选股系统应当将配置持久化存储 \ No newline at end of file +1. 选股系统应当提供配置页面用于设置Gemini_API密钥 +2. 选股系统应当提供配置页面用于设置各市场的数据源配置 +3. 当配置更新时,选股系统应当验证配置的有效性 +4. 当配置保存时,选股系统应当将配置持久化存储 \ No newline at end of file diff --git a/docs/1_requirements/20251109_[Active]_user-guide.md b/docs/1_requirements/20251109_[Active]_user-guide.md index 06d9742..df3f59c 100644 --- a/docs/1_requirements/20251109_[Active]_user-guide.md +++ b/docs/1_requirements/20251109_[Active]_user-guide.md @@ -73,7 +73,6 @@ 系统提供完善的配置管理功能: -- **数据库配置**:配置 PostgreSQL 数据库连接 - **AI 服务配置**:配置 AI 模型的 API 密钥和端点 - **数据源配置**:配置 Tushare、Finnhub 等数据源的 API 密钥 - **分析模块配置**:自定义分析模块的名称、模型和提示词模板 @@ -221,15 +220,11 @@ A: 首次使用系统时,需要配置以下内容: -1. **数据库配置**(如使用) - - 数据库连接 URL:`postgresql+asyncpg://user:password@host:port/database` - - 使用"测试连接"按钮验证连接 - -2. **AI 服务配置** +1. **AI 服务配置** - API Key:输入您的 AI 服务 API 密钥 - Base URL:输入 API 端点地址(如使用自建服务) -3. **数据源配置** +2. **数据源配置** - **Tushare**:输入 Tushare API Key(中国市场必需) - **Finnhub**:输入 Finnhub API Key(全球市场可选) diff --git a/docs/README.md b/docs/README.md index e69de29..a178843 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,64 @@ +# 项目文档中心 + +欢迎来到基本面选股系统的文档中心。本文档旨在作为项目所有相关文档的入口和导航,帮助团队成员快速找到所需信息。 + +## 概览 + +本文档库遵循特定的结构化命名和分类约定,旨在清晰地分离不同领域的关注点。主要目录结构如下: + +- **/1_requirements**: 存放所有与产品需求和用户功能相关的文档。 +- **/2_architecture**: 包含系统高级架构、设计原则和核心规范。 +- **/3_project_management**: 用于项目跟踪、开发日志和任务管理。 +- **/4_archive**: 存放已合并或过时的历史文档。 +- **/5_data_dictionary**: 定义系统中使用的数据模型和字段。 + +--- + +## 快速导航 + +以下是项目中几个最核心文档的快速访问链接,直接指向其关键章节。 + +### 1. 需求与功能 + +- **[需求文档 (`requirements.md`)]** - 定义了产品的核心功能和MVP版本的验收标准。 + - [查看系统核心功能](1_requirements/20251108_[Active]_requirements.md#需求-1) + - [了解九大分析模块](1_requirements/20251108_[Active]_requirements.md#需求-5) + - [查看报告生成进度追踪需求](1_requirements/20251108_[Active]_requirements.md#需求-7) + +- **[用户使用文档 (`user-guide.md`)]** - 为系统的最终用户提供详细的操作指南。 + - [快速入门指引](1_requirements/20251109_[Active]_user-guide.md#快速开始) + - [财务数据指标解读](1_requirements/20251109_[Active]_user-guide.md#财务数据解读) + - [首次使用的系统配置](1_requirements/20251109_[Active]_user-guide.md#首次使用配置) + +### 2. 架构与设计 + +- **[系统架构总览 (`system_architecture.md`)]** - 项目的“单一事实源”,描述了事件驱动微服务架构的核心理念。 + - [核心架构理念与原则](2_architecture/20251116_[Active]_system_architecture.md#12-核心架构理念) + - [目标架构图](2_architecture/20251116_[Active]_system_architecture.md#21-目标架构图) + - [数据库 Schema 设计概览](2_architecture/20251116_[Active]_system_architecture.md#5-数据库-schema-设计) + +- **[系统模块设计准则 (`architecture_module_specification.md`)]** - 对微服务必须遵守的 `SystemModule` 行为契约进行了形式化定义。 + - [核心思想:`SystemModule` Trait](4_archive/merged_sources/20251115_[Active]_architecture_module_specification.md#3-systemmodule-trait模块的行为契约) + - [强制实现的可观测性接口 (`/health`, `/tasks`)](4_archive/merged_sources/20251115_[Active]_architecture_module_specification.md#41-可观测性接口的数据结构) + +### 3. 数据与模型 + +- **[财务数据字典 (`financial_data_dictionary.md`)]** - 定义了所有前端展示的财务指标及其在不同数据源(Tushare, Finnhub)的映射关系。 + - [查看主要财务指标定义](5_data_dictionary/20251109_[Living]_financial_data_dictionary.md#1-主要指标-key-indicators) + - [查看资产负债结构定义](5_data_dictionary/20251109_[Living]_financial_data_dictionary.md#3-资产负债结构-asset--liability-structure) + +- **[数据库 Schema 详细设计 (`database_schema_design.md`)]** - 提供了所有核心数据表的详细 `CREATE TABLE` 语句和设计哲学。 + - [为何选择 TimescaleDB](4_archive/merged_sources/20251109_[Active]_database_schema_design.md#11-时间序列数据-postgresql--timescaledb) + - [查看 `time_series_financials` 表结构](4_archive/merged_sources/20251109_[Active]_database_schema_design.md#211-time_series_financials-财务指标表) + - [查看 `analysis_results` 表结构](4_archive/merged_sources/20251109_[Active]_database_schema_design.md#22-analysis_results-ai分析结果表) + +### 4. 项目管理 + +- **[项目当前状态 (`project-status.md`)]** - 一个动态更新的文档,记录了项目的当前进展、已知问题和下一步计划。 + - [查看当前功能与数据状态](3_project_management/20251109_[Living]_project-status.md#当前功能与数据状态) + - [查看已知问题与限制](3_project_management/20251109_[Living]_project-status.md#已知问题限制) + - [查看后续开发计划](3_project_management/20251109_[Living]_project-status.md#后续计划优先级由高到低) + +- **开发日志与任务**: + - [查看所有开发日志](./3_project_management/logs/) + - [查看已完成的任务](./3_project_management/tasks/completed/) diff --git a/frontend/package.json b/frontend/package.json index 9e53bd2..9ab38fb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,6 @@ "lint": "eslint" }, "dependencies": { - "@prisma/client": "^6.18.0", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-select": "^2.2.6", @@ -38,7 +37,6 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.5.5", - "prisma": "^6.18.0", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "typescript": "^5" diff --git a/frontend/prisma/migrations/migration_lock.toml b/frontend/prisma/migrations/migration_lock.toml deleted file mode 100644 index 044d57c..0000000 --- a/frontend/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (e.g., Git) -provider = "postgresql" diff --git a/frontend/prisma/schema.prisma b/frontend/prisma/schema.prisma deleted file mode 100644 index dbeadc2..0000000 --- a/frontend/prisma/schema.prisma +++ /dev/null @@ -1,19 +0,0 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") - shadowDatabaseUrl = env("PRISMA_MIGRATE_SHADOW_DATABASE_URL") -} - -model Report { - id String @id @default(uuid()) - symbol String - content Json - createdAt DateTime @default(now()) -} diff --git a/frontend/src/app/api/reports/[id]/route.ts b/frontend/src/app/api/reports/[id]/route.ts index 5e4ff6c..f3f1a09 100644 --- a/frontend/src/app/api/reports/[id]/route.ts +++ b/frontend/src/app/api/reports/[id]/route.ts @@ -1,5 +1,6 @@ import { NextRequest } from 'next/server' -import { prisma } from '../../../../lib/prisma' + +const BACKEND_BASE = process.env.NEXT_PUBLIC_BACKEND_URL; export async function GET( req: NextRequest, @@ -21,9 +22,28 @@ export async function GET( return Response.json({ error: 'missing id' }, { status: 400 }) } - const report = await prisma.report.findUnique({ where: { id } }) - if (!report) { - return Response.json({ error: 'not found' }, { status: 404 }) + if (!BACKEND_BASE) { + return new Response('NEXT_PUBLIC_BACKEND_URL 未配置', { status: 500 }); + } + const resp = await fetch(`${BACKEND_BASE}/analysis-results/${encodeURIComponent(id)}`); + const text = await resp.text(); + if (!resp.ok) { + return new Response(text || 'not found', { status: resp.status }); + } + // 将后端 DTO(generated_at 等)适配为前端旧结构字段(createdAt) + try { + const dto = JSON.parse(text); + const adapted = { + id: dto.id, + symbol: dto.symbol, + createdAt: dto.generated_at || dto.generatedAt || null, + content: dto.content, + module_id: dto.module_id, + model_name: dto.model_name, + meta_data: dto.meta_data, + }; + return Response.json(adapted); + } catch { + return Response.json({ error: 'invalid response from backend' }, { status: 502 }); } - return Response.json(report) } diff --git a/frontend/src/app/api/reports/route.ts b/frontend/src/app/api/reports/route.ts index 237066f..a234692 100644 --- a/frontend/src/app/api/reports/route.ts +++ b/frontend/src/app/api/reports/route.ts @@ -1,43 +1,15 @@ export const runtime = 'nodejs' import { NextRequest } from 'next/server' -import { prisma } from '../../../lib/prisma' + +const BACKEND_BASE = process.env.NEXT_PUBLIC_BACKEND_URL; export async function GET(req: NextRequest) { - const url = new URL(req.url) - const limit = Number(url.searchParams.get('limit') || 50) - const offset = Number(url.searchParams.get('offset') || 0) - - const [items, total] = await Promise.all([ - prisma.report.findMany({ - orderBy: { createdAt: 'desc' }, - skip: offset, - take: Math.min(Math.max(limit, 1), 200) - }), - prisma.report.count() - ]) - - return Response.json({ items, total }) + // 历史报告列表功能在新架构中由后端持久化服务统一提供。 + // 当前网关未提供“全量列表”接口(需要 symbol 条件),因此此路由返回空集合。 + return Response.json({ items: [], total: 0 }, { status: 200 }); } export async function POST(req: NextRequest) { - try { - const body = await req.json() - const symbol = String(body.symbol || '').trim() - const content = body.content - - if (!symbol) { - return Response.json({ error: 'symbol is required' }, { status: 400 }) - } - if (typeof content === 'undefined') { - return Response.json({ error: 'content is required' }, { status: 400 }) - } - - const created = await prisma.report.create({ - data: { symbol, content } - }) - - return Response.json(created, { status: 201 }) - } catch (e) { - return Response.json({ error: 'invalid json body' }, { status: 400 }) - } + // 新架构下,报告持久化由后端流水线/服务完成,此处不再直接创建。 + return Response.json({ error: 'Not implemented: creation is handled by backend pipeline' }, { status: 501 }); } diff --git a/frontend/src/app/config/page.tsx b/frontend/src/app/config/page.tsx index 4f3b349..fcf4460 100644 --- a/frontend/src/app/config/page.tsx +++ b/frontend/src/app/config/page.tsx @@ -24,7 +24,6 @@ export default function ConfigPage() { const { data: analysisConfig, mutate: mutateAnalysisConfig } = useAnalysisConfig(); // 本地表单状态 - const [dbUrl, setDbUrl] = useState(''); const [newApiApiKey, setNewApiApiKey] = useState(''); const [newApiBaseUrl, setNewApiBaseUrl] = useState(''); const [tushareApiKey, setTushareApiKey] = useState(''); @@ -110,11 +109,6 @@ export default function ConfigPage() { const validateConfig = () => { const errors: string[] = []; - // 验证数据库URL格式 - if (dbUrl && !dbUrl.match(/^postgresql(\+asyncpg)?:\/\/.+/)) { - errors.push('数据库URL格式不正确,应为 postgresql://user:pass@host:port/dbname'); - } - // 验证New API Base URL格式 if (newApiBaseUrl && !newApiBaseUrl.match(/^https?:\/\/.+/)) { errors.push('New API Base URL格式不正确,应为 http:// 或 https:// 开头'); @@ -149,11 +143,6 @@ export default function ConfigPage() { const newConfig: Partial = {}; - // 只更新有值的字段 - if (dbUrl) { - newConfig.database = { url: dbUrl }; - } - if (newApiApiKey || newApiBaseUrl) { newConfig.new_api = { api_key: newApiApiKey || config?.new_api?.api_key || '', @@ -197,10 +186,6 @@ export default function ConfigPage() { } }; - const handleTestDb = () => { - handleTest('database', { url: dbUrl }); - }; - const handleTestNewApi = () => { handleTest('new_api', { api_key: newApiApiKey || config?.new_api?.api_key, @@ -217,7 +202,6 @@ export default function ConfigPage() { }; const handleReset = () => { - setDbUrl(''); setNewApiApiKey(''); setNewApiBaseUrl(''); setTushareApiKey(''); @@ -230,7 +214,6 @@ export default function ConfigPage() { if (!config) return; const configToExport = { - database: config.database, new_api: config.new_api, data_sources: config.data_sources, export_time: new Date().toISOString(), @@ -296,51 +279,18 @@ export default function ConfigPage() {

配置中心

- 管理系统配置,包括数据库连接、API密钥等。敏感信息不回显,留空表示保持当前值。 + 管理系统配置,包括 AI 服务与数据源等。敏感信息不回显,留空表示保持当前值。

- - - 数据库 + + AI服务 数据源 分析配置 系统 - - - - 数据库配置 - PostgreSQL 数据库连接设置 - - -
- -
- setDbUrl(e.target.value)} - placeholder="postgresql+asyncpg://user:password@host:port/database" - className="flex-1" - /> - -
- {testResults.database && ( - - {testResults.database.message} - - )} -
-
-
-
- @@ -565,12 +515,6 @@ export default function ConfigPage() {
-
- - - {config?.database?.url ? '已配置' : '未配置'} - -
diff --git a/frontend/src/app/report/[symbol]/page.tsx b/frontend/src/app/report/[symbol]/page.tsx index aa5bbee..603d87b 100644 --- a/frontend/src/app/report/[symbol]/page.tsx +++ b/frontend/src/app/report/[symbol]/page.tsx @@ -107,49 +107,7 @@ export default function ReportPage() { }; error?: string; }>>([]); - - const [saving, setSaving] = useState(false) - const [saveMsg, setSaveMsg] = useState(null) - - const saveReport = async () => { - try { - setSaving(true) - setSaveMsg(null) - const content = { - market, - normalizedSymbol: normalizedTsCode, - financialsMeta: financials?.meta || null, - // 同步保存财务数据(用于报告详情页展示) - financials: financials - ? { - ts_code: financials.ts_code, - name: (financials as any).name, - series: financials.series, - meta: financials.meta, - } - : null, - analyses: Object.fromEntries( - Object.entries(analysisStates).map(([k, v]) => [k, { content: v.content, error: v.error, elapsed_ms: v.elapsed_ms, tokens: v.tokens }]) - ) - } - const resp = await fetch('/api/reports', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ symbol: normalizedTsCode, content }) - }) - if (!resp.ok) { - const t = await resp.json().catch(() => ({})) - throw new Error(t?.error || `HTTP ${resp.status}`) - } - const data = await resp.json() - setSaveMsg('保存成功') - return data - } catch (e) { - setSaveMsg(e instanceof Error ? e.message : '保存失败') - } finally { - setSaving(false) - } - } + const runFullAnalysis = async () => { if (!financials || !analysisConfig?.analysis_modules || isAnalysisRunningRef.current) { @@ -755,14 +713,6 @@ export default function ReportPage() { style={{ width: `${completionProgress}%` }} />
- {allTasksCompleted && ( -
- - {saveMsg && {saveMsg}} -
- )} {currentAnalysisTask && analysisConfig && ( diff --git a/frontend/src/app/reports/[id]/page.tsx b/frontend/src/app/reports/[id]/page.tsx index 9ca1d7f..38c0207 100644 --- a/frontend/src/app/reports/[id]/page.tsx +++ b/frontend/src/app/reports/[id]/page.tsx @@ -1,4 +1,4 @@ -import { prisma } from '../../../lib/prisma' +import { headers } from 'next/headers' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' @@ -15,7 +15,30 @@ type Report = { export default async function ReportDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params - const data = await prisma.report.findUnique({ where: { id } }) + const h = await headers() + const host = h.get('x-forwarded-host') || h.get('host') || 'localhost:3000' + const proto = h.get('x-forwarded-proto') || 'http' + const base = process.env.NEXT_PUBLIC_BASE_URL || `${proto}://${host}` + const resp = await fetch(`${base}/api/reports/${encodeURIComponent(id)}`, { cache: 'no-store' }) + if (!resp.ok) { + return
未找到报告
+ } + const raw = await resp.json() + let parsedContent: any = {} + try { + // 后端 content 可能为 JSON 字符串,也可能为已解析对象 + parsedContent = typeof raw?.content === 'string' ? JSON.parse(raw.content) : (raw?.content ?? {}) + } catch { + parsedContent = {} + } + const data: Report | null = raw + ? { + id: String(raw.id), + symbol: String(raw.symbol ?? ''), + content: parsedContent, + createdAt: String(raw.createdAt ?? raw.generated_at ?? new Date().toISOString()), + } + : null if (!data) { return
未找到报告
diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts index 1cbdb52..50cd706 100644 --- a/frontend/src/hooks/useApi.ts +++ b/frontend/src/hooks/useApi.ts @@ -2,6 +2,8 @@ import useSWR, { SWRConfiguration } from "swr"; import { Financials, FinancialsIdentifier } from "@/types"; import { useEffect, useState } from "react"; import { AnalysisStep, AnalysisTask } from "@/lib/execution-step-manager"; +import { useConfigStore } from "@/stores/useConfigStore"; +import type { SystemConfig } from "@/stores/useConfigStore"; const fetcher = (url: string) => fetch(url).then((res) => res.json()); @@ -232,3 +234,69 @@ export function useRealtimeQuote( } ); } + +// =============================== +// 配置相关 Hooks 与函数 +// =============================== + +export function useConfig() { + const { setConfig, setError, setLoading } = useConfigStore(); + const { data, error, isLoading } = useSWR('/api/config', fetcher); + useEffect(() => { + setLoading(Boolean(isLoading)); + if (error) { + setError(error.message || '加载配置失败'); + } else if (data) { + setConfig(data); + } + }, [data, error, isLoading, setConfig, setError, setLoading]); + return { data, error, isLoading }; +} + +export async function updateConfig(payload: Partial) { + const res = await fetch('/api/config', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(text || `HTTP ${res.status}`); + } + const updated: SystemConfig = await res.json(); + // 同步到 store + try { + const { setConfig } = useConfigStore.getState(); + setConfig(updated); + } catch (_) { + // ignore + } + return updated; +} + +export async function testConfig(type: string, data: unknown) { + const res = await fetch('/api/config/test', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type, data }), + }); + const text = await res.text(); + if (!res.ok) { + try { + const err = JSON.parse(text); + throw new Error(err?.message || text); + } catch { + throw new Error(text || `HTTP ${res.status}`); + } + } + try { + return JSON.parse(text); + } catch { + return { success: true, message: text || 'OK' }; + } +} + +export function useFinancialConfig() { + // 透传后端的财务配置(如指标分组、显示名映射等) + return useSWR('/api/financials/config', fetcher); +} diff --git a/frontend/src/lib/prisma.ts b/frontend/src/lib/prisma.ts deleted file mode 100644 index ee17f49..0000000 --- a/frontend/src/lib/prisma.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { PrismaClient } from '@prisma/client' -import fs from 'node:fs' -import path from 'node:path' - -const globalForPrisma = global as unknown as { prisma?: PrismaClient } - -function loadDatabaseUrlFromConfig(): string | undefined { - try { - const configPath = path.resolve(process.cwd(), '..', 'config', 'config.json') - const raw = fs.readFileSync(configPath, 'utf-8') - const json = JSON.parse(raw) - const dbUrl: unknown = json?.database?.url - if (typeof dbUrl !== 'string' || !dbUrl) return undefined - - // 将后端风格的 "postgresql+asyncpg://" 转换为 Prisma 需要的 "postgresql://" - let url = dbUrl.replace(/^postgresql\+[^:]+:\/\//, 'postgresql://') - // 若未指定 schema,默认 public - if (!/[?&]schema=/.test(url)) { - url += (url.includes('?') ? '&' : '?') + 'schema=public' - } - return url - } catch { - return undefined - } -} - -const databaseUrl = loadDatabaseUrlFromConfig() || process.env.DATABASE_URL - -export const prisma = - globalForPrisma.prisma || - new PrismaClient({ - datasources: databaseUrl ? { db: { url: databaseUrl } } : undefined, - log: ['error', 'warn'] - }) - -if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma - - - - - - - -