Files
yusheng-php/application/common/service/LotteryService.php
2025-12-21 17:12:37 +08:00

308 lines
12 KiB
PHP
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.

<?php
namespace app\common\service;
use app\common\library\LotteryGiftLua;
use think\Cache;
use think\Db;
use think\Exception;
class LotteryService
{
// Redis实例
private $redis;
// 配置参数
private $config;
public function __construct()
{
$this->redis = Cache::store('redis')->handler();
// 加载配置
$this->config = Db::name('bb_lottery_config')->column('value', 'key');
// 初始化Redis缓存若Redis数据丢失从数据库恢复
$this->initRedisFromDb();
}
/**
* 缓存恢复:独立恢复大小轮次+对应金额
*/
private function initRedisFromDb()
{
// 1. 恢复小奖池轮次取pool_type=1的最大times
$maxSmallRound = Db::name('bb_lottery_pool_flow')->where('pool_type', 1)->max('times') ?: 1;
if (!$this->redis->get('lottery:small_pool:round')) {
$this->redis->set('lottery:small_pool:round', $maxSmallRound);
}
// 2. 恢复大奖池轮次取pool_type=2的最大times
$maxBigRound = Db::name('bb_lottery_pool_flow')->where('pool_type', 2)->max('times') ?: 1;
if (!$this->redis->get('lottery:big_pool:round')) {
$this->redis->set('lottery:big_pool:round', $maxBigRound);
}
// 3. 恢复小奖池当前轮次的次数/金额
$small_round = intval($this->redis->get('lottery:small_pool:round'));
if (!$this->redis->get('lottery:small_pool:total_times')) {
$smallTotalTimes = Db::name('bb_lottery_pool_flow')
->where(['pool_type' => 1, 'type' => 1, 'times' => $small_round])
->count();
$this->redis->set('lottery:small_pool:total_times', $smallTotalTimes);
}
if (!$this->redis->get('lottery:small_pool:total_gold')) {
$smallTotalGold = Db::name('bb_lottery_pool_flow')
->where(['pool_type' => 1, 'type' => 1, 'times' => $small_round])
->sum('amount') ?: 0;
$this->redis->set('lottery:small_pool:total_gold', $smallTotalGold);
}
// 4. 恢复大奖池当前轮次的金额
$big_round = intval($this->redis->get('lottery:big_pool:round'));
if (!$this->redis->get('lottery:big_pool:total_gold')) {
$bigAddGold = Db::name('bb_lottery_pool_flow')
->where(['pool_type' => 2, 'type' => 3, 'times' => $big_round])
->sum('amount') ?: 0;
$bigReduceGold = Db::name('bb_lottery_pool_flow')
->where(['pool_type' => 2, 'type' => ['in',[2,4]], 'times' => $big_round])
->sum('amount') ?: 0;
$this->redis->set('lottery:big_pool:total_gold', $bigAddGold + $bigReduceGold);
}
}
/**
* 处理送礼抽奖逻辑
* @param int $send_uid 送礼用户ID
* @param float $gift_gold 礼物金币数
* @param int $giftId 礼物ID
* @return array 处理结果
* @throws Exception
*/
public function handleGift($send_uid, $gift_gold, $giftId)
{
// 参数校验
if ($gift_gold <= 0 || !$send_uid) {
throw new Exception('参数错误');
}
// 读取配置+独立轮次+大奖池金额
$small_trigger_times = intval($this->config['small_pool_trigger_times'] ?? 200);
$big_threshold = floatval($this->config['big_pool_threshold'] ?? 1000);
$small_round = intval($this->redis->get('lottery:small_pool:round') ?: 1);
$big_round = intval($this->redis->get('lottery:big_pool:round') ?: 1);
$big_total_gold = floatval($this->redis->get('lottery:big_pool:total_gold') ?: 0);
// 加载Lua脚本
$luaSha = LotteryGiftLua::getLotteryLuaScript();
// 执行Lua脚本入参small_round + big_round
$result = $this->redis->eval($luaSha, [
$send_uid, 0, $gift_gold,
$small_trigger_times, $big_threshold,
$small_round, $big_round, $big_total_gold
], 0);
// 开启数据库事务
Db::startTrans();
// try {
// . 1记录小奖池累计流水未开奖时
if ($result['is_small_prize'] == 0) {
$this->addPoolFlow(
1, // 小奖池
1, // 累计
$result['small_pool_add'],
$result['small_total_gold'] - $result['small_pool_add'],
$result['small_total_gold'],
$giftId,
$result['small_round'], // 新增:传入轮次
"小奖池累计:用户{$send_uid}送礼,轮次{$result['current_round']}"
);
} else {
$winnerUid = $send_uid; // 奖默认给当前送礼用户
// 1. 只要开小奖,小奖剩余划入大奖池流水
$this->addPoolFlow(
2, // 大奖池
3, // 划转
$result['small_remain_amount'],//小奖剩余金额
$result['big_total_gold'] - $result['small_remain_amount'],
$result['big_total_gold'],
$giftId,
$result['big_round'] - ($result['is_big_prize'] ? 1 : 0),
"小奖剩余划转大奖池:{$result['small_remain_amount']}金币"
);
//2.开小奖剩余划入大奖后 大奖够开奖
if ($result['is_big_prize'] == 1) {
// 大奖中奖记录
$this->addWinnerRecord(
$winnerUid,
2, // 大奖
$result['big_prize_amount'],//中奖金额
$result['big_total_gold'], // 开奖时大奖池金额
$this->getBigRatio($result['big_prize_amount'], $result['big_total_gold']),
$result['big_release_amount']//释放金额
);
// 大奖释放流水
$this->addPoolFlow(
2, // 大奖池
4, // 释放
-$result['big_release_amount'],//释放金额
$result['big_total_gold'],// 开奖时大奖池金额
0,
$giftId,
$result['big_round'] - 1, // 关联已结束的小奖池轮次
"大奖释放金额:{$result['big_release_amount']}金币"
);
// 4. 小奖开奖金额划转下一次大奖池流水
if ($result['small_prize_to_big_next_round'] > 0) {
$this->addPoolFlow(
2,// 大奖池
3,// 划转
$result['small_prize_to_big_next_round'],
0,
$result['small_prize_to_big_next_round'],
$giftId,
$result['big_round'],
"小奖开奖金额划入大奖池下一轮:大轮次{$result['big_round']},金额{$result['small_prize_to_big_next_round']}");
}
} else {//只有小奖中奖
// 小奖中奖记录
$this->addWinnerRecord(
$winnerUid,
1, // 小奖
$result['small_prize_amount'],//中奖金额
$result['small_total_gold'],//奖池总金额
$this->getSmallRatio($result['small_prize_amount'], $result['small_total_gold']),//中奖比例
0 //释放金额
);
// 3. 小奖池开奖流水
$this->addPoolFlow(
1, // 小奖池
2, // 开奖扣除
-$result['small_total_gold'],
$result['small_total_gold'],
0,
$giftId,
$result['small_round'] - 1, // 开奖轮次为当前轮次-1已结束的轮次
"小奖池开奖:轮次" . ($result['small_round'] - 1).",累计{$result['small_total_gold']}金币"
);
}
}
Db::commit();
return [
'code' => 1,
'msg' => '处理成功',
'data' => $result
];
// } catch (Exception $e) {
// Db::rollback();
// throw new Exception($e->getMessage());
// }
}
/**
* 添加奖池流水
* @param int $pool_type 奖池类型1-小 2-大
* @param int $type 流水类型1-累计 2-开奖 3-划转 4-释放
* @param float $amount 金额
* @param float $before_amount 操作前金额
* @param float $after_amount 操作后金额
* @param int $relate_id 关联ID
* @param int $times 轮次
* @param string $remark 备注
*/
private function addPoolFlow($pool_type, $type, $amount, $before_amount, $after_amount, $relate_id, $times, $remark)
{
Db::name('bb_lottery_pool_flow')->insert([
'pool_type' => $pool_type,
'type' => $type,
'amount' => $amount,
'before_amount' => $before_amount,
'after_amount' => $after_amount,
'relate_id' => $relate_id,
'times' => $times, // 新增:写入轮次
'remark' => $remark,
'create_time' => time()
]);
}
/**
* 添加中奖记录
* @param int $uid 中奖用户ID
* @param int $prize_type 奖项类型1-小 2-大
* @param float $prize_amount 中奖金额
* @param float $pool_amount 奖池总金额
* @param int $ratio 中奖比例
* @param float $release_amount 释放金额
*/
private function addWinnerRecord($uid, $prize_type, $prize_amount, $pool_amount, $ratio, $release_amount)
{
Db::name('bb_lottery_winner_record')->insert([
'uid' => $uid,
'prize_type' => $prize_type,
'prize_amount' => $prize_amount,
'pool_amount' => $pool_amount,
'ratio' => $ratio,
'release_amount' => $release_amount,
'create_time' => time(),
'status' => 1 // 已发放
]);
// 此处可添加用户金币入账逻辑(如更新用户金币表)
}
/**
* 计算小奖中奖比例
* @param float $prize_amount 中奖金额
* @param float $pool_amount 奖池金额
* @return int 比例(%
*/
private function getSmallRatio($prize_amount, $pool_amount)
{
return intval(round($prize_amount / $pool_amount * 100));
}
/**
* 计算大奖中奖比例
* @param float $prize_amount 中奖金额
* @param float $pool_amount 奖池金额
* @return int 比例(%
*/
private function getBigRatio($prize_amount, $pool_amount)
{
return intval(round($prize_amount / $pool_amount * 100));
}
/**
* 统计中奖数据
* @param array $where 筛选条件如uid、prize_type、time
* @return array 统计结果
*/
public function statWinner($where = [])
{
$query = Db::name('bb_lottery_winner_record');
if (!empty($where['uid'])) {
$query->where('uid', $where['uid']);
}
if (!empty($where['prize_type'])) {
$query->where('prize_type', $where['prize_type']);
}
if (!empty($where['start_time']) && !empty($where['end_time'])) {
$query->whereBetween('create_time', [$where['start_time'], $where['end_time']]);
}
// 总中奖金额、总释放金额、中奖次数
$stat = $query->field([
'SUM(prize_amount) as total_prize',
'SUM(release_amount) as total_release',
'COUNT(id) as total_times'
])->find();
return [
'total_prize' => $stat['total_prize'] ?? 0,
'total_release' => $stat['total_release'] ?? 0,
'total_times' => $stat['total_times'] ?? 0
];
}
}