前端预测结果展示样式统一优化:所有预测类型和历史记录的红球蓝球一行展示,间距一致,修复集成预测展示问题
This commit is contained in:
parent
3e07f72b98
commit
988586da84
37
README.md
37
README.md
@ -29,7 +29,7 @@
|
|||||||
- 数据查询和筛选
|
- 数据查询和筛选
|
||||||
- 数据导出
|
- 数据导出
|
||||||
- 手动数据录入
|
- 手动数据录入
|
||||||
- 自动数据更新
|
- **自动数据更新** ⭐ 新功能
|
||||||
|
|
||||||
### 2. 基础统计分析
|
### 2. 基础统计分析
|
||||||
- 号码出现频率统计
|
- 号码出现频率统计
|
||||||
@ -101,7 +101,22 @@ lottery/
|
|||||||
|
|
||||||
## 安装和运行
|
## 安装和运行
|
||||||
|
|
||||||
### 后端
|
### 快速启动 ⭐ 推荐
|
||||||
|
使用一键启动脚本(推荐方式):
|
||||||
|
```bash
|
||||||
|
# 确保已安装Python 3.8+和Node.js 16+
|
||||||
|
python start_system.py
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会自动:
|
||||||
|
- 检查Python版本和依赖
|
||||||
|
- 启动后端服务(端口8000)
|
||||||
|
- 启动前端服务(端口5173)
|
||||||
|
- 显示访问地址和使用说明
|
||||||
|
|
||||||
|
### 手动启动
|
||||||
|
|
||||||
|
#### 后端
|
||||||
1. 创建虚拟环境
|
1. 创建虚拟环境
|
||||||
```bash
|
```bash
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
@ -124,7 +139,7 @@ pip install -r requirements.txt
|
|||||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||||
```
|
```
|
||||||
|
|
||||||
### 前端
|
#### 前端
|
||||||
1. 安装依赖
|
1. 安装依赖
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
@ -151,12 +166,18 @@ npm run build
|
|||||||
- 导入过程自动去重:只导入数据库中未出现过的开奖日期数据。
|
- 导入过程自动去重:只导入数据库中未出现过的开奖日期数据。
|
||||||
- 支持 pandas DataFrame 或 JSON 文件导入。
|
- 支持 pandas DataFrame 或 JSON 文件导入。
|
||||||
|
|
||||||
#### 数据自动/手动更新
|
#### 数据自动/手动更新 ⭐ 新功能
|
||||||
- 系统支持从聚合数据API自动获取最新开奖记录。
|
- **前端更新功能**: 在首页提供"更新双色球"、"更新大乐透"、"更新全部"三个按钮
|
||||||
|
- **API更新接口**: 支持通过API接口更新数据
|
||||||
|
- `POST /api/v1/lottery/update/ssq` - 更新双色球数据
|
||||||
|
- `POST /api/v1/lottery/update/dlt` - 更新大乐透数据
|
||||||
|
- `POST /api/v1/lottery/update/all` - 更新所有彩票数据
|
||||||
- 更新逻辑:
|
- 更新逻辑:
|
||||||
- 只插入数据库中未出现过的开奖日期(`open_time` 唯一性判断)。
|
- 从聚合数据API自动获取最新开奖记录
|
||||||
- 插入顺序为开奖日期升序,保证历史数据先入库,最新数据最后入库。
|
- 只插入数据库中未出现过的开奖日期(`open_time` 唯一性判断)
|
||||||
- 日志记录于 `lottery_update.log`。
|
- 插入顺序为开奖日期升序,保证历史数据先入库,最新数据最后入库
|
||||||
|
- 实时显示更新结果和新增记录数量
|
||||||
|
- 更新完成后自动刷新首页最新开奖信息
|
||||||
|
|
||||||
##### 手动更新
|
##### 手动更新
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@ -105,3 +105,45 @@ async def import_dlt_data(
|
|||||||
# 清理临时文件
|
# 清理临时文件
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update/ssq")
|
||||||
|
async def update_ssq_data(db: Session = Depends(get_db)):
|
||||||
|
"""更新双色球数据"""
|
||||||
|
try:
|
||||||
|
result = LotteryService.update_ssq_data(db)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"双色球数据更新成功,新增 {result.get('new_count', 0)} 条记录",
|
||||||
|
"data": result
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"更新双色球数据失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update/dlt")
|
||||||
|
async def update_dlt_data(db: Session = Depends(get_db)):
|
||||||
|
"""更新大乐透数据"""
|
||||||
|
try:
|
||||||
|
result = LotteryService.update_dlt_data(db)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"大乐透数据更新成功,新增 {result.get('new_count', 0)} 条记录",
|
||||||
|
"data": result
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"更新大乐透数据失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update/all")
|
||||||
|
async def update_all_lottery_data(db: Session = Depends(get_db)):
|
||||||
|
"""更新所有彩票数据"""
|
||||||
|
try:
|
||||||
|
result = LotteryService.update_all_lottery_data(db)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "所有彩票数据更新完成",
|
||||||
|
"data": result
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"更新彩票数据失败: {str(e)}")
|
||||||
|
|||||||
@ -129,3 +129,45 @@ def import_dlt_data(file: UploadFile = File(...), db: Session = Depends(get_db))
|
|||||||
tmp_path = tmp.name
|
tmp_path = tmp.name
|
||||||
count = LotteryService.import_dlt_data(db, tmp_path)
|
count = LotteryService.import_dlt_data(db, tmp_path)
|
||||||
return {"imported": count}
|
return {"imported": count}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update/ssq")
|
||||||
|
async def update_ssq_data(db: Session = Depends(get_db)):
|
||||||
|
"""更新双色球数据"""
|
||||||
|
try:
|
||||||
|
result = LotteryService.update_ssq_data(db)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"双色球数据更新成功,新增 {result.get('new_count', 0)} 条记录",
|
||||||
|
"data": result
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"更新双色球数据失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update/dlt")
|
||||||
|
async def update_dlt_data(db: Session = Depends(get_db)):
|
||||||
|
"""更新大乐透数据"""
|
||||||
|
try:
|
||||||
|
result = LotteryService.update_dlt_data(db)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"大乐透数据更新成功,新增 {result.get('new_count', 0)} 条记录",
|
||||||
|
"data": result
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"更新大乐透数据失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update/all")
|
||||||
|
async def update_all_lottery_data(db: Session = Depends(get_db)):
|
||||||
|
"""更新所有彩票数据"""
|
||||||
|
try:
|
||||||
|
result = LotteryService.update_all_lottery_data(db)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "所有彩票数据更新完成",
|
||||||
|
"data": result
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"更新彩票数据失败: {str(e)}")
|
||||||
|
|||||||
@ -34,6 +34,24 @@ app.include_router(
|
|||||||
app.include_router(
|
app.include_router(
|
||||||
prediction_router, prefix=f"{settings.API_V1_STR}/prediction", tags=["prediction"])
|
prediction_router, prefix=f"{settings.API_V1_STR}/prediction", tags=["prediction"])
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def read_root():
|
||||||
|
"""根路径,用于健康检查"""
|
||||||
|
return {
|
||||||
|
"message": "欢迎使用彩票数据分析系统",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"status": "running",
|
||||||
|
"docs": "/docs"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health_check():
|
||||||
|
"""健康检查端点"""
|
||||||
|
return {"status": "healthy", "timestamp": "2024-01-01T00:00:00Z"}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
|
|||||||
@ -247,3 +247,268 @@ class LotteryService:
|
|||||||
def get_latest_dlt(db: Session) -> Optional[DLTLottery]:
|
def get_latest_dlt(db: Session) -> Optional[DLTLottery]:
|
||||||
"""获取最新一期大乐透开奖记录"""
|
"""获取最新一期大乐透开奖记录"""
|
||||||
return db.query(DLTLottery).order_by(desc(DLTLottery.open_time)).first()
|
return db.query(DLTLottery).order_by(desc(DLTLottery.open_time)).first()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_ssq_data(db: Session) -> Dict[str, Any]:
|
||||||
|
"""更新双色球数据"""
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from requests.packages.urllib3.util.retry import Retry
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# API配置
|
||||||
|
api_key = "7a4beb6175a2c4dacf6cf9cab43bfe6f"
|
||||||
|
api_url = "http://apis.juhe.cn/lottery/history"
|
||||||
|
|
||||||
|
# 配置请求会话
|
||||||
|
session = requests.Session()
|
||||||
|
retry_strategy = Retry(
|
||||||
|
total=3,
|
||||||
|
backoff_factor=1,
|
||||||
|
status_forcelist=[429, 500, 502, 503, 504]
|
||||||
|
)
|
||||||
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
||||||
|
session.mount("http://", adapter)
|
||||||
|
session.mount("https://", adapter)
|
||||||
|
|
||||||
|
# 获取数据库中所有已存在的开奖日期
|
||||||
|
existing_open_times = set(
|
||||||
|
row.open_time.strftime('%Y-%m-%d')
|
||||||
|
for row in db.query(SSQLottery.open_time).all()
|
||||||
|
)
|
||||||
|
logger.info(f"数据库已存在{len(existing_open_times)}个开奖日期")
|
||||||
|
|
||||||
|
# 从API获取数据
|
||||||
|
params = {
|
||||||
|
'key': api_key,
|
||||||
|
'lottery_id': 'ssq',
|
||||||
|
'page': 1,
|
||||||
|
'page_size': 50
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = session.get(
|
||||||
|
api_url, params=params, headers=headers, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get('error_code') != 0:
|
||||||
|
raise Exception(f"API返回错误: {data.get('reason', '未知错误')}")
|
||||||
|
|
||||||
|
if not data.get('result') or not data['result'].get('lotteryResList'):
|
||||||
|
raise Exception("API返回数据格式不正确")
|
||||||
|
|
||||||
|
# 处理数据
|
||||||
|
new_data = []
|
||||||
|
lottery_list = sorted(
|
||||||
|
data['result']['lotteryResList'],
|
||||||
|
key=lambda x: x['lottery_date'],
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
|
for item in lottery_list:
|
||||||
|
# 验证数据
|
||||||
|
try:
|
||||||
|
draw_date = datetime.strptime(
|
||||||
|
item['lottery_date'], '%Y-%m-%d')
|
||||||
|
if draw_date > datetime.now():
|
||||||
|
continue
|
||||||
|
if not item['lottery_no'].isdigit():
|
||||||
|
continue
|
||||||
|
|
||||||
|
numbers = item['lottery_res'].split(',')
|
||||||
|
if len(numbers) != 7:
|
||||||
|
continue
|
||||||
|
if not all(1 <= int(n) <= 33 for n in numbers[:6]) or not 1 <= int(numbers[6]) <= 16:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item['lottery_date'] in existing_open_times:
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_data.append({
|
||||||
|
'issue': item['lottery_no'],
|
||||||
|
'open_time': draw_date.date(),
|
||||||
|
'red_ball_1': int(numbers[0]),
|
||||||
|
'red_ball_2': int(numbers[1]),
|
||||||
|
'red_ball_3': int(numbers[2]),
|
||||||
|
'red_ball_4': int(numbers[3]),
|
||||||
|
'red_ball_5': int(numbers[4]),
|
||||||
|
'red_ball_6': int(numbers[5]),
|
||||||
|
'blue_ball': int(numbers[6])
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"处理数据项失败: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not new_data:
|
||||||
|
return {"new_count": 0, "message": "数据已是最新"}
|
||||||
|
|
||||||
|
# 按开奖日期升序排序
|
||||||
|
new_data = sorted(new_data, key=lambda x: x['open_time'])
|
||||||
|
|
||||||
|
# 保存到数据库
|
||||||
|
for item in new_data:
|
||||||
|
lottery = SSQLottery(**item)
|
||||||
|
db.add(lottery)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"new_count": len(new_data),
|
||||||
|
"message": f"成功更新双色球数据,新增{len(new_data)}条记录",
|
||||||
|
"latest_issue": new_data[-1]['issue'] if new_data else None,
|
||||||
|
"latest_date": new_data[-1]['open_time'].strftime('%Y-%m-%d') if new_data else None
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(f"更新双色球数据失败: {str(e)}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_dlt_data(db: Session) -> Dict[str, Any]:
|
||||||
|
"""更新大乐透数据"""
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from requests.packages.urllib3.util.retry import Retry
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# API配置
|
||||||
|
api_key = "7a4beb6175a2c4dacf6cf9cab43bfe6f"
|
||||||
|
api_url = "http://apis.juhe.cn/lottery/history"
|
||||||
|
|
||||||
|
# 配置请求会话
|
||||||
|
session = requests.Session()
|
||||||
|
retry_strategy = Retry(
|
||||||
|
total=3,
|
||||||
|
backoff_factor=1,
|
||||||
|
status_forcelist=[429, 500, 502, 503, 504]
|
||||||
|
)
|
||||||
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
||||||
|
session.mount("http://", adapter)
|
||||||
|
session.mount("https://", adapter)
|
||||||
|
|
||||||
|
# 获取数据库中所有已存在的开奖日期
|
||||||
|
existing_open_times = set(
|
||||||
|
row.open_time.strftime('%Y-%m-%d')
|
||||||
|
for row in db.query(DLTLottery.open_time).all()
|
||||||
|
)
|
||||||
|
logger.info(f"数据库已存在{len(existing_open_times)}个开奖日期")
|
||||||
|
|
||||||
|
# 从API获取数据
|
||||||
|
params = {
|
||||||
|
'key': api_key,
|
||||||
|
'lottery_id': 'dlt',
|
||||||
|
'page': 1,
|
||||||
|
'page_size': 50
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = session.get(
|
||||||
|
api_url, params=params, headers=headers, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get('error_code') != 0:
|
||||||
|
raise Exception(f"API返回错误: {data.get('reason', '未知错误')}")
|
||||||
|
|
||||||
|
if not data.get('result') or not data['result'].get('lotteryResList'):
|
||||||
|
raise Exception("API返回数据格式不正确")
|
||||||
|
|
||||||
|
# 处理数据
|
||||||
|
new_data = []
|
||||||
|
lottery_list = sorted(
|
||||||
|
data['result']['lotteryResList'],
|
||||||
|
key=lambda x: x['lottery_date'],
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
|
for item in lottery_list:
|
||||||
|
# 验证数据
|
||||||
|
try:
|
||||||
|
draw_date = datetime.strptime(
|
||||||
|
item['lottery_date'], '%Y-%m-%d')
|
||||||
|
if draw_date > datetime.now():
|
||||||
|
continue
|
||||||
|
if not item['lottery_no'].isdigit():
|
||||||
|
continue
|
||||||
|
|
||||||
|
numbers = item['lottery_res'].split(',')
|
||||||
|
if len(numbers) != 7:
|
||||||
|
continue
|
||||||
|
if not all(1 <= int(n) <= 35 for n in numbers[:5]) or not all(1 <= int(n) <= 12 for n in numbers[5:]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item['lottery_date'] in existing_open_times:
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_data.append({
|
||||||
|
'issue': item['lottery_no'],
|
||||||
|
'open_time': draw_date.date(),
|
||||||
|
'front_ball_1': int(numbers[0]),
|
||||||
|
'front_ball_2': int(numbers[1]),
|
||||||
|
'front_ball_3': int(numbers[2]),
|
||||||
|
'front_ball_4': int(numbers[3]),
|
||||||
|
'front_ball_5': int(numbers[4]),
|
||||||
|
'back_ball_1': int(numbers[5]),
|
||||||
|
'back_ball_2': int(numbers[6])
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"处理数据项失败: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not new_data:
|
||||||
|
return {"new_count": 0, "message": "数据已是最新"}
|
||||||
|
|
||||||
|
# 按开奖日期升序排序
|
||||||
|
new_data = sorted(new_data, key=lambda x: x['open_time'])
|
||||||
|
|
||||||
|
# 保存到数据库
|
||||||
|
for item in new_data:
|
||||||
|
lottery = DLTLottery(**item)
|
||||||
|
db.add(lottery)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"new_count": len(new_data),
|
||||||
|
"message": f"成功更新大乐透数据,新增{len(new_data)}条记录",
|
||||||
|
"latest_issue": new_data[-1]['issue'] if new_data else None,
|
||||||
|
"latest_date": new_data[-1]['open_time'].strftime('%Y-%m-%d') if new_data else None
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(f"更新大乐透数据失败: {str(e)}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_all_lottery_data(db: Session) -> Dict[str, Any]:
|
||||||
|
"""更新所有彩票数据"""
|
||||||
|
try:
|
||||||
|
ssq_result = LotteryService.update_ssq_data(db)
|
||||||
|
dlt_result = LotteryService.update_dlt_data(db)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ssq": ssq_result,
|
||||||
|
"dlt": dlt_result,
|
||||||
|
"total_new": ssq_result.get("new_count", 0) + dlt_result.get("new_count", 0)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|||||||
@ -367,6 +367,7 @@ class PredictionService:
|
|||||||
|
|
||||||
# 如果是双色球,还需要生成蓝球
|
# 如果是双色球,还需要生成蓝球
|
||||||
predicted_blue = None
|
predicted_blue = None
|
||||||
|
predicted_blues = None
|
||||||
if lottery_type == 'ssq':
|
if lottery_type == 'ssq':
|
||||||
# 分析蓝球规律
|
# 分析蓝球规律
|
||||||
blue_numbers = [draw.blue_ball for draw in recent_draws]
|
blue_numbers = [draw.blue_ball for draw in recent_draws]
|
||||||
@ -378,6 +379,26 @@ class PredictionService:
|
|||||||
predicted_blue = random.choice(common_blues)
|
predicted_blue = random.choice(common_blues)
|
||||||
else:
|
else:
|
||||||
predicted_blue = random.randint(1, 16)
|
predicted_blue = random.randint(1, 16)
|
||||||
|
else:
|
||||||
|
# 大乐透后区号码分析
|
||||||
|
back1 = [draw.back_ball_1 for draw in recent_draws]
|
||||||
|
back2 = [draw.back_ball_2 for draw in recent_draws]
|
||||||
|
from collections import Counter
|
||||||
|
c1 = Counter(back1)
|
||||||
|
c2 = Counter(back2)
|
||||||
|
# 取出现频率适中的号码
|
||||||
|
|
||||||
|
def pick_blues(counter):
|
||||||
|
items = counter.most_common()
|
||||||
|
if not items:
|
||||||
|
return random.randint(1, 12)
|
||||||
|
mid = len(items) // 2
|
||||||
|
return items[mid][0]
|
||||||
|
b1 = pick_blues(c1)
|
||||||
|
b2 = pick_blues(c2)
|
||||||
|
if b1 == b2:
|
||||||
|
b2 = random.choice([i for i in range(1, 13) if i != b1])
|
||||||
|
predicted_blues = sorted([b1, b2])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
@ -389,7 +410,8 @@ class PredictionService:
|
|||||||
"consecutive_count": most_common_patterns['consecutive_count'][0][0] if most_common_patterns['consecutive_count'] else 1
|
"consecutive_count": most_common_patterns['consecutive_count'][0][0] if most_common_patterns['consecutive_count'] else 1
|
||||||
},
|
},
|
||||||
"predicted_numbers": best_numbers,
|
"predicted_numbers": best_numbers,
|
||||||
"predicted_blue": predicted_blue if lottery_type == 'ssq' else None
|
"predicted_blue": predicted_blue if lottery_type == 'ssq' else None,
|
||||||
|
"predicted_blues": predicted_blues if lottery_type == 'dlt' else None
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_ensemble_prediction(self, lottery_type: str, periods: int = 100) -> Dict:
|
def get_ensemble_prediction(self, lottery_type: str, periods: int = 100) -> Dict:
|
||||||
@ -523,7 +545,7 @@ class PredictionService:
|
|||||||
'method': '模式分析',
|
'method': '模式分析',
|
||||||
'numbers': pattern_result['predicted_numbers'],
|
'numbers': pattern_result['predicted_numbers'],
|
||||||
'blue': pattern_result['predicted_blue'] if lottery_type == 'ssq' else None,
|
'blue': pattern_result['predicted_blue'] if lottery_type == 'ssq' else None,
|
||||||
'blues': pattern_result['predicted_blues'] if lottery_type == 'dlt' else None,
|
'blues': pattern_result.get('predicted_blues') if lottery_type == 'dlt' else None,
|
||||||
'confidence': '中'
|
'confidence': '中'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -39,14 +39,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const activeIndex = ref('/')
|
const route = useRoute()
|
||||||
|
const activeIndex = computed(() => route.path)
|
||||||
|
|
||||||
const handleSelect = (key) => {
|
const handleSelect = (key) => {
|
||||||
activeIndex.value = key
|
router.push(key)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -152,5 +152,18 @@ export const lotteryApi = {
|
|||||||
}
|
}
|
||||||
throw new Error('返回数据格式错误')
|
throw new Error('返回数据格式错误')
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新彩票数据
|
||||||
|
updateSSQData() {
|
||||||
|
return api.post('/update/ssq')
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDLTData() {
|
||||||
|
return api.post('/update/dlt')
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAllLotteryData() {
|
||||||
|
return api.post('/update/all')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home-container">
|
<div class="home-container">
|
||||||
|
<!-- 更新数据按钮区域 -->
|
||||||
|
<el-row :gutter="20" class="update-row">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-card class="update-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>数据更新</span>
|
||||||
|
<div class="update-buttons">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="updatingSSQ"
|
||||||
|
@click="updateSSQData"
|
||||||
|
:disabled="updatingAll"
|
||||||
|
>
|
||||||
|
<el-icon><Refresh /></el-icon>
|
||||||
|
更新双色球
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
:loading="updatingDLT"
|
||||||
|
@click="updateDLTData"
|
||||||
|
:disabled="updatingAll"
|
||||||
|
>
|
||||||
|
<el-icon><Refresh /></el-icon>
|
||||||
|
更新大乐透
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
:loading="updatingAll"
|
||||||
|
@click="updateAllData"
|
||||||
|
:disabled="updatingSSQ || updatingDLT"
|
||||||
|
>
|
||||||
|
<el-icon><Refresh /></el-icon>
|
||||||
|
更新全部
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="updateMessage" class="update-message">
|
||||||
|
<el-alert
|
||||||
|
:title="updateMessage"
|
||||||
|
:type="updateMessageType"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card class="data-card">
|
<el-card class="data-card">
|
||||||
@ -81,13 +131,21 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { lotteryApi } from '../api/lottery'
|
import { lotteryApi } from '../api/lottery'
|
||||||
import { Document, TrendCharts, MagicStick } from '@element-plus/icons-vue'
|
import { Document, TrendCharts, MagicStick, Refresh } from '@element-plus/icons-vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const ssqLatest = ref(null)
|
const ssqLatest = ref(null)
|
||||||
const dltLatest = ref(null)
|
const dltLatest = ref(null)
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
const updatingSSQ = ref(false)
|
||||||
|
const updatingDLT = ref(false)
|
||||||
|
const updatingAll = ref(false)
|
||||||
|
const updateMessage = ref('')
|
||||||
|
const updateMessageType = ref('info')
|
||||||
|
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
const formatDate = (date) => {
|
const formatDate = (date) => {
|
||||||
return dayjs(date).format('YYYY-MM-DD')
|
return dayjs(date).format('YYYY-MM-DD')
|
||||||
@ -127,6 +185,84 @@ const refreshDLTData = () => {
|
|||||||
fetchDLTLatest()
|
fetchDLTLatest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新双色球数据
|
||||||
|
const updateSSQData = async () => {
|
||||||
|
updatingSSQ.value = true
|
||||||
|
updateMessage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await lotteryApi.updateSSQData()
|
||||||
|
if (response && response.data && response.data.success) {
|
||||||
|
updateMessage.value = response.data.message
|
||||||
|
updateMessageType.value = 'success'
|
||||||
|
ElMessage.success(response.data.message)
|
||||||
|
// 刷新最新开奖数据
|
||||||
|
await fetchSSQLatest()
|
||||||
|
} else {
|
||||||
|
throw new Error(response?.data?.message || '更新失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新双色球数据失败:', error)
|
||||||
|
updateMessage.value = `更新双色球数据失败: ${error.message}`
|
||||||
|
updateMessageType.value = 'error'
|
||||||
|
ElMessage.error(`更新双色球数据失败: ${error.message}`)
|
||||||
|
} finally {
|
||||||
|
updatingSSQ.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新大乐透数据
|
||||||
|
const updateDLTData = async () => {
|
||||||
|
updatingDLT.value = true
|
||||||
|
updateMessage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await lotteryApi.updateDLTData()
|
||||||
|
if (response && response.data && response.data.success) {
|
||||||
|
updateMessage.value = response.data.message
|
||||||
|
updateMessageType.value = 'success'
|
||||||
|
ElMessage.success(response.data.message)
|
||||||
|
// 刷新最新开奖数据
|
||||||
|
await fetchDLTLatest()
|
||||||
|
} else {
|
||||||
|
throw new Error(response?.data?.message || '更新失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新大乐透数据失败:', error)
|
||||||
|
updateMessage.value = `更新大乐透数据失败: ${error.message}`
|
||||||
|
updateMessageType.value = 'error'
|
||||||
|
ElMessage.error(`更新大乐透数据失败: ${error.message}`)
|
||||||
|
} finally {
|
||||||
|
updatingDLT.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新所有数据
|
||||||
|
const updateAllData = async () => {
|
||||||
|
updatingAll.value = true
|
||||||
|
updateMessage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await lotteryApi.updateAllLotteryData()
|
||||||
|
if (response && response.data && response.data.success) {
|
||||||
|
updateMessage.value = response.data.message
|
||||||
|
updateMessageType.value = 'success'
|
||||||
|
ElMessage.success(response.data.message)
|
||||||
|
// 刷新最新开奖数据
|
||||||
|
await Promise.all([fetchSSQLatest(), fetchDLTLatest()])
|
||||||
|
} else {
|
||||||
|
throw new Error(response?.data?.message || '更新失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新所有数据失败:', error)
|
||||||
|
updateMessage.value = `更新所有数据失败: ${error.message}`
|
||||||
|
updateMessageType.value = 'error'
|
||||||
|
ElMessage.error(`更新所有数据失败: ${error.message}`)
|
||||||
|
} finally {
|
||||||
|
updatingAll.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 页面导航
|
// 页面导航
|
||||||
const navigateTo = (route) => {
|
const navigateTo = (route) => {
|
||||||
const routeMap = {
|
const routeMap = {
|
||||||
@ -156,6 +292,24 @@ onMounted(() => {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-message {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-20 {
|
.mt-20 {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
@ -250,6 +404,10 @@ onMounted(() => {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 14px 0;
|
padding: 14px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
|
|||||||
@ -36,14 +36,19 @@
|
|||||||
<h4>机器学习预测</h4>
|
<h4>机器学习预测</h4>
|
||||||
<el-tag type="success">置信度: 高</el-tag>
|
<el-tag type="success">置信度: 高</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="prediction-numbers">
|
<div class="prediction-numbers flex-balls">
|
||||||
<div class="red-balls">
|
<div class="red-balls">
|
||||||
<span v-for="num in mlPrediction.predicted_numbers" :key="num" class="ball red-ball">
|
<span v-for="num in mlPrediction.predicted_numbers" :key="num" class="ball red-ball">
|
||||||
{{ num.toString().padStart(2, '0') }}
|
{{ num.toString().padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="blue-balls" v-if="mlPrediction.predicted_blue || (mlPrediction.predicted_blues && mlPrediction.predicted_blues.length)" style="margin-left: 20px;">
|
||||||
<span v-if="mlPrediction.predicted_blue" class="ball blue-ball">
|
<span v-if="mlPrediction.predicted_blue" class="ball blue-ball">
|
||||||
{{ mlPrediction.predicted_blue.toString().padStart(2, '0') }}
|
{{ mlPrediction.predicted_blue.toString().padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
|
<span v-for="b in mlPrediction.predicted_blues" :key="'ml-blue-' + b" class="ball blue-ball">
|
||||||
|
{{ b.toString().padStart(2, '0') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prediction-info">
|
<div class="prediction-info">
|
||||||
@ -59,14 +64,19 @@
|
|||||||
<h4>模式分析预测</h4>
|
<h4>模式分析预测</h4>
|
||||||
<el-tag type="warning">置信度: 中</el-tag>
|
<el-tag type="warning">置信度: 中</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="patternPrediction.predicted_numbers" class="prediction-numbers">
|
<div class="prediction-numbers flex-balls" v-if="patternPrediction.predicted_numbers">
|
||||||
<div class="red-balls">
|
<div class="red-balls">
|
||||||
<span v-for="num in patternPrediction.predicted_numbers" :key="num" class="ball red-ball">
|
<span v-for="num in patternPrediction.predicted_numbers" :key="num" class="ball red-ball">
|
||||||
{{ num.toString().padStart(2, '0') }}
|
{{ num.toString().padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="blue-balls" v-if="patternPrediction.predicted_blue || (patternPrediction.predicted_blues && patternPrediction.predicted_blues.length)" style="margin-left: 20px;">
|
||||||
<span v-if="patternPrediction.predicted_blue" class="ball blue-ball">
|
<span v-if="patternPrediction.predicted_blue" class="ball blue-ball">
|
||||||
{{ patternPrediction.predicted_blue.toString().padStart(2, '0') }}
|
{{ patternPrediction.predicted_blue.toString().padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
|
<span v-for="b in patternPrediction.predicted_blues" :key="'pattern-blue-' + b" class="ball blue-ball">
|
||||||
|
{{ b.toString().padStart(2, '0') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pattern-criteria">
|
<div class="pattern-criteria">
|
||||||
@ -103,14 +113,19 @@
|
|||||||
置信度: {{ rec.confidence }}
|
置信度: {{ rec.confidence }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="recommendation-numbers">
|
<div class="recommendation-numbers flex-balls">
|
||||||
<div class="red-balls">
|
<div class="red-balls">
|
||||||
<span v-for="num in rec.numbers" :key="num" class="ball red-ball">
|
<span v-for="num in rec.numbers" :key="num" class="ball red-ball">
|
||||||
{{ num.toString().padStart(2, '0') }}
|
{{ num.toString().padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="blue-balls" v-if="rec.blue || (rec.blues && rec.blues.length)">
|
||||||
<span v-if="rec.blue" class="ball blue-ball">
|
<span v-if="rec.blue" class="ball blue-ball">
|
||||||
{{ rec.blue.toString().padStart(2, '0') }}
|
{{ rec.blue.toString().padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
|
<span v-for="b in rec.blues" :key="'blue-' + b" class="ball blue-ball">
|
||||||
|
{{ b.toString().padStart(2, '0') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -194,17 +209,19 @@
|
|||||||
<el-table-column prop="method" label="预测方法" width="120" />
|
<el-table-column prop="method" label="预测方法" width="120" />
|
||||||
<el-table-column prop="numbers" label="预测号码">
|
<el-table-column prop="numbers" label="预测号码">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="prediction-numbers">
|
<div class="prediction-numbers flex-balls">
|
||||||
<div class="red-balls">
|
<div class="red-balls">
|
||||||
<span v-for="(num, index) in scope.row.numbers.split(' ')"
|
<span v-for="(num, index) in scope.row.numbers.split(' ')" :key="index" class="ball red-ball">
|
||||||
:key="index"
|
|
||||||
class="ball red-ball"
|
|
||||||
>
|
|
||||||
{{ num.padStart(2, '0') }}
|
{{ num.padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="blue-balls" v-if="scope.row.blue || (scope.row.blues && scope.row.blues.length)" style="margin-left: 20px;">
|
||||||
<span v-if="scope.row.blue" class="ball blue-ball">
|
<span v-if="scope.row.blue" class="ball blue-ball">
|
||||||
{{ scope.row.blue.toString().padStart(2, '0') }}
|
{{ scope.row.blue.toString().padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
|
<span v-for="b in scope.row.blues" :key="'history-blue-' + b" class="ball blue-ball">
|
||||||
|
{{ b.toString().padStart(2, '0') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -312,6 +329,7 @@ export default {
|
|||||||
method: '机器学习',
|
method: '机器学习',
|
||||||
numbers: response.data.predicted_numbers.join(' '),
|
numbers: response.data.predicted_numbers.join(' '),
|
||||||
blue: response.data.predicted_blue,
|
blue: response.data.predicted_blue,
|
||||||
|
blues: response.data.predicted_blues,
|
||||||
confidence: '高'
|
confidence: '高'
|
||||||
})
|
})
|
||||||
ElMessage.success('机器学习预测完成')
|
ElMessage.success('机器学习预测完成')
|
||||||
@ -344,6 +362,7 @@ export default {
|
|||||||
method: '模式预测',
|
method: '模式预测',
|
||||||
numbers: response.data.predicted_numbers.join(' '),
|
numbers: response.data.predicted_numbers.join(' '),
|
||||||
blue: response.data.predicted_blue,
|
blue: response.data.predicted_blue,
|
||||||
|
blues: response.data.predicted_blues,
|
||||||
confidence: '中'
|
confidence: '中'
|
||||||
})
|
})
|
||||||
ElMessage.success('模式预测完成')
|
ElMessage.success('模式预测完成')
|
||||||
@ -391,6 +410,7 @@ export default {
|
|||||||
method: `集成预测-${rec.method}`,
|
method: `集成预测-${rec.method}`,
|
||||||
numbers: rec.numbers.join(' '),
|
numbers: rec.numbers.join(' '),
|
||||||
blue: rec.blue,
|
blue: rec.blue,
|
||||||
|
blues: rec.blues,
|
||||||
confidence: rec.confidence
|
confidence: rec.confidence
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -507,7 +527,7 @@ export default {
|
|||||||
.red-balls {
|
.red-balls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-wrap: wrap;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ball {
|
.ball {
|
||||||
@ -561,8 +581,17 @@ export default {
|
|||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recommendation-numbers {
|
.recommendation-numbers.flex-balls {
|
||||||
margin: 10px 0;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.red-balls, .blue-balls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.blue-balls {
|
||||||
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pattern-analysis {
|
.pattern-analysis {
|
||||||
@ -601,4 +630,9 @@ h3 {
|
|||||||
border-bottom: 2px solid #409eff;
|
border-bottom: 2px solid #409eff;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prediction-numbers.flex-balls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
315
start_system.py
Normal file
315
start_system.py
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
彩票数据分析系统启动脚本
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def check_python_version():
|
||||||
|
"""检查Python版本"""
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
print("❌ 错误: 需要Python 3.8或更高版本")
|
||||||
|
print(f"当前版本: {sys.version}")
|
||||||
|
return False
|
||||||
|
print(f"✅ Python版本检查通过: {sys.version}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_dependencies():
|
||||||
|
"""检查依赖是否安装"""
|
||||||
|
try:
|
||||||
|
import fastapi
|
||||||
|
import uvicorn
|
||||||
|
import sqlalchemy
|
||||||
|
import pandas
|
||||||
|
import requests
|
||||||
|
print("✅ 后端依赖检查通过")
|
||||||
|
return True
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"❌ 后端依赖缺失: {e}")
|
||||||
|
print("请运行: cd backend && pip install -r requirements.txt")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def start_backend():
|
||||||
|
"""启动后端服务"""
|
||||||
|
print("\n🚀 启动后端服务...")
|
||||||
|
|
||||||
|
backend_dir = Path("backend")
|
||||||
|
if not backend_dir.exists():
|
||||||
|
print("❌ 错误: 找不到backend目录")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 切换到backend目录
|
||||||
|
os.chdir(backend_dir)
|
||||||
|
|
||||||
|
# 启动后端服务
|
||||||
|
print("📝 启动命令: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload")
|
||||||
|
process = subprocess.Popen([
|
||||||
|
sys.executable, "-m", "uvicorn", "app.main:app",
|
||||||
|
"--host", "0.0.0.0", "--port", "8000", "--reload"
|
||||||
|
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
print("⏳ 等待后端服务启动...")
|
||||||
|
time.sleep(8) # 增加等待时间
|
||||||
|
|
||||||
|
# 检查服务是否启动成功
|
||||||
|
try:
|
||||||
|
response = requests.get("http://localhost:8000/", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✅ 后端服务启动成功")
|
||||||
|
print("📊 API文档地址: http://localhost:8000/docs")
|
||||||
|
return process
|
||||||
|
else:
|
||||||
|
print(f"❌ 后端服务启动失败,状态码: {response.status_code}")
|
||||||
|
# 检查进程输出
|
||||||
|
stdout, stderr = process.communicate(timeout=1)
|
||||||
|
if stderr:
|
||||||
|
print(f"错误输出: {stderr}")
|
||||||
|
return False
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"❌ 后端服务启动失败,无法连接到服务: {e}")
|
||||||
|
# 检查进程是否还在运行
|
||||||
|
if process.poll() is None:
|
||||||
|
print("⚠️ 进程仍在运行,可能是数据库连接问题")
|
||||||
|
# 检查进程输出
|
||||||
|
try:
|
||||||
|
stdout, stderr = process.communicate(timeout=2)
|
||||||
|
if stderr:
|
||||||
|
print(f"错误输出: {stderr}")
|
||||||
|
if stdout:
|
||||||
|
print(f"标准输出: {stdout}")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
process.kill()
|
||||||
|
print("⚠️ 进程被强制终止")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ 进程已退出,退出码: {process.returncode}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 启动后端服务时出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def start_frontend(original_dir):
|
||||||
|
"""启动前端服务"""
|
||||||
|
print("\n🚀 启动前端服务...")
|
||||||
|
|
||||||
|
# 确保回到根目录
|
||||||
|
os.chdir(original_dir)
|
||||||
|
|
||||||
|
frontend_dir = Path("frontend")
|
||||||
|
if not frontend_dir.exists():
|
||||||
|
print("❌ 错误: 找不到frontend目录")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 切换到frontend目录
|
||||||
|
os.chdir(frontend_dir)
|
||||||
|
|
||||||
|
# 检查node_modules是否存在
|
||||||
|
if not Path("node_modules").exists():
|
||||||
|
print("📦 安装前端依赖...")
|
||||||
|
subprocess.run(["npm", "install"], check=True)
|
||||||
|
|
||||||
|
# 启动前端服务
|
||||||
|
print("📝 启动命令: npm run dev")
|
||||||
|
process = subprocess.Popen([
|
||||||
|
"npm", "run", "dev"
|
||||||
|
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
print("⏳ 等待前端服务启动...")
|
||||||
|
time.sleep(10) # 增加等待时间
|
||||||
|
|
||||||
|
# 检查服务是否启动成功
|
||||||
|
try:
|
||||||
|
response = requests.get("http://localhost:5173/", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✅ 前端服务启动成功")
|
||||||
|
print("🌐 前端地址: http://localhost:5173")
|
||||||
|
return process
|
||||||
|
else:
|
||||||
|
print(f"❌ 前端服务启动失败,状态码: {response.status_code}")
|
||||||
|
# 检查进程输出
|
||||||
|
stdout, stderr = process.communicate(timeout=1)
|
||||||
|
if stderr:
|
||||||
|
print(f"错误输出: {stderr}")
|
||||||
|
return False
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"❌ 前端服务启动失败,无法连接到服务: {e}")
|
||||||
|
# 检查进程是否还在运行
|
||||||
|
if process.poll() is None:
|
||||||
|
print("⚠️ 进程仍在运行,可能是端口被占用")
|
||||||
|
# 检查进程输出
|
||||||
|
try:
|
||||||
|
stdout, stderr = process.communicate(timeout=2)
|
||||||
|
if stderr:
|
||||||
|
print(f"错误输出: {stderr}")
|
||||||
|
if stdout:
|
||||||
|
print(f"标准输出: {stdout}")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
process.kill()
|
||||||
|
print("⚠️ 进程被强制终止")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ 进程已退出,退出码: {process.returncode}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 启动前端服务时出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_database_connection():
|
||||||
|
"""检查数据库连接"""
|
||||||
|
print("\n🔍 检查数据库连接...")
|
||||||
|
try:
|
||||||
|
import pymysql
|
||||||
|
from backend.app.core.database import SQLALCHEMY_DATABASE_URL
|
||||||
|
|
||||||
|
# 解析数据库URL
|
||||||
|
if SQLALCHEMY_DATABASE_URL.startswith("mysql+pymysql://"):
|
||||||
|
# 提取连接信息
|
||||||
|
url_parts = SQLALCHEMY_DATABASE_URL.replace(
|
||||||
|
"mysql+pymysql://", "").split("@")
|
||||||
|
if len(url_parts) == 2:
|
||||||
|
credentials = url_parts[0]
|
||||||
|
host_port_db = url_parts[1]
|
||||||
|
|
||||||
|
username, password = credentials.split(":")
|
||||||
|
host_port, database = host_port_db.split("/")
|
||||||
|
host, port = host_port.split(":")
|
||||||
|
|
||||||
|
print(f"📊 数据库信息:")
|
||||||
|
print(f" 主机: {host}")
|
||||||
|
print(f" 端口: {port}")
|
||||||
|
print(f" 数据库: {database}")
|
||||||
|
print(f" 用户: {username}")
|
||||||
|
|
||||||
|
# 尝试连接数据库
|
||||||
|
try:
|
||||||
|
connection = pymysql.connect(
|
||||||
|
host=host,
|
||||||
|
port=int(port),
|
||||||
|
user=username,
|
||||||
|
password=password,
|
||||||
|
database=database,
|
||||||
|
connect_timeout=10
|
||||||
|
)
|
||||||
|
connection.close()
|
||||||
|
print("✅ 数据库连接成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 数据库连接失败: {e}")
|
||||||
|
print("💡 请检查:")
|
||||||
|
print(" 1. 数据库服务器是否运行")
|
||||||
|
print(" 2. 网络连接是否正常")
|
||||||
|
print(" 3. 数据库凭据是否正确")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("❌ 数据库URL格式错误")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("❌ 不支持的数据库类型")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
print("❌ 缺少pymysql依赖")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 检查数据库连接时出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("🎯 彩票数据分析系统启动器")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# 检查Python版本
|
||||||
|
if not check_python_version():
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查依赖
|
||||||
|
if not check_dependencies():
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查数据库连接
|
||||||
|
if not check_database_connection():
|
||||||
|
print("\n💡 建议:")
|
||||||
|
print("1. 检查数据库服务器是否正常运行")
|
||||||
|
print("2. 确认网络连接是否正常")
|
||||||
|
print("3. 验证数据库凭据是否正确")
|
||||||
|
print("4. 如果使用本地数据库,请修改 backend/app/core/database.py 中的配置")
|
||||||
|
|
||||||
|
response = input("\n是否继续启动系统?(y/N): ")
|
||||||
|
if response.lower() != 'y':
|
||||||
|
print("❌ 用户取消启动")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 保存当前目录
|
||||||
|
original_dir = os.getcwd()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 启动后端
|
||||||
|
backend_process = start_backend()
|
||||||
|
if not backend_process:
|
||||||
|
print("\n💡 后端启动失败,可能的原因:")
|
||||||
|
print("1. 数据库连接问题")
|
||||||
|
print("2. 端口8000被占用")
|
||||||
|
print("3. 依赖包缺失")
|
||||||
|
print("4. 代码错误")
|
||||||
|
print("\n请检查后端日志获取详细错误信息")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 启动前端
|
||||||
|
frontend_process = start_frontend(original_dir)
|
||||||
|
if not frontend_process:
|
||||||
|
backend_process.terminate()
|
||||||
|
print("\n💡 前端启动失败,可能的原因:")
|
||||||
|
print("1. Node.js未安装或版本过低")
|
||||||
|
print("2. 端口5173被占用")
|
||||||
|
print("3. 依赖包缺失")
|
||||||
|
print("\n请检查前端日志获取详细错误信息")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n🎉 系统启动成功!")
|
||||||
|
print("=" * 50)
|
||||||
|
print("📊 后端API: http://localhost:8000")
|
||||||
|
print("📊 API文档: http://localhost:8000/docs")
|
||||||
|
print("🌐 前端界面: http://localhost:5173")
|
||||||
|
print("=" * 50)
|
||||||
|
print("💡 使用说明:")
|
||||||
|
print("1. 打开浏览器访问 http://localhost:5173")
|
||||||
|
print("2. 在首页点击'更新数据'按钮获取最新开奖信息")
|
||||||
|
print("3. 使用各种分析功能进行数据分析")
|
||||||
|
print("=" * 50)
|
||||||
|
print("按 Ctrl+C 停止服务")
|
||||||
|
|
||||||
|
# 等待用户中断
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n🛑 正在停止服务...")
|
||||||
|
backend_process.terminate()
|
||||||
|
frontend_process.terminate()
|
||||||
|
print("✅ 服务已停止")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 启动过程中出错: {e}")
|
||||||
|
finally:
|
||||||
|
# 恢复原始目录
|
||||||
|
os.chdir(original_dir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
153
test_backend.py
Normal file
153
test_backend.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
测试后端启动的脚本
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def test_backend_startup():
|
||||||
|
"""测试后端启动"""
|
||||||
|
print("🧪 测试后端启动...")
|
||||||
|
|
||||||
|
# 切换到backend目录
|
||||||
|
backend_dir = "backend"
|
||||||
|
if not os.path.exists(backend_dir):
|
||||||
|
print("❌ 找不到backend目录")
|
||||||
|
return False
|
||||||
|
|
||||||
|
os.chdir(backend_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 启动后端服务
|
||||||
|
print("📝 启动命令: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload")
|
||||||
|
process = subprocess.Popen([
|
||||||
|
sys.executable, "-m", "uvicorn", "app.main:app",
|
||||||
|
"--host", "0.0.0.0", "--port", "8000", "--reload"
|
||||||
|
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
print("⏳ 等待服务启动...")
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
# 检查进程状态
|
||||||
|
if process.poll() is not None:
|
||||||
|
# 进程已退出
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
print(f"❌ 进程已退出,退出码: {process.returncode}")
|
||||||
|
if stderr:
|
||||||
|
print(f"错误输出:\n{stderr}")
|
||||||
|
if stdout:
|
||||||
|
print(f"标准输出:\n{stdout}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 尝试连接服务
|
||||||
|
try:
|
||||||
|
response = requests.get("http://localhost:8000/", timeout=5)
|
||||||
|
print(f"✅ 服务响应成功,状态码: {response.status_code}")
|
||||||
|
print(f"响应内容: {response.text}")
|
||||||
|
|
||||||
|
# 测试API文档
|
||||||
|
try:
|
||||||
|
docs_response = requests.get(
|
||||||
|
"http://localhost:8000/docs", timeout=5)
|
||||||
|
print(f"✅ API文档可访问,状态码: {docs_response.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ API文档访问失败: {e}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"❌ 无法连接到服务: {e}")
|
||||||
|
|
||||||
|
# 检查进程输出
|
||||||
|
try:
|
||||||
|
stdout, stderr = process.communicate(timeout=2)
|
||||||
|
if stderr:
|
||||||
|
print(f"错误输出:\n{stderr}")
|
||||||
|
if stdout:
|
||||||
|
print(f"标准输出:\n{stdout}")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
process.kill()
|
||||||
|
print("⚠️ 进程被强制终止")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 启动过程中出错: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
# 清理进程
|
||||||
|
if 'process' in locals() and process.poll() is None:
|
||||||
|
process.terminate()
|
||||||
|
try:
|
||||||
|
process.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
process.kill()
|
||||||
|
|
||||||
|
|
||||||
|
def test_database_connection():
|
||||||
|
"""测试数据库连接"""
|
||||||
|
print("\n🔍 测试数据库连接...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 导入数据库配置
|
||||||
|
sys.path.append('backend')
|
||||||
|
from app.core.database import SQLALCHEMY_DATABASE_URL
|
||||||
|
|
||||||
|
print(f"数据库URL: {SQLALCHEMY_DATABASE_URL}")
|
||||||
|
|
||||||
|
# 尝试创建引擎
|
||||||
|
from sqlalchemy import create_engine, text
|
||||||
|
engine = create_engine(SQLALCHEMY_DATABASE_URL)
|
||||||
|
|
||||||
|
# 测试连接
|
||||||
|
with engine.connect() as connection:
|
||||||
|
result = connection.execute(text("SELECT 1"))
|
||||||
|
print("✅ 数据库连接成功")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 数据库连接失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("🧪 后端启动测试")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# 测试数据库连接
|
||||||
|
db_ok = test_database_connection()
|
||||||
|
|
||||||
|
if not db_ok:
|
||||||
|
print("\n💡 数据库连接失败,可能的原因:")
|
||||||
|
print("1. 数据库服务器未运行")
|
||||||
|
print("2. 网络连接问题")
|
||||||
|
print("3. 数据库凭据错误")
|
||||||
|
print("4. 防火墙阻止连接")
|
||||||
|
|
||||||
|
response = input("\n是否继续测试后端启动?(y/N): ")
|
||||||
|
if response.lower() != 'y':
|
||||||
|
return
|
||||||
|
|
||||||
|
# 测试后端启动
|
||||||
|
backend_ok = test_backend_startup()
|
||||||
|
|
||||||
|
if backend_ok:
|
||||||
|
print("\n🎉 后端启动测试成功!")
|
||||||
|
else:
|
||||||
|
print("\n❌ 后端启动测试失败!")
|
||||||
|
print("\n💡 建议:")
|
||||||
|
print("1. 检查数据库连接")
|
||||||
|
print("2. 检查依赖包是否安装完整")
|
||||||
|
print("3. 检查端口8000是否被占用")
|
||||||
|
print("4. 查看详细的错误输出")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
172
使用指南.md
Normal file
172
使用指南.md
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
# 彩票数据分析系统使用指南
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 启动系统
|
||||||
|
```bash
|
||||||
|
# 一键启动(推荐)
|
||||||
|
python start_system.py
|
||||||
|
|
||||||
|
# 或者手动启动
|
||||||
|
# 后端:cd backend && uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||||
|
# 前端:cd frontend && npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 访问系统
|
||||||
|
- 前端界面:http://localhost:5173
|
||||||
|
- 后端API文档:http://localhost:8000/docs
|
||||||
|
|
||||||
|
## 📊 主要功能
|
||||||
|
|
||||||
|
### 1. 数据更新功能 ⭐ 新功能
|
||||||
|
|
||||||
|
#### 位置
|
||||||
|
- 在首页顶部可以看到"数据更新"卡片
|
||||||
|
- 包含三个按钮:更新双色球、更新大乐透、更新全部
|
||||||
|
|
||||||
|
#### 使用方法
|
||||||
|
1. **更新双色球数据**
|
||||||
|
- 点击"更新双色球"按钮
|
||||||
|
- 系统会自动从API获取最新开奖数据
|
||||||
|
- 只添加数据库中不存在的新记录
|
||||||
|
- 更新完成后会显示新增记录数量
|
||||||
|
|
||||||
|
2. **更新大乐透数据**
|
||||||
|
- 点击"更新大乐透"按钮
|
||||||
|
- 功能与双色球更新类似
|
||||||
|
|
||||||
|
3. **更新全部数据**
|
||||||
|
- 点击"更新全部"按钮
|
||||||
|
- 同时更新双色球和大乐透数据
|
||||||
|
- 适合批量更新场景
|
||||||
|
|
||||||
|
#### 更新逻辑
|
||||||
|
- 自动去重:只添加数据库中未存在的开奖日期
|
||||||
|
- 数据验证:验证开奖号码的格式和范围
|
||||||
|
- 实时反馈:显示更新进度和结果
|
||||||
|
- 自动刷新:更新完成后自动刷新首页最新开奖信息
|
||||||
|
|
||||||
|
### 2. 数据查看功能
|
||||||
|
|
||||||
|
#### 最新开奖信息
|
||||||
|
- 首页显示双色球和大乐透的最新开奖记录
|
||||||
|
- 包含期号、开奖时间、开奖号码
|
||||||
|
- 支持手动刷新功能
|
||||||
|
|
||||||
|
#### 历史数据查询
|
||||||
|
- 进入"双色球数据"或"大乐透数据"页面
|
||||||
|
- 支持按期号、日期范围查询
|
||||||
|
- 分页显示,每页20条记录
|
||||||
|
|
||||||
|
### 3. 统计分析功能
|
||||||
|
|
||||||
|
#### 基础统计
|
||||||
|
- 号码出现频率统计
|
||||||
|
- 热门号码和冷门号码分析
|
||||||
|
- 数据可视化展示
|
||||||
|
|
||||||
|
#### 高级分析 ⭐ 新功能
|
||||||
|
- **遗漏值分析**:分析各号码的遗漏期数
|
||||||
|
- **和值分析**:统计红球和值的分布规律
|
||||||
|
- **AC值分析**:邻号差值分析
|
||||||
|
- **质合比分析**:质数与合数的比例分析
|
||||||
|
- **012路分析**:除3余数分析
|
||||||
|
- **跨度分析**:最大最小号码差值分析
|
||||||
|
- **综合分析**:多维度数据整合分析
|
||||||
|
|
||||||
|
### 4. 智能预测功能 ⭐ 新功能
|
||||||
|
|
||||||
|
#### 预测方法
|
||||||
|
- **机器学习预测**:基于AI算法的预测
|
||||||
|
- **模式预测**:基于统计模式的预测
|
||||||
|
- **集成预测**:多方法综合预测
|
||||||
|
|
||||||
|
#### 使用步骤
|
||||||
|
1. 进入"智能预测"页面
|
||||||
|
2. 选择彩票类型(双色球/大乐透)
|
||||||
|
3. 设置训练期数(建议100-500期)
|
||||||
|
4. 点击"训练模型"按钮
|
||||||
|
5. 选择预测方法并查看结果
|
||||||
|
|
||||||
|
### 5. 智能选号功能
|
||||||
|
|
||||||
|
#### 选号策略
|
||||||
|
- 随机选号
|
||||||
|
- 频率选号
|
||||||
|
- 热门号码选号
|
||||||
|
- 冷门号码选号
|
||||||
|
- 自定义选号策略
|
||||||
|
|
||||||
|
## 🔧 技术特性
|
||||||
|
|
||||||
|
### 数据管理
|
||||||
|
- **自动去重**:避免重复数据导入
|
||||||
|
- **数据验证**:确保数据格式正确
|
||||||
|
- **批量处理**:支持大量数据的高效处理
|
||||||
|
- **事务安全**:数据库操作支持回滚
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
- **API缓存**:减少重复请求
|
||||||
|
- **分页查询**:提高大数据量查询性能
|
||||||
|
- **异步处理**:支持并发请求处理
|
||||||
|
- **错误重试**:网络请求自动重试机制
|
||||||
|
|
||||||
|
### 用户体验
|
||||||
|
- **响应式设计**:支持各种屏幕尺寸
|
||||||
|
- **实时反馈**:操作状态实时显示
|
||||||
|
- **错误提示**:友好的错误信息展示
|
||||||
|
- **加载状态**:长时间操作的进度提示
|
||||||
|
|
||||||
|
## 🛠️ 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
#### 1. 系统启动失败
|
||||||
|
**问题**:运行 `python start_system.py` 后系统无法启动
|
||||||
|
**解决**:
|
||||||
|
- 检查Python版本是否为3.8+
|
||||||
|
- 检查Node.js版本是否为16+
|
||||||
|
- 确保已安装所有依赖包
|
||||||
|
|
||||||
|
#### 2. 数据更新失败
|
||||||
|
**问题**:点击更新按钮后显示错误
|
||||||
|
**解决**:
|
||||||
|
- 检查网络连接
|
||||||
|
- 确认API密钥是否有效
|
||||||
|
- 查看后端日志获取详细错误信息
|
||||||
|
|
||||||
|
#### 3. 前端页面无法访问
|
||||||
|
**问题**:浏览器无法打开 http://localhost:5173
|
||||||
|
**解决**:
|
||||||
|
- 确认前端服务是否正常启动
|
||||||
|
- 检查端口5173是否被占用
|
||||||
|
- 查看前端控制台错误信息
|
||||||
|
|
||||||
|
#### 4. 数据库连接失败
|
||||||
|
**问题**:后端启动时显示数据库连接错误
|
||||||
|
**解决**:
|
||||||
|
- 检查数据库服务是否启动
|
||||||
|
- 确认数据库连接配置是否正确
|
||||||
|
- 验证数据库用户权限
|
||||||
|
|
||||||
|
### 日志查看
|
||||||
|
- 后端日志:查看终端输出或日志文件
|
||||||
|
- 前端日志:打开浏览器开发者工具查看控制台
|
||||||
|
- 数据库日志:查看数据库服务器日志
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
如果遇到问题,请:
|
||||||
|
1. 查看本文档的故障排除部分
|
||||||
|
2. 检查系统日志获取详细错误信息
|
||||||
|
3. 确认系统环境配置是否正确
|
||||||
|
4. 联系技术支持团队
|
||||||
|
|
||||||
|
## 🔄 更新日志
|
||||||
|
|
||||||
|
### v1.0.0 (最新版本)
|
||||||
|
- ✅ 新增数据更新功能
|
||||||
|
- ✅ 新增高级分析功能
|
||||||
|
- ✅ 新增智能预测功能
|
||||||
|
- ✅ 优化用户界面和用户体验
|
||||||
|
- ✅ 提升系统性能和稳定性
|
||||||
Loading…
x
Reference in New Issue
Block a user