Files
lottery/server/services/lottery.js
T
DeveloperandClaude Opus 4.6 7b1073e23b Initial commit: lottery project structure
Add core project files including:
- Client and server directories
- Backup script
- Package configuration
- Git ignore rules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:26:49 +08:00

251 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const axios = require('axios');
const cheerio = require('cheerio');
const db = require('../db/init');
// 获取预计开奖时间
function getEstimatedDrawTime(game, issue) {
const now = new Date();
const currentDay = now.getDay();
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
let drawDays = []; // 开奖日期(周日=0,周一=1...)
if (game === '双色球') {
drawDays = [2, 4, 0]; // 周二、周四、周日
} else if (game === '大乐透') {
drawDays = [1, 3, 5]; // 周一、周三、周六
}
if (drawDays.length === 0) return null;
// 按日期排序
drawDays.sort((a, b) => a - b);
// 找到最近的下一个开奖日
let daysUntilDraw = -1;
for (const day of drawDays) {
if (day > currentDay) {
daysUntilDraw = day - currentDay;
break;
}
}
// 如果今天就是开奖日,检查是否已过开奖时间
if (daysUntilDraw === -1) {
// 没有找到更晚的日期,循环回到第一个开奖日
daysUntilDraw = (drawDays[0] + 7) - currentDay;
} else if (daysUntilDraw === 0) {
// 今天是开奖日,检查是否已过21:15
if (currentHour > 21 || (currentHour === 21 && currentMinute >= 15)) {
daysUntilDraw = (drawDays[0] + 7) - currentDay;
}
}
const nextDraw = new Date(now);
nextDraw.setDate(now.getDate() + daysUntilDraw);
nextDraw.setHours(21, 15, 0, 0);
return nextDraw.toISOString();
}
// 获取开奖结果
async function fetchLotteryResult(game, issue) {
// 先查本地缓存
const cached = db.prepare('SELECT * FROM lottery_results WHERE game = ? AND issue = ?').get(game, issue);
if (cached) {
return { ...cached, numbers: JSON.parse(cached.numbers) };
}
// 从网络获取
try {
const result = await fetchFromWeb(game, issue);
if (result) {
// 缓存到数据库
db.prepare(`
INSERT OR REPLACE INTO lottery_results (game, issue, numbers, draw_date)
VALUES (?, ?, ?, ?)
`).run(game, issue, JSON.stringify(result.numbers), result.draw_date);
}
return result;
} catch (err) {
console.error('获取开奖数据失败:', err.message);
return null;
}
}
// 从500.com获取数据
async function fetchFromWeb(game, issue) {
try {
if (game === '双色球') {
return await fetchSSQ(issue);
} else if (game === '大乐透') {
return await fetchDLT(issue);
}
} catch (err) {
console.error(`获取${game}开奖数据失败:`, err.message);
}
return null;
}
// 获取双色球开奖数据
async function fetchSSQ(issue) {
const url = 'https://datachart.500.com/ssq/history/newinc/history.php?limit=50';
const response = await axios.get(url, {
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
timeout: 10000
});
const $ = cheerio.load(response.data);
let result = null;
// 处理期号格式:2026019 -> 26019 或 26019 -> 26019
const shortIssue = issue.length > 5 ? issue.slice(-5) : issue;
$('#tdata tr').each((i, row) => {
const tds = $(row).find('td');
const rowIssue = $(tds[0]).text().trim();
if (rowIssue === shortIssue || rowIssue === issue) {
const red = [];
for (let j = 1; j <= 6; j++) {
red.push($(tds[j]).text().trim());
}
const blue = $(tds[7]).text().trim();
const drawDate = $(tds[15]).text().trim();
result = {
game: '双色球',
issue: issue, // 返回原始期号
numbers: { red, blue },
draw_date: drawDate
};
return false;
}
});
return result;
}
// 获取大乐透开奖数据
async function fetchDLT(issue) {
const url = 'https://datachart.500.com/dlt/history/newinc/history.php?limit=50';
const response = await axios.get(url, {
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
timeout: 10000
});
const $ = cheerio.load(response.data);
let result = null;
// 处理期号格式:2026019 -> 26019 或 26019 -> 26019
const shortIssue = issue.length > 5 ? issue.slice(-5) : issue;
$('#tdata tr').each((i, row) => {
const tds = $(row).find('td');
const rowIssue = $(tds[0]).text().trim();
if (rowIssue === shortIssue || rowIssue === issue) {
const front = [];
for (let j = 1; j <= 5; j++) {
front.push($(tds[j]).text().trim());
}
const back = [];
for (let j = 6; j <= 7; j++) {
back.push($(tds[j]).text().trim());
}
const drawDate = $(tds[14]).text().trim();
result = {
game: '大乐透',
issue: issue, // 返回原始期号
numbers: { front, back },
draw_date: drawDate
};
return false;
}
});
return result;
}
// 检查中奖
function checkWinning(game, ticketNumbers, resultNumbers) {
if (!ticketNumbers || !resultNumbers) return { won: false, prize: 0 };
if (game === '双色球') {
return checkSSQWinning(ticketNumbers, resultNumbers);
} else if (game === '大乐透') {
return checkDLTWinning(ticketNumbers, resultNumbers);
}
return { won: false, prize: 0 };
}
// 双色球中奖检查
function checkSSQWinning(ticket, result) {
const redMatch = ticket.red.filter(n => result.red.includes(n)).length;
const blueMatch = ticket.blue === result.blue;
if (redMatch === 6 && blueMatch) return { won: true, level: 1, prize: 5000000 };
if (redMatch === 6) return { won: true, level: 2, prize: 100000 };
if (redMatch === 5 && blueMatch) return { won: true, level: 3, prize: 3000 };
if (redMatch === 5 || (redMatch === 4 && blueMatch)) return { won: true, level: 4, prize: 200 };
if (redMatch === 4 || (redMatch === 3 && blueMatch)) return { won: true, level: 5, prize: 10 };
if (blueMatch) return { won: true, level: 6, prize: 5 };
return { won: false, prize: 0 };
}
// 大乐透中奖检查
function checkDLTWinning(ticket, result) {
const frontMatch = ticket.front.filter(n => result.front.includes(n)).length;
const backMatch = ticket.back.filter(n => result.back.includes(n)).length;
if (frontMatch === 5 && backMatch === 2) return { won: true, level: 1, prize: 10000000 };
if (frontMatch === 5 && backMatch === 1) return { won: true, level: 2, prize: 100000 };
if (frontMatch === 5) return { won: true, level: 3, prize: 10000 };
if (frontMatch === 4 && backMatch === 2) return { won: true, level: 4, prize: 3000 };
if (frontMatch === 4 && backMatch === 1) return { won: true, level: 5, prize: 300 };
if (frontMatch === 3 && backMatch === 2) return { won: true, level: 6, prize: 200 };
if (frontMatch === 4) return { won: true, level: 7, prize: 100 };
// 8等奖:3+1 或 2+215元
if ((frontMatch === 3 && backMatch === 1) || (frontMatch === 2 && backMatch === 2)) return { won: true, level: 8, prize: 15 };
// 9等奖:3+0 或 2+1 或 1+2 或 后区2个,5元
if (frontMatch === 3 || (frontMatch === 2 && backMatch === 1) || (frontMatch === 1 && backMatch === 2) || backMatch === 2) return { won: true, level: 9, prize: 5 };
return { won: false, prize: 0 };
}
// 批量更新彩票状态
async function updateTicketsStatus(userId) {
const pendingTickets = db.prepare(`
SELECT * FROM tickets WHERE user_id = ? AND status = 'pending' AND issue IS NOT NULL
`).all(userId);
for (const ticket of pendingTickets) {
const result = await fetchLotteryResult(ticket.game, ticket.issue);
if (result) {
const ticketNumbers = ticket.numbers ? JSON.parse(ticket.numbers) : null;
if (ticketNumbers && ticketNumbers.length > 0) {
let totalPrize = 0;
for (const num of ticketNumbers) {
const winning = checkWinning(ticket.game, num, result.numbers);
if (winning.won) {
totalPrize += winning.prize * ticket.multiple;
// 大乐透只有1等奖和2等奖追加才有80%浮动奖金,其他等级没有
if (ticket.is_additional && winning.level >= 1 && winning.level <= 2) {
totalPrize += Math.floor(winning.prize * 0.8) * ticket.multiple;
}
}
}
db.prepare('UPDATE tickets SET prize = ?, status = ? WHERE id = ?')
.run(totalPrize, totalPrize > 0 ? 'won' : 'lost', ticket.id);
}
}
}
}
module.exports = { fetchLotteryResult, checkWinning, updateTicketsStatus, getEstimatedDrawTime };