From 7a9aad43a8a67399a815cda33c3344427c16e4fe Mon Sep 17 00:00:00 2001 From: Lxy Date: Sun, 12 Apr 2026 09:06:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0redis=E7=BC=93?= =?UTF-8?q?=E5=AD=98=EF=BC=9B=E5=A2=9E=E5=8A=A0=E5=BC=80=E5=8F=91=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/cache_service.py | 87 +- docs/API.md | 1226 ++++++++++++++++--------- docs/system.md | 415 +++++++++ 3 files changed, 1281 insertions(+), 447 deletions(-) create mode 100644 docs/system.md diff --git a/backend/app/services/cache_service.py b/backend/app/services/cache_service.py index 63f9d55..5fdda41 100644 --- a/backend/app/services/cache_service.py +++ b/backend/app/services/cache_service.py @@ -71,11 +71,94 @@ class CacheService: return codes def get_future_varieties(self) -> List[str]: - """获取期货品种列表""" + """获取期货品种列表(带Redis缓存)""" + cache_key = "future_varieties" + + cached = redis_client.get(cache_key) + if cached is not None: + logger.info(f"从Redis缓存获取期货品种列表, 共{len(cached)}个") + return cached + + adapter = sdk_manager.get_default_connection() + if not adapter: + raise RuntimeError("SDK连接失败") + + varieties = adapter.get_future_varieties() + + if varieties: + redis_client.set(cache_key, varieties, expire=24 * 60 * 60) + logger.info(f"期货品种列表已缓存到Redis, 共{len(varieties)}个, 有效期24小时") + + return varieties + + def get_trading_calendar_cached(self, market: str, start_date: date = None, end_date: date = None) -> List[date]: + """获取交易日历(带Redis缓存)""" + cache_key = f"trading_calendar:{market}" + + cached = redis_client.get(cache_key) + if cached is not None: + logger.info(f"从Redis缓存获取交易日历: {market}") + from app.utils.date_utils import int_to_date + all_dates = [int_to_date(d) for d in cached] + + if start_date: + all_dates = [d for d in all_dates if d >= start_date] + if end_date: + all_dates = [d for d in all_dates if d <= end_date] + + return all_dates + + trading_days = self.base_service.get_trading_calendar(market) + + from app.utils.date_utils import date_to_int + all_dates_int = [date_to_int(d) for d in trading_days] + + redis_client.set(cache_key, all_dates_int, expire=365 * 24 * 60 * 60) + logger.info(f"交易日历已缓存到Redis: {market}, 有效期1年") + + return trading_days + + def get_main_contract_cached(self, variety: str) -> Optional[str]: + """获取主力合约(带Redis缓存)""" + cache_key = f"main_contract:{variety}" + + cached = redis_client.get(cache_key) + if cached is not None: + logger.info(f"从Redis缓存获取主力合约: {variety} -> {cached}") + return cached + adapter = sdk_manager.get_default_connection() if not adapter: raise RuntimeError("SDK连接失败") - return adapter.get_future_varieties() + + main_contract = adapter.get_main_contract(variety) + + if main_contract: + redis_client.set(cache_key, main_contract, expire=1 * 60 * 60) + logger.info(f"主力合约已缓存到Redis: {variety} -> {main_contract}, 有效期1小时") + + return main_contract + + def get_all_main_contracts_cached(self) -> Dict[str, str]: + """获取所有主力合约(带Redis缓存)""" + cache_key = "all_main_contracts" + + cached = redis_client.get(cache_key) + if cached is not None: + logger.info(f"从Redis缓存获取所有主力合约, 共{len(cached)}个") + return cached + + adapter = sdk_manager.get_default_connection() + if not adapter: + raise RuntimeError("SDK连接失败") + + main_contracts = adapter.get_all_main_contracts() + + if main_contracts: + redis_client.set(cache_key, main_contracts, expire=1 * 60 * 60) + logger.info(f"所有主力合约已缓存到Redis, 共{len(main_contracts)}个, 有效期1小时") + + return main_contracts def detect_all_missing_data( self, diff --git a/docs/API.md b/docs/API.md index 7abba4c..398b949 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,577 +1,913 @@ -# AmazingData 金融数据服务平台 - API接口文档 +# AmazingData 金融数据服务平台 - API 开发文档 -## 基础信息 +## 一、API 概述 + +### 1.1 基本信息 - **Base URL**: `http://localhost:8000/api/v1` -- **Content-Type**: `application/json` - **认证方式**: JWT Bearer Token +- **数据格式**: JSON +- **编码**: UTF-8 -## 认证相关 +### 1.2 认证说明 -### 1. 用户登录 -```http -POST /auth/login -Content-Type: application/json +除登录接口外,所有接口都需要在请求头中携带 JWT Token: -{ - "username": "admin", - "password": "admin123" -} ``` +Authorization: Bearer {access_token} +``` + +--- + +## 二、认证接口 + +### 2.1 用户登录 + +**接口**: `POST /auth/login` + +**功能**: 用户登录获取 JWT Token + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| username | string | 是 | 用户名 | +| password | string | 是 | 密码 | + +**请求示例**: -**响应**: ```json { - "code": 200, - "message": "success", - "data": { - "access_token": "eyJhbGciOiJIUzI1NiIs...", - "token_type": "bearer", - "expires_in": 86400 - } + "username": "admin", + "password": "admin123" } ``` -### 2. 获取当前用户 -```http -GET /auth/me -Authorization: Bearer {token} -``` - -## 配置管理 +**响应参数**: -### 3. 获取SDK配置列表 -```http -GET /configs/sdk -Authorization: Bearer {token} -``` +| 参数名 | 类型 | 说明 | +|--------|------|------| +| code | int | 状态码 (200成功) | +| message | string | 消息 | +| data.access_token | string | JWT Token | +| data.token_type | string | Token类型 (bearer) | +| data.expires_in | int | 有效期(秒) | -### 4. 创建SDK配置 -```http -POST /configs/sdk -Authorization: Bearer {token} -Content-Type: application/json +**响应示例**: +```json { - "name": "银河证券生产环境", - "username": "your_username", - "password": "your_password", - "host": "xxx.xxx.xxx.xxx", - "port": 8080, - "local_path": "./amazing_data_cache/", - "is_default": true + "code": 200, + "message": "登录成功", + "data": { + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "token_type": "bearer", + "expires_in": 86400 + } } ``` -### 5. 测试SDK连接 -```http -POST /configs/sdk/{id}/test -Authorization: Bearer {token} -``` +**调用示例**: -## 基础数据 +```python +import requests -### 6. 获取代码列表 -```http -GET /base/codes?security_type=EXTRA_STOCK_A -Authorization: Bearer {token} +response = requests.post( + 'http://localhost:8000/api/v1/auth/login', + json={'username': 'admin', 'password': 'admin123'} +) +token = response.json()['data']['access_token'] ``` -**参数**: -- `security_type`: 证券类型 - - `EXTRA_STOCK_A` - 沪深A股 - - `EXTRA_FUTURE` - 期货 - - `EXTRA_ETF` - ETF - - `EXTRA_INDEX_A` - 指数 +--- -### 7. 获取证券信息 -```http -GET /base/codes/{code}/info -Authorization: Bearer {token} -``` +### 2.2 获取当前用户信息 + +**接口**: `GET /auth/me` + +**功能**: 获取当前登录用户信息 -### 8. 获取交易日历 -```http -GET /base/calendar?market=SH&start_date=20240101&end_date=20241231 -Authorization: Bearer {token} +**请求参数**: 无 + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.id | int | 用户ID | +| data.username | string | 用户名 | +| data.email | string | 邮箱 | +| data.role | string | 角色 | +| data.is_active | bool | 是否激活 | + +**调用示例**: + +```python +headers = {'Authorization': f'Bearer {token}'} +response = requests.get('http://localhost:8000/api/v1/auth/me', headers=headers) +user_info = response.json()['data'] ``` -## 股票数据 +--- + +## 三、SDK配置接口 + +### 3.1 获取SDK配置列表 -### 9. 获取股票K线数据 -```http -GET /stock/kline?codes=000001.SZ&start_date=20240101&end_date=20241231&period=daily -Authorization: Bearer {token} +**接口**: `GET /configs/sdk` + +**功能**: 获取所有SDK配置列表 + +**请求参数**: 无 + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.items | array | 配置列表 | +| data.total | int | 总数 | + +**调用示例**: + +```python +response = requests.get('http://localhost:8000/api/v1/configs/sdk', headers=headers) +configs = response.json()['data']['items'] ``` -**参数**: -- `codes`: 股票代码,多个用逗号分隔 -- `start_date`: 开始日期 (YYYYMMDD) -- `end_date`: 结束日期 (YYYYMMDD) -- `period`: 周期 (daily, min1, min5, min15, min30, min60) +--- + +### 3.2 创建SDK配置 + +**接口**: `POST /configs/sdk` + +**功能**: 创建新的SDK配置 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| name | string | 是 | 配置名称 | +| username | string | 是 | SDK用户名 | +| password | string | 是 | SDK密码 | +| host | string | 是 | 服务器地址 | +| port | int | 否 | 端口号 (默认8600) | +| local_path | string | 否 | 本地路径 | +| is_default | bool | 否 | 是否默认配置 | + +**请求示例**: -**响应**: ```json { - "code": 200, - "message": "success", - "data": { - "000001.SZ": [ - { - "trade_date": "2024-01-02", - "open": 10.50, - "high": 10.80, - "low": 10.40, - "close": 10.65, - "volume": 1234567, - "amount": 12845678.90 - } - ] - } + "name": "生产环境SDK", + "username": "your_username", + "password": "your_password", + "host": "140.206.44.234", + "port": 8600, + "is_default": true } ``` -### 10. 获取股票K线图数据 -```http -GET /stock/kline/{code}/chart?start_date=20240101&end_date=20241231&period=daily -Authorization: Bearer {token} -``` +**调用示例**: -**响应** (ECharts格式): -```json -{ - "code": 200, - "message": "success", - "data": { - "categoryData": ["2024-01-02", "2024-01-03", ...], - "values": [ - [10.50, 10.80, 10.40, 10.65, 1234567], - [10.65, 10.70, 10.50, 10.60, 987654], - ... - ], - "volumes": [ - [0, 1234567, 1], - [1, 987654, -1], - ... - ] +```python +response = requests.post( + 'http://localhost:8000/api/v1/configs/sdk', + headers=headers, + json={ + 'name': '生产环境SDK', + 'username': 'your_username', + 'password': 'your_password', + 'host': '140.206.44.234', + 'port': 8600, + 'is_default': True } -} +) ``` -### 11. 批量获取股票K线 -```http -POST /stock/kline/batch -Authorization: Bearer {token} -Content-Type: application/json +--- -{ - "codes": ["000001.SZ", "600000.SH"], - "start_date": "20240101", - "end_date": "20241231", - "period": "daily" -} -``` +### 3.3 测试SDK连接 -## 期货数据 +**接口**: `POST /configs/sdk/{id}/test` -### 12. 获取期货K线数据 -```http -GET /future/kline?codes=IF2501.CFE&start_date=20240101&end_date=20241231&period=daily -Authorization: Bearer {token} -``` +**功能**: 测试指定SDK配置的连接 -### 13. 获取期货K线图数据 -```http -GET /future/kline/{code}/chart?start_date=20240101&end_date=20241231&period=daily -Authorization: Bearer {token} -``` +**请求参数**: 无 + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.success | bool | 是否成功 | +| data.message | string | 消息 | +| data.response_time | float | 响应时间(秒) | -## 实时数据 +**调用示例**: -### 14. 获取最新快照 -```http -GET /realtime/snapshot?codes=000001.SZ,600000.SH -Authorization: Bearer {token} +```python +response = requests.post( + 'http://localhost:8000/api/v1/configs/sdk/1/test', + headers=headers, + timeout=60 +) +result = response.json()['data'] ``` -### 15. 开始实时订阅 -```http -POST /realtime/subscribe -Authorization: Bearer {token} -Content-Type: application/json +--- -{ - "codes": ["000001.SZ", "600000.SH"], - "types": ["snapshot"], - "callback_url": "ws://localhost:8000/api/v1/realtime/stream" -} +## 四、基础数据接口 + +### 4.1 获取代码列表 + +**接口**: `GET /base/codes` + +**功能**: 获取指定类型的证券代码列表 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| security_type | string | 是 | 证券类型 | + +**证券类型**: +- `EXTRA_STOCK_A`: 沪深A股 +- `EXTRA_FUTURE`: 期货 +- `EXTRA_ETF`: ETF +- `EXTRA_INDEX_A`: 指数 + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data | array | 代码列表 | + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/base/codes', + params={'security_type': 'EXTRA_STOCK_A'}, + headers=headers +) +codes = response.json()['data'] ``` -### 16. WebSocket实时数据流 +--- + +### 4.2 获取交易日历 + +**接口**: `GET /base/calendar` + +**功能**: 获取指定市场的交易日历 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| market | string | 是 | 市场代码 (SH, SZ, CFE) | +| start_date | string | 否 | 开始日期 (YYYYMMDD) | +| end_date | string | 否 | 结束日期 (YYYYMMDD) | + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data | array | 交易日列表 | + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/base/calendar', + params={ + 'market': 'SH', + 'start_date': '20240101', + 'end_date': '20240131' + }, + headers=headers +) +trading_days = response.json()['data'] ``` -WS /realtime/stream?codes=000001.SZ,600000.SH&types=snapshot -Authorization: Bearer {token} + +--- + +### 4.3 获取证券信息 + +**接口**: `GET /base/codes/{code}/info` + +**功能**: 获取指定代码的证券信息 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| code | string | 是 | 证券代码 (URL路径参数) | + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.code | string | 代码 | +| data.name | string | 名称 | +| data.market | string | 市场 | +| data.type | string | 类型 | + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/base/codes/600000.SH/info', + headers=headers +) +info = response.json()['data'] ``` -**消息格式**: -```json -{ - "type": "snapshot", - "code": "000001.SZ", - "data": { - "trade_time": "2025-01-15T10:30:00", - "last": 10.50, - "open": 10.30, - "high": 10.60, - "low": 10.20, - "volume": 1234567, - "amount": 12845678.90 - } -} +--- + +## 五、股票数据接口 + +### 5.1 获取股票K线数据 + +**接口**: `GET /stock/kline` + +**功能**: 获取股票K线数据 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| codes | string | 是 | 代码列表 (逗号分隔) | +| start_date | string | 是 | 开始日期 (YYYYMMDD) | +| end_date | string | 是 | 结束日期 (YYYYMMDD) | +| period | string | 否 | 周期 (daily, min1, min5等) | + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.{code} | array | K线数据列表 | +| data.{code}[].trade_date | string | 交易日期 | +| data.{code}[].open | float | 开盘价 | +| data.{code}[].high | float | 最高价 | +| data.{code}[].low | float | 最低价 | +| data.{code}[].close | float | 收盘价 | +| data.{code}[].volume | int | 成交量 | +| data.{code}[].amount | float | 成交额 | + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/stock/kline', + params={ + 'codes': '600000.SH,000001.SZ', + 'start_date': '20240101', + 'end_date': '20240131', + 'period': 'daily' + }, + headers=headers +) +kline_data = response.json()['data'] ``` -## 财务数据 +--- + +### 5.2 获取股票K线图表数据 + +**接口**: `GET /stock/kline/{code}/chart` + +**功能**: 获取股票K线图表数据 (ECharts格式) + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| code | string | 是 | 股票代码 (URL路径参数) | +| start_date | string | 是 | 开始日期 | +| end_date | string | 是 | 结束日期 | +| period | string | 否 | 周期 | -### 17. 获取资产负债表 -```http -GET /finance/balance-sheet?codes=000001.SZ&start_date=20240930&end_date=20240930 -Authorization: Bearer {token} +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.categoryData | array | 日期列表 | +| data.values | array | K线值 [open, close, low, high, volume] | +| data.volumes | array | 成交量数据 | + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/stock/kline/600000.SH/chart', + params={ + 'start_date': '20240101', + 'end_date': '20240131', + 'period': 'daily' + }, + headers=headers +) +chart_data = response.json()['data'] ``` -**响应**: +--- + +### 5.3 批量获取股票K线 + +**接口**: `POST /stock/kline/batch` + +**功能**: 批量获取多个股票的K线数据 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| codes | array | 是 | 代码列表 | +| start_date | string | 是 | 开始日期 | +| end_date | string | 是 | 结束日期 | +| period | string | 否 | 周期 | + +**请求示例**: + ```json { - "code": 200, - "message": "success", - "data": { - "000001.SZ": [ - { - "report_date": "2024-09-30", - "total_assets": 123456789012.34, - "total_cur_assets": 98765432109.87, - "total_liab": 87654321098.76, - "tot_share_equity": 35802467913.58 - } - ] - } + "codes": ["600000.SH", "000001.SZ", "000002.SZ"], + "start_date": "20240101", + "end_date": "20240131", + "period": "daily" } ``` -### 18. 获取现金流量表 -```http -GET /finance/cash-flow?codes=000001.SZ&start_date=20240930&end_date=20240930 -Authorization: Bearer {token} -``` +**调用示例**: -### 19. 获取利润表 -```http -GET /finance/income?codes=000001.SZ&start_date=20240930&end_date=20240930 -Authorization: Bearer {token} +```python +response = requests.post( + 'http://localhost:8000/api/v1/stock/kline/batch', + headers=headers, + json={ + 'codes': ['600000.SH', '000001.SZ'], + 'start_date': '20240101', + 'end_date': '20240131', + 'period': 'daily' + } +) ``` -### 20. 获取业绩快报 -```http -GET /finance/profit-express?codes=000001.SZ&start_date=20240930&end_date=20240930 -Authorization: Bearer {token} -``` +--- -### 21. 获取业绩预告 -```http -GET /finance/profit-notice?codes=000001.SZ&start_date=20240930&end_date=20240930 -Authorization: Bearer {token} -``` +## 六、期货数据接口 -## 股东数据 +### 6.1 获取期货K线数据 -### 22. 获取十大股东 -```http -GET /shareholder/top10?codes=000001.SZ&start_date=20240930&end_date=20240930 -Authorization: Bearer {token} -``` +**接口**: `GET /future/kline` -### 23. 获取股东户数 -```http -GET /shareholder/count?codes=000001.SZ&start_date=20240930&end_date=20240930 -Authorization: Bearer {token} -``` +**功能**: 获取期货K线数据 -### 24. 获取股本结构 -```http -GET /shareholder/equity?codes=000001.SZ&start_date=20240930&end_date=20240930 -Authorization: Bearer {token} -``` +**请求参数**: -## 融资融券 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| codes | string | 是 | 代码列表 (逗号分隔) | +| start_date | string | 是 | 开始日期 | +| end_date | string | 是 | 结束日期 | +| period | string | 否 | 周期 | -### 25. 获取融资融券汇总 -```http -GET /margin/summary?start_date=20240101&end_date=20241231 -Authorization: Bearer {token} -``` +**响应参数**: -### 26. 获取融资融券明细 -```http -GET /margin/detail?codes=000001.SZ&start_date=20240101&end_date=20241231 -Authorization: Bearer {token} -``` +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.{code} | array | K线数据 | +| data.{code}[].trade_date | string | 交易日期 | +| data.{code}[].open | float | 开盘价 | +| data.{code}[].high | float | 最高价 | +| data.{code}[].low | float | 最低价 | +| data.{code}[].close | float | 收盘价 | +| data.{code}[].volume | int | 成交量 | +| data.{code}[].settle | float | 结算价 | +| data.{code}[].open_interest | int | 持仓量 | -## 指数数据 +**调用示例**: -### 27. 获取指数成分股 -```http -GET /index/constituents?codes=000300.SH -Authorization: Bearer {token} +```python +response = requests.get( + 'http://localhost:8000/api/v1/future/kline', + params={ + 'codes': 'IF2401.CFE', + 'start_date': '20240101', + 'end_date': '20240131', + 'period': 'daily' + }, + headers=headers +) ``` -### 28. 获取指数权重 -```http -GET /index/weights?codes=000300.SH&start_date=20240101&end_date=20241231 -Authorization: Bearer {token} -``` +--- -## ETF数据 +### 6.2 获取期货品种列表 -### 29. 获取ETF申赎数据 -```http -GET /etf/pcf?codes=510050.SH -Authorization: Bearer {token} -``` +**接口**: `GET /cache/future-varieties` + +**功能**: 获取所有期货品种列表 + +**请求参数**: 无 + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.varieties | array | 品种列表 | + +**调用示例**: -### 30. 获取ETF份额 -```http -GET /etf/share?codes=510050.SH&start_date=20240101&end_date=20241231 -Authorization: Bearer {token} +```python +response = requests.get( + 'http://localhost:8000/api/v1/cache/future-varieties', + headers=headers +) +varieties = response.json()['data']['varieties'] ``` -## 可转债数据 +--- + +### 6.3 获取主力合约 + +**接口**: `GET /cache/main-contracts` + +**功能**: 获取所有品种的主力合约 + +**请求参数**: 无 + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.main_contracts | object | {品种: 主力合约} | -### 31. 获取可转债发行数据 -```http -GET /kzz/issuance?codes=128XXX.SZ -Authorization: Bearer {token} +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/cache/main-contracts', + headers=headers +) +main_contracts = response.json()['data']['main_contracts'] ``` -## 缓存管理 +--- + +## 七、缓存管理接口 + +### 7.1 一键检测所有数据 + +**接口**: `POST /cache/detect-all-missing` + +**功能**: 检测所有数据的缺失情况 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| security_type | string | 是 | 证券类型 (stock, future) | +| period_type | string | 否 | 周期类型 (daily) | +| start_date | string | 是 | 开始日期 | +| end_date | string | 是 | 结束日期 | +| contract_type | string | 否 | 合约类型 (all, main) | -### 32. 检测缺失数据 -```http -POST /cache/detect-missing -Authorization: Bearer {token} -Content-Type: application/json +**请求示例**: +```json { - "security_type": "stock", - "period_type": "daily", - "start_date": "20240101", - "end_date": "20241231", - "code_list": ["000001.SZ", "600000.SH"] + "security_type": "stock", + "period_type": "daily", + "start_date": "20240101", + "end_date": "20240131", + "contract_type": "all" } ``` -**响应**: -```json -{ - "code": 200, - "message": "success", - "data": { - "task_id": 1, - "total_codes": 2, - "missing_codes": [ - { - "code": "000001.SZ", - "missing_dates": [ - { - "date": "2024-06-01", - "expected": 1, - "actual": 0, - "missing_ratio": 1.0 - } - ] - } - ] +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.task_id | int | 任务ID | +| data.status | string | 任务状态 | +| data.total_count | int | 检测总数 | +| data.complete_count | int | 完整数量 | +| data.missing_count | int | 缺失数量 | +| data.error_count | int | 错误数量 | +| data.daily_stats | object | 每日统计 | +| data.missing_codes | array | 缺失代码列表 | + +**调用示例**: + +```python +response = requests.post( + 'http://localhost:8000/api/v1/cache/detect-all-missing', + headers=headers, + json={ + 'security_type': 'stock', + 'period_type': 'daily', + 'start_date': '20240101', + 'end_date': '20240131', + 'contract_type': 'all' } -} +) +result = response.json()['data'] ``` -### 33. 批量缓存数据 -```http -POST /cache/batch-cache -Authorization: Bearer {token} -Content-Type: application/json +--- -{ - "security_type": "stock", - "period_type": "daily", - "start_date": "20240101", - "end_date": "20241231", - "code_list": ["000001.SZ", "600000.SH"] -} -``` +### 7.2 一键缓存所有数据 -### 34. 获取缓存任务列表 -```http -GET /cache/tasks?page=1&page_size=20 -Authorization: Bearer {token} -``` +**接口**: `POST /cache/cache-all-missing` -### 35. 获取缓存任务详情 -```http -GET /cache/tasks/{task_id} -Authorization: Bearer {token} -``` +**功能**: 缓存所有数据 (异步执行) -### 36. 取消缓存任务 -```http -DELETE /cache/tasks/{task_id} -Authorization: Bearer {token} -``` +**请求参数**: 同检测接口 -### 37. 获取代码缓存状态 -```http -GET /cache/status/{code}?security_type=stock&period_type=daily -Authorization: Bearer {token} -``` +**响应参数**: -**响应**: -```json -{ - "code": 200, - "message": "success", - "data": { - "code": "000001.SZ", - "security_type": "stock", - "period_type": "daily", - "record_count": 242, - "min_date": "2024-01-02", - "max_date": "2024-12-31", - "missing_ratio": 0.0 +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.task_id | int | 任务ID | +| data.status | string | 任务状态 | +| data.total_count | int | 总数 | +| data.progress | float | 进度 | + +**调用示例**: + +```python +response = requests.post( + 'http://localhost:8000/api/v1/cache/cache-all-missing', + headers=headers, + json={ + 'security_type': 'stock', + 'period_type': 'daily', + 'start_date': '20240101', + 'end_date': '20240131' } -} +) +task_id = response.json()['data']['task_id'] ``` -## 测试中心 +--- + +### 7.3 一键补齐缺失数据 + +**接口**: `POST /cache/fill-missing` + +**功能**: 只补齐检测到的缺失数据 (异步执行) -### 38. 获取测试分类 -```http -GET /test/categories -Authorization: Bearer {token} +**请求参数**: 同检测接口 + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.task_id | int | 任务ID | +| data.missing_count | int | 缺失代码数量 | +| data.status | string | 任务状态 | + +**调用示例**: + +```python +response = requests.post( + 'http://localhost:8000/api/v1/cache/fill-missing', + headers=headers, + json={ + 'security_type': 'stock', + 'period_type': 'daily', + 'start_date': '20240101', + 'end_date': '20240131' + } +) ``` -**响应**: -```json -{ - "code": 200, - "message": "success", - "data": [ - {"key": "base_data", "name": "基础数据"}, - {"key": "stock", "name": "股票数据"}, - {"key": "future", "name": "期货数据"}, - {"key": "realtime", "name": "实时数据"}, - {"key": "finance", "name": "财务数据"}, - {"key": "shareholder", "name": "股东数据"}, - {"key": "margin", "name": "融资融券"}, - {"key": "index", "name": "指数数据"}, - {"key": "etf", "name": "ETF数据"}, - {"key": "kzz", "name": "可转债数据"} - ] -} +--- + +### 7.4 获取任务进度 + +**接口**: `GET /cache/tasks/{task_id}` + +**功能**: 获取缓存任务进度 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| task_id | int | 是 | 任务ID (URL路径参数) | + +**响应参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| data.task.id | int | 任务ID | +| data.task.status | string | 状态 | +| data.task.progress | float | 进度 | +| data.task.total_count | int | 总数 | +| data.task.success_count | int | 成功数 | +| data.task.error_count | int | 错误数 | + +**调用示例**: + +```python +response = requests.get( + f'http://localhost:8000/api/v1/cache/tasks/{task_id}', + headers=headers +) +task_status = response.json()['data']['task'] ``` -### 39. 获取接口列表 -```http -GET /test/endpoints?category=stock -Authorization: Bearer {token} +--- + +### 7.5 获取任务列表 + +**接口**: `GET /cache/tasks` + +**功能**: 获取缓存任务列表 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| page | int | 否 | 页码 (默认1) | +| page_size | int | 否 | 每页数量 (默认20) | + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/cache/tasks', + params={'page': 1, 'page_size': 10}, + headers=headers +) +tasks = response.json()['data']['items'] ``` -### 40. 执行单个接口测试 -```http -POST /test/run -Authorization: Bearer {token} -Content-Type: application/json +--- -{ - "endpoint": "/api/v1/stock/kline", - "method": "GET", - "params": { - "codes": "000001.SZ", - "start_date": "20240101", - "end_date": "20241231", - "period": "daily" - } -} +## 八、财务数据接口 + +### 8.1 获取资产负债表 + +**接口**: `GET /finance/balance-sheet` + +**功能**: 获取资产负债表数据 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| codes | string | 是 | 代码列表 | +| start_date | string | 是 | 开始日期 | +| end_date | string | 是 | 结束日期 | + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/finance/balance-sheet', + params={ + 'codes': '600000.SH', + 'start_date': '20230101', + 'end_date': '20231231' + }, + headers=headers +) ``` -### 41. 执行全部接口测试 -```http -POST /test/run-all -Authorization: Bearer {token} -Content-Type: application/json +--- -{ - "categories": ["stock", "future", "finance"] -} +### 8.2 获取利润表 + +**接口**: `GET /finance/income` + +**功能**: 获取利润表数据 + +**请求参数**: 同资产负债表 + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/finance/income', + params={ + 'codes': '600000.SH', + 'start_date': '20230101', + 'end_date': '20231231' + }, + headers=headers +) ``` -### 42. 获取测试历史 -```http -GET /test/history?page=1&page_size=20 -Authorization: Bearer {token} +--- + +### 8.3 获取现金流量表 + +**接口**: `GET /finance/cash-flow` + +**功能**: 获取现金流量表数据 + +**请求参数**: 同资产负债表 + +**调用示例**: + +```python +response = requests.get( + 'http://localhost:8000/api/v1/finance/cash-flow', + params={ + 'codes': '600000.SH', + 'start_date': '20230101', + 'end_date': '20231231' + }, + headers=headers +) ``` -## 错误码 +--- + +## 九、错误码说明 | 错误码 | 说明 | |--------|------| | 200 | 成功 | -| 400 | 参数错误 | -| 401 | 未授权 | +| 400 | 请求参数错误 | +| 401 | 未授权 (Token无效或过期) | | 403 | 禁止访问 | | 404 | 资源不存在 | | 500 | 服务器内部错误 | -| 1001 | SDK连接失败 | -| 1002 | 数据不存在 | -| 1003 | 任务执行失败 | - -## 数据类型说明 - -### K线数据结构 - -| 字段 | 类型 | 说明 | -|------|------|------| -| trade_date | string | 交易日期 (YYYY-MM-DD) | -| trade_datetime | string | 交易时间 (YYYY-MM-DD HH:MM:SS) | -| open | number | 开盘价 | -| high | number | 最高价 | -| low | number | 最低价 | -| close | number | 收盘价 | -| volume | number | 成交量 | -| amount | number | 成交金额 | - -### 期货K线额外字段 - -| 字段 | 类型 | 说明 | -|------|------|------| -| settle | number | 结算价 | -| open_interest | number | 持仓量 | - -### 快照数据结构 - -| 字段 | 类型 | 说明 | -|------|------|------| -| code | string | 证券代码 | -| trade_time | string | 交易时间 | -| last | number | 最新价 | -| open | number | 开盘价 | -| high | number | 最高价 | -| low | number | 最低价 | -| volume | number | 成交量 | -| amount | number | 成交金额 | -| ask_price1-5 | number | 卖1-5价格 | -| ask_volume1-5 | number | 卖1-5数量 | -| bid_price1-5 | number | 买1-5价格 | -| bid_volume1-5 | number | 买1-5数量 | --- -**文档版本**: 1.0 -**更新日期**: 2025年 +## 十、最佳实践 + +### 10.1 Token 管理 + +```python +class AmazingDataClient: + def __init__(self, base_url, username, password): + self.base_url = base_url + self.token = None + self.login(username, password) + + def login(self, username, password): + response = requests.post( + f'{self.base_url}/auth/login', + json={'username': username, 'password': password} + ) + self.token = response.json()['data']['access_token'] + + def get_headers(self): + return {'Authorization': f'Bearer {self.token}'} + + def request(self, method, path, **kwargs): + kwargs['headers'] = self.get_headers() + return requests.request(method, f'{self.base_url}{path}', **kwargs) +``` + +### 10.2 批量数据处理 + +```python +# 分批处理大量代码 +codes = ['600000.SH', '000001.SZ', ...] # 大量代码 +batch_size = 50 + +for i in range(0, len(codes), batch_size): + batch = codes[i:i+batch_size] + response = client.request('POST', '/stock/kline/batch', json={ + 'codes': batch, + 'start_date': '20240101', + 'end_date': '20240131' + }) +``` + +### 10.3 异步任务轮询 + +```python +import time + +def wait_for_task(client, task_id, timeout=300): + start_time = time.time() + while time.time() - start_time < timeout: + response = client.request('GET', f'/cache/tasks/{task_id}') + task = response.json()['data']['task'] + + if task['status'] == 'completed': + return task + elif task['status'] == 'failed': + raise Exception(task['error_message']) + + time.sleep(2) + + raise Exception('任务超时') + +# 使用 +response = client.request('POST', '/cache/fill-missing', json={...}) +task_id = response.json()['data']['task_id'] +result = wait_for_task(client, task_id) +``` + +--- + +## 十一、联系与支持 + +如有问题,请联系技术支持团队。 \ No newline at end of file diff --git a/docs/system.md b/docs/system.md new file mode 100644 index 0000000..fb01f6e --- /dev/null +++ b/docs/system.md @@ -0,0 +1,415 @@ +# AmazingData 金融数据服务平台 - 系统文档 + +## 一、系统概述 + +AmazingData 金融数据服务平台是一个基于 FastAPI + Vue3 的金融数据管理系统,提供股票、期货等金融数据的查询、缓存和管理功能。 + +### 技术栈 + +| 组件 | 技术 | 版本 | +|------|------|------| +| 后端框架 | FastAPI | 0.100+ | +| 前端框架 | Vue 3 + Element Plus | 3.x | +| 数据库 | PostgreSQL / SQLite | 15+ / 3.x | +| 缓存 | Redis | 7.x | +| SDK | AmazingData | 最新版 | + +--- + +## 二、数据库设计 + +### 2.1 数据库初始化 + +系统使用 SQLAlchemy ORM,支持 PostgreSQL 和 SQLite。数据库初始化脚本位于 `database/init.sql`。 + +### 2.2 主要数据表结构 + +#### 用户表 (users) + +```sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + email VARCHAR(100), + role VARCHAR(20) DEFAULT 'user', + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +#### SDK配置表 (sdk_configs) + +```sql +CREATE TABLE sdk_configs ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + username VARCHAR(100) NOT NULL, + password VARCHAR(100) NOT NULL, + host VARCHAR(100) NOT NULL, + port INTEGER DEFAULT 8600, + local_path VARCHAR(255), + is_default BOOLEAN DEFAULT FALSE, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +#### 缓存任务表 (cache_tasks) + +```sql +CREATE TABLE cache_tasks ( + id SERIAL PRIMARY KEY, + task_name VARCHAR(200) NOT NULL, + task_type VARCHAR(50) NOT NULL, + security_type VARCHAR(20) NOT NULL, + period_type VARCHAR(10), + start_date DATE NOT NULL, + end_date DATE NOT NULL, + code_list TEXT, + status VARCHAR(20) DEFAULT 'pending', + progress DECIMAL(5,2) DEFAULT 0, + total_count INTEGER DEFAULT 0, + success_count INTEGER DEFAULT 0, + error_count INTEGER DEFAULT 0, + error_message TEXT, + created_by INTEGER REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + started_at TIMESTAMP, + completed_at TIMESTAMP, + + CONSTRAINT cache_tasks_task_type_check + CHECK (task_type IN ('detect_missing', 'cache_data', 'sync_data', 'detect_all_missing', 'cache_all_data', 'fill_missing_data')) +); +``` + +#### 缓存任务详情表 (cache_task_details) + +```sql +CREATE TABLE cache_task_details ( + id BIGSERIAL PRIMARY KEY, + task_id INTEGER REFERENCES cache_tasks(id) ON DELETE CASCADE, + code VARCHAR(20) NOT NULL, + trade_date DATE NOT NULL, + expected_count INTEGER DEFAULT 0, + actual_count INTEGER DEFAULT 0, + is_missing BOOLEAN DEFAULT FALSE, + status VARCHAR(20) DEFAULT 'pending', + error_message TEXT, + processed_at TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +#### 股票日线K线表 (stock_kline_daily) + +```sql +CREATE TABLE stock_kline_daily ( + id BIGSERIAL PRIMARY KEY, + code VARCHAR(20) NOT NULL, + trade_date DATE NOT NULL, + open DECIMAL(10,3), + high DECIMAL(10,3), + low DECIMAL(10,3), + close DECIMAL(10,3), + volume BIGINT, + amount DECIMAL(18,2), + adj_factor DECIMAL(10,5), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + UNIQUE(code, trade_date) +); + +CREATE INDEX idx_stock_kline_code ON stock_kline_daily(code); +CREATE INDEX idx_stock_kline_date ON stock_kline_daily(trade_date); +``` + +#### 期货日线K线表 (future_kline_daily) + +```sql +CREATE TABLE future_kline_daily ( + id BIGSERIAL PRIMARY KEY, + code VARCHAR(20) NOT NULL, + trade_date DATE NOT NULL, + open DECIMAL(10,3), + high DECIMAL(10,3), + low DECIMAL(10,3), + close DECIMAL(10,3), + volume BIGINT, + amount DECIMAL(18,2), + settle DECIMAL(10,3), + open_interest BIGINT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + UNIQUE(code, trade_date) +); + +CREATE INDEX idx_future_kline_code ON future_kline_daily(code); +CREATE INDEX idx_future_kline_date ON future_kline_daily(trade_date); +``` + +--- + +## 三、Redis 缓存设计 + +### 3.1 Redis 客户端 + +Redis 客户端位于 `app/core/redis_client.py`,提供以下方法: + +| 方法 | 功能 | 参数 | +|------|------|------| +| `get(key)` | 获取缓存 | key: 缓存键 | +| `set(key, value, expire)` | 设置缓存 | key, value, expire(秒) | +| `delete(key)` | 删除缓存 | key: 缓存键 | +| `exists(key)` | 检查键是否存在 | key: 缓存键 | +| `is_connected()` | 检查连接状态 | 无 | + +### 3.2 缓存键设计 + +| 缓存键 | 数据内容 | 有效期 | 使用位置 | +|--------|----------|--------|----------| +| `code_list:{type}:{contract}` | 代码列表 | 12小时 | `cache_service.get_all_codes()` | +| `future_varieties` | 期货品种列表 | 24小时 | `cache_service.get_future_varieties()` | +| `trading_calendar:{market}` | 交易日历 | 1年 | `cache_service.get_trading_calendar_cached()` | +| `main_contract:{variety}` | 单个主力合约 | 1小时 | `cache_service.get_main_contract_cached()` | +| `all_main_contracts` | 所有主力合约 | 1小时 | `cache_service.get_all_main_contracts_cached()` | + +### 3.3 Redis 使用位置汇总 + +| 文件 | 方法 | 缓存键 | 有效期 | +|------|------|--------|--------| +| `cache_service.py` | `get_all_codes()` | `code_list:{type}:{contract}` | 12小时 | +| `cache_service.py` | `get_future_varieties()` | `future_varieties` | 24小时 | +| `cache_service.py` | `get_trading_calendar_cached()` | `trading_calendar:{market}` | 1年 | +| `cache_service.py` | `get_main_contract_cached()` | `main_contract:{variety}` | 1小时 | +| `cache_service.py` | `get_all_main_contracts_cached()` | `all_main_contracts` | 1小时 | + +--- + +## 四、部署指南 + +### 4.1 环境要求 + +- Python 3.11+ +- Node.js 18+ +- PostgreSQL 15+ (或 SQLite 3.x) +- Redis 7+ + +### 4.2 后端部署 + +#### 1. 安装依赖 + +```bash +cd backend +pip install -r requirements.txt +``` + +#### 2. 配置环境变量 + +创建 `.env` 文件: + +```env +DATABASE_URL=postgresql://user:password@localhost:5432/amazing_data +REDIS_URL=redis://localhost:6379/0 +SECRET_KEY=your-secret-key +DEBUG=False +``` + +#### 3. 初始化数据库 + +```bash +# PostgreSQL +psql -U postgres -d amazing_data -f database/init.sql + +# 或使用 SQLAlchemy 自动创建 +python -c "from app.db.base import Base; from app.db.session import engine; Base.metadata.create_all(bind=engine)" +``` + +#### 4. 启动服务 + +```bash +# 开发模式 +python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + +# 生产模式 +python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 +``` + +### 4.3 前端部署 + +#### 1. 安装依赖 + +```bash +cd frontend +npm install +``` + +#### 2. 配置环境 + +创建 `.env` 文件: + +```env +VITE_API_BASE_URL=http://localhost:8000/api/v1 +``` + +#### 3. 开发模式 + +```bash +npm run dev +``` + +#### 4. 生产构建 + +```bash +npm run build +npm run preview +``` + +### 4.4 Docker 部署 (可选) + +创建 `docker-compose.yml`: + +```yaml +version: '3.8' + +services: + postgres: + image: postgres:15 + environment: + POSTGRES_DB: amazing_data + POSTGRES_USER: admin + POSTGRES_PASSWORD: password + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + + redis: + image: redis:7 + ports: + - "6379:6379" + + backend: + build: ./backend + ports: + - "8000:8000" + depends_on: + - postgres + - redis + environment: + DATABASE_URL: postgresql://admin:password@postgres:5432/amazing_data + REDIS_URL: redis://redis:6379/0 + + frontend: + build: ./frontend + ports: + - "3000:3000" + depends_on: + - backend + +volumes: + postgres_data: +``` + +启动: + +```bash +docker-compose up -d +``` + +--- + +## 五、系统配置 + +### 5.1 配置文件 + +配置文件位于 `backend/app/config.py`: + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `DATABASE_URL` | sqlite:///./amazing_data.db | 数据库连接 | +| `REDIS_URL` | redis://localhost:6379/0 | Redis连接 | +| `SECRET_KEY` | your-secret-key | JWT密钥 | +| `ACCESS_TOKEN_EXPIRE_HOURS` | 24 | Token有效期 | +| `CACHE_DEFAULT_DAYS` | 365 | 默认缓存天数 | +| `CACHE_BATCH_SIZE` | 100 | 批量处理大小 | + +### 5.2 SDK 配置 + +通过管理界面配置 SDK 连接参数: + +- 用户名 +- 密码 +- 服务器地址 +- 端口号 +- 本地路径 + +--- + +## 六、监控与维护 + +### 6.1 日志 + +日志配置位于 `backend/app/core/logging.py`,日志文件存储在 `logs/` 目录。 + +### 6.2 健康检查 + +- API健康检查: `GET /health` +- Redis连接检查: `redis_client.is_connected()` +- SDK连接检查: `sdk_manager.get_default_connection().is_connected()` + +### 6.3 缓存清理 + +定期清理过期缓存: + +```python +# 清理代码列表缓存 +redis_client.delete("code_list:stock:all") + +# 清理交易日历缓存 +redis_client.delete("trading_calendar:SH") +``` + +--- + +## 七、安全注意事项 + +1. **JWT密钥**: 生产环境必须更换 `SECRET_KEY` +2. **数据库密码**: 使用强密码 +3. **Redis密码**: 生产环境建议启用 Redis 认证 +4. **HTTPS**: 生产环境建议启用 HTTPS +5. **API限流**: 建议添加请求限流中间件 + +--- + +## 八、常见问题 + +### Q1: Redis连接失败 + +检查 Redis 服务是否启动: + +```bash +redis-cli ping +``` + +### Q2: SDK连接失败 + +检查 SDK 配置是否正确,用户名密码是否有效。 + +### Q3: 数据库连接失败 + +检查数据库服务是否启动,连接字符串是否正确。 + +--- + +## 九、版本历史 + +| 版本 | 日期 | 更新内容 | +|------|------|----------| +| 1.0.0 | 2024-01 | 初始版本 | +| 1.1.0 | 2024-04 | 添加Redis缓存支持 | +| 1.2.0 | 2024-04 | 添加一键检测/补齐功能 | \ No newline at end of file