diff --git a/nginx-110.42.32.207.conf b/nginx-110.42.32.207.conf index 0dbd9985..54021130 100644 --- a/nginx-110.42.32.207.conf +++ b/nginx-110.42.32.207.conf @@ -62,7 +62,7 @@ server { # ============================================ # CORS 配置(允许 CDN 域名访问) # ============================================ - set $cors_origin '*'; + set $cors_origin 'https://valuefrontier.cn'; # 如果需要限制来源,取消下面注释 # set $cors_origin ''; @@ -78,13 +78,20 @@ server { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; - add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization, X-Requested-With' always; + add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization, X-Requested-With, Cookie' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Max-Age' 86400; add_header 'Content-Length' 0; return 204; } + # 隐藏后端返回的 CORS 头(避免重复) + proxy_hide_header 'Access-Control-Allow-Origin'; + proxy_hide_header 'Access-Control-Allow-Credentials'; + proxy_hide_header 'Access-Control-Allow-Methods'; + proxy_hide_header 'Access-Control-Allow-Headers'; + proxy_hide_header 'Access-Control-Expose-Headers'; + proxy_pass http://127.0.0.1:5001; proxy_http_version 1.1; proxy_set_header Host $host; @@ -93,7 +100,7 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Connection ""; - # CORS 头 + # 统一添加 CORS 头 add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Credentials' 'true' always; diff --git a/src/components/Auth/AuthFormContent.js b/src/components/Auth/AuthFormContent.js index 25b5f6b9..1431f479 100644 --- a/src/components/Auth/AuthFormContent.js +++ b/src/components/Auth/AuthFormContent.js @@ -37,6 +37,7 @@ import VerificationCodeInput from './VerificationCodeInput'; import WechatRegister from './WechatRegister'; import { setCurrentUser } from '../../mocks/data/users'; import { logger } from '../../utils/logger'; +import { getApiBase } from '../../utils/apiConfig'; import { useAuthEvents } from '../../hooks/useAuthEvents'; // 统一配置对象 @@ -186,7 +187,7 @@ export default function AuthFormContent() { purpose: config.api.purpose }; - const response = await fetch('/api/auth/send-verification-code', { + const response = await fetch(`${getApiBase()}/api/auth/send-verification-code`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -300,7 +301,7 @@ export default function AuthFormContent() { }; // 调用API(根据模式选择不同的endpoint - const response = await fetch('/api/auth/login-with-code', { + const response = await fetch(`${getApiBase()}/api/auth/login-with-code`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/InvestmentCalendar/index.js b/src/components/InvestmentCalendar/index.js index fc689cce..a936913e 100644 --- a/src/components/InvestmentCalendar/index.js +++ b/src/components/InvestmentCalendar/index.js @@ -20,6 +20,7 @@ import CitationMark from '@components/Citation/CitationMark'; import CitedContent from '@components/Citation/CitedContent'; import { processCitationData } from '@utils/citationUtils'; import { logger } from '@utils/logger'; +import { getApiBase } from '@utils/apiConfig'; import './InvestmentCalendar.css'; const { TabPane } = Tabs; @@ -153,7 +154,7 @@ const InvestmentCalendar = () => { const code = codes[i]; const originalCode = normalizedStocks[i].code; // 使用归一化后的代码作为key try { - const response = await fetch(`/api/market/trade/${code}?days=1`); + const response = await fetch(`${getApiBase()}/api/market/trade/${code}?days=1`); if (response.ok) { const data = await response.json(); if (data.success && data.data && data.data.length > 0) { diff --git a/src/components/Subscription/SubscriptionContent.js b/src/components/Subscription/SubscriptionContent.js index 5c1d8523..45f18a95 100644 --- a/src/components/Subscription/SubscriptionContent.js +++ b/src/components/Subscription/SubscriptionContent.js @@ -53,6 +53,7 @@ import { FaChevronDown, FaChevronUp, } from 'react-icons/fa'; +import { getApiBase } from '../../utils/apiConfig'; export default function SubscriptionContent() { // Auth context @@ -129,7 +130,7 @@ export default function SubscriptionContent() { const fetchSubscriptionPlans = async () => { try { logger.debug('SubscriptionContent', '正在获取订阅套餐'); - const response = await fetch('/api/subscription/plans'); + const response = await fetch(`${getApiBase()}/api/subscription/plans`); if (response.ok) { const data = await response.json(); @@ -175,7 +176,7 @@ export default function SubscriptionContent() { validPromoCode }); - const response = await fetch('/api/subscription/calculate-price', { + const response = await fetch(`${getApiBase()}/api/subscription/calculate-price`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -307,7 +308,7 @@ export default function SubscriptionContent() { orderId: null // Will be set after order creation }); - const response = await fetch('/api/payment/create-order', { + const response = await fetch(`${getApiBase()}/api/payment/create-order`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -379,7 +380,7 @@ export default function SubscriptionContent() { const checkInterval = setInterval(async () => { try { - const response = await fetch(`/api/payment/order/${orderId}/status`, { + const response = await fetch(`${getApiBase()}/api/payment/order/${orderId}/status`, { credentials: 'include' }); @@ -456,7 +457,7 @@ export default function SubscriptionContent() { setForceUpdating(true); try { - const response = await fetch(`/api/payment/order/${paymentOrder.id}/force-update`, { + const response = await fetch(`${getApiBase()}/api/payment/order/${paymentOrder.id}/force-update`, { method: 'POST', credentials: 'include' }); @@ -517,7 +518,7 @@ export default function SubscriptionContent() { setCheckingPayment(true); try { - const response = await fetch(`/api/payment/order/${paymentOrder.id}/status`, { + const response = await fetch(`${getApiBase()}/api/payment/order/${paymentOrder.id}/status`, { credentials: 'include' }); diff --git a/src/components/Subscription/SubscriptionContentNew.tsx b/src/components/Subscription/SubscriptionContentNew.tsx index cb5fb2e0..7a9ef046 100644 --- a/src/components/Subscription/SubscriptionContentNew.tsx +++ b/src/components/Subscription/SubscriptionContentNew.tsx @@ -44,6 +44,7 @@ import { logger } from '../../utils/logger'; import { useAuth } from '../../contexts/AuthContext'; import { useSubscriptionEvents } from '../../hooks/useSubscriptionEvents'; import { subscriptionConfig, themeColors } from '../../views/Pages/Account/subscription-content'; +import { getApiBase } from '../../utils/apiConfig'; // 计费周期选择器组件 - 移动端垂直布局(年付在上),桌面端水平布局 interface CycleSelectorProps { @@ -207,8 +208,8 @@ export default function SubscriptionContentNew() { // 优先使用 sessionStorage 中的 orderId,否则使用 order_no 查询 const orderId = sessionStorage.getItem('alipay_order_id'); const statusUrl = orderId - ? `/api/payment/alipay/order/${orderId}/status` - : `/api/payment/alipay/order-by-no/${orderNo}/status`; + ? `${getApiBase()}/api/payment/alipay/order/${orderId}/status` + : `${getApiBase()}/api/payment/alipay/order-by-no/${orderNo}/status`; const response = await fetch(statusUrl, { credentials: 'include', @@ -261,7 +262,7 @@ export default function SubscriptionContentNew() { const fetchSubscriptionPlans = async () => { try { logger.debug('SubscriptionContentNew', '正在获取订阅套餐'); - const response = await fetch('/api/subscription/plans'); + const response = await fetch(`${getApiBase()}/api/subscription/plans`); if (response.ok) { const data = await response.json(); @@ -324,7 +325,7 @@ export default function SubscriptionContentNew() { ? promoCodeValue.trim() : null; - const response = await fetch('/api/subscription/calculate-price', { + const response = await fetch(`${getApiBase()}/api/subscription/calculate-price`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -430,7 +431,7 @@ export default function SubscriptionContentNew() { // 检查是否为免费升级(剩余价值足够抵扣新套餐价格) if (price === 0 && priceInfo?.is_upgrade) { - const response = await fetch('/api/subscription/free-upgrade', { + const response = await fetch(`${getApiBase()}/api/subscription/free-upgrade`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -479,8 +480,8 @@ export default function SubscriptionContentNew() { // 根据支付方式选择不同的 API const apiUrl = paymentMethod === 'alipay' - ? '/api/payment/alipay/create-order' - : '/api/payment/create-order'; + ? `${getApiBase()}/api/payment/alipay/create-order` + : `${getApiBase()}/api/payment/create-order`; // 检测是否为移动端设备 const userAgent = navigator.userAgent; @@ -615,8 +616,8 @@ export default function SubscriptionContentNew() { const startAutoPaymentCheck = (orderId: string, method: 'wechat' | 'alipay' = 'wechat') => { // 根据支付方式选择不同的状态查询 API const statusApiUrl = method === 'alipay' - ? `/api/payment/alipay/order/${orderId}/status` - : `/api/payment/order/${orderId}/status`; + ? `${getApiBase()}/api/payment/alipay/order/${orderId}/status` + : `${getApiBase()}/api/payment/order/${orderId}/status`; const checkInterval = setInterval(async () => { try { @@ -666,8 +667,8 @@ export default function SubscriptionContentNew() { // 根据订单的支付方式选择不同的查询 API const orderPaymentMethod = (paymentOrder as any).payment_method || paymentMethod; const statusApiUrl = orderPaymentMethod === 'alipay' - ? `/api/payment/alipay/order/${(paymentOrder as any).id}/status` - : `/api/payment/order/${(paymentOrder as any).id}/status`; + ? `${getApiBase()}/api/payment/alipay/order/${(paymentOrder as any).id}/status` + : `${getApiBase()}/api/payment/order/${(paymentOrder as any).id}/status`; const response = await fetch(statusApiUrl, { credentials: 'include', diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js index bc5d2f39..99aa563b 100755 --- a/src/contexts/AuthContext.js +++ b/src/contexts/AuthContext.js @@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { useToast } from '@chakra-ui/react'; import { logger } from '@utils/logger'; import { performanceMonitor } from '@utils/performanceMonitor'; +import { getApiBase } from '@utils/apiConfig'; import { useNotification } from '@contexts/NotificationContext'; // ⚡ PostHog 延迟加载:移除同步导入,首屏减少 ~180KB // import { identifyUser, resetUser, trackEvent } from '@lib/posthog'; @@ -119,7 +120,7 @@ export const AuthProvider = ({ children }) => { controller.abort(new Error('Session check timeout after 5 seconds')); }, 5000); // 5秒超时 - const response = await fetch(`/api/auth/session`, { + const response = await fetch(`${getApiBase()}/api/auth/session`, { method: 'GET', credentials: 'include', headers: { @@ -251,7 +252,7 @@ export const AuthProvider = ({ children }) => { loginType }); - const response = await fetch(`/api/auth/login`, { + const response = await fetch(`${getApiBase()}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -315,7 +316,7 @@ export const AuthProvider = ({ children }) => { try { setIsLoading(true); - const response = await fetch(`/api/auth/register/phone`, { + const response = await fetch(`${getApiBase()}/api/auth/register/phone`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -370,7 +371,7 @@ export const AuthProvider = ({ children }) => { // 发送手机验证码 const sendSmsCode = async (phone) => { try { - const response = await fetch(`/api/auth/send-sms-code`, { + const response = await fetch(`${getApiBase()}/api/auth/send-sms-code`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -400,7 +401,7 @@ export const AuthProvider = ({ children }) => { const logout = async () => { try { // 调用后端登出API - await fetch(`/api/auth/logout`, { + await fetch(`${getApiBase()}/api/auth/logout`, { method: 'POST', credentials: 'include' }); diff --git a/src/hooks/useIndexQuote.js b/src/hooks/useIndexQuote.js index 3a4cfe7a..e15e5acd 100644 --- a/src/hooks/useIndexQuote.js +++ b/src/hooks/useIndexQuote.js @@ -3,6 +3,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { logger } from '../utils/logger'; +import { getApiBase } from '../utils/apiConfig'; // 交易日数据会从后端获取,这里只做时间判断 const TRADING_SESSIONS = [ @@ -29,7 +30,7 @@ const isInTradingSession = () => { */ const fetchIndexRealtime = async (indexCode) => { try { - const response = await fetch(`/api/index/${indexCode}/realtime`); + const response = await fetch(`${getApiBase()}/api/index/${indexCode}/realtime`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } diff --git a/src/store/slices/communityDataSlice.js b/src/store/slices/communityDataSlice.js index f6ac344f..63192ab7 100644 --- a/src/store/slices/communityDataSlice.js +++ b/src/store/slices/communityDataSlice.js @@ -3,6 +3,7 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { eventService } from '../../services/eventService'; import { logger } from '../../utils/logger'; import { localCacheManager, CACHE_EXPIRY_STRATEGY } from '../../utils/CacheManager'; +import { getApiBase } from '../../utils/apiConfig'; // ==================== 常量定义 ==================== @@ -284,7 +285,7 @@ export const toggleEventFollow = createAsyncThunk( logger.debug('CommunityData', '切换事件关注状态', { eventId }); // 调用 API(自动切换关注状态,后端根据当前状态决定关注/取消关注) - const response = await fetch(`/api/events/${eventId}/follow`, { + const response = await fetch(`${getApiBase()}/api/events/${eventId}/follow`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include' diff --git a/src/views/Community/components/HeroPanel.js b/src/views/Community/components/HeroPanel.js index 7a1ba32c..465fc65f 100644 --- a/src/views/Community/components/HeroPanel.js +++ b/src/views/Community/components/HeroPanel.js @@ -63,7 +63,7 @@ if (typeof document !== 'undefined') { */ const fetchIndexKline = async (indexCode) => { try { - const response = await fetch(`/api/index/${indexCode}/kline?type=daily`); + const response = await fetch(`${getApiBase()}/api/index/${indexCode}/kline?type=daily`); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const data = await response.json(); return data; diff --git a/src/views/EventDetail/components/TransmissionChainAnalysis.js b/src/views/EventDetail/components/TransmissionChainAnalysis.js index cfe86023..f87efc8c 100644 --- a/src/views/EventDetail/components/TransmissionChainAnalysis.js +++ b/src/views/EventDetail/components/TransmissionChainAnalysis.js @@ -37,6 +37,7 @@ import ReactECharts from 'echarts-for-react'; import { eventService } from '../../../services/eventService'; import CitedContent from '../../../components/Citation/CitedContent'; import { logger } from '../../../utils/logger'; +import { getApiBase } from '../../../utils/apiConfig'; import { PROFESSIONAL_COLORS } from '../../../constants/professionalTheme'; // 节点样式配置 - 完全复刻Flask版本 @@ -522,7 +523,7 @@ const TransmissionChainAnalysis = ({ eventId }) => { // 获取节点详情 - 完全复刻Flask版本API调用 async function getChainNodeDetail(nodeId) { try { - const response = await fetch(`/api/events/${eventId}/chain-node/${nodeId}`); + const response = await fetch(`${getApiBase()}/api/events/${eventId}/chain-node/${nodeId}`); const result = await response.json(); if (result.success) { return result.data; diff --git a/src/views/StockOverview/components/FlexScreen/hooks/constants.ts b/src/views/StockOverview/components/FlexScreen/hooks/constants.ts index f9d41b30..a0b1335d 100644 --- a/src/views/StockOverview/components/FlexScreen/hooks/constants.ts +++ b/src/views/StockOverview/components/FlexScreen/hooks/constants.ts @@ -3,10 +3,11 @@ */ import type { Exchange } from '../types'; +import { getApiBase } from '@utils/apiConfig'; /** * 获取 WebSocket 配置 - * - 生产环境 (HTTPS): 通过 Nginx 代理使用 wss:// + * - 生产环境 (HTTPS): 通过 API 服务器 Nginx 代理使用 wss:// * - 开发环境 (HTTP): 直连 ws:// */ const getWsConfig = (): Record => { @@ -19,13 +20,15 @@ const getWsConfig = (): Record => { } const isHttps = window.location.protocol === 'https:'; - const host = window.location.host; if (isHttps) { - // 生产环境:通过 Nginx 代理 + // 生产环境:通过 API 服务器的 Nginx 代理 + // CDN 不支持 WebSocket,需要连接到 api.valuefrontier.cn + const apiBase = getApiBase(); + const apiHost = apiBase.replace(/^https?:\/\//, ''); return { - SSE: `wss://${host}/ws/sse`, // 上交所 - Nginx 代理 - SZSE: `wss://${host}/ws/szse`, // 深交所 - Nginx 代理 + SSE: `wss://${apiHost}/ws/sse`, // 上交所 - 通过 API 服务器代理 + SZSE: `wss://${apiHost}/ws/szse`, // 深交所 - 通过 API 服务器代理 }; } diff --git a/src/views/StockOverview/components/FlexScreen/hooks/useRealtimeQuote.ts b/src/views/StockOverview/components/FlexScreen/hooks/useRealtimeQuote.ts index e6fff9e4..f4a6988f 100644 --- a/src/views/StockOverview/components/FlexScreen/hooks/useRealtimeQuote.ts +++ b/src/views/StockOverview/components/FlexScreen/hooks/useRealtimeQuote.ts @@ -14,6 +14,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { logger } from '@utils/logger'; +import { getApiBase } from '@utils/apiConfig'; import { WS_CONFIG, HEARTBEAT_INTERVAL, RECONNECT_INTERVAL } from './constants'; import { getExchange, normalizeCode, calcChangePct } from './utils'; import type { @@ -337,7 +338,7 @@ const fetchInitialQuotes = async ( if (codes.length === 0) return {}; try { - const response = await fetch('/api/flex-screen/quotes', { + const response = await fetch(`${getApiBase()}/api/flex-screen/quotes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ codes, include_order_book: includeOrderBook }), diff --git a/src/views/StockOverview/components/FlexScreen/index.tsx b/src/views/StockOverview/components/FlexScreen/index.tsx index 8a56f7b7..ab78ecd8 100644 --- a/src/views/StockOverview/components/FlexScreen/index.tsx +++ b/src/views/StockOverview/components/FlexScreen/index.tsx @@ -62,6 +62,7 @@ import { useRealtimeQuote } from './hooks'; import { getFullCode } from './hooks/utils'; import QuoteTile from './components/QuoteTile'; import { logger } from '@utils/logger'; +import { getApiBase } from '@utils/apiConfig'; import type { WatchlistItem, ConnectionStatus } from './types'; // 本地存储 key @@ -179,7 +180,7 @@ const FlexScreen: React.FC = () => { setIsSearching(true); try { - const response = await fetch(`/api/stocks/search?q=${encodeURIComponent(query)}&limit=10`); + const response = await fetch(`${getApiBase()}/api/stocks/search?q=${encodeURIComponent(query)}&limit=10`); const data: SearchApiResponse = await response.json(); if (data.success) { diff --git a/src/views/StockOverview/components/HotspotOverview/components/AlertDetailDrawer.js b/src/views/StockOverview/components/HotspotOverview/components/AlertDetailDrawer.js index ea3b213e..4a8f55f7 100644 --- a/src/views/StockOverview/components/HotspotOverview/components/AlertDetailDrawer.js +++ b/src/views/StockOverview/components/HotspotOverview/components/AlertDetailDrawer.js @@ -46,6 +46,7 @@ import { } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; +import { getApiBase } from '@utils/apiConfig'; import { colors, glassEffect } from '../../../theme/glassTheme'; import { ALERT_TYPE_CONFIG, @@ -408,7 +409,7 @@ const AlertDetailDrawer = ({ isOpen, onClose, alertData }) => { setLoadingConcepts(prev => ({ ...prev, [conceptId]: true })); try { - const response = await axios.get(`/api/concept/${encodeURIComponent(conceptId)}/stocks`); + const response = await axios.get(`${getApiBase()}/api/concept/${encodeURIComponent(conceptId)}/stocks`); if (response.data?.success && response.data?.data?.stocks) { setConceptStocks(prev => ({ ...prev, diff --git a/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js b/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js index 9842b75d..23b16ba7 100644 --- a/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js +++ b/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js @@ -46,6 +46,7 @@ import { } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; +import { getApiBase } from '@utils/apiConfig'; import { ALERT_TYPE_CONFIG, METRIC_CONFIG, @@ -692,7 +693,7 @@ const ConceptAlertList = ({ }); try { - const response = await axios.get(`/api/concept/${encodeURIComponent(conceptId)}/stocks`); + const response = await axios.get(`${getApiBase()}/api/concept/${encodeURIComponent(conceptId)}/stocks`); if (response.data?.success && response.data?.data?.stocks) { setConceptStocks(prev => ({ ...prev, diff --git a/src/views/StockOverview/components/HotspotOverview/hooks/useHotspotData.js b/src/views/StockOverview/components/HotspotOverview/hooks/useHotspotData.js index 16c5bddf..713077ee 100644 --- a/src/views/StockOverview/components/HotspotOverview/hooks/useHotspotData.js +++ b/src/views/StockOverview/components/HotspotOverview/hooks/useHotspotData.js @@ -4,6 +4,7 @@ */ import { useState, useEffect, useCallback, useRef } from 'react'; import { logger } from '@utils/logger'; +import { getApiBase } from '@utils/apiConfig'; /** * @param {Date|null} selectedDate - 选中的交易日期 @@ -40,7 +41,7 @@ export const useHotspotData = (selectedDate) => { const dateParam = selectedDate ? `?date=${dateStr}` : ''; - const response = await fetch(`/api/market/hotspot-overview${dateParam}`); + const response = await fetch(`${getApiBase()}/api/market/hotspot-overview${dateParam}`); const result = await response.json(); if (result.success) { diff --git a/src/views/StockOverview/index.js b/src/views/StockOverview/index.js index 3ec98b23..edc8e816 100644 --- a/src/views/StockOverview/index.js +++ b/src/views/StockOverview/index.js @@ -1,5 +1,6 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; +import { getApiBase } from '@utils/apiConfig'; import { Box, Container, @@ -146,7 +147,7 @@ const StockOverview = () => { setIsSearching(true); try { logger.debug('StockOverview', '开始搜索股票', { query }); - const response = await fetch(`/api/stocks/search?q=${encodeURIComponent(query)}&limit=10`); + const response = await fetch(`${getApiBase()}/api/stocks/search?q=${encodeURIComponent(query)}&limit=10`); const data = await response.json(); logger.debug('StockOverview', 'API返回数据', { status: response.status,