Files
midi-php/application/common/service/RedpacketService.php

562 lines
17 KiB
PHP
Raw Normal View History

2025-10-10 19:15:41 +08:00
<?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);
}
/**
* 抢红包并返回详细结果
*/
2025-10-11 14:49:25 +08:00
public function grabWithResult($redpacketId, $userId)
2025-10-10 19:15:41 +08:00
{
$redpacketModel = new Redpacket();
$redpacket = $redpacketModel->getRedpacketInfo($redpacketId);
if (!$redpacket) {
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'],
2025-10-11 14:49:25 +08:00
//记录日志 32-发红包金币29-发红包钻石30-抢红包金币31-抢红包(钻石)
'change_type' => $redpacket['coin_type'] == 1 ? 30 : 31,
2025-10-10 19:15:41 +08:00
'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 [
2025-10-11 14:49:25 +08:00
'code' => 1,
'msg' => '抢红包成功',
2025-10-10 19:15:41 +08:00
'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 [
2025-10-11 14:49:25 +08:00
'code' => 0,
'msg' => '系统错误,请重试',
'data' => null
2025-10-10 19:15:41 +08:00
];
}
}
/**
* 获取抢红包结果详情
*/
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'],
2025-10-11 18:09:36 +08:00
'status' => $redpacket['status'],
'nickname' => Db::name('user')->where('id', $redpacket['user_id'])->value('nickname')
2025-10-10 19:15:41 +08:00
],
'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 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());
}
}
}
2025-10-11 15:02:13 +08:00
2025-10-10 19:15:41 +08:00
/**
* 验证创建红包数据
*/
private function validateCreateData($data)
{
if (empty($data['user_id'])) {
2025-10-11 15:02:13 +08:00
return ['code' => 0, 'msg' => '用户ID不能为空', 'data' => null];
2025-10-10 19:15:41 +08:00
}
if (!in_array($data['type'], [Redpacket::TYPE_NORMAL, Redpacket::TYPE_PASSWORD])) {
2025-10-11 15:02:13 +08:00
return ['code' => 0, 'msg' => '红包类型错误', 'data' => null];
2025-10-10 19:15:41 +08:00
}
if ($data['type'] == Redpacket::TYPE_PASSWORD && empty($data['password'])) {
2025-10-11 15:02:13 +08:00
return ['code' => 0, 'msg' => '口令红包必须设置口令', 'data' => null];
2025-10-10 19:15:41 +08:00
}
if (!in_array($data['coin_type'], [Redpacket::COIN_GOLD, Redpacket::COIN_DIAMOND])) {
2025-10-11 15:02:13 +08:00
return ['code' => 0, 'msg' => '币种类型错误', 'data' => null];
2025-10-10 19:15:41 +08:00
}
if ($data['total_amount'] <= 0 || $data['total_count'] <= 0) {
2025-10-11 15:02:13 +08:00
return ['code' => 0, 'msg' => '金额和数量必须大于0', 'data' => null];
2025-10-10 19:15:41 +08:00
}
if ($data['total_amount'] < $data['total_count']) {
2025-10-11 15:02:13 +08:00
return ['code' => 0, 'msg' => '总金额不能小于总个数', 'data' => null];
2025-10-10 19:15:41 +08:00
}
// 验证领取条件
if (isset($data['conditions'])) {
2025-10-11 15:02:13 +08:00
$res_con = $this->validateConditions($data['conditions']);
2025-10-10 19:15:41 +08:00
if ($res_con !== true) {
return $res_con;
}
}
2025-10-11 15:02:13 +08:00
return ['code' => 1, 'msg' => '验证成功', 'data' => null];
2025-10-10 19:15:41 +08:00
}
2025-10-11 15:08:25 +08:00
2025-10-10 19:15:41 +08:00
/**
* 验证领取条件
*/
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;
}
}