前端预测结果展示样式统一优化:所有预测类型和历史记录的红球蓝球一行展示,间距一致,修复集成预测展示问题
This commit is contained in:
parent
3e07f72b98
commit
988586da84
37
README.md
37
README.md
@ -29,7 +29,7 @@
|
||||
- 数据查询和筛选
|
||||
- 数据导出
|
||||
- 手动数据录入
|
||||
- 自动数据更新
|
||||
- **自动数据更新** ⭐ 新功能
|
||||
|
||||
### 2. 基础统计分析
|
||||
- 号码出现频率统计
|
||||
@ -101,7 +101,22 @@ lottery/
|
||||
|
||||
## 安装和运行
|
||||
|
||||
### 后端
|
||||
### 快速启动 ⭐ 推荐
|
||||
使用一键启动脚本(推荐方式):
|
||||
```bash
|
||||
# 确保已安装Python 3.8+和Node.js 16+
|
||||
python start_system.py
|
||||
```
|
||||
|
||||
脚本会自动:
|
||||
- 检查Python版本和依赖
|
||||
- 启动后端服务(端口8000)
|
||||
- 启动前端服务(端口5173)
|
||||
- 显示访问地址和使用说明
|
||||
|
||||
### 手动启动
|
||||
|
||||
#### 后端
|
||||
1. 创建虚拟环境
|
||||
```bash
|
||||
python -m venv venv
|
||||
@ -124,7 +139,7 @@ pip install -r requirements.txt
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### 前端
|
||||
#### 前端
|
||||
1. 安装依赖
|
||||
```bash
|
||||
cd frontend
|
||||
@ -151,12 +166,18 @@ npm run build
|
||||
- 导入过程自动去重:只导入数据库中未出现过的开奖日期数据。
|
||||
- 支持 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` 唯一性判断)。
|
||||
- 插入顺序为开奖日期升序,保证历史数据先入库,最新数据最后入库。
|
||||
- 日志记录于 `lottery_update.log`。
|
||||
- 从聚合数据API自动获取最新开奖记录
|
||||
- 只插入数据库中未出现过的开奖日期(`open_time` 唯一性判断)
|
||||
- 插入顺序为开奖日期升序,保证历史数据先入库,最新数据最后入库
|
||||
- 实时显示更新结果和新增记录数量
|
||||
- 更新完成后自动刷新首页最新开奖信息
|
||||
|
||||
##### 手动更新
|
||||
```bash
|
||||
|
||||
@ -105,3 +105,45 @@ async def import_dlt_data(
|
||||
# 清理临时文件
|
||||
if os.path.exists(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
|
||||
count = LotteryService.import_dlt_data(db, tmp_path)
|
||||
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(
|
||||
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__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
|
||||
@ -247,3 +247,268 @@ class LotteryService:
|
||||
def get_latest_dlt(db: Session) -> Optional[DLTLottery]:
|
||||
"""获取最新一期大乐透开奖记录"""
|
||||
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_blues = None
|
||||
if lottery_type == 'ssq':
|
||||
# 分析蓝球规律
|
||||
blue_numbers = [draw.blue_ball for draw in recent_draws]
|
||||
@ -378,6 +379,26 @@ class PredictionService:
|
||||
predicted_blue = random.choice(common_blues)
|
||||
else:
|
||||
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 {
|
||||
"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
|
||||
},
|
||||
"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:
|
||||
@ -523,7 +545,7 @@ class PredictionService:
|
||||
'method': '模式分析',
|
||||
'numbers': pattern_result['predicted_numbers'],
|
||||
'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': '中'
|
||||
})
|
||||
|
||||
|
||||
@ -39,14 +39,15 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const activeIndex = ref('/')
|
||||
const route = useRoute()
|
||||
const activeIndex = computed(() => route.path)
|
||||
|
||||
const handleSelect = (key) => {
|
||||
activeIndex.value = key
|
||||
router.push(key)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -152,5 +152,18 @@ export const lotteryApi = {
|
||||
}
|
||||
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>
|
||||
<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-col :span="12">
|
||||
<el-card class="data-card">
|
||||
@ -81,13 +131,21 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
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 { ElMessage } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const ssqLatest = 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) => {
|
||||
return dayjs(date).format('YYYY-MM-DD')
|
||||
@ -127,6 +185,84 @@ const refreshDLTData = () => {
|
||||
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 routeMap = {
|
||||
@ -156,6 +292,24 @@ onMounted(() => {
|
||||
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 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
@ -250,6 +404,10 @@ onMounted(() => {
|
||||
font-size: 16px;
|
||||
padding: 14px 0;
|
||||
}
|
||||
|
||||
.update-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
|
||||
@ -36,14 +36,19 @@
|
||||
<h4>机器学习预测</h4>
|
||||
<el-tag type="success">置信度: 高</el-tag>
|
||||
</div>
|
||||
<div class="prediction-numbers">
|
||||
<div class="prediction-numbers flex-balls">
|
||||
<div class="red-balls">
|
||||
<span v-for="num in mlPrediction.predicted_numbers" :key="num" class="ball red-ball">
|
||||
{{ num.toString().padStart(2, '0') }}
|
||||
</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">
|
||||
{{ mlPrediction.predicted_blue.toString().padStart(2, '0') }}
|
||||
</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 class="prediction-info">
|
||||
@ -59,14 +64,19 @@
|
||||
<h4>模式分析预测</h4>
|
||||
<el-tag type="warning">置信度: 中</el-tag>
|
||||
</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">
|
||||
<span v-for="num in patternPrediction.predicted_numbers" :key="num" class="ball red-ball">
|
||||
{{ num.toString().padStart(2, '0') }}
|
||||
</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">
|
||||
{{ patternPrediction.predicted_blue.toString().padStart(2, '0') }}
|
||||
</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 class="pattern-criteria">
|
||||
@ -103,14 +113,19 @@
|
||||
置信度: {{ rec.confidence }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="recommendation-numbers">
|
||||
<div class="recommendation-numbers flex-balls">
|
||||
<div class="red-balls">
|
||||
<span v-for="num in rec.numbers" :key="num" class="ball red-ball">
|
||||
{{ num.toString().padStart(2, '0') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="blue-balls" v-if="rec.blue || (rec.blues && rec.blues.length)">
|
||||
<span v-if="rec.blue" class="ball blue-ball">
|
||||
{{ rec.blue.toString().padStart(2, '0') }}
|
||||
</span>
|
||||
<span v-for="b in rec.blues" :key="'blue-' + b" class="ball blue-ball">
|
||||
{{ b.toString().padStart(2, '0') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -194,17 +209,19 @@
|
||||
<el-table-column prop="method" label="预测方法" width="120" />
|
||||
<el-table-column prop="numbers" label="预测号码">
|
||||
<template #default="scope">
|
||||
<div class="prediction-numbers">
|
||||
<div class="prediction-numbers flex-balls">
|
||||
<div class="red-balls">
|
||||
<span v-for="(num, index) in scope.row.numbers.split(' ')"
|
||||
:key="index"
|
||||
class="ball red-ball"
|
||||
>
|
||||
<span v-for="(num, index) in scope.row.numbers.split(' ')" :key="index" class="ball red-ball">
|
||||
{{ num.padStart(2, '0') }}
|
||||
</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">
|
||||
{{ scope.row.blue.toString().padStart(2, '0') }}
|
||||
</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>
|
||||
</template>
|
||||
@ -312,6 +329,7 @@ export default {
|
||||
method: '机器学习',
|
||||
numbers: response.data.predicted_numbers.join(' '),
|
||||
blue: response.data.predicted_blue,
|
||||
blues: response.data.predicted_blues,
|
||||
confidence: '高'
|
||||
})
|
||||
ElMessage.success('机器学习预测完成')
|
||||
@ -344,6 +362,7 @@ export default {
|
||||
method: '模式预测',
|
||||
numbers: response.data.predicted_numbers.join(' '),
|
||||
blue: response.data.predicted_blue,
|
||||
blues: response.data.predicted_blues,
|
||||
confidence: '中'
|
||||
})
|
||||
ElMessage.success('模式预测完成')
|
||||
@ -391,6 +410,7 @@ export default {
|
||||
method: `集成预测-${rec.method}`,
|
||||
numbers: rec.numbers.join(' '),
|
||||
blue: rec.blue,
|
||||
blues: rec.blues,
|
||||
confidence: rec.confidence
|
||||
})
|
||||
})
|
||||
@ -507,7 +527,7 @@ export default {
|
||||
.red-balls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ball {
|
||||
@ -561,8 +581,17 @@ export default {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.recommendation-numbers {
|
||||
margin: 10px 0;
|
||||
.recommendation-numbers.flex-balls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.red-balls, .blue-balls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
.blue-balls {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.pattern-analysis {
|
||||
@ -601,4 +630,9 @@ h3 {
|
||||
border-bottom: 2px solid #409eff;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.prediction-numbers.flex-balls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</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