红包
This commit is contained in:
647
application/common/service/RedpacketService.php
Normal file
647
application/common/service/RedpacketService.php
Normal file
@@ -0,0 +1,647 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use think\Db;
|
||||
use think\Cache;
|
||||
use app\common\model\Redpacket;
|
||||
use app\common\model\RedpacketRecord;
|
||||
use app\common\model\UserWallet;
|
||||
use app\common\library\RedpacketLua;
|
||||
|
||||
class RedpacketService
|
||||
{
|
||||
/**
|
||||
* 发红包
|
||||
*/
|
||||
public function create($data)
|
||||
{
|
||||
// 验证数据
|
||||
$res = $this->validateCreateData($data);
|
||||
if ($res['code'] == 0) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
$redpacketModel = new Redpacket();
|
||||
return $redpacketModel->createRedpacket($data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 抢红包并返回详细结果
|
||||
*/
|
||||
public function grabWithResult($redpacketId, $userId, $password = '')
|
||||
{
|
||||
$redpacketModel = new Redpacket();
|
||||
$redpacket = $redpacketModel->getRedpacketInfo($redpacketId);
|
||||
|
||||
if (!$redpacket) {
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '红包不存在',
|
||||
'data' => null
|
||||
];
|
||||
}
|
||||
|
||||
// 验证口令红包
|
||||
if ($redpacket['type'] == Redpacket::TYPE_PASSWORD) {
|
||||
if (empty($password) || $password != $redpacket['password']) {
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '口令错误',
|
||||
'data' => null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 验证领取条件
|
||||
$conditionCheck = $this->checkConditionsWithResult($redpacket, $userId);
|
||||
if ($conditionCheck['code'] != 1) {
|
||||
return $conditionCheck;
|
||||
}
|
||||
|
||||
// 检查红包状态
|
||||
$statusCheck = $this->checkRedpacketStatus($redpacket);
|
||||
if ($statusCheck['code'] != 1) {
|
||||
return $statusCheck;
|
||||
}
|
||||
|
||||
// 检查是否已经抢过
|
||||
if ($this->hasUserGrabbed($redpacketId, $userId)) {
|
||||
$detail = $this->getGrabResult($redpacketId, $userId);
|
||||
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '已经抢过该红包',
|
||||
'data' => $detail
|
||||
];
|
||||
}
|
||||
|
||||
// 使用Redis+Lua保证原子性操作
|
||||
$redis = Cache::store('redis')->handler();
|
||||
$script = RedpacketLua::grabRedpacketScript();
|
||||
|
||||
$redpacketKey = "redpacket:{$redpacketId}";
|
||||
$userSetKey = "redpacket_users:{$redpacketId}";
|
||||
|
||||
$result = $redis->eval($script, [
|
||||
$redpacketKey,
|
||||
$userSetKey,
|
||||
$userId,
|
||||
time()
|
||||
], 3);
|
||||
|
||||
if ($result[0] == 0) {
|
||||
$message = $result[1];
|
||||
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => $message,
|
||||
'data' => null
|
||||
];
|
||||
}
|
||||
|
||||
$amount = floatval($result[1]);
|
||||
|
||||
// Lua脚本执行成功,记录到数据库
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 创建领取记录
|
||||
$recordData = [
|
||||
'redpacket_id' => $redpacketId,
|
||||
'user_id' => $userId,
|
||||
'amount' => $amount
|
||||
];
|
||||
|
||||
$recordModel = new RedpacketRecord();
|
||||
$recordModel->save($recordData);
|
||||
|
||||
// 更新用户钱包
|
||||
$coinField = $redpacket['coin_type'] == 1 ? 'coin' : 'earnings';
|
||||
//增加余额
|
||||
$addres = Db::name('user_wallet')
|
||||
->where('user_id', $userId)
|
||||
->inc($coinField, $amount)
|
||||
->update();
|
||||
//记录用户金币日志
|
||||
$data = [
|
||||
'user_id' => $userId,
|
||||
'change_value' => $amount,
|
||||
'room_id' => $redpacket['room_id'],
|
||||
'money_type' => $redpacket['coin_type'],
|
||||
'change_type' => 66,//抢红包收入
|
||||
'from_id' => $redpacket['room_id'],
|
||||
'remarks' => '抢红包收入',
|
||||
'createtime' => time()
|
||||
];
|
||||
|
||||
$res = Db::name('vs_user_money_log')->insert($data);
|
||||
if(!$res || !$addres){
|
||||
Db::rollback();
|
||||
}
|
||||
|
||||
// 更新红包剩余数量和金额
|
||||
Db::name('redpacket')
|
||||
->where('id', $redpacketId)
|
||||
->dec('left_amount', $amount)
|
||||
->dec('left_count', 1)
|
||||
->update();
|
||||
|
||||
Db::commit();
|
||||
|
||||
// 获取抢红包结果详情
|
||||
$grabResult = $this->getGrabResult($redpacketId, $userId);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'code' => 'grab_success',
|
||||
'message' => '抢红包成功',
|
||||
'data' => $grabResult
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
// 回滚Redis操作
|
||||
$redis->hIncrByFloat($redpacketKey, 'left_amount', $amount);
|
||||
$redis->hIncrBy($redpacketKey, 'left_count', 1);
|
||||
$redis->sRem($userSetKey, $userId);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'code' => 'system_error',
|
||||
'message' => '系统错误,请重试'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取抢红包结果详情
|
||||
*/
|
||||
public function getGrabResult($redpacketId, $userId)
|
||||
{
|
||||
// 获取红包基本信息
|
||||
$redpacket = Db::name('redpacket')
|
||||
->where('id', $redpacketId)
|
||||
->find();
|
||||
|
||||
if (!$redpacket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取当前用户的领取记录
|
||||
$myRecord = Db::name('redpacket_record')
|
||||
->alias('r')
|
||||
->field('r.*, u.nickname, u.avatar')
|
||||
->join('user u', 'u.id = r.user_id')
|
||||
->where('r.redpacket_id', $redpacketId)
|
||||
->where('r.user_id', $userId)
|
||||
->find();
|
||||
|
||||
// 获取在我之前抢到的用户记录
|
||||
$previousRecords = [];
|
||||
if ($myRecord) {
|
||||
$previousRecords = Db::name('redpacket_record')
|
||||
->alias('r')
|
||||
->field('r.*, u.nickname, u.avatar')
|
||||
->join('user u', 'u.id = r.user_id')
|
||||
->where('r.redpacket_id', $redpacketId)
|
||||
->where('r.createtime', '<', $myRecord['createtime'])
|
||||
->order('r.createtime ASC')
|
||||
->select();
|
||||
}
|
||||
|
||||
// 获取所有记录用于统计
|
||||
$allRecords = Db::name('redpacket_record')
|
||||
->alias('r')
|
||||
->field('r.*, u.nickname, u.avatar')
|
||||
->join('user u', 'u.id = r.user_id')
|
||||
->where('r.redpacket_id', $redpacketId)
|
||||
->order('r.createtime ASC')
|
||||
->select();
|
||||
|
||||
// 统计信息
|
||||
$totalGrabbed = count($allRecords);
|
||||
$totalAmount = array_sum(array_column($allRecords, 'amount'));
|
||||
|
||||
// 手气最佳
|
||||
$bestRecord = null;
|
||||
if ($allRecords) {
|
||||
$maxAmount = max(array_column($allRecords, 'amount'));
|
||||
foreach ($allRecords as $record) {
|
||||
if ($record['amount'] == $maxAmount) {
|
||||
$bestRecord = $record;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'redpacket_info' => [
|
||||
'id' => $redpacket['id'],
|
||||
'total_amount' => $redpacket['total_amount'],
|
||||
'total_count' => $redpacket['total_count'],
|
||||
'left_amount' => $redpacket['left_amount'],
|
||||
'left_count' => $redpacket['left_count'],
|
||||
'coin_type' => $redpacket['coin_type'],
|
||||
'status' => $redpacket['status']
|
||||
],
|
||||
'my_record' => $myRecord ? [
|
||||
'amount' => $myRecord['amount'],
|
||||
'createtime' => $myRecord['createtime'],
|
||||
'nickname' => $myRecord['nickname'],
|
||||
'avatar' => $myRecord['avatar']
|
||||
] : null,
|
||||
'previous_records' => $previousRecords,
|
||||
'all_records' => $allRecords,
|
||||
'statistics' => [
|
||||
'total_grabbed' => $totalGrabbed,
|
||||
'total_amount_grabbed' => $totalAmount,
|
||||
'best_luck' => $bestRecord ? [
|
||||
'nickname' => $bestRecord['nickname'],
|
||||
'avatar' => $bestRecord['avatar'],
|
||||
'amount' => $bestRecord['amount']
|
||||
] : null
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查红包状态
|
||||
*/
|
||||
private function checkRedpacketStatus($redpacket)
|
||||
{
|
||||
$now = time();
|
||||
|
||||
if ($redpacket['status'] == Redpacket::STATUS_PENDING) {
|
||||
if ($now < $redpacket['start_time']) {
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '红包还未开始',
|
||||
'data' => null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($redpacket['status'] == Redpacket::STATUS_FINISHED ||
|
||||
$redpacket['status'] == Redpacket::STATUS_REFUNDED) {
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '红包已结束',
|
||||
'data' => null
|
||||
];
|
||||
}
|
||||
|
||||
if ($now > $redpacket['end_time']) {
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '红包已结束',
|
||||
'data' => null
|
||||
];
|
||||
}
|
||||
|
||||
return ['code' => 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已经抢过
|
||||
*/
|
||||
private function hasUserGrabbed($redpacketId, $userId)
|
||||
{
|
||||
$record = Db::name('redpacket_record')
|
||||
->where('redpacket_id', $redpacketId)
|
||||
->where('user_id', $userId)
|
||||
->find();
|
||||
|
||||
return !empty($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查领取条件(返回结果格式)
|
||||
*/
|
||||
private function checkConditionsWithResult($redpacket, $userId)
|
||||
{
|
||||
$conditions = $redpacket['conditions'] ? explode(',', $redpacket['conditions']): [];
|
||||
|
||||
if (empty($conditions)) {
|
||||
return ['code' => 1];
|
||||
}
|
||||
|
||||
if (in_array(Redpacket::CONDITION_NONE, $conditions)) {
|
||||
return ['code' => 1];
|
||||
}
|
||||
|
||||
foreach ($conditions as $condition) {
|
||||
switch ($condition) {
|
||||
case Redpacket::CONDITION_COLLECT_ROOM:
|
||||
if (!$this->checkUserCollectedRoom($userId, $redpacket['room_id'])) {
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '不满足收藏房间条件',
|
||||
'data' => null
|
||||
];
|
||||
}
|
||||
break;
|
||||
|
||||
case Redpacket::CONDITION_MIC_USER:
|
||||
if (!$this->checkUserOnMic($userId, $redpacket['room_id'])) {
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '不满足麦位用户条件',
|
||||
'data' => null
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ['code' => 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 抢红包
|
||||
*/
|
||||
// public function grab($redpacketId, $userId, $password = '')
|
||||
// {
|
||||
// $redpacketModel = new Redpacket();
|
||||
// $redpacket = $redpacketModel->getRedpacketInfo($redpacketId);
|
||||
//
|
||||
// if (!$redpacket) {
|
||||
// throw new \Exception('红包不存在');
|
||||
// }
|
||||
//
|
||||
// // 验证口令红包
|
||||
// if ($redpacket['type'] == Redpacket::TYPE_PASSWORD) {
|
||||
// if (empty($password) || $password != $redpacket['password']) {
|
||||
// throw new \Exception('口令错误');
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 验证领取条件
|
||||
// $this->checkConditions($redpacket, $userId);
|
||||
//
|
||||
// // 使用Redis+Lua保证原子性操作
|
||||
// $redis = Cache::store('redis')->handler();
|
||||
// $script = RedpacketLua::grabRedpacketScript();
|
||||
//
|
||||
// $redpacketKey = "redpacket:{$redpacketId}";
|
||||
// $userSetKey = "redpacket_users:{$redpacketId}";
|
||||
//
|
||||
// $result = $redis->eval($script, [
|
||||
// $redpacketKey,
|
||||
// $userSetKey,
|
||||
// $userId,
|
||||
// time()
|
||||
// ], 3);
|
||||
//
|
||||
// if ($result[0] == 0) {
|
||||
// throw new \Exception($result[1]);
|
||||
// }
|
||||
//
|
||||
// $amount = floatval($result[1]);
|
||||
//
|
||||
// // Lua脚本执行成功,记录到数据库
|
||||
// Db::startTrans();
|
||||
// try {
|
||||
// // 创建领取记录
|
||||
// $recordData = [
|
||||
// 'redpacket_id' => $redpacketId,
|
||||
// 'user_id' => $userId,
|
||||
// 'amount' => $amount
|
||||
// ];
|
||||
//
|
||||
// $recordModel = new RedpacketRecord();
|
||||
// $recordModel->save($recordData);
|
||||
//
|
||||
// // 更新用户钱包
|
||||
// $walletModel = new UserWallet();
|
||||
// $walletModel->increaseBalance($userId, $redpacket['coin_type'], $amount);
|
||||
//
|
||||
// // 更新红包剩余数量和金额
|
||||
// Db::name('redpacket')
|
||||
// ->where('id', $redpacketId)
|
||||
// ->dec('left_amount', $amount)
|
||||
// ->dec('left_count', 1)
|
||||
// ->update();
|
||||
//
|
||||
// Db::commit();
|
||||
//
|
||||
// return $amount;
|
||||
//
|
||||
// } catch (\Exception $e) {
|
||||
// Db::rollback();
|
||||
// // 回滚Redis操作
|
||||
// $redis->hIncrByFloat($redpacketKey, 'left_amount', $amount);
|
||||
// $redis->hIncrBy($redpacketKey, 'left_count', 1);
|
||||
// $redis->sRem($userSetKey, $userId);
|
||||
// throw $e;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 获取红包详情和领取记录
|
||||
*/
|
||||
public function getDetail($redpacketId, $currentUserId = 0)
|
||||
{
|
||||
$redpacketModel = new Redpacket();
|
||||
$redpacket = $redpacketModel->getRedpacketInfo($redpacketId);
|
||||
|
||||
if (!$redpacket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取领取记录
|
||||
$records = Db::name('redpacket_record')
|
||||
->alias('r')
|
||||
->field('r.*, u.nickname, u.avatar')
|
||||
->join('user u', 'u.id = r.user_id')
|
||||
->where('r.redpacket_id', $redpacketId)
|
||||
->order('r.createtime ASC')
|
||||
->select();
|
||||
|
||||
$redpacket['records'] = $records;
|
||||
|
||||
// 检查当前用户是否已抢
|
||||
$redpacket['has_grabbed'] = false;
|
||||
$redpacket['my_record'] = null;
|
||||
|
||||
if ($currentUserId > 0) {
|
||||
foreach ($records as $record) {
|
||||
if ($record['user_id'] == $currentUserId) {
|
||||
$redpacket['has_grabbed'] = true;
|
||||
$redpacket['my_record'] = $record;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $redpacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过期红包退款
|
||||
*/
|
||||
public function processExpiredRedpackets()
|
||||
{
|
||||
$now = time();
|
||||
$redpacketModel = new Redpacket();
|
||||
|
||||
// 查找已结束但未退款的红包
|
||||
$expiredRedpackets = Db::name('redpacket')
|
||||
->where('status', Redpacket::STATUS_ACTIVE)
|
||||
->where('end_time', '<', $now)
|
||||
->where('left_count', '>', 0)
|
||||
->select();
|
||||
|
||||
foreach ($expiredRedpackets as $redpacket) {
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 退款给发红包用户
|
||||
if ($redpacket['left_amount'] > 0) {
|
||||
$walletModel = new UserWallet();
|
||||
$walletModel->increaseBalance(
|
||||
$redpacket['user_id'],
|
||||
$redpacket['coin_type'],
|
||||
$redpacket['left_amount']
|
||||
);
|
||||
}
|
||||
|
||||
// 更新红包状态
|
||||
Db::name('redpacket')
|
||||
->where('id', $redpacket['id'])
|
||||
->update([
|
||||
'status' => Redpacket::STATUS_REFUNDED,
|
||||
'updatetime' => $now
|
||||
]);
|
||||
|
||||
// 清理Redis缓存
|
||||
$redis = Cache::store('redis')->handler();
|
||||
$redisKey = "redpacket:{$redpacket['id']}";
|
||||
$redis->del($redisKey);
|
||||
|
||||
Db::commit();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
// 记录日志
|
||||
\think\Log::error("红包退款失败: {$redpacket['id']}, 错误: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证创建红包数据
|
||||
*/
|
||||
private function validateCreateData($data)
|
||||
{
|
||||
if (empty($data['user_id'])) {
|
||||
return V(0, '用户ID不能为空');
|
||||
}
|
||||
|
||||
if (!in_array($data['type'], [Redpacket::TYPE_NORMAL, Redpacket::TYPE_PASSWORD])) {
|
||||
return V(0, '红包类型错误');
|
||||
}
|
||||
|
||||
if ($data['type'] == Redpacket::TYPE_PASSWORD && empty($data['password'])) {
|
||||
return V(0, '口令红包必须设置口令');
|
||||
}
|
||||
|
||||
if (!in_array($data['coin_type'], [Redpacket::COIN_GOLD, Redpacket::COIN_DIAMOND])) {
|
||||
return V(0, '币种类型错误');
|
||||
}
|
||||
|
||||
if ($data['total_amount'] <= 0 || $data['total_count'] <= 0) {
|
||||
return V(0, '金额和数量必须大于0');
|
||||
}
|
||||
|
||||
if ($data['total_amount'] < $data['total_count']) {
|
||||
return V(0, '总金额不能小于总个数');
|
||||
}
|
||||
|
||||
// 验证领取条件
|
||||
if (isset($data['conditions'])) {
|
||||
$res_con = $this->validateConditions($data['conditions']);
|
||||
if ($res_con !== true) {
|
||||
return $res_con;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证领取条件
|
||||
*/
|
||||
private function validateConditions($conditions)
|
||||
{
|
||||
if (empty($conditions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//字符串转为数组
|
||||
$conditions = explode(',', $conditions);
|
||||
|
||||
if (in_array(Redpacket::CONDITION_NONE, $conditions) && count($conditions) > 1) {
|
||||
return V(0, '选择"无"条件时不能选择其他条件');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否满足领取条件
|
||||
*/
|
||||
private function checkConditions($redpacket, $userId)
|
||||
{
|
||||
$conditions = $redpacket['conditions'] ?: [];
|
||||
|
||||
if (empty($conditions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array(Redpacket::CONDITION_NONE, $conditions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($conditions as $condition) {
|
||||
switch ($condition) {
|
||||
case Redpacket::CONDITION_COLLECT_ROOM:
|
||||
// 检查用户是否收藏了房间
|
||||
if (!$this->checkUserCollectedRoom($userId)) {
|
||||
throw new \Exception('不满足收藏房间条件');
|
||||
}
|
||||
break;
|
||||
|
||||
case Redpacket::CONDITION_MIC_USER:
|
||||
// 检查用户是否在麦位上
|
||||
if (!$this->checkUserOnMic($userId)) {
|
||||
throw new \Exception('不满足麦位用户条件');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否收藏了房间(需要根据实际业务实现)
|
||||
*/
|
||||
private function checkUserCollectedRoom($userId,$roomId)
|
||||
{
|
||||
$collect = Db::name('user_follow')->where(['user_id' => $userId,'type' => 2,'follow_id' => $roomId])->find();
|
||||
if (!$collect) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否在麦位上(需要根据实际业务实现)
|
||||
*/
|
||||
private function checkUserOnMic($userId,$roomId)
|
||||
{
|
||||
$onPit = Db::name('vs_room_pit')->where(['user_id' => $userId,'room_id' => $roomId])->value('pit_number');
|
||||
if ($onPit <= 0){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user