修改总结

This commit is contained in:
2025-11-03 16:10:35 +08:00
parent cb84b0238a
commit 0d84ffe87f
4 changed files with 307 additions and 155 deletions

133
CLAUDE.md
View File

@@ -4,40 +4,61 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
This is a hybrid React dashboard application with a Flask/Python backend. The project is built on the Argon Dashboard Chakra PRO template and includes financial/trading analysis features.
This is a hybrid React dashboard application with a Flask/Python backend for financial/trading analysis. Built on the Argon Dashboard Chakra PRO template with extensive customization.
### Frontend (React + Chakra UI)
- **Framework**: React 18.3.1 with Chakra UI 2.8.2
- **State Management**: Redux Toolkit (@reduxjs/toolkit)
- **Routing**: React Router DOM v6 with lazy loading for code splitting
- **Styling**: Tailwind CSS + custom Chakra theme
- **Build Tool**: React Scripts with custom Gulp tasks
- **Charts**: ApexCharts, ECharts, and custom visualization components
- **Build Tool**: CRACO (Create React App Configuration Override) with custom webpack optimizations
- **Charts**: ApexCharts, ECharts, Recharts, D3
- **UI Components**: Ant Design (antd) alongside Chakra UI
- **Other Libraries**: Three.js (@react-three), FullCalendar, Leaflet maps
### Backend (Flask/Python)
- **Framework**: Flask with SQLAlchemy ORM
- **Database**: ClickHouse for analytics + MySQL/PostgreSQL
- **Features**: Real-time data processing, trading analysis, user authentication
- **Task Queue**: Celery for background processing
- **Database**: ClickHouse for analytics queries + MySQL/PostgreSQL
- **Real-time**: Flask-SocketIO for WebSocket connections
- **Task Queue**: Celery with Redis for background processing
- **External APIs**: Tencent Cloud SMS, WeChat Pay integration
## Development Commands
### Frontend Development
```bash
npm start # Start development server (port 3000, proxies to localhost:5001)
npm run build # Production build with license headers
npm test # Run React test suite
npm run lint:check # Check ESLint rules
npm run lint:fix # Auto-fix ESLint issues
npm run install:clean # Clean install (removes node_modules and package-lock)
npm start # Start with mock data (.env.mock), proxies to localhost:5001
npm run start:real # Start with real backend (.env.local)
npm run start:dev # Start with development config (.env.development)
npm run start:test # Starts both backend (app_2.py) and frontend (.env.test) concurrently
npm run dev # Alias for 'npm start'
npm run backend # Start Flask server only (python app_2.py)
npm run build # Production build with Gulp license headers
npm run build:analyze # Build with webpack bundle analyzer
npm test # Run React test suite with CRACO
npm run lint:check # Check ESLint rules (exits 0)
npm run lint:fix # Auto-fix ESLint issues
npm run clean # Remove node_modules and package-lock.json
npm run reinstall # Clean install (runs clean + install)
```
### Backend Development
```bash
python app_2.py # Start Flask server (main backend)
python simulation_background_processor.py # Background data processor
python app.py # Main Flask server (newer version)
python app_2.py # Flask server (appears to be current main)
python simulation_background_processor.py # Background data processor for simulations
```
### Deployment
```bash
npm run deploy # Executes scripts/deploy-from-local.sh
npm run deploy:setup # Setup deployment (scripts/setup-deployment.sh)
npm run rollback # Rollback deployment (scripts/rollback-from-local.sh)
```
### Python Dependencies
Install from requirements.txt:
```bash
pip install -r requirements.txt
```
@@ -45,47 +66,69 @@ pip install -r requirements.txt
## Architecture
### Frontend Structure
- `src/layouts/` - Main layout components (Admin, Auth, Home)
- `src/views/` - Page components organized by feature (Dashboard, Company, Community, etc.)
- `src/components/` - Reusable UI components (Charts, Cards, Buttons, etc.)
- `src/theme/` - Chakra UI theme customization
- `src/routes.js` - Application routing configuration
- `src/contexts/` - React context providers
- `src/services/` - API service layer
- **src/App.js** - Main application entry with route definitions (routing moved from src/routes.js)
- **src/layouts/** - Layout wrappers (Auth, Home, MainLayout)
- **src/views/** - Page components (Community, Company, TradingSimulation, etc.)
- **src/components/** - Reusable UI components
- **src/contexts/** - React contexts (AuthContext, NotificationContext, IndustryContext)
- **src/store/** - Redux store with slices (posthogSlice, etc.)
- **src/services/** - API service layer
- **src/theme/** - Chakra UI theme customization
- **src/mocks/** - MSW (Mock Service Worker) handlers for development
- src/mocks/handlers/ - Request handlers by domain
- src/mocks/data/ - Mock data files
- src/mocks/browser.js - MSW browser setup
### Backend Structure
- `app_2.py` - Main Flask application with routes and business logic
- `simulation_background_processor.py` - Background data processing service
- `wechat_pay.py` / `wechat_pay_config.py` - Payment integration
- `tdays.csv` - Trading days data
- **app.py / app_2.py** - Main Flask application with routes, authentication, and business logic
- **simulation_background_processor.py** - Background processor for trading simulations
- **wechat_pay.py / wechat_pay_config.py** - WeChat payment integration
- **concept_api.py** - API for concept/industry analysis
- **tdays.csv** - Trading days calendar data (loaded into memory at startup)
### Key Integrations
- ClickHouse for high-performance analytics queries
- Celery + Redis for background task processing
- Flask-SocketIO for real-time data updates
- Tencent Cloud services (SMS, etc.)
- WeChat Pay integration
- **ClickHouse** - High-performance analytics queries
- **Celery + Redis** - Background task processing
- **Flask-SocketIO** - Real-time data updates via WebSocket
- **Tencent Cloud** - SMS services
- **WeChat Pay** - Payment processing
- **PostHog** - Analytics (initialized in Redux)
- **MSW** - API mocking for development/testing
### Routing & Code Splitting
- Routing is defined in **src/App.js** (not src/routes.js - that file is deprecated)
- Heavy components use React.lazy() for code splitting (Community, TradingSimulation, etc.)
- Protected routes use ProtectedRoute and ProtectedRouteRedirect components
## Configuration
### Proxy Setup
The React dev server proxies API calls to `http://localhost:5001` (see package.json).
### Environment Files
- `.env` - Environment variables for both frontend and backend
Multiple environment configurations available:
- **.env.mock** - Mock data mode (default for `npm start`)
- **.env.local** - Real backend connection
- **.env.development** - Development environment
- **.env.test** - Test environment
### Build Process
The build process includes custom Gulp tasks that add Creative Tim license headers to JS, CSS, and HTML files.
### Build Configuration (craco.config.js)
- **Webpack caching**: Filesystem cache for faster rebuilds (50-80% improvement)
- **Code splitting**: Aggressive chunk splitting by library (react-vendor, charts-lib, chakra-ui, antd-lib, three-lib, etc.)
- **Path aliases**: `@` → src/, `@components` → src/components/, `@views` → src/views/, `@assets` → src/assets/, `@contexts` → src/contexts/
- **Optimizations**: ESLint plugin removed from build for speed, Babel caching enabled, moment locale stripping
- **Source maps**: Disabled in production, eval-cheap-module-source-map in development
- **Dev server proxy**: `/api` requests proxy to http://49.232.185.254:5001
### Styling Architecture
- Tailwind CSS for utility classes
- Custom Chakra UI theme with extended color palette
- Component-specific SCSS files in `src/assets/scss/`
### Important Build Notes
- Uses NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' for Node compatibility
- Gulp task adds Creative Tim license headers post-build
- Bundle analyzer available via `ANALYZE=true npm run build:analyze`
- Pre-build: kills any process on port 3000
## Testing
- React Testing Library setup for frontend components
- Test command: `npm test`
- **React Testing Library** for component tests
- **MSW** (Mock Service Worker) for API mocking during tests
- Run tests: `npm test`
## Deployment
- Build: `npm run build`
- Deploy: `npm run deploy` (builds the project)
- Deployment scripts in **scripts/** directory
- Build output processed by Gulp for licensing
- Supports rollback via scripts/rollback-from-local.sh

73
app.py
View File

@@ -97,6 +97,38 @@ def get_trading_day_near_date(target_date):
return trading_days[-1] if trading_days else None
def get_previous_trading_day(target_date):
"""
获取指定日期的上一个交易日
如果目标日期不是交易日,先找到对应的交易日,然后返回前一个交易日
"""
if not trading_days:
load_trading_days()
if not trading_days:
return None
# 如果目标日期是datetime转换为date
if isinstance(target_date, datetime):
target_date = target_date.date()
# 确保目标日期是交易日
if target_date not in trading_days_set:
target_date = get_trading_day_near_date(target_date)
if not target_date:
return None
# 查找上一个交易日
try:
index = trading_days.index(target_date)
if index > 0:
return trading_days[index - 1]
else:
return None # 没有上一个交易日
except ValueError:
return None
# 应用启动时加载交易日数据
load_trading_days()
@@ -6595,6 +6627,9 @@ def api_get_events():
event_status = request.args.get('status', 'active')
importance = request.args.get('importance', 'all')
# 交易日筛选参数
tday = request.args.get('tday') # 交易日格式YYYY-MM-DD 或 YYYY/M/D
# 日期筛选参数
start_date = request.args.get('start_date')
end_date = request.args.get('end_date')
@@ -6680,7 +6715,41 @@ def api_get_events():
text(f"JSON_SEARCH(keywords, 'one', '%{search_query}%') IS NOT NULL")
)
)
if recent_days:
# 交易日筛选逻辑
if tday:
from datetime import datetime, timedelta, time
try:
# 解析交易日参数,支持 YYYY-MM-DD 和 YYYY/M/D 格式
if '/' in tday:
target_tday = datetime.strptime(tday, '%Y/%m/%d').date()
else:
target_tday = datetime.strptime(tday, '%Y-%m-%d').date()
# 获取该交易日的上一个交易日
prev_tday = get_previous_trading_day(target_tday)
if prev_tday:
# 计算时间范围:[前一个交易日 15:00, 当前交易日 15:00]
start_datetime = datetime.combine(prev_tday, time(15, 0, 0))
end_datetime = datetime.combine(target_tday, time(15, 0, 0))
query = query.filter(
Event.created_at >= start_datetime,
Event.created_at <= end_datetime
)
else:
# 如果没有上一个交易日,则筛选当天的事件
start_datetime = datetime.combine(target_tday, time(0, 0, 0))
end_datetime = datetime.combine(target_tday, time(15, 0, 0))
query = query.filter(
Event.created_at >= start_datetime,
Event.created_at <= end_datetime
)
except (ValueError, TypeError) as e:
# 日期格式错误,忽略该参数
app.logger.warning(f"无效的交易日参数: {tday}, 错误: {e}")
elif recent_days:
from datetime import datetime, timedelta
cutoff_date = datetime.now() - timedelta(days=recent_days)
query = query.filter(Event.created_at >= cutoff_date)
@@ -6786,6 +6855,8 @@ def api_get_events():
applied_filters['type'] = event_type
if importance != 'all':
applied_filters['importance'] = importance
if tday:
applied_filters['tday'] = tday
if start_date:
applied_filters['start_date'] = start_date
if end_date:

View File

@@ -1,109 +0,0 @@
"""
测试脚本:手动创建事件到数据库
用于测试 WebSocket 实时推送功能
"""
import sys
from datetime import datetime
from sqlalchemy import create_engine, Column, Integer, String, Text, Float, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# 数据库连接(从 app.py 复制)
DATABASE_URI = 'mysql+pymysql://root:Zzl5588161!@111.198.58.126:33060/stock?charset=utf8mb4'
engine = create_engine(DATABASE_URI, echo=False)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
# Event 模型(简化版,只包含必要字段)
class Event(Base):
__tablename__ = 'events'
id = Column(Integer, primary_key=True)
title = Column(String(500), nullable=False)
description = Column(Text)
event_type = Column(String(100))
importance = Column(String(10))
status = Column(String(50), default='active')
hot_score = Column(Float, default=0)
view_count = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def create_test_event():
"""创建一个测试事件"""
import random
event_types = ['policy', 'market', 'tech', 'industry', 'finance']
importances = ['S', 'A', 'B', 'C']
test_event = Event(
title=f'测试事件 - {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}',
description=f'这是一个用于测试 WebSocket 实时推送的事件,创建于 {datetime.now()}',
event_type=random.choice(event_types),
importance=random.choice(importances),
status='active',
hot_score=round(random.uniform(50, 100), 2),
view_count=random.randint(100, 1000)
)
try:
session.add(test_event)
session.commit()
print("✅ 测试事件创建成功!")
print(f" ID: {test_event.id}")
print(f" 标题: {test_event.title}")
print(f" 类型: {test_event.event_type}")
print(f" 重要性: {test_event.importance}")
print(f" 热度: {test_event.hot_score}")
print(f"\n💡 提示: 轮询将在 2 分钟内检测到此事件并推送到前端")
print(f" (如果需要立即推送,请将轮询间隔改为更短)")
return test_event.id
except Exception as e:
session.rollback()
print(f"❌ 创建事件失败: {e}")
return None
finally:
session.close()
def create_multiple_events(count=3):
"""创建多个测试事件"""
print(f"正在创建 {count} 个测试事件...\n")
for i in range(count):
event_id = create_test_event()
if event_id:
print(f"[{i+1}/{count}] 事件 #{event_id} 创建成功\n")
else:
print(f"[{i+1}/{count}] 创建失败\n")
print(f"\n✅ 完成!共创建 {count} 个事件")
if __name__ == '__main__':
print("=" * 60)
print("WebSocket 事件推送测试 - 手动创建事件")
print("=" * 60)
print()
if len(sys.argv) > 1:
try:
count = int(sys.argv[1])
create_multiple_events(count)
except ValueError:
print("❌ 参数必须是数字")
print("用法: python test_create_event.py [数量]")
else:
# 默认创建 1 个事件
create_test_event()
print("\n" + "=" * 60)

147
test_events_api.py Normal file
View File

@@ -0,0 +1,147 @@
"""
测试 /api/events 接口的分页和交易日筛选功能
"""
import requests
from datetime import datetime, timedelta
# 接口地址
BASE_URL = "http://localhost:5001"
EVENTS_API = f"{BASE_URL}/api/events"
def test_pagination():
"""测试分页功能"""
print("\n=== 测试分页功能 ===")
# 测试第一页
params = {
'page': 1,
'per_page': 5
}
response = requests.get(EVENTS_API, params=params)
data = response.json()
if data.get('success'):
pagination = data['data']['pagination']
print(f"✓ 第一页请求成功")
print(f" - 当前页: {pagination['page']}")
print(f" - 每页数量: {pagination['per_page']}")
print(f" - 总记录数: {pagination['total']}")
print(f" - 总页数: {pagination['pages']}")
print(f" - 是否有下一页: {pagination['has_next']}")
print(f" - 本页事件数: {len(data['data']['events'])}")
# 测试第二页
if pagination['has_next']:
params['page'] = 2
response = requests.get(EVENTS_API, params=params)
data = response.json()
if data.get('success'):
print(f"✓ 第二页请求成功,返回 {len(data['data']['events'])} 个事件")
else:
print(f"✗ 请求失败: {data.get('error')}")
def test_trading_day_filter():
"""测试交易日筛选功能"""
print("\n=== 测试交易日筛选功能 ===")
# 测试使用 YYYY-MM-DD 格式
tday = "2024-11-01" # 使用一个交易日
params = {
'tday': tday,
'per_page': 10
}
response = requests.get(EVENTS_API, params=params)
data = response.json()
if data.get('success'):
print(f"✓ 交易日筛选成功 (格式: YYYY-MM-DD)")
print(f" - 交易日: {tday}")
print(f" - 筛选到的事件数: {len(data['data']['events'])}")
print(f" - 总记录数: {data['data']['pagination']['total']}")
if data['data']['events']:
print(f" - 第一个事件创建时间: {data['data']['events'][0]['created_at']}")
# 检查 applied_filters
filters = data['data']['filters']['applied_filters']
if 'tday' in filters:
print(f" - 应用的交易日筛选: {filters['tday']}")
else:
print(f"✗ 请求失败: {data.get('error')}")
# 测试使用 YYYY/M/D 格式
tday2 = "2024/11/1"
params['tday'] = tday2
response = requests.get(EVENTS_API, params=params)
data = response.json()
if data.get('success'):
print(f"✓ 交易日筛选成功 (格式: YYYY/M/D)")
print(f" - 交易日: {tday2}")
print(f" - 筛选到的事件数: {len(data['data']['events'])}")
else:
print(f"✗ 请求失败: {data.get('error')}")
def test_combined_filters():
"""测试组合筛选功能"""
print("\n=== 测试组合筛选 (分页 + 交易日) ===")
params = {
'tday': '2024-10-31',
'page': 1,
'per_page': 3,
'status': 'active'
}
response = requests.get(EVENTS_API, params=params)
data = response.json()
if data.get('success'):
print(f"✓ 组合筛选成功")
print(f" - 筛选到的事件数: {len(data['data']['events'])}")
print(f" - 应用的筛选条件: {data['data']['filters']['applied_filters']}")
if data['data']['events']:
for i, event in enumerate(data['data']['events'], 1):
print(f" - 事件{i}: {event['title'][:30]}... (创建时间: {event['created_at']})")
else:
print(f"✗ 请求失败: {data.get('error')}")
def test_latest_trading_day():
"""测试获取最新数据(不传 tday 参数)"""
print("\n=== 测试获取最新数据 ===")
params = {
'page': 1,
'per_page': 5,
'sort': 'new'
}
response = requests.get(EVENTS_API, params=params)
data = response.json()
if data.get('success'):
print(f"✓ 获取最新数据成功")
print(f" - 返回事件数: {len(data['data']['events'])}")
if data['data']['events']:
print(f" - 最新事件: {data['data']['events'][0]['title'][:50]}")
print(f" - 创建时间: {data['data']['events'][0]['created_at']}")
else:
print(f"✗ 请求失败: {data.get('error')}")
if __name__ == "__main__":
print("开始测试 /api/events 接口")
print("=" * 50)
try:
# 测试各项功能
test_pagination()
test_trading_day_filter()
test_combined_filters()
test_latest_trading_day()
print("\n" + "=" * 50)
print("测试完成!")
except requests.exceptions.ConnectionError:
print("\n✗ 连接失败:请确保后端服务正在运行 (python app.py)")
except Exception as e:
print(f"\n✗ 测试出错: {e}")