2025-12-21 16:04:34 +08:00
|
|
|
|
<?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)
|
2025-12-21 16:39:53 +08:00
|
|
|
|
$maxSmallRound = Db::name('bb_lottery_pool_flow')->where('pool_type', 1)->max('times') ?: 1;
|
2025-12-21 16:04:34 +08:00
|
|
|
|
if (!$this->redis->get('lottery:small_pool:round')) {
|
|
|
|
|
|
$this->redis->set('lottery:small_pool:round', $maxSmallRound);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 恢复大奖池轮次(取pool_type=2的最大times)
|
2025-12-21 16:39:53 +08:00
|
|
|
|
$maxBigRound = Db::name('bb_lottery_pool_flow')->where('pool_type', 2)->max('times') ?: 1;
|
2025-12-21 16:04:34 +08:00
|
|
|
|
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')) {
|
2025-12-21 16:39:53 +08:00
|
|
|
|
$smallTotalTimes = Db::name('bb_lottery_pool_flow')
|
2025-12-21 16:04:34 +08:00
|
|
|
|
->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')) {
|
2025-12-21 16:39:53 +08:00
|
|
|
|
$smallTotalGold = Db::name('bb_lottery_pool_flow')
|
2025-12-21 16:04:34 +08:00
|
|
|
|
->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')) {
|
2025-12-21 16:39:53 +08:00
|
|
|
|
$bigAddGold = Db::name('bb_lottery_pool_flow')
|
2025-12-21 16:04:34 +08:00
|
|
|
|
->where(['pool_type' => 2, 'type' => 3, 'times' => $big_round])
|
|
|
|
|
|
->sum('amount') ?: 0;
|
2025-12-21 16:39:53 +08:00
|
|
|
|
$bigReduceGold = Db::name('bb_lottery_pool_flow')
|
2025-12-21 16:04:34 +08:00
|
|
|
|
->where(['pool_type' => 2, 'type' => [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->evalSha($luaSha, [
|
|
|
|
|
|
$send_uid, 0, $gift_gold,
|
|
|
|
|
|
$small_trigger_times, $big_threshold,
|
|
|
|
|
|
$small_round, $big_round, $big_total_gold
|
|
|
|
|
|
], 0);
|
|
|
|
|
|
$result = json_decode($result, true);
|
|
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
|
|
|
|
throw new Exception('Lua脚本执行失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 开启数据库事务
|
|
|
|
|
|
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
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|