feat: 一键下单与投注历史页面文案风格统一为早餐铺趣味化风格,修复推荐注数等核心体验问题

This commit is contained in:
Mars 2025-06-25 16:08:02 +08:00
parent 715b2ea85b
commit ff9d9a857e
12 changed files with 1104 additions and 1450 deletions

View File

@ -0,0 +1,71 @@
from update_lottery import LotteryUpdater
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from ...core.database import get_db
from ...models.lottery import SSQLotteryBetRecord, DLTLotteryBetRecord
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))))
router = APIRouter()
@router.get("/{lottery_type}")
def get_bet_history(
lottery_type: str,
page: int = 1,
size: int = 20,
db: Session = Depends(get_db)
):
"""获取投注历史记录"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
# 选择对应的模型
BetModel = SSQLotteryBetRecord if lottery_type == 'ssq' else DLTLotteryBetRecord
# 计算偏移量
offset = (page - 1) * size
# 查询记录
records = db.query(BetModel).order_by(
BetModel.created_at.desc()
).offset(offset).limit(size).all()
# 查询总数
total = db.query(BetModel).count()
# 格式化返回数据
formatted_records = []
for record in records:
formatted_records.append({
'id': record.id,
'batch_id': record.batch_id,
'issue': record.issue,
'numbers': record.numbers,
'recommend_type': record.recommend_type,
'created_at': record.created_at.isoformat() if record.created_at else None,
'is_winner': record.is_winner,
'win_level': record.win_level,
'win_amount': record.win_amount
})
return {
'success': True,
'records': formatted_records,
'total': total,
'page': page,
'size': size,
'pages': (total + size - 1) // size
}
@router.post("/check-win/{lottery_type}")
def check_win(lottery_type: str, db: Session = Depends(get_db)):
"""手动触发比对并更新中奖信息"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
updater = LotteryUpdater()
updater.check_and_update_bet_wins(lottery_type, db)
return {"success": True, "message": f"{lottery_type}投注中奖比对已完成"}

View File

@ -3,6 +3,12 @@ from sqlalchemy.orm import Session
from typing import Dict
from ...core.database import get_db
from ...services.prediction_service import PredictionService
from ...models.lottery import SSQLotteryBetRecord, DLTLotteryBetRecord, SSQLottery, DLTLottery
import datetime
import random
from ...services.telegram_bot import send_message
from ...services.analysis_service import LotteryAnalysisService
import os
router = APIRouter()
@ -61,3 +67,148 @@ def get_ensemble_prediction(
service = PredictionService(db)
return service.get_ensemble_prediction(lottery_type, periods)
@router.post("/recommend/today")
def recommend_today(db: Session = Depends(get_db)):
"""
每日推荐一三六推荐大乐透二四日推荐双色球固定投注1注+推荐4注共5注推送内容合并快乐8
"""
# 固定投注号码(写死)
FIXED_SSQ = {'red': [4, 6, 7, 12, 18, 19], 'blue': [9]}
FIXED_DLT = {'red': [4, 6, 9, 18, 19], 'blue': [7, 12]}
# 判断彩种
weekday = datetime.datetime.now().isoweekday()
if weekday in [1, 3, 6]:
lottery_type = 'dlt'
BetModel = DLTLotteryBetRecord
recommend_type = '集成预测-每日推荐'
def number_format(red, blue): return ' '.join(
f"{n:02d}" for n in red) + ' 蓝球:' + ' '.join(f"{n:02d}" for n in blue)
LotteryModel = DLTLottery
fixed = FIXED_DLT
title = "你好,帮忙买下大乐透。"
dlt_append = True
else:
lottery_type = 'ssq'
BetModel = SSQLotteryBetRecord
recommend_type = '集成预测-每日推荐'
def number_format(red, blue): return ' '.join(
f"{n:02d}" for n in red) + ' 蓝球:' + ' '.join(f"{n:02d}" for n in blue)
LotteryModel = SSQLottery
fixed = FIXED_SSQ
title = "你好,帮忙买下双色球。"
dlt_append = False
# 生成batch_id
today = datetime.datetime.now().strftime('%Y%m%d')
batch_id = f"{today}-{random.randint(1000, 9999)}"
# 1. 推荐4注集成预测+智能选号补足)
service = PredictionService(db)
pred = service.get_ensemble_prediction(lottery_type, periods=200)
if not pred.get('success'):
raise HTTPException(
status_code=500, detail=pred.get('message', '预测失败'))
recs = pred.get('recommendations', [])
results = []
used_numbers = set()
# 先取集成预测推荐
for rec in recs:
if len(results) >= 4:
break
red = rec.get('numbers')
blue = [rec.get('blue')] if rec.get(
'blue') is not None else rec.get('blues', [])
if not red or not blue:
continue
numbers = ','.join(f"{n:02d}" for n in red) + \
'|' + ','.join(f"{n:02d}" for n in blue)
if numbers in used_numbers:
continue
used_numbers.add(numbers)
last = db.query(LotteryModel).order_by(
LotteryModel.open_time.desc()).first()
issue = last.issue if last else ''
bet = BetModel(
batch_id=batch_id,
issue=issue,
numbers=numbers,
recommend_type=rec.get('method', recommend_type)
)
db.add(bet)
db.commit()
db.refresh(bet)
results.append({
'id': bet.id,
'batch_id': batch_id,
'issue': issue,
'numbers': numbers,
'red': red,
'blue': blue,
'recommend_type': rec.get('method', recommend_type)
})
# 补足到4注
analysis_service = LotteryAnalysisService(db)
while len(results) < 4:
smart = analysis_service.generate_smart_numbers(
lottery_type, strategy='balanced', count=1)
if not smart:
break
red = smart[0].get('red_numbers')
blue = smart[0].get('blue_numbers')
numbers = ','.join(f"{n:02d}" for n in red) + \
'|' + ','.join(f"{n:02d}" for n in blue)
if numbers in used_numbers:
continue
used_numbers.add(numbers)
last = db.query(LotteryModel).order_by(
LotteryModel.open_time.desc()).first()
issue = last.issue if last else ''
bet = BetModel(
batch_id=batch_id,
issue=issue,
numbers=numbers,
recommend_type='智能选号补足'
)
db.add(bet)
db.commit()
db.refresh(bet)
results.append({
'id': bet.id,
'batch_id': batch_id,
'issue': issue,
'numbers': numbers,
'red': red,
'blue': blue,
'recommend_type': '智能选号补足'
})
# 2. 组装推送内容(固定投注+4注推荐
msg_lines = [title]
# 固定投注
msg_lines.append(f"红球:{' '.join(f'{n:02d}' for n in fixed['red'])} 蓝球:{
' '.join(f'{n:02d}' for n in fixed['blue'])}")
# 推荐号码
for rec in results:
msg_lines.append(f"红球:{' '.join(f'{n:02d}' for n in rec['red'])} 蓝球:{
' '.join(f'{n:02d}' for n in rec['blue'])}")
if dlt_append:
msg_lines.append("都追加,谢谢。")
# 3. 快乐8推送3注十选合并到主消息
msg_lines.append("\n快乐8单式选十")
for _ in range(3):
nums = sorted(random.sample(range(1, 81), 10))
msg_lines.append(' '.join(f"{n:02d}" for n in nums))
# 4. 推送到Telegram
try:
token = os.getenv("TELEGRAM_BOT_TOKEN")
chat_id = os.getenv("TELEGRAM_CHAT_ID")
send_message('\n'.join(msg_lines), token=token, chat_id=chat_id)
except Exception as e:
print(f"[WARN] Telegram推送失败: {e}")
return {
'success': True,
'lottery_type': lottery_type,
'batch_id': batch_id,
'recommend': results
}

View File

@ -5,6 +5,7 @@ from app.api.v1.lottery import router as lottery_router
from app.api.endpoints.analysis import router as analysis_router
from app.api.endpoints.advanced_analysis import router as advanced_analysis_router
from app.api.endpoints.prediction import router as prediction_router
from app.api.endpoints.bet_history import router as bet_history_router
from app.core.database import Base, engine
# 创建数据库表
@ -33,6 +34,8 @@ app.include_router(
advanced_analysis_router, prefix=f"{settings.API_V1_STR}/advanced-analysis", tags=["advanced-analysis"])
app.include_router(
prediction_router, prefix=f"{settings.API_V1_STR}/prediction", tags=["prediction"])
app.include_router(
bet_history_router, prefix=f"{settings.API_V1_STR}/lottery/bet-history", tags=["bet-history"])
@app.get("/")

View File

@ -36,3 +36,47 @@ class DLTLottery(Base):
created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间")
updated_at = Column(TIMESTAMP, server_default=func.now(),
onupdate=func.now(), comment="更新时间")
class SSQLotteryBetRecord(Base):
__tablename__ = "ssq_bet_record"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
batch_id = Column(String(32), index=True, nullable=False, comment="批次ID")
issue = Column(String(10), index=True, nullable=False, comment="期号")
bet_time = Column(TIMESTAMP, server_default=func.now(), comment="投注时间")
numbers = Column(String(64), nullable=False,
comment="投注号码格式01,02,03,04,05,06|07")
recommend_type = Column(String(32), nullable=True, comment="推荐方式")
telegram_msg_id = Column(String(64), nullable=True, comment="推送消息ID")
is_winner = Column(Integer, default=0, comment="是否中奖 0否 1是")
win_level = Column(String(32), nullable=True, comment="中奖等级")
win_amount = Column(String(32), nullable=True, comment="中奖金额")
open_code = Column(String(64), nullable=True, comment="开奖号码")
open_date = Column(Date, nullable=True, comment="开奖日期")
prize_detail = Column(String(1024), nullable=True, comment="开奖详情JSON")
created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间")
updated_at = Column(TIMESTAMP, server_default=func.now(),
onupdate=func.now(), comment="更新时间")
class DLTLotteryBetRecord(Base):
__tablename__ = "dlt_bet_record"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
batch_id = Column(String(32), index=True, nullable=False, comment="批次ID")
issue = Column(String(10), index=True, nullable=False, comment="期号")
bet_time = Column(TIMESTAMP, server_default=func.now(), comment="投注时间")
numbers = Column(String(64), nullable=False,
comment="投注号码格式01,02,03,04,05|06,07")
recommend_type = Column(String(32), nullable=True, comment="推荐方式")
telegram_msg_id = Column(String(64), nullable=True, comment="推送消息ID")
is_winner = Column(Integer, default=0, comment="是否中奖 0否 1是")
win_level = Column(String(32), nullable=True, comment="中奖等级")
win_amount = Column(String(32), nullable=True, comment="中奖金额")
open_code = Column(String(64), nullable=True, comment="开奖号码")
open_date = Column(Date, nullable=True, comment="开奖日期")
prize_detail = Column(String(1024), nullable=True, comment="开奖详情JSON")
created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间")
updated_at = Column(TIMESTAMP, server_default=func.now(),
onupdate=func.now(), comment="更新时间")

View File

@ -59,3 +59,59 @@ class LotteryQuery(BaseModel):
issue: Optional[str] = None
page: int = 1
page_size: int = 20
class SSQLotteryBetRecordBase(BaseModel):
batch_id: str
issue: str
bet_time: Optional[datetime] = None
numbers: str
recommend_type: Optional[str] = None
telegram_msg_id: Optional[str] = None
is_winner: Optional[int] = 0
win_level: Optional[str] = None
win_amount: Optional[str] = None
open_code: Optional[str] = None
open_date: Optional[date] = None
prize_detail: Optional[str] = None
class SSQLotteryBetRecordCreate(SSQLotteryBetRecordBase):
pass
class SSQLotteryBetRecord(SSQLotteryBetRecordBase):
id: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class DLTLotteryBetRecordBase(BaseModel):
batch_id: str
issue: str
bet_time: Optional[datetime] = None
numbers: str
recommend_type: Optional[str] = None
telegram_msg_id: Optional[str] = None
is_winner: Optional[int] = 0
win_level: Optional[str] = None
win_amount: Optional[str] = None
open_code: Optional[str] = None
open_date: Optional[date] = None
prize_detail: Optional[str] = None
class DLTLotteryBetRecordCreate(DLTLotteryBetRecordBase):
pass
class DLTLotteryBetRecord(DLTLotteryBetRecordBase):
id: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True

View File

@ -415,15 +415,7 @@ class PredictionService:
}
def get_ensemble_prediction(self, lottery_type: str, periods: int = 100) -> Dict:
"""集成预测(结合多种方法)
Args:
lottery_type: 彩票类型 ('ssq' 'dlt')
periods: 分析期数
Returns:
Dict: 预测结果
"""
"""集成预测(结合多种方法)"""
# 机器学习预测
ml_result = self.predict_next_numbers(lottery_type, periods)
@ -440,6 +432,7 @@ class PredictionService:
# 综合推荐
recommendations = []
# 机器学习推荐
if ml_result.get('success'):
recommendations.append({
'method': '机器学习',
@ -449,6 +442,7 @@ class PredictionService:
'confidence': ''
})
# 热冷号分析推荐
if freq_result:
max_red = 33 if lottery_type == 'ssq' else 35
max_blue = 16 if lottery_type == 'ssq' else 12
@ -501,10 +495,9 @@ class PredictionService:
selected_numbers.add(num)
remaining_numbers.remove(num)
# 生成蓝球
# 生成蓝球/后区
if lottery_type == 'ssq':
if 'hot_blues' in freq_result and freq_result['hot_blues']:
# 从热门蓝球中随机选择
blue_prediction = random.choice(
freq_result['hot_blues'][:3])
else:
@ -520,19 +513,15 @@ class PredictionService:
# 大乐透后区号码选择
blue_predictions = []
if 'hot_blues' in freq_result and freq_result['hot_blues']:
# 从热门后区号码中选择
available_blues = freq_result['hot_blues'][:4] # 取前4个热门号码
available_blues = freq_result['hot_blues'][:4]
while len(blue_predictions) < 2 and available_blues:
num = random.choice(available_blues)
blue_predictions.append(num)
available_blues.remove(num)
# 如果还不够2个随机补充
while len(blue_predictions) < 2:
num = random.randint(1, max_blue)
if num not in blue_predictions:
blue_predictions.append(num)
recommendations.append({
'method': '热冷号分析',
'numbers': sorted(list(selected_numbers)),
@ -540,6 +529,7 @@ class PredictionService:
'confidence': ''
})
# 模式分析推荐
if pattern_result and pattern_result.get('success'):
recommendations.append({
'method': '模式分析',

View File

@ -0,0 +1,20 @@
import requests
import os
def send_message(text, token=None, chat_id=None):
token = token or os.getenv(
"TELEGRAM_BOT_TOKEN", "1034761353:AAG8AydVpLCCURPhWAUfBX7I4MyDOb-9H8M")
chat_id = chat_id or os.getenv("TELEGRAM_CHAT_ID", "43709453")
url = f"https://api.telegram.org/bot{token}/sendMessage"
data = {
"chat_id": chat_id,
"text": text,
"parse_mode": "Markdown"
}
try:
resp = requests.post(url, data=data, timeout=10)
return resp.json()
except Exception as e:
print(f"[ERROR] Telegram推送失败: {e}")
return None

View File

@ -9,7 +9,7 @@ from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
from sqlalchemy import create_engine, desc
from sqlalchemy.orm import sessionmaker
from app.models.lottery import SSQLottery, DLTLottery
from app.models.lottery import SSQLottery, DLTLottery, SSQLotteryBetRecord, DLTLotteryBetRecord
from app.core.database import engine, SessionLocal
# 配置日志
@ -244,6 +244,9 @@ class LotteryUpdater:
finally:
db.close()
# 在开奖数据入库后自动调用
self.check_and_update_bet_wins(lottery_type, db)
except Exception as e:
logger.error(f"更新{lottery_type}数据失败: {str(e)}")
logger.exception(e) # 打印完整的错误堆栈
@ -254,6 +257,101 @@ class LotteryUpdater:
logger.info(f"开始更新{self.lottery_types[lottery_type]['name']}数据...")
self.update_lottery_data(lottery_type)
def check_and_update_bet_wins(self, lottery_type: str, db):
"""批量比对投注记录并更新中奖信息支持ssq和dlt"""
if lottery_type == 'ssq':
LotteryModel = SSQLottery
BetModel = SSQLotteryBetRecord
else:
LotteryModel = DLTLottery
BetModel = DLTLotteryBetRecord
# 查询所有已开奖的期号和开奖号码
open_draws = {d.issue: d for d in db.query(LotteryModel).all()}
# 查询所有未比对或未中奖的投注记录
bets = db.query(BetModel).filter(BetModel.is_winner == 0).all()
for bet in bets:
draw = open_draws.get(bet.issue)
if not draw:
continue # 未开奖
# 解析投注号码
bet_red, bet_blue = [], []
try:
red_str, blue_str = bet.numbers.split('|')
bet_red = [int(x) for x in red_str.split(',') if x]
bet_blue = [int(x) for x in blue_str.split(',') if x]
except Exception:
continue
# 解析开奖号码
if lottery_type == 'ssq':
open_red = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
open_blue = [draw.blue_ball]
else:
open_red = [draw.front_ball_1, draw.front_ball_2,
draw.front_ball_3, draw.front_ball_4, draw.front_ball_5]
open_blue = [draw.back_ball_1, draw.back_ball_2]
# 计算命中数
red_hit = len(set(bet_red) & set(open_red))
blue_hit = len(set(bet_blue) & set(open_blue))
# 判断中奖等级和金额
win_level, win_amount = self.calc_prize(
lottery_type, red_hit, blue_hit, bet_red, bet_blue, open_red, open_blue)
is_winner = 1 if win_level else 0
# 更新投注记录
bet.is_winner = is_winner
bet.win_level = win_level
bet.win_amount = str(win_amount) if win_amount else None
bet.open_code = ','.join(
[str(x) for x in open_red]) + '|' + ','.join([str(x) for x in open_blue])
bet.open_date = draw.open_time
db.add(bet)
db.commit()
print(f"已完成{lottery_type}投注中奖比对与更新")
def calc_prize(self, lottery_type, red_hit, blue_hit, bet_red, bet_blue, open_red, open_blue):
"""根据命中数判断中奖等级和金额,返回(等级, 金额)"""
# 双色球规则
if lottery_type == 'ssq':
# 中奖规则(简化版,实际可查官方)
if red_hit == 6 and blue_hit == 1:
return '一等奖', 10000000
elif red_hit == 6 and blue_hit == 0:
return '二等奖', 5000000
elif red_hit == 5 and blue_hit == 1:
return '三等奖', 3000
elif red_hit == 5 and blue_hit == 0:
return '四等奖', 200
elif red_hit == 4 and blue_hit == 1:
return '四等奖', 200
elif red_hit == 4 and blue_hit == 0:
return '五等奖', 10
elif red_hit == 3 and blue_hit == 1:
return '五等奖', 10
elif blue_hit == 1:
return '六等奖', 5
else:
return None, None
# 大乐透规则
else:
# 中奖规则(简化版,实际可查官方)
if red_hit == 5 and blue_hit == 2:
return '一等奖', 10000000
elif red_hit == 5 and blue_hit == 1:
return '二等奖', 5000000
elif red_hit == 5 and blue_hit == 0:
return '三等奖', 3000
elif red_hit == 4 and blue_hit == 2:
return '四等奖', 200
elif red_hit == 4 and blue_hit == 1:
return '五等奖', 10
elif red_hit == 3 and blue_hit == 2:
return '五等奖', 10
elif (red_hit == 2 and blue_hit == 2) or (red_hit == 1 and blue_hit == 2) or (red_hit == 0 and blue_hit == 2):
return '六等奖', 5
else:
return None, None
if __name__ == "__main__":
updater = LotteryUpdater()

View File

@ -165,5 +165,25 @@ export const lotteryApi = {
updateAllLotteryData() {
return api.post('/update/all')
},
// 获取投注历史记录
getBetHistory(lotteryType, params = {}) {
return request({
url: `/api/v1/lottery/bet-history/${lotteryType}`,
method: 'get',
params: {
page: params.page || 1,
size: params.size || 20
}
})
},
// 手动触发中奖比对
checkWin(lotteryType) {
return request({
url: `/api/v1/lottery/bet-history/check-win/${lotteryType}`,
method: 'post'
})
}
}

View File

@ -35,3 +35,11 @@ export function getEnsemblePrediction(lotteryType, periods = 100) {
params: { periods }
})
}
// 今日推荐
export function recommendToday() {
return request({
url: '/api/v1/prediction/recommend/today',
method: 'post'
})
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
<template #header>
<el-form :inline="true" class="prediction-form">
<el-form-item label="早点类型">
<el-select v-model="lotteryType">
<el-select v-model="lotteryType" placeholder="请选择" style="width: 120px">
<el-option label="红蓝球煎饼" value="ssq" />
<el-option label="大乐斗豆浆" value="dlt" />
</el-select>
@ -442,6 +442,9 @@ export default {
//
onMounted(() => {
if (!lotteryType.value) {
lotteryType.value = 'ssq'
}
trainModel()
})